diff --git a/docker-compose.yml b/docker-compose.yml
index 3aece86d..7b91697d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,4 @@
-version: '3.8'
+version: "3.8"
volumes:
postgres:
@@ -10,7 +10,7 @@ services:
ports:
- 8092:80
environment:
- DEBUG: true
+ DEBUG: true
postgres:
image: "postgres:14-alpine"
@@ -82,7 +82,7 @@ services:
image: golang:1.23.4-alpine
command: go run ./ server
healthcheck:
- test: [ "CMD", "curl", "-f", "http://127.0.0.1:8080/_healthcheck" ]
+ test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/_healthcheck"]
interval: 10s
timeout: 5s
retries: 5
@@ -105,13 +105,13 @@ services:
PLUGIN_MAGIC_COOKIE: mysupercookie
TEMPORAL_INIT_SEARCH_ATTRIBUTES: true
STACK_URL: http://gateway:8092
- STACK_PUBLIC_URL: ${STACK_PUBLIC_URL:?mandatory}
+ STACK_PUBLIC_URL: http://localhost:8080
payments-worker:
image: golang:1.23.4-alpine
command: go run ./ worker
healthcheck:
- test: [ "CMD", "curl", "-f", "http://127.0.0.1:8080/_healthcheck" ]
+ test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/_healthcheck"]
interval: 10s
timeout: 5s
retries: 5
@@ -133,4 +133,4 @@ services:
PLUGIN_MAGIC_COOKIE: mysupercookie
TEMPORAL_INIT_SEARCH_ATTRIBUTES: false
STACK_URL: http://gateway:8092
- STACK_PUBLIC_URL: ${STACK_PUBLIC_URL:?mandatory}
+ STACK_PUBLIC_URL: http://localhost:8080
diff --git a/docs/api/README.md b/docs/api/README.md
index 8f92dbaf..97017336 100644
--- a/docs/api/README.md
+++ b/docs/api/README.md
@@ -5730,6 +5730,12 @@ xor
xor
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|*anonymous*|[V3CheckoutConfig](#schemav3checkoutconfig)|false|none|none|
+
+xor
+
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
|*anonymous*|[V3ColumnConfig](#schemav3columnconfig)|false|none|none|
@@ -5912,6 +5918,42 @@ xor
|userCertificateKey|string|true|none|none|
|username|string|true|none|none|
+
V3CheckoutConfig
+
+
+
+
+
+
+```json
+{
+ "entityId": "string",
+ "environment": "string",
+ "name": "string",
+ "oauthClientID": "string",
+ "oauthClientSecret": "string",
+ "pageSize": "25",
+ "pollingPeriod": "2m",
+ "processingChannelId": "string",
+ "provider": "Checkout"
+}
+
+```
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|entityId|string|true|none|none|
+|environment|string|true|none|none|
+|name|string|true|none|none|
+|oauthClientID|string|true|none|none|
+|oauthClientSecret|string|true|none|none|
+|pageSize|integer|false|none|none|
+|pollingPeriod|string|false|none|none|
+|processingChannelId|string|true|none|none|
+|provider|string|false|none|none|
+
V3ColumnConfig
diff --git a/go.mod b/go.mod
index 6c1361a7..bc42e99d 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
github.com/ThreeDotsLabs/watermill v1.4.7
github.com/adyen/adyen-go-api-library/v7 v7.3.1
github.com/bombsimon/logrusr/v3 v3.1.0
+ github.com/checkout/checkout-sdk-go v1.7.2
github.com/emvi/iso-639-1 v1.1.1
github.com/formancehq/go-libs/v3 v3.0.2-0.20250814071617-0f5bb98d939b
github.com/formancehq/payments/genericclient v0.0.0-00010101000000-000000000000
@@ -132,6 +133,7 @@ require (
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.5 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
diff --git a/go.sum b/go.sum
index da4ffb2c..fa509a4f 100644
--- a/go.sum
+++ b/go.sum
@@ -693,6 +693,8 @@ github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/checkout/checkout-sdk-go v1.7.2 h1:tRxQ4bT0zxfV1Pox+x5pvAC55Il+BJIQBB1hcuA9HSw=
+github.com/checkout/checkout-sdk-go v1.7.2/go.mod h1:NL0iaELZA1BleG4dUINHaa8LgcizKUnzWWuTVukTswo=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -776,6 +778,7 @@ github.com/formancehq/go-libs/v3 v3.0.2-0.20250814071617-0f5bb98d939b h1:jmWZvgO
github.com/formancehq/go-libs/v3 v3.0.2-0.20250814071617-0f5bb98d939b/go.mod h1:HgSiCLTW/yu3Or5gWB20/N8/xWV6zkdI8WoQXP4vLLs=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/get-momo/atlar-v1-go-client v1.4.0 h1:DIRwP3gRfvdXAxEeMNua6HPsScXZzDB9nySdYVv5FD0=
@@ -915,6 +918,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
diff --git a/internal/connectors/plugins/public/checkout/accounts.go b/internal/connectors/plugins/public/checkout/accounts.go
new file mode 100644
index 00000000..f2230037
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/accounts.go
@@ -0,0 +1,58 @@
+package checkout
+
+import (
+ "context"
+ "encoding/json"
+ "time"
+
+ "github.com/formancehq/payments/internal/models"
+)
+
+type accountsState struct {
+ LastPage int `json:"lastPage"`
+}
+
+func (p *Plugin) fetchNextAccounts(ctx context.Context, req models.FetchNextAccountsRequest) (models.FetchNextAccountsResponse, error) {
+ const page = 0
+
+ pagedAccounts, err := p.client.GetAccounts(ctx, page, req.PageSize)
+ if err != nil {
+ return models.FetchNextAccountsResponse{}, err
+ }
+
+ accounts := make([]models.PSPAccount, 0, len(pagedAccounts))
+ for _, acc := range pagedAccounts {
+ raw, _ := json.Marshal(acc)
+
+ md := map[string]string{
+ "status": acc.Status,
+ }
+
+ var namePtr *string
+ if acc.Name != "" {
+ n := acc.Name
+ namePtr = &n
+ }
+
+ accounts = append(accounts, models.PSPAccount{
+ Reference: acc.ID,
+ Name: namePtr,
+ DefaultAsset: nil,
+ Metadata: md,
+ Raw: raw,
+ CreatedAt: time.Now().UTC(),
+ })
+ }
+
+ if req.PageSize > 0 && len(accounts) > req.PageSize {
+ accounts = accounts[:req.PageSize]
+ }
+
+ newState, _ := json.Marshal(accountsState{LastPage: 0})
+
+ return models.FetchNextAccountsResponse{
+ Accounts: accounts,
+ NewState: newState,
+ HasMore: false,
+ }, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/balances.go b/internal/connectors/plugins/public/checkout/balances.go
new file mode 100644
index 00000000..cae0208f
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/balances.go
@@ -0,0 +1,35 @@
+package checkout
+
+import (
+ "context"
+ "math/big"
+ "time"
+
+ "github.com/formancehq/payments/internal/models"
+ "github.com/formancehq/go-libs/v3/currency"
+)
+
+func (p *Plugin) fetchNextBalances(ctx context.Context, req models.FetchNextBalancesRequest) (models.FetchNextBalancesResponse, error) {
+ balances, err := p.client.GetAccountBalances(ctx)
+ if err != nil {
+ return models.FetchNextBalancesResponse{}, err
+ }
+
+ accountBalances := make([]models.PSPBalance, 0, len(balances))
+
+ for _, b := range balances {
+ asset := currency.FormatAsset(supportedCurrenciesWithDecimal, b.Currency)
+
+ accountBalances = append(accountBalances, models.PSPBalance{
+ AccountReference: b.CurrencyAccountID,
+ Asset: asset,
+ Amount: big.NewInt(b.Available),
+ CreatedAt: time.Now().UTC(),
+ })
+ }
+
+ return models.FetchNextBalancesResponse{
+ Balances: accountBalances,
+ HasMore: false,
+ }, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/capabilities.go b/internal/connectors/plugins/public/checkout/capabilities.go
new file mode 100644
index 00000000..5069899a
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/capabilities.go
@@ -0,0 +1,12 @@
+package checkout
+
+import "github.com/formancehq/payments/internal/models"
+
+var capabilities = []models.Capability{
+ models.CAPABILITY_FETCH_ACCOUNTS,
+ models.CAPABILITY_FETCH_BALANCES,
+ models.CAPABILITY_FETCH_PAYMENTS,
+
+ models.CAPABILITY_CREATE_TRANSFER,
+ models.CAPABILITY_CREATE_PAYOUT,
+}
diff --git a/internal/connectors/plugins/public/checkout/client/accounts.go b/internal/connectors/plugins/public/checkout/client/accounts.go
new file mode 100644
index 00000000..9c390db9
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/accounts.go
@@ -0,0 +1,51 @@
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type Account struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Status string `json:"status"`
+}
+
+func (c *client) GetAccounts(ctx context.Context, page int, pageSize int) ([]*Account, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "list_accounts")
+
+ if page > 1 {
+ return []*Account{}, nil
+ }
+ if c.sdk == nil || c.entityID == "" {
+ return nil, fmt.Errorf("checkout sdk not initialized or missing entityID")
+ }
+
+ entity, err := c.sdk.Accounts.GetEntity(c.entityID)
+ if err != nil {
+ return nil, fmt.Errorf("checkout.accounts.getEntity(%s): %w", c.entityID, err)
+ }
+
+ if b, err := json.MarshalIndent(entity, "", " "); err == nil {
+ fmt.Printf("Received entity from Checkout: %s\n", string(b))
+ }
+
+ id := c.entityID
+ name := fmt.Sprint(entity.Company.LegalName)
+ status := fmt.Sprint(entity.Status)
+
+ accounts := []*Account{{
+ ID: id,
+ Name: name,
+ Status: status,
+ }}
+
+ if b, _ := json.Marshal(accounts); true {
+ fmt.Printf("[checkout] GetAccounts returns %d account(s): %s\n", len(accounts), string(b))
+ }
+
+ return accounts, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/balances.go b/internal/connectors/plugins/public/checkout/client/balances.go
new file mode 100644
index 00000000..446a4830
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/balances.go
@@ -0,0 +1,56 @@
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/checkout/checkout-sdk-go/balances"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type Balance struct {
+ Descriptor string `json:"descriptor"`
+ CurrencyAccountID string `json:"currencyAccountId"`
+ Currency string `json:"currency"`
+ Available int64 `json:"available"`
+ Pending int64 `json:"pending"`
+ Payable int64 `json:"payable"`
+ Collateral int64 `json:"collateral"`
+}
+
+func (c *client) GetAccountBalances(ctx context.Context) ([]*Balance, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "list_account_balances")
+
+ if c.sdk == nil || c.entityID == "" {
+ return nil, fmt.Errorf("checkout sdk not initialized or missing entityID")
+ }
+
+ resp, err := c.sdk.Balances.RetrieveEntityBalances(
+ c.entityID,
+ balances.QueryFilter{WithCurrencyAccountId: true},
+ )
+ if err != nil {
+ return nil, fmt.Errorf("checkout.accounts.getEntityBalances(%s): %w", c.entityID, err)
+ }
+
+ balances := make([]*Balance, 0, len(resp.Data))
+ for _, ab := range resp.Data {
+ balances = append(balances, &Balance{
+ Descriptor: ab.Descriptor,
+ CurrencyAccountID: ab.CurrencyAccountId,
+ Currency: ab.HoldingCurrency,
+ Available: ab.Balances.Available,
+ Pending: ab.Balances.Pending,
+ Payable: ab.Balances.Payable,
+ Collateral: ab.Balances.Collateral,
+ })
+ }
+
+ if b, _ := json.Marshal(balances); true {
+ fmt.Printf("[checkout] GetAccountBalances returns %d balance(s): %s\n", len(balances), string(b))
+ }
+
+ return balances, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/client.go b/internal/connectors/plugins/public/checkout/client/client.go
new file mode 100644
index 00000000..022b9c85
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/client.go
@@ -0,0 +1,141 @@
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/checkout/checkout-sdk-go"
+ "github.com/checkout/checkout-sdk-go/configuration"
+ "github.com/checkout/checkout-sdk-go/nas"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type Client interface {
+ GetAccounts(ctx context.Context, page int, pageSize int) ([]*Account, error)
+ GetAccountBalances(ctx context.Context) ([]*Balance, error)
+ GetExternalAccounts(ctx context.Context, page int, pageSize int) ([]*ExternalAccount, error)
+ GetTransactions(ctx context.Context, page, pageSize int) ([]*Transaction, error)
+ InitiateTransfer(ctx context.Context, tr *TransferRequest) (*TransferResponse, error)
+ InitiatePayout(ctx context.Context, pr *PayoutRequest) (*PayoutResponse, error)
+}
+
+type client struct {
+ sdk *nas.Api
+ httpClient *http.Client
+ apiBase string
+ apiAuthUrl string
+ oauthClientID string
+ oauthClientSecret string
+ entityID string
+ processingChannelId string
+}
+
+type acceptHeaderTransport struct {
+ base http.RoundTripper
+}
+
+func (t *acceptHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ r := req.Clone(req.Context())
+ r.Header.Set("Accept", "application/json; schema_version=3.0")
+ if r.Header.Get("Content-Type") == "" {
+ r.Header.Set("Content-Type", "application/json")
+ }
+ return t.base.RoundTrip(r)
+}
+
+func New(
+ env string,
+ oauthClientID string,
+ oauthClientSecret string,
+ entityID string,
+ processingChannelId string,
+) *client {
+ var environment configuration.Environment
+ switch strings.ToLower(strings.TrimSpace(env)) {
+ case "sandbox":
+ environment = configuration.Sandbox()
+ default:
+ environment = configuration.Production()
+ }
+
+ apiBase := environment.BaseUri()
+ apiAuthUrl := environment.AuthorizationUri()
+
+ httpClient := &http.Client{
+ Transport: &acceptHeaderTransport{
+ base: metrics.NewTransport("checkout", metrics.TransportOpts{}),
+ },
+ Timeout: 30 * time.Second,
+ }
+
+ sdk, err := checkout.Builder().
+ OAuth().
+ WithClientCredentials(strings.TrimSpace(oauthClientID), strings.TrimSpace(oauthClientSecret)).
+ WithEnvironment(environment).
+ WithHttpClient(httpClient).
+ WithScopes(getOAuthScopes()).
+ Build()
+ if err != nil {
+ panic(err)
+ }
+
+ return &client{
+ sdk: sdk,
+ httpClient: httpClient,
+ apiBase: apiBase,
+ apiAuthUrl: apiAuthUrl,
+ oauthClientID: oauthClientID,
+ oauthClientSecret: oauthClientSecret,
+ entityID: entityID,
+ processingChannelId: processingChannelId,
+ }
+}
+
+func getOAuthScopes() []string {
+ return []string{"accounts", "balances", "payments:search"}
+}
+
+func (c *client) getAccessToken(ctx context.Context) (string, error) {
+ form := url.Values{}
+ form.Set("grant_type", "client_credentials")
+ form.Set("scope", strings.Join(getOAuthScopes(), " "))
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, strings.TrimSpace(c.apiAuthUrl), strings.NewReader(form.Encode()))
+ if err != nil {
+ return "", err
+ }
+ req.SetBasicAuth(strings.TrimSpace(c.oauthClientID), strings.TrimSpace(c.oauthClientSecret))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode > 299 {
+ var apiErr map[string]any
+ _ = json.NewDecoder(resp.Body).Decode(&apiErr)
+ return "", fmt.Errorf("oauth token %d: %v", resp.StatusCode, apiErr)
+ }
+
+ var tok struct {
+ AccessToken string `json:"access_token"`
+ ExpiresIn int64 `json:"expires_in"`
+ TokenType string `json:"token_type"`
+ Scope string `json:"scope"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&tok); err != nil {
+ return "", err
+ }
+ if tok.AccessToken == "" {
+ return "", fmt.Errorf("oauth token missing access_token")
+ }
+ return tok.AccessToken, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/external_accounts.go b/internal/connectors/plugins/public/checkout/client/external_accounts.go
new file mode 100644
index 00000000..5a1f9fbf
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/external_accounts.go
@@ -0,0 +1,16 @@
+package client
+
+import (
+ "context"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type ExternalAccount struct {}
+
+func (c *client) GetExternalAccounts(ctx context.Context, page int, pageSize int) ([]*ExternalAccount, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "list_external_accounts")
+
+ // TODO: call PSP to fetch external accounts
+ return nil, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/payouts.go b/internal/connectors/plugins/public/checkout/client/payouts.go
new file mode 100644
index 00000000..34d0c827
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/payouts.go
@@ -0,0 +1,64 @@
+package client
+
+import (
+ "context"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+
+ "github.com/checkout/checkout-sdk-go/common"
+ "github.com/checkout/checkout-sdk-go/payments/nas"
+ "github.com/checkout/checkout-sdk-go/payments/nas/sources"
+)
+
+type PayoutRequest struct {
+ SourceEntityID string
+ DestinationInstrumentID string
+
+ Amount int64
+ Currency string
+ BillingDescriptor string
+ Reference string
+ IdempotencyKey string
+}
+
+type PayoutResponse struct {
+ ID string
+ Status string
+ Reference string
+}
+
+func (c *client) InitiatePayout(ctx context.Context, pr *PayoutRequest) (*PayoutResponse, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "initiate_payout")
+
+ src := sources.NewRequestIdSource()
+ src.Id = pr.SourceEntityID
+ dest := nas.NewRequestIdDestination()
+ dest.Id = pr.DestinationInstrumentID
+
+ var billing *nas.PayoutBillingDescriptor
+ if pr.BillingDescriptor != "" {
+ billing = &nas.PayoutBillingDescriptor{Reference: pr.BillingDescriptor}
+ }
+
+ req := nas.PayoutRequest{
+ Source: src,
+ Destination: dest,
+ Amount: pr.Amount,
+ Currency: common.Currency(pr.Currency),
+ Reference: pr.Reference,
+ ProcessingChannelId: c.processingChannelId,
+ BillingDescriptor: billing,
+ }
+
+ res, err := c.sdk.Payments.RequestPayout(req, &pr.IdempotencyKey)
+ if err != nil {
+ return nil, err
+ }
+
+ out := &PayoutResponse{
+ ID: res.Id,
+ Status: string(res.Status),
+ Reference: res.Reference,
+ }
+ return out, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/transactions.go b/internal/connectors/plugins/public/checkout/client/transactions.go
new file mode 100644
index 00000000..d015c089
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/transactions.go
@@ -0,0 +1,130 @@
+package client
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type TransactionAction struct {
+ Type string `json:"type"`
+}
+
+type Transaction struct {
+ ID string `json:"id"`
+ PaymentID string `json:"payment_id"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ Amount int64 `json:"amount"`
+ Currency string `json:"currency"`
+ Scheme string `json:"scheme"`
+ SourceAccountReference string `json:"sourceAccountReference"`
+ Actions []TransactionAction `json:"actions"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+type searchSort struct {
+ Field string `json:"field"`
+ Order string `json:"order"`
+}
+type searchFilters struct {
+ EntityIDs []string `json:"entity_ids,omitempty"`
+}
+type searchPaymentsRequest struct {
+ Query string `json:"query,omitempty"`
+ From int `json:"from,omitempty"`
+ Limit int `json:"limit,omitempty"`
+ Sort []searchSort `json:"sort,omitempty"`
+ Filters *searchFilters `json:"filters,omitempty"`
+}
+type searchPaymentsResponse struct {
+ Data []struct {
+ ID string `json:"id"`
+ Amount int64 `json:"amount"`
+ Currency string `json:"currency"`
+ Status string `json:"status"`
+ Approved bool `json:"approved"`
+ Source struct {
+ Scheme string `json:"scheme"`
+ } `json:"source"`
+ RequestedOn time.Time `json:"requested_on"`
+ Actions []TransactionAction `json:"actions"`
+ } `json:"data"`
+}
+
+func (c *client) GetTransactions(ctx context.Context, page, pageSize int) ([]*Transaction, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "list_transactions")
+
+ if page < 1 {
+ page = 1
+ }
+ if pageSize <= 0 {
+ pageSize = 100
+ }
+
+ accessToken, err := c.getAccessToken(ctx)
+ if err != nil {
+ return nil, fmt.Errorf("oauth token: %w", err)
+ }
+
+ reqBody := searchPaymentsRequest{
+ Query: "",
+ Limit: pageSize,
+ }
+ body, _ := json.Marshal(reqBody)
+
+ url := strings.TrimRight(c.apiBase, "/") + "/payments/search"
+
+ fmt.Sprintf("PAYMENTS URL : %s", url)
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
+ if err != nil {
+ return nil, fmt.Errorf("create search request: %w", err)
+ }
+ req.Header.Set("Authorization", "Bearer "+accessToken)
+ req.Header.Set("Content-Type", "application/json")
+ if req.Header.Get("Accept") == "" {
+ req.Header.Set("Accept", "application/json; schema_version=3.0")
+ }
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("search payments http: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode > 299 {
+ var apiErr map[string]any
+ _ = json.NewDecoder(resp.Body).Decode(&apiErr)
+ return nil, fmt.Errorf("search payments %d: %v", resp.StatusCode, apiErr)
+ }
+
+ var sr searchPaymentsResponse
+ if err := json.NewDecoder(resp.Body).Decode(&sr); err != nil {
+ return nil, fmt.Errorf("decode search payments: %w", err)
+ }
+
+ transactions := make([]*Transaction, 0, len(sr.Data))
+ for _, it := range sr.Data {
+ transactions = append(transactions, &Transaction{
+ ID: it.ID,
+ PaymentID: it.ID,
+ Type: "payment",
+ Scheme: it.Source.Scheme,
+ Status: it.Status,
+ Amount: it.Amount,
+ Currency: it.Currency,
+ SourceAccountReference: c.entityID,
+ Actions: it.Actions,
+ CreatedAt: it.RequestedOn,
+ })
+ }
+
+ return transactions, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/client/transfers.go b/internal/connectors/plugins/public/checkout/client/transfers.go
new file mode 100644
index 00000000..d32494b0
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/client/transfers.go
@@ -0,0 +1,73 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/checkout/checkout-sdk-go/common"
+ "github.com/checkout/checkout-sdk-go/transfers"
+
+ "github.com/formancehq/payments/internal/connectors/metrics"
+)
+
+type TransferRequest struct {
+ Reference string `json:"reference,omitempty"`
+ Reason string `json:"reason,omitempty"`
+
+ Source struct {
+ EntityID string `json:"entity_id"`
+ Currency string `json:"currency"`
+ } `json:"source"`
+
+ Destination struct {
+ EntityID string `json:"entity_id"`
+ Currency string `json:"currency"`
+ } `json:"destination"`
+
+ Amount int64 `json:"amount"`
+ IdempotencyKey string `json:"-"`
+}
+
+type TransferResponse struct {
+ ID string `json:"id"`
+ Status string `json:"status,omitempty"`
+ CreatedOn *time.Time `json:"created_on,omitempty"`
+ Raw any `json:"raw,omitempty"`
+}
+
+type sdkTransferRequest = transfers.TransferRequest
+
+func (c *client) InitiateTransfer(ctx context.Context, tr *TransferRequest) (*TransferResponse, error) {
+ ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "initiate_transfer")
+
+ req := transfers.TransferRequest{
+ Reference: tr.Reference,
+ TransferType: "commission",
+ Source: &transfers.TransferSourceRequest{
+ Id: tr.Source.EntityID,
+ Amount: tr.Amount,
+ Currency: common.Currency(tr.Source.Currency),
+ },
+ Destination: &transfers.TransferDestinationRequest{
+ Id: tr.Destination.EntityID,
+ },
+ }
+
+ var idem *string
+ if tr.IdempotencyKey != "" {
+ idem = &tr.IdempotencyKey
+ }
+
+ resp, err := c.sdk.Transfers.InitiateTransferOfFounds(req, idem)
+ if err != nil {
+ return nil, fmt.Errorf("checkout.accounts.transfers: %w", err)
+ }
+
+ out := &TransferResponse{
+ ID: resp.Id,
+ Status: resp.Status,
+ Raw: resp,
+ }
+ return out, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/config.go b/internal/connectors/plugins/public/checkout/config.go
new file mode 100644
index 00000000..4f8b8c65
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/config.go
@@ -0,0 +1,29 @@
+package checkout
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/go-playground/validator/v10"
+ "github.com/formancehq/payments/internal/models"
+)
+
+type Config struct {
+ // This is the config a user will pass when installing this connector.
+ // Authentication criteria for connecting to your connector should be provided here. Example:
+ Environment string `json:"environment" validate:"required"`
+ OAuthClientID string `json:"oauthClientID" validate:"required"`
+ OAuthClientSecret string `json:"oauthClientSecret" validate:"required"`
+ EntityID string `json:"entityId" validate:"required"`
+ ProcessingChannelId string `json:"processingChannelId" validate:"required"`
+}
+
+func unmarshalAndValidateConfig(payload json.RawMessage) (Config, error) {
+ var config Config
+ if err := json.Unmarshal(payload, &config); err != nil {
+ return Config{}, fmt.Errorf("%w: %w", err, models.ErrInvalidConfig)
+ }
+
+ validate := validator.New(validator.WithRequiredStructEnabled())
+ return config, validate.Struct(config)
+}
diff --git a/internal/connectors/plugins/public/checkout/currencies.go b/internal/connectors/plugins/public/checkout/currencies.go
new file mode 100644
index 00000000..1aa10ef9
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/currencies.go
@@ -0,0 +1,15 @@
+package checkout
+
+import "github.com/formancehq/go-libs/v3/currency"
+
+var (
+ // TODO: the next line tells that the connector is supporting all currencies.
+ // If you only want to support specific currencies, you will have to remove
+ // this line and set the map yourselves
+ // Example:
+ // supportedCurrenciesWithDecimal = map[string]int{
+ // "EUR": currency.ISO4217Currencies["EUR"], // Euro
+ // "DKK": currency.ISO4217Currencies["DKK"],
+ // }
+ supportedCurrenciesWithDecimal = currency.ISO4217Currencies
+)
diff --git a/internal/connectors/plugins/public/checkout/external_accounts.go b/internal/connectors/plugins/public/checkout/external_accounts.go
new file mode 100644
index 00000000..93982fe9
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/external_accounts.go
@@ -0,0 +1,76 @@
+package checkout
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/formancehq/payments/internal/models"
+ "github.com/formancehq/payments/internal/utils/pagination"
+)
+
+type externalAccountsState struct {
+ // TODO: externalAccountsState will be used to know at what point we're at
+ // when fetching the PSP external accounts. We highly recommend to use this
+ // state to not poll data already polled.
+ // This struct will be stored as a raw json, you're free to put whatever
+ // you want.
+ // Example:
+ // LastPage int `json:"lastPage"`
+ // LastIDCreated int64 `json:"lastIDCreated"`
+}
+
+func (p *Plugin) fetchNextExternalAccounts(ctx context.Context, req models.FetchNextExternalAccountsRequest) (models.FetchNextExternalAccountsResponse, error) {
+ var oldState externalAccountsState
+ if req.State != nil {
+ if err := json.Unmarshal(req.State, &oldState); err != nil {
+ return models.FetchNextExternalAccountsResponse{}, err
+ }
+ }
+
+ // TODO: if needed, uncomment the following lines to get the related account in request
+ // var from models.PSPAccount
+ // if req.FromPayload == nil {
+ // return models.FetchNextExternalAccountsResponse{}, models.ErrMissingFromPayloadInRequest
+ // }
+ // if err := json.Unmarshal(req.FromPayload, &from); err != nil {
+ // return models.FetchNextExternalAccountsResponse{}, err
+ // }
+
+ newState := externalAccountsState{
+ // TODO: fill new state with old state values
+ }
+
+ needMore := false
+ hasMore := false
+ accounts := make([]models.PSPAccount, 0, req.PageSize)
+ for /* TODO: range over pages or others */ page := 0; ; page++ {
+ pagedRecipients, err := p.client.GetExternalAccounts(ctx, page, req.PageSize)
+ if err != nil {
+ return models.FetchNextExternalAccountsResponse{}, err
+ }
+
+ // TODO: transfer PSP object into formance object
+ accounts = append(accounts, models.PSPAccount{})
+
+ needMore, hasMore = pagination.ShouldFetchMore(accounts, pagedRecipients, req.PageSize)
+ if !needMore || !hasMore {
+ break
+ }
+ }
+
+ if !needMore {
+ accounts = accounts[:req.PageSize]
+ }
+
+ // TODO: don't forget to update your state accordingly
+ payload, err := json.Marshal(newState)
+ if err != nil {
+ return models.FetchNextExternalAccountsResponse{}, err
+ }
+
+ return models.FetchNextExternalAccountsResponse{
+ ExternalAccounts: accounts,
+ NewState: payload,
+ HasMore: hasMore,
+ }, nil
+}
\ No newline at end of file
diff --git a/internal/connectors/plugins/public/checkout/payments.go b/internal/connectors/plugins/public/checkout/payments.go
new file mode 100644
index 00000000..b785477f
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/payments.go
@@ -0,0 +1,176 @@
+package checkout
+
+import (
+ "context"
+ "encoding/json"
+ "math/big"
+ "strings"
+
+ "github.com/formancehq/payments/internal/models"
+ "github.com/formancehq/payments/internal/utils/pagination"
+ "github.com/formancehq/go-libs/v3/currency"
+)
+
+type paymentsState struct {
+ LastPage int `json:"lastPage"`
+}
+
+func mapCheckoutPaymentStatus(s string) models.PaymentStatus {
+ if s == "" {
+ return models.PAYMENT_STATUS_UNKNOWN
+ }
+ ls := strings.ToLower(strings.TrimSpace(s))
+
+ switch ls {
+ case "authorized", "authorised", "card verified", "approved":
+ return models.PAYMENT_STATUS_AUTHORISATION
+ case "captured", "capture", "partially captured":
+ return models.PAYMENT_STATUS_CAPTURE
+ case "refunded", "partially refunded":
+ return models.PAYMENT_STATUS_REFUNDED
+ case "pending", "capture pending", "refund pending":
+ return models.PAYMENT_STATUS_PENDING
+ case "declined", "failed", "failure":
+ return models.PAYMENT_STATUS_FAILED
+ case "expired":
+ return models.PAYMENT_STATUS_EXPIRED
+ case "canceled", "cancelled", "voided", "void":
+ return models.PAYMENT_STATUS_CANCELLED
+ case "refund declined", "refund_failed", "refund failed":
+ return models.PAYMENT_STATUS_REFUNDED_FAILURE
+ case "refund reversed", "reversed":
+ return models.PAYMENT_STATUS_REFUND_REVERSED
+ case "disputed", "chargeback":
+ return models.PAYMENT_STATUS_DISPUTE
+ case "chargeback won", "dispute won":
+ return models.PAYMENT_STATUS_DISPUTE_WON
+ case "chargeback lost", "dispute lost":
+ return models.PAYMENT_STATUS_DISPUTE_LOST
+ default:
+ return models.PAYMENT_STATUS_OTHER
+ }
+}
+
+func mapCheckoutScheme(scheme string) models.PaymentScheme {
+ switch strings.ToUpper(scheme) {
+ case "VISA":
+ return models.PAYMENT_SCHEME_CARD_VISA
+ case "MASTERCARD":
+ return models.PAYMENT_SCHEME_CARD_MASTERCARD
+ case "AMEX", "AMERICAN_EXPRESS":
+ return models.PAYMENT_SCHEME_CARD_AMEX
+ case "DINERS":
+ return models.PAYMENT_SCHEME_CARD_DINERS
+ case "DISCOVER":
+ return models.PAYMENT_SCHEME_CARD_DISCOVER
+ case "JCB":
+ return models.PAYMENT_SCHEME_CARD_JCB
+ case "UNIONPAY", "UNION_PAY":
+ return models.PAYMENT_SCHEME_CARD_UNION_PAY
+ case "ALIPAY":
+ return models.PAYMENT_SCHEME_CARD_ALIPAY
+ case "CUP":
+ return models.PAYMENT_SCHEME_CARD_CUP
+ case "GOOGLEPAY", "GOOGLE_PAY":
+ return models.PAYMENT_SCHEME_GOOGLE_PAY
+ case "APPLEPAY", "APPLE_PAY":
+ return models.PAYMENT_SCHEME_APPLE_PAY
+ case "MAESTRO":
+ return models.PAYMENT_SCHEME_MAESTRO
+ case "ACH":
+ return models.PAYMENT_SCHEME_ACH
+ case "ACH_DEBIT":
+ return models.PAYMENT_SCHEME_ACH_DEBIT
+ case "RTP":
+ return models.PAYMENT_SCHEME_RTP
+ case "SEPA":
+ return models.PAYMENT_SCHEME_SEPA
+ case "SEPA_CREDIT":
+ return models.PAYMENT_SCHEME_SEPA_CREDIT
+ case "SEPA_DEBIT":
+ return models.PAYMENT_SCHEME_SEPA_DEBIT
+ default:
+ return models.PAYMENT_SCHEME_OTHER
+ }
+}
+
+func (p *Plugin) fetchNextPayments(ctx context.Context, req models.FetchNextPaymentsRequest) (models.FetchNextPaymentsResponse, error) {
+ var oldState paymentsState
+ if req.State != nil {
+ if err := json.Unmarshal(req.State, &oldState); err != nil {
+ return models.FetchNextPaymentsResponse{}, err
+ }
+ }
+
+ startPage := oldState.LastPage + 1
+ newState := paymentsState{
+ LastPage: oldState.LastPage,
+ }
+
+ payments := make([]models.PSPPayment, 0, req.PageSize)
+ needMore := false
+ hasMore := false
+
+ for page := startPage; ; page++ {
+ pagedTxs, err := p.client.GetTransactions(ctx, page, req.PageSize)
+ if err != nil {
+ return models.FetchNextPaymentsResponse{}, err
+ }
+
+ for _, t := range pagedTxs {
+ raw, _ := json.Marshal(t)
+
+ asset := currency.FormatAsset(supportedCurrenciesWithDecimal, t.Currency)
+
+ md := map[string]string{
+ "payment_id": t.PaymentID,
+ "type": t.Type,
+ "status": t.Status,
+ }
+
+ paymentType := models.PAYMENT_TYPE_PAYIN
+ for _, act := range t.Actions {
+ if strings.EqualFold(act.Type, "Payout") {
+ paymentType = models.PAYMENT_TYPE_PAYOUT
+ break
+ }
+ }
+
+ payments = append(payments, models.PSPPayment{
+ ParentReference: "",
+ Reference: t.ID,
+ CreatedAt: t.CreatedAt,
+ Type: paymentType,
+ Amount: big.NewInt(t.Amount),
+ Asset: asset,
+ Scheme: mapCheckoutScheme(t.Scheme),
+ Status: mapCheckoutPaymentStatus(t.Status),
+ SourceAccountReference: &t.SourceAccountReference,
+ Metadata: md,
+ Raw: raw,
+ })
+ }
+
+ needMore, hasMore = pagination.ShouldFetchMore(payments, pagedTxs, req.PageSize)
+ newState.LastPage = page
+
+ if !needMore || !hasMore {
+ break
+ }
+ }
+
+ if !needMore && len(payments) > req.PageSize {
+ payments = payments[:req.PageSize]
+ }
+
+ payload, err := json.Marshal(newState)
+ if err != nil {
+ return models.FetchNextPaymentsResponse{}, err
+ }
+
+ return models.FetchNextPaymentsResponse{
+ Payments: payments,
+ NewState: payload,
+ HasMore: hasMore,
+ }, nil
+}
diff --git a/internal/connectors/plugins/public/checkout/payouts.go b/internal/connectors/plugins/public/checkout/payouts.go
new file mode 100644
index 00000000..7f0f6bb5
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/payouts.go
@@ -0,0 +1,58 @@
+package checkout
+
+import (
+ "context"
+ "errors"
+
+ "github.com/formancehq/payments/internal/connectors/plugins/public/checkout/client"
+ "github.com/formancehq/payments/internal/models"
+)
+
+func (p *Plugin) createPayout(ctx context.Context, pi models.PSPPaymentInitiation) (*models.PSPPayment, error) {
+ if err := p.validateTransferPayoutRequests(pi); err != nil {
+ return nil, err
+ }
+
+ var pr client.PayoutRequest
+ pr.Amount = pi.Amount.Int64()
+ pr.Currency = pi.Asset
+ pr.Reference = pi.Reference
+ pr.SourceEntityID = pi.SourceAccount.Reference
+ pr.DestinationInstrumentID = pi.DestinationAccount.Reference
+ pr.BillingDescriptor = pi.Description
+ pr.IdempotencyKey = p.generateIdempotencyKey(pi.Reference)
+
+ resp, err := p.client.InitiatePayout(ctx, &pr)
+ if err != nil {
+ return nil, err
+ }
+
+ return payoutToPayment(resp)
+}
+
+func payoutToPayment(from *client.PayoutResponse) (*models.PSPPayment, error) {
+ if from == nil {
+ return nil, errors.New("nil payout response")
+ }
+
+ p := &models.PSPPayment{
+ Status: mapStatus(from),
+ Reference: from.Reference,
+ }
+ return p, nil
+}
+
+func mapStatus(from *client.PayoutResponse) models.PaymentStatus {
+ switch from.Status {
+ case "Pending":
+ return models.PAYMENT_STATUS_PENDING
+ case "Captured", "Authorized", "Active":
+ return models.PAYMENT_STATUS_SUCCEEDED
+ case "Declined", "Failed", "Voided":
+ return models.PAYMENT_STATUS_FAILED
+ case "Canceled":
+ return models.PAYMENT_STATUS_CANCELLED
+ default:
+ return models.PAYMENT_STATUS_UNKNOWN
+ }
+}
\ No newline at end of file
diff --git a/internal/connectors/plugins/public/checkout/plugin.go b/internal/connectors/plugins/public/checkout/plugin.go
new file mode 100644
index 00000000..aea6ef40
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/plugin.go
@@ -0,0 +1,164 @@
+package checkout
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/formancehq/go-libs/v3/logging"
+ "github.com/formancehq/payments/internal/connectors/plugins"
+ "github.com/formancehq/payments/internal/connectors/plugins/public/checkout/client"
+ "github.com/formancehq/payments/internal/connectors/plugins/registry"
+ "github.com/formancehq/payments/internal/models"
+)
+
+const ProviderName = "checkout"
+
+func init() {
+ registry.RegisterPlugin(ProviderName, models.PluginTypePSP, func(_ models.ConnectorID, name string, logger logging.Logger, rm json.RawMessage) (models.Plugin, error) {
+ return New(name, logger, rm)
+ }, capabilities, Config{})
+}
+
+type Plugin struct {
+ models.Plugin
+
+ name string
+ logger logging.Logger
+
+ client client.Client
+}
+
+func New(name string, logger logging.Logger, rawConfig json.RawMessage) (*Plugin, error) {
+ cfg, err := unmarshalAndValidateConfig(rawConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ cl := client.New(
+ cfg.Environment,
+ cfg.OAuthClientID,
+ cfg.OAuthClientSecret,
+ cfg.EntityID,
+ cfg.ProcessingChannelId,
+ )
+
+ return &Plugin{
+ Plugin: plugins.NewBasePlugin(),
+ name: name,
+ logger: logger,
+ client: cl,
+ }, nil
+}
+
+
+func (p *Plugin) Name() string {
+ return p.name
+}
+
+func (p *Plugin) Install(_ context.Context, req models.InstallRequest) (models.InstallResponse, error) {
+ return models.InstallResponse{
+ Workflow: workflow(),
+ }, nil
+}
+
+func (p *Plugin) Uninstall(ctx context.Context, req models.UninstallRequest) (models.UninstallResponse, error) {
+ return models.UninstallResponse{}, nil
+}
+
+func (p *Plugin) FetchNextAccounts(ctx context.Context, req models.FetchNextAccountsRequest) (models.FetchNextAccountsResponse, error) {
+ if p.client == nil {
+ return models.FetchNextAccountsResponse{}, plugins.ErrNotYetInstalled
+ }
+ return p.fetchNextAccounts(ctx, req)
+}
+
+func (p *Plugin) FetchNextBalances(ctx context.Context, req models.FetchNextBalancesRequest) (models.FetchNextBalancesResponse, error) {
+ if p.client == nil {
+ return models.FetchNextBalancesResponse{}, plugins.ErrNotYetInstalled
+ }
+ return p.fetchNextBalances(ctx, req)
+}
+
+func (p *Plugin) FetchNextExternalAccounts(ctx context.Context, req models.FetchNextExternalAccountsRequest) (models.FetchNextExternalAccountsResponse, error) {
+ if p.client == nil {
+ return models.FetchNextExternalAccountsResponse{}, plugins.ErrNotYetInstalled
+ }
+ return p.fetchNextExternalAccounts(ctx, req)
+}
+
+func (p *Plugin) FetchNextPayments(ctx context.Context, req models.FetchNextPaymentsRequest) (models.FetchNextPaymentsResponse, error) {
+ if p.client == nil {
+ return models.FetchNextPaymentsResponse{}, plugins.ErrNotYetInstalled
+ }
+ return p.fetchNextPayments(ctx, req)
+}
+
+func (p *Plugin) FetchNextOthers(ctx context.Context, req models.FetchNextOthersRequest) (models.FetchNextOthersResponse, error) {
+ return models.FetchNextOthersResponse{}, plugins.ErrNotImplemented
+}
+
+func (p *Plugin) CreateBankAccount(ctx context.Context, req models.CreateBankAccountRequest) (models.CreateBankAccountResponse, error) {
+ return models.CreateBankAccountResponse{}, plugins.ErrNotImplemented
+}
+
+func (p *Plugin) CreateTransfer(ctx context.Context, req models.CreateTransferRequest) (models.CreateTransferResponse, error) {
+ if p.client == nil {
+ return models.CreateTransferResponse{}, plugins.ErrNotYetInstalled
+ }
+
+ payment, err := p.createTransfer(ctx, req.PaymentInitiation)
+ if err != nil {
+ return models.CreateTransferResponse{}, err
+ }
+
+ return models.CreateTransferResponse{
+ Payment: payment,
+ }, nil
+}
+
+func (p *Plugin) ReverseTransfer(ctx context.Context, req models.ReverseTransferRequest) (models.ReverseTransferResponse, error) {
+ return models.ReverseTransferResponse{}, plugins.ErrNotImplemented
+}
+
+// Note: Fill only if we cannot have the related payment in the CreateTransfer method
+func (p *Plugin) PollTransferStatus(ctx context.Context, req models.PollTransferStatusRequest) (models.PollTransferStatusResponse, error) {
+ return models.PollTransferStatusResponse{}, plugins.ErrNotImplemented
+}
+
+func (p *Plugin) CreatePayout(ctx context.Context, req models.CreatePayoutRequest) (models.CreatePayoutResponse, error) {
+ if p.client == nil {
+ return models.CreatePayoutResponse{}, plugins.ErrNotYetInstalled
+ }
+
+ payment, err := p.createPayout(ctx, req.PaymentInitiation)
+ if err != nil {
+ return models.CreatePayoutResponse{}, err
+ }
+
+ return models.CreatePayoutResponse{
+ Payment: payment,
+ }, nil
+}
+
+func (p *Plugin) ReversePayout(ctx context.Context, req models.ReversePayoutRequest) (models.ReversePayoutResponse, error) {
+ return models.ReversePayoutResponse{}, plugins.ErrNotImplemented
+}
+
+// Note: Fill only if we cannot have the related payment in the CreatePayout method
+func (p *Plugin) PollPayoutStatus(ctx context.Context, req models.PollPayoutStatusRequest) (models.PollPayoutStatusResponse, error) {
+ return models.PollPayoutStatusResponse{}, plugins.ErrNotImplemented
+}
+
+// Note: if the connector has webhooks, use this method to create the related
+// webhooks on the PSP.
+func (p *Plugin) CreateWebhooks(ctx context.Context, req models.CreateWebhooksRequest) (models.CreateWebhooksResponse, error) {
+ return models.CreateWebhooksResponse{}, plugins.ErrNotImplemented
+}
+
+// Note: if the connector has webhooks, use this method to translate incoming
+// webhooks to a formance object.
+func (p *Plugin) TranslateWebhook(ctx context.Context, req models.TranslateWebhookRequest) (models.TranslateWebhookResponse, error) {
+ return models.TranslateWebhookResponse{}, plugins.ErrNotImplemented
+}
+
+var _ models.Plugin = &Plugin{}
diff --git a/internal/connectors/plugins/public/checkout/transfers.go b/internal/connectors/plugins/public/checkout/transfers.go
new file mode 100644
index 00000000..14751fb4
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/transfers.go
@@ -0,0 +1,76 @@
+package checkout
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+ "time"
+ "math/big"
+
+ "github.com/formancehq/payments/internal/connectors/plugins/public/checkout/client"
+ "github.com/formancehq/payments/internal/models"
+
+ "github.com/formancehq/go-libs/v3/currency"
+)
+
+func (p *Plugin) createTransfer(ctx context.Context, pi models.PSPPaymentInitiation) (*models.PSPPayment, error) {
+ if err := p.validateTransferPayoutRequests(pi); err != nil {
+ return nil, err
+ }
+
+ tr := &client.TransferRequest{
+ Reference: pi.Reference,
+ Reason: "Formance transfer",
+ }
+ tr.Source.EntityID = pi.SourceAccount.Reference
+ tr.Source.Currency = pi.Asset
+ tr.Destination.EntityID = pi.DestinationAccount.Reference
+ tr.Destination.Currency = pi.Asset
+ tr.Amount = pi.Amount.Int64()
+ tr.IdempotencyKey = p.generateIdempotencyKey(pi.Reference)
+
+ resp, err := p.client.InitiateTransfer(ctx, tr)
+ if err != nil {
+ return nil, err
+ }
+
+ raw, _ := json.Marshal(resp)
+
+ createdAt := time.Now().UTC()
+ if resp.CreatedOn != nil {
+ createdAt = *resp.CreatedOn
+ }
+
+ asset := currency.FormatAsset(supportedCurrenciesWithDecimal, tr.Source.Currency)
+
+ return &models.PSPPayment{
+ ParentReference: "",
+ Reference: resp.ID,
+ CreatedAt: createdAt,
+ Type: models.PAYMENT_TYPE_TRANSFER,
+ Status: mapTransferStatusToPaymentStatus(resp.Status),
+ Amount: big.NewInt(tr.Amount),
+ Asset: asset,
+ Raw: raw,
+ }, nil
+}
+
+func mapTransferStatusToPaymentStatus(s string) models.PaymentStatus {
+ ls := strings.ToLower(strings.TrimSpace(s))
+ switch ls {
+ case "pending", "requested", "processing":
+ return models.PAYMENT_STATUS_PENDING
+ case "approved", "succeeded", "completed", "successful":
+ return models.PAYMENT_STATUS_SUCCEEDED
+ case "failed", "declined", "rejected", "error":
+ return models.PAYMENT_STATUS_FAILED
+ case "canceled", "cancelled", "voided":
+ return models.PAYMENT_STATUS_CANCELLED
+ case "reversed":
+ return models.PAYMENT_STATUS_REFUND_REVERSED
+ default:
+ return models.PAYMENT_STATUS_OTHER
+ }
+}
+
+func strPtr(s string) *string { return &s }
diff --git a/internal/connectors/plugins/public/checkout/utils.go b/internal/connectors/plugins/public/checkout/utils.go
new file mode 100644
index 00000000..6705cb36
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/utils.go
@@ -0,0 +1,28 @@
+package checkout
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "github.com/formancehq/payments/internal/models"
+)
+
+func (p *Plugin) validateTransferPayoutRequests(pi models.PSPPaymentInitiation) error {
+ if pi.SourceAccount == nil {
+ return fmt.Errorf("source account is required: %w", models.ErrInvalidRequest)
+ }
+
+ if pi.DestinationAccount == nil {
+ return fmt.Errorf("destination account is required: %w", models.ErrInvalidRequest)
+ }
+
+ return nil
+}
+
+func (p *Plugin) generateIdempotencyKey(values ...string) string {
+ joined := strings.Join(values, "-")
+ hash := sha256.Sum256([]byte(joined))
+ return hex.EncodeToString(hash[:])
+}
\ No newline at end of file
diff --git a/internal/connectors/plugins/public/checkout/workflow.go b/internal/connectors/plugins/public/checkout/workflow.go
new file mode 100644
index 00000000..5ad3f711
--- /dev/null
+++ b/internal/connectors/plugins/public/checkout/workflow.go
@@ -0,0 +1,27 @@
+package checkout
+
+import "github.com/formancehq/payments/internal/models"
+
+func workflow() models.ConnectorTasksTree {
+ return []models.ConnectorTaskTree{
+ {
+
+ TaskType: models.TASK_FETCH_ACCOUNTS,
+ Name: "fetch_accounts",
+ Periodically: true,
+ NextTasks: []models.ConnectorTaskTree{},
+ },
+ {
+ TaskType: models.TASK_FETCH_BALANCES,
+ Name: "fetch_balances",
+ Periodically: true,
+ NextTasks: []models.ConnectorTaskTree{},
+ },
+ {
+ TaskType: models.TASK_FETCH_PAYMENTS,
+ Name: "fetch_payments",
+ Periodically: true,
+ NextTasks: []models.ConnectorTaskTree{},
+ },
+ }
+}
\ No newline at end of file
diff --git a/internal/connectors/plugins/public/list.go b/internal/connectors/plugins/public/list.go
index 7f919fb5..ab045929 100644
--- a/internal/connectors/plugins/public/list.go
+++ b/internal/connectors/plugins/public/list.go
@@ -4,6 +4,7 @@ import (
_ "github.com/formancehq/payments/internal/connectors/plugins/public/adyen"
_ "github.com/formancehq/payments/internal/connectors/plugins/public/atlar"
_ "github.com/formancehq/payments/internal/connectors/plugins/public/bankingcircle"
+ _ "github.com/formancehq/payments/internal/connectors/plugins/public/checkout"
_ "github.com/formancehq/payments/internal/connectors/plugins/public/column"
_ "github.com/formancehq/payments/internal/connectors/plugins/public/currencycloud"
_ "github.com/formancehq/payments/internal/connectors/plugins/public/dummypay"
diff --git a/openapi.yaml b/openapi.yaml
index d4ac7ae8..306af810 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -5125,6 +5125,7 @@ components:
Adyen: '#/components/schemas/V3AdyenConfig'
Atlar: '#/components/schemas/V3AtlarConfig'
Bankingcircle: '#/components/schemas/V3BankingcircleConfig'
+ Checkout: '#/components/schemas/V3CheckoutConfig'
Column: '#/components/schemas/V3ColumnConfig'
Currencycloud: '#/components/schemas/V3CurrencycloudConfig'
Dummypay: '#/components/schemas/V3DummypayConfig'
@@ -5142,6 +5143,7 @@ components:
- $ref: '#/components/schemas/V3AdyenConfig'
- $ref: '#/components/schemas/V3AtlarConfig'
- $ref: '#/components/schemas/V3BankingcircleConfig'
+ - $ref: '#/components/schemas/V3CheckoutConfig'
- $ref: '#/components/schemas/V3ColumnConfig'
- $ref: '#/components/schemas/V3CurrencycloudConfig'
- $ref: '#/components/schemas/V3DummypayConfig'
@@ -5242,6 +5244,37 @@ components:
type: string
username:
type: string
+ V3CheckoutConfig:
+ type: object
+ required:
+ - name
+ - environment
+ - oauthClientID
+ - oauthClientSecret
+ - entityId
+ - processingChannelId
+ properties:
+ entityId:
+ type: string
+ environment:
+ type: string
+ name:
+ type: string
+ oauthClientID:
+ type: string
+ oauthClientSecret:
+ type: string
+ pageSize:
+ type: integer
+ default: "25"
+ pollingPeriod:
+ type: string
+ default: 2m
+ processingChannelId:
+ type: string
+ provider:
+ type: string
+ default: Checkout
V3ColumnConfig:
type: object
required:
diff --git a/openapi/v3/v3-connectors-config.yaml b/openapi/v3/v3-connectors-config.yaml
index 70e3519f..78775d5f 100644
--- a/openapi/v3/v3-connectors-config.yaml
+++ b/openapi/v3/v3-connectors-config.yaml
@@ -7,6 +7,7 @@ components:
Adyen: '#/components/schemas/V3AdyenConfig'
Atlar: '#/components/schemas/V3AtlarConfig'
Bankingcircle: '#/components/schemas/V3BankingcircleConfig'
+ Checkout: '#/components/schemas/V3CheckoutConfig'
Column: '#/components/schemas/V3ColumnConfig'
Currencycloud: '#/components/schemas/V3CurrencycloudConfig'
Dummypay: '#/components/schemas/V3DummypayConfig'
@@ -24,6 +25,7 @@ components:
- $ref: '#/components/schemas/V3AdyenConfig'
- $ref: '#/components/schemas/V3AtlarConfig'
- $ref: '#/components/schemas/V3BankingcircleConfig'
+ - $ref: '#/components/schemas/V3CheckoutConfig'
- $ref: '#/components/schemas/V3ColumnConfig'
- $ref: '#/components/schemas/V3CurrencycloudConfig'
- $ref: '#/components/schemas/V3DummypayConfig'
@@ -124,6 +126,37 @@ components:
type: string
username:
type: string
+ V3CheckoutConfig:
+ type: object
+ required:
+ - name
+ - environment
+ - oauthClientID
+ - oauthClientSecret
+ - entityId
+ - processingChannelId
+ properties:
+ entityId:
+ type: string
+ environment:
+ type: string
+ name:
+ type: string
+ oauthClientID:
+ type: string
+ oauthClientSecret:
+ type: string
+ pageSize:
+ type: integer
+ default: "25"
+ pollingPeriod:
+ type: string
+ default: 2m
+ processingChannelId:
+ type: string
+ provider:
+ type: string
+ default: Checkout
V3ColumnConfig:
type: object
required:
diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock
index c586d001..73480268 100644
--- a/pkg/client/.speakeasy/gen.lock
+++ b/pkg/client/.speakeasy/gen.lock
@@ -1,7 +1,7 @@
lockVersion: 2.0.0
id: 1fa8a26f-45d9-44b7-8b97-fbeebcdcd8b1
management:
- docChecksum: 6f38fd737465b7a4c97ae7e0794b7b8c
+ docChecksum: dd8764ef73990fc8d6eaa115b4ea8507
docVersion: v1
speakeasyVersion: 1.525.0
generationVersion: 2.562.2
@@ -119,6 +119,7 @@ generatedFiles:
- /models/components/v3bankaccountrelatedaccount.go
- /models/components/v3bankaccountscursorresponse.go
- /models/components/v3bankingcircleconfig.go
+ - /models/components/v3checkoutconfig.go
- /models/components/v3columnconfig.go
- /models/components/v3connector.go
- /models/components/v3connectorconfig.go
@@ -421,6 +422,7 @@ generatedFiles:
- docs/models/components/v3bankaccountscursorresponse.md
- docs/models/components/v3bankaccountscursorresponsecursor.md
- docs/models/components/v3bankingcircleconfig.md
+ - docs/models/components/v3checkoutconfig.md
- docs/models/components/v3columnconfig.md
- docs/models/components/v3connector.md
- docs/models/components/v3connectorconfig.md
diff --git a/pkg/client/docs/models/components/v3checkoutconfig.md b/pkg/client/docs/models/components/v3checkoutconfig.md
new file mode 100644
index 00000000..5280302b
--- /dev/null
+++ b/pkg/client/docs/models/components/v3checkoutconfig.md
@@ -0,0 +1,16 @@
+# V3CheckoutConfig
+
+
+## Fields
+
+| Field | Type | Required | Description |
+| --------------------- | --------------------- | --------------------- | --------------------- |
+| `EntityID` | *string* | :heavy_check_mark: | N/A |
+| `Environment` | *string* | :heavy_check_mark: | N/A |
+| `Name` | *string* | :heavy_check_mark: | N/A |
+| `OauthClientID` | *string* | :heavy_check_mark: | N/A |
+| `OauthClientSecret` | *string* | :heavy_check_mark: | N/A |
+| `PageSize` | **int64* | :heavy_minus_sign: | N/A |
+| `PollingPeriod` | **string* | :heavy_minus_sign: | N/A |
+| `ProcessingChannelID` | *string* | :heavy_check_mark: | N/A |
+| `Provider` | **string* | :heavy_minus_sign: | N/A |
\ No newline at end of file
diff --git a/pkg/client/docs/models/components/v3connectorconfig.md b/pkg/client/docs/models/components/v3connectorconfig.md
index b41f700d..13725724 100644
--- a/pkg/client/docs/models/components/v3connectorconfig.md
+++ b/pkg/client/docs/models/components/v3connectorconfig.md
@@ -21,6 +21,12 @@ v3ConnectorConfig := components.CreateV3ConnectorConfigAtlar(components.V3AtlarC
v3ConnectorConfig := components.CreateV3ConnectorConfigBankingcircle(components.V3BankingcircleConfig{/* values here */})
```
+### V3CheckoutConfig
+
+```go
+v3ConnectorConfig := components.CreateV3ConnectorConfigCheckout(components.V3CheckoutConfig{/* values here */})
+```
+
### V3ColumnConfig
```go
diff --git a/pkg/client/docs/models/components/v3installconnectorrequest.md b/pkg/client/docs/models/components/v3installconnectorrequest.md
index 19281bc7..296d889c 100644
--- a/pkg/client/docs/models/components/v3installconnectorrequest.md
+++ b/pkg/client/docs/models/components/v3installconnectorrequest.md
@@ -21,6 +21,12 @@ v3InstallConnectorRequest := components.CreateV3InstallConnectorRequestAtlar(com
v3InstallConnectorRequest := components.CreateV3InstallConnectorRequestBankingcircle(components.V3BankingcircleConfig{/* values here */})
```
+### V3CheckoutConfig
+
+```go
+v3InstallConnectorRequest := components.CreateV3InstallConnectorRequestCheckout(components.V3CheckoutConfig{/* values here */})
+```
+
### V3ColumnConfig
```go
diff --git a/pkg/client/docs/models/components/v3updateconnectorrequest.md b/pkg/client/docs/models/components/v3updateconnectorrequest.md
index dc284bf9..e3117990 100644
--- a/pkg/client/docs/models/components/v3updateconnectorrequest.md
+++ b/pkg/client/docs/models/components/v3updateconnectorrequest.md
@@ -21,6 +21,12 @@ v3UpdateConnectorRequest := components.CreateV3UpdateConnectorRequestAtlar(compo
v3UpdateConnectorRequest := components.CreateV3UpdateConnectorRequestBankingcircle(components.V3BankingcircleConfig{/* values here */})
```
+### V3CheckoutConfig
+
+```go
+v3UpdateConnectorRequest := components.CreateV3UpdateConnectorRequestCheckout(components.V3CheckoutConfig{/* values here */})
+```
+
### V3ColumnConfig
```go
diff --git a/pkg/client/models/components/v3checkoutconfig.go b/pkg/client/models/components/v3checkoutconfig.go
new file mode 100644
index 00000000..d63add3f
--- /dev/null
+++ b/pkg/client/models/components/v3checkoutconfig.go
@@ -0,0 +1,93 @@
+// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
+
+package components
+
+import (
+ "github.com/formancehq/payments/pkg/client/internal/utils"
+)
+
+type V3CheckoutConfig struct {
+ EntityID string `json:"entityId"`
+ Environment string `json:"environment"`
+ Name string `json:"name"`
+ OauthClientID string `json:"oauthClientID"`
+ OauthClientSecret string `json:"oauthClientSecret"`
+ PageSize *int64 `default:"25" json:"pageSize"`
+ PollingPeriod *string `default:"2m" json:"pollingPeriod"`
+ ProcessingChannelID string `json:"processingChannelId"`
+ Provider *string `default:"Checkout" json:"provider"`
+}
+
+func (v V3CheckoutConfig) MarshalJSON() ([]byte, error) {
+ return utils.MarshalJSON(v, "", false)
+}
+
+func (v *V3CheckoutConfig) UnmarshalJSON(data []byte) error {
+ if err := utils.UnmarshalJSON(data, &v, "", false, false); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (o *V3CheckoutConfig) GetEntityID() string {
+ if o == nil {
+ return ""
+ }
+ return o.EntityID
+}
+
+func (o *V3CheckoutConfig) GetEnvironment() string {
+ if o == nil {
+ return ""
+ }
+ return o.Environment
+}
+
+func (o *V3CheckoutConfig) GetName() string {
+ if o == nil {
+ return ""
+ }
+ return o.Name
+}
+
+func (o *V3CheckoutConfig) GetOauthClientID() string {
+ if o == nil {
+ return ""
+ }
+ return o.OauthClientID
+}
+
+func (o *V3CheckoutConfig) GetOauthClientSecret() string {
+ if o == nil {
+ return ""
+ }
+ return o.OauthClientSecret
+}
+
+func (o *V3CheckoutConfig) GetPageSize() *int64 {
+ if o == nil {
+ return nil
+ }
+ return o.PageSize
+}
+
+func (o *V3CheckoutConfig) GetPollingPeriod() *string {
+ if o == nil {
+ return nil
+ }
+ return o.PollingPeriod
+}
+
+func (o *V3CheckoutConfig) GetProcessingChannelID() string {
+ if o == nil {
+ return ""
+ }
+ return o.ProcessingChannelID
+}
+
+func (o *V3CheckoutConfig) GetProvider() *string {
+ if o == nil {
+ return nil
+ }
+ return o.Provider
+}
diff --git a/pkg/client/models/components/v3connectorconfig.go b/pkg/client/models/components/v3connectorconfig.go
index bb03414c..b34f287a 100644
--- a/pkg/client/models/components/v3connectorconfig.go
+++ b/pkg/client/models/components/v3connectorconfig.go
@@ -15,6 +15,7 @@ const (
V3ConnectorConfigTypeAdyen V3ConnectorConfigType = "Adyen"
V3ConnectorConfigTypeAtlar V3ConnectorConfigType = "Atlar"
V3ConnectorConfigTypeBankingcircle V3ConnectorConfigType = "Bankingcircle"
+ V3ConnectorConfigTypeCheckout V3ConnectorConfigType = "Checkout"
V3ConnectorConfigTypeColumn V3ConnectorConfigType = "Column"
V3ConnectorConfigTypeCurrencycloud V3ConnectorConfigType = "Currencycloud"
V3ConnectorConfigTypeDummypay V3ConnectorConfigType = "Dummypay"
@@ -34,6 +35,7 @@ type V3ConnectorConfig struct {
V3AdyenConfig *V3AdyenConfig `queryParam:"inline"`
V3AtlarConfig *V3AtlarConfig `queryParam:"inline"`
V3BankingcircleConfig *V3BankingcircleConfig `queryParam:"inline"`
+ V3CheckoutConfig *V3CheckoutConfig `queryParam:"inline"`
V3ColumnConfig *V3ColumnConfig `queryParam:"inline"`
V3CurrencycloudConfig *V3CurrencycloudConfig `queryParam:"inline"`
V3DummypayConfig *V3DummypayConfig `queryParam:"inline"`
@@ -87,6 +89,18 @@ func CreateV3ConnectorConfigBankingcircle(bankingcircle V3BankingcircleConfig) V
}
}
+func CreateV3ConnectorConfigCheckout(checkout V3CheckoutConfig) V3ConnectorConfig {
+ typ := V3ConnectorConfigTypeCheckout
+
+ typStr := string(typ)
+ checkout.Provider = &typStr
+
+ return V3ConnectorConfig{
+ V3CheckoutConfig: &checkout,
+ Type: typ,
+ }
+}
+
func CreateV3ConnectorConfigColumn(column V3ColumnConfig) V3ConnectorConfig {
typ := V3ConnectorConfigTypeColumn
@@ -282,6 +296,15 @@ func (u *V3ConnectorConfig) UnmarshalJSON(data []byte) error {
u.V3BankingcircleConfig = v3BankingcircleConfig
u.Type = V3ConnectorConfigTypeBankingcircle
return nil
+ case "Checkout":
+ v3CheckoutConfig := new(V3CheckoutConfig)
+ if err := utils.UnmarshalJSON(data, &v3CheckoutConfig, "", true, false); err != nil {
+ return fmt.Errorf("could not unmarshal `%s` into expected (Provider == Checkout) type V3CheckoutConfig within V3ConnectorConfig: %w", string(data), err)
+ }
+
+ u.V3CheckoutConfig = v3CheckoutConfig
+ u.Type = V3ConnectorConfigTypeCheckout
+ return nil
case "Column":
v3ColumnConfig := new(V3ColumnConfig)
if err := utils.UnmarshalJSON(data, &v3ColumnConfig, "", true, false); err != nil {
@@ -417,6 +440,10 @@ func (u V3ConnectorConfig) MarshalJSON() ([]byte, error) {
return utils.MarshalJSON(u.V3BankingcircleConfig, "", true)
}
+ if u.V3CheckoutConfig != nil {
+ return utils.MarshalJSON(u.V3CheckoutConfig, "", true)
+ }
+
if u.V3ColumnConfig != nil {
return utils.MarshalJSON(u.V3ColumnConfig, "", true)
}
diff --git a/pkg/client/models/components/v3getconnectorconfigresponse.go b/pkg/client/models/components/v3getconnectorconfigresponse.go
index a7b67f5a..3fab6a89 100644
--- a/pkg/client/models/components/v3getconnectorconfigresponse.go
+++ b/pkg/client/models/components/v3getconnectorconfigresponse.go
@@ -25,6 +25,10 @@ func (o *V3GetConnectorConfigResponse) GetDataBankingcircle() *V3BankingcircleCo
return o.GetData().V3BankingcircleConfig
}
+func (o *V3GetConnectorConfigResponse) GetDataCheckout() *V3CheckoutConfig {
+ return o.GetData().V3CheckoutConfig
+}
+
func (o *V3GetConnectorConfigResponse) GetDataColumn() *V3ColumnConfig {
return o.GetData().V3ColumnConfig
}
diff --git a/pkg/client/models/components/v3installconnectorrequest.go b/pkg/client/models/components/v3installconnectorrequest.go
index 0ca8261d..38195516 100644
--- a/pkg/client/models/components/v3installconnectorrequest.go
+++ b/pkg/client/models/components/v3installconnectorrequest.go
@@ -15,6 +15,7 @@ const (
V3InstallConnectorRequestTypeAdyen V3InstallConnectorRequestType = "Adyen"
V3InstallConnectorRequestTypeAtlar V3InstallConnectorRequestType = "Atlar"
V3InstallConnectorRequestTypeBankingcircle V3InstallConnectorRequestType = "Bankingcircle"
+ V3InstallConnectorRequestTypeCheckout V3InstallConnectorRequestType = "Checkout"
V3InstallConnectorRequestTypeColumn V3InstallConnectorRequestType = "Column"
V3InstallConnectorRequestTypeCurrencycloud V3InstallConnectorRequestType = "Currencycloud"
V3InstallConnectorRequestTypeDummypay V3InstallConnectorRequestType = "Dummypay"
@@ -34,6 +35,7 @@ type V3InstallConnectorRequest struct {
V3AdyenConfig *V3AdyenConfig `queryParam:"inline"`
V3AtlarConfig *V3AtlarConfig `queryParam:"inline"`
V3BankingcircleConfig *V3BankingcircleConfig `queryParam:"inline"`
+ V3CheckoutConfig *V3CheckoutConfig `queryParam:"inline"`
V3ColumnConfig *V3ColumnConfig `queryParam:"inline"`
V3CurrencycloudConfig *V3CurrencycloudConfig `queryParam:"inline"`
V3DummypayConfig *V3DummypayConfig `queryParam:"inline"`
@@ -87,6 +89,18 @@ func CreateV3InstallConnectorRequestBankingcircle(bankingcircle V3BankingcircleC
}
}
+func CreateV3InstallConnectorRequestCheckout(checkout V3CheckoutConfig) V3InstallConnectorRequest {
+ typ := V3InstallConnectorRequestTypeCheckout
+
+ typStr := string(typ)
+ checkout.Provider = &typStr
+
+ return V3InstallConnectorRequest{
+ V3CheckoutConfig: &checkout,
+ Type: typ,
+ }
+}
+
func CreateV3InstallConnectorRequestColumn(column V3ColumnConfig) V3InstallConnectorRequest {
typ := V3InstallConnectorRequestTypeColumn
@@ -282,6 +296,15 @@ func (u *V3InstallConnectorRequest) UnmarshalJSON(data []byte) error {
u.V3BankingcircleConfig = v3BankingcircleConfig
u.Type = V3InstallConnectorRequestTypeBankingcircle
return nil
+ case "Checkout":
+ v3CheckoutConfig := new(V3CheckoutConfig)
+ if err := utils.UnmarshalJSON(data, &v3CheckoutConfig, "", true, false); err != nil {
+ return fmt.Errorf("could not unmarshal `%s` into expected (Provider == Checkout) type V3CheckoutConfig within V3InstallConnectorRequest: %w", string(data), err)
+ }
+
+ u.V3CheckoutConfig = v3CheckoutConfig
+ u.Type = V3InstallConnectorRequestTypeCheckout
+ return nil
case "Column":
v3ColumnConfig := new(V3ColumnConfig)
if err := utils.UnmarshalJSON(data, &v3ColumnConfig, "", true, false); err != nil {
@@ -417,6 +440,10 @@ func (u V3InstallConnectorRequest) MarshalJSON() ([]byte, error) {
return utils.MarshalJSON(u.V3BankingcircleConfig, "", true)
}
+ if u.V3CheckoutConfig != nil {
+ return utils.MarshalJSON(u.V3CheckoutConfig, "", true)
+ }
+
if u.V3ColumnConfig != nil {
return utils.MarshalJSON(u.V3ColumnConfig, "", true)
}
diff --git a/pkg/client/models/components/v3updateconnectorrequest.go b/pkg/client/models/components/v3updateconnectorrequest.go
index 0f5c22e8..b5e12f57 100644
--- a/pkg/client/models/components/v3updateconnectorrequest.go
+++ b/pkg/client/models/components/v3updateconnectorrequest.go
@@ -15,6 +15,7 @@ const (
V3UpdateConnectorRequestTypeAdyen V3UpdateConnectorRequestType = "Adyen"
V3UpdateConnectorRequestTypeAtlar V3UpdateConnectorRequestType = "Atlar"
V3UpdateConnectorRequestTypeBankingcircle V3UpdateConnectorRequestType = "Bankingcircle"
+ V3UpdateConnectorRequestTypeCheckout V3UpdateConnectorRequestType = "Checkout"
V3UpdateConnectorRequestTypeColumn V3UpdateConnectorRequestType = "Column"
V3UpdateConnectorRequestTypeCurrencycloud V3UpdateConnectorRequestType = "Currencycloud"
V3UpdateConnectorRequestTypeDummypay V3UpdateConnectorRequestType = "Dummypay"
@@ -34,6 +35,7 @@ type V3UpdateConnectorRequest struct {
V3AdyenConfig *V3AdyenConfig `queryParam:"inline"`
V3AtlarConfig *V3AtlarConfig `queryParam:"inline"`
V3BankingcircleConfig *V3BankingcircleConfig `queryParam:"inline"`
+ V3CheckoutConfig *V3CheckoutConfig `queryParam:"inline"`
V3ColumnConfig *V3ColumnConfig `queryParam:"inline"`
V3CurrencycloudConfig *V3CurrencycloudConfig `queryParam:"inline"`
V3DummypayConfig *V3DummypayConfig `queryParam:"inline"`
@@ -87,6 +89,18 @@ func CreateV3UpdateConnectorRequestBankingcircle(bankingcircle V3BankingcircleCo
}
}
+func CreateV3UpdateConnectorRequestCheckout(checkout V3CheckoutConfig) V3UpdateConnectorRequest {
+ typ := V3UpdateConnectorRequestTypeCheckout
+
+ typStr := string(typ)
+ checkout.Provider = &typStr
+
+ return V3UpdateConnectorRequest{
+ V3CheckoutConfig: &checkout,
+ Type: typ,
+ }
+}
+
func CreateV3UpdateConnectorRequestColumn(column V3ColumnConfig) V3UpdateConnectorRequest {
typ := V3UpdateConnectorRequestTypeColumn
@@ -282,6 +296,15 @@ func (u *V3UpdateConnectorRequest) UnmarshalJSON(data []byte) error {
u.V3BankingcircleConfig = v3BankingcircleConfig
u.Type = V3UpdateConnectorRequestTypeBankingcircle
return nil
+ case "Checkout":
+ v3CheckoutConfig := new(V3CheckoutConfig)
+ if err := utils.UnmarshalJSON(data, &v3CheckoutConfig, "", true, false); err != nil {
+ return fmt.Errorf("could not unmarshal `%s` into expected (Provider == Checkout) type V3CheckoutConfig within V3UpdateConnectorRequest: %w", string(data), err)
+ }
+
+ u.V3CheckoutConfig = v3CheckoutConfig
+ u.Type = V3UpdateConnectorRequestTypeCheckout
+ return nil
case "Column":
v3ColumnConfig := new(V3ColumnConfig)
if err := utils.UnmarshalJSON(data, &v3ColumnConfig, "", true, false); err != nil {
@@ -417,6 +440,10 @@ func (u V3UpdateConnectorRequest) MarshalJSON() ([]byte, error) {
return utils.MarshalJSON(u.V3BankingcircleConfig, "", true)
}
+ if u.V3CheckoutConfig != nil {
+ return utils.MarshalJSON(u.V3CheckoutConfig, "", true)
+ }
+
if u.V3ColumnConfig != nil {
return utils.MarshalJSON(u.V3ColumnConfig, "", true)
}
diff --git a/pkg/client/models/operations/v3installconnector.go b/pkg/client/models/operations/v3installconnector.go
index 2fa914a6..485e1a29 100644
--- a/pkg/client/models/operations/v3installconnector.go
+++ b/pkg/client/models/operations/v3installconnector.go
@@ -47,6 +47,13 @@ func (o *V3InstallConnectorRequest) GetV3InstallConnectorRequestBankingcircle()
return nil
}
+func (o *V3InstallConnectorRequest) GetV3InstallConnectorRequestCheckout() *components.V3CheckoutConfig {
+ if v := o.GetV3InstallConnectorRequest(); v != nil {
+ return v.V3CheckoutConfig
+ }
+ return nil
+}
+
func (o *V3InstallConnectorRequest) GetV3InstallConnectorRequestColumn() *components.V3ColumnConfig {
if v := o.GetV3InstallConnectorRequest(); v != nil {
return v.V3ColumnConfig
diff --git a/pkg/client/models/operations/v3updateconnectorconfig.go b/pkg/client/models/operations/v3updateconnectorconfig.go
index 5dd0cd7d..8a87a0f7 100644
--- a/pkg/client/models/operations/v3updateconnectorconfig.go
+++ b/pkg/client/models/operations/v3updateconnectorconfig.go
@@ -47,6 +47,13 @@ func (o *V3UpdateConnectorConfigRequest) GetV3UpdateConnectorRequestBankingcircl
return nil
}
+func (o *V3UpdateConnectorConfigRequest) GetV3UpdateConnectorRequestCheckout() *components.V3CheckoutConfig {
+ if v := o.GetV3UpdateConnectorRequest(); v != nil {
+ return v.V3CheckoutConfig
+ }
+ return nil
+}
+
func (o *V3UpdateConnectorConfigRequest) GetV3UpdateConnectorRequestColumn() *components.V3ColumnConfig {
if v := o.GetV3UpdateConnectorRequest(); v != nil {
return v.V3ColumnConfig