From f74735fe4b77c0652dd02ffd46ce7a218796558b Mon Sep 17 00:00:00 2001 From: Paul Nicolas Date: Wed, 11 Oct 2023 16:06:50 +0200 Subject: [PATCH] feat(payments): link bank accounts to payment accounts (#626) --- .../payments/internal/app/api/bank_account.go | 20 +++++++++++++++---- .../payments/internal/app/api/router.go | 12 +++++------ .../payments/internal/app/models/account.go | 4 ++-- .../internal/app/models/bank_account.go | 2 ++ .../internal/app/storage/bank_accounts.go | 18 +++++++++++++++-- .../internal/app/storage/migrations.go | 20 +++++++++++++++++++ components/payments/openapi.yaml | 8 ++++---- docs/openapi/v2.json | 10 +++++----- openapi/build/generate.json | 10 +++++----- 9 files changed, 76 insertions(+), 28 deletions(-) diff --git a/components/payments/internal/app/api/bank_account.go b/components/payments/internal/app/api/bank_account.go index b0e678bc1d..29107145e8 100644 --- a/components/payments/internal/app/api/bank_account.go +++ b/components/payments/internal/app/api/bank_account.go @@ -24,6 +24,7 @@ type bankAccountResponse struct { CreatedAt time.Time `json:"createdAt"` Country string `json:"country"` Provider string `json:"provider"` + AccountID string `json:"accountId,omitempty"` Iban string `json:"iban,omitempty"` AccountNumber string `json:"accountNumber,omitempty"` SwiftBicCode string `json:"swiftBicCode,omitempty"` @@ -90,6 +91,7 @@ func listBankAccountsHandler(repo bankAccountsRepository) http.HandlerFunc { CreatedAt: ret[i].CreatedAt, Country: ret[i].Country, Provider: ret[i].Provider.String(), + AccountID: ret[i].AccountID.String(), } } @@ -143,6 +145,7 @@ func readBankAccountHandler(repo readBankAccountRepository) http.HandlerFunc { CreatedAt: account.CreatedAt, Country: account.Country, Provider: account.Provider.String(), + AccountID: account.AccountID.String(), Iban: account.IBAN, AccountNumber: account.AccountNumber, SwiftBicCode: account.SwiftBicCode, @@ -163,6 +166,7 @@ func readBankAccountHandler(repo readBankAccountRepository) http.HandlerFunc { type createBankAccountRepository interface { UpsertAccounts(ctx context.Context, provider models.ConnectorProvider, accounts []*models.Account) error CreateBankAccount(ctx context.Context, account *models.BankAccount) error + LinkBankAccountWithAccount(ctx context.Context, id uuid.UUID, accountID *models.AccountID) error IsInstalled(ctx context.Context, provider models.ConnectorProvider) (bool, error) } @@ -248,12 +252,13 @@ func createBankAccountHandler(repo createBankAccountRepository) http.HandlerFunc // BankingCircle does not have external accounts so we need to create // one by hand if provider == models.ConnectorProviderBankingCircle { + accountID := models.AccountID{ + Reference: bankAccount.ID.String(), + Provider: provider, + } err = repo.UpsertAccounts(r.Context(), provider, []*models.Account{ { - ID: models.AccountID{ - Reference: bankAccount.ID.String(), - Provider: provider, - }, + ID: accountID, CreatedAt: time.Now(), Reference: bankAccount.ID.String(), Provider: provider, @@ -266,6 +271,13 @@ func createBankAccountHandler(repo createBankAccountRepository) http.HandlerFunc return } + + err = repo.LinkBankAccountWithAccount(r.Context(), bankAccount.ID, &accountID) + if err != nil { + handleStorageErrors(w, r, err) + + return + } } data := &bankAccountResponse{ diff --git a/components/payments/internal/app/api/router.go b/components/payments/internal/app/api/router.go index 1f9308c92b..8b0bb1b052 100644 --- a/components/payments/internal/app/api/router.go +++ b/components/payments/internal/app/api/router.go @@ -62,12 +62,12 @@ func httpRouter( for _, h := range connectorHandlers { paymentsHandlers[h.Provider] = h.initiatePayment } - authGroup.Path("/transfer-initiation").Methods(http.MethodPost).Handler(createTransferInitiationHandler(store, paymentsHandlers)) - authGroup.Path("/transfer-initiation").Methods(http.MethodGet).Handler(listTransferInitiationsHandler(store)) - authGroup.Path("/transfer-initiation/{transferID}/status").Methods(http.MethodPost).Handler(updateTransferInitiationStatusHandler(store, paymentsHandlers)) - authGroup.Path("/transfer-initiation/{transferID}/retry").Methods(http.MethodPost).Handler(retryTransferInitiationHandler(store, paymentsHandlers)) - authGroup.Path("/transfer-initiation/{transferID}").Methods(http.MethodGet).Handler(readTransferInitiationHandler(store)) - authGroup.Path("/transfer-initiation/{transferID}").Methods(http.MethodDelete).Handler(deleteTransferInitiationHandler(store)) + authGroup.Path("/transfer-initiations").Methods(http.MethodPost).Handler(createTransferInitiationHandler(store, paymentsHandlers)) + authGroup.Path("/transfer-initiations").Methods(http.MethodGet).Handler(listTransferInitiationsHandler(store)) + authGroup.Path("/transfer-initiations/{transferID}/status").Methods(http.MethodPost).Handler(updateTransferInitiationStatusHandler(store, paymentsHandlers)) + authGroup.Path("/transfer-initiations/{transferID}/retry").Methods(http.MethodPost).Handler(retryTransferInitiationHandler(store, paymentsHandlers)) + authGroup.Path("/transfer-initiations/{transferID}").Methods(http.MethodGet).Handler(readTransferInitiationHandler(store)) + authGroup.Path("/transfer-initiations/{transferID}").Methods(http.MethodDelete).Handler(deleteTransferInitiationHandler(store)) authGroup.HandleFunc("/connectors", readConnectorsHandler(store)) diff --git a/components/payments/internal/app/models/account.go b/components/payments/internal/app/models/account.go index e67bbc4d19..053e240d20 100644 --- a/components/payments/internal/app/models/account.go +++ b/components/payments/internal/app/models/account.go @@ -53,8 +53,8 @@ type AccountID struct { Provider ConnectorProvider } -func (aid AccountID) String() string { - if aid.Reference == "" { +func (aid *AccountID) String() string { + if aid == nil || aid.Reference == "" { return "" } diff --git a/components/payments/internal/app/models/bank_account.go b/components/payments/internal/app/models/bank_account.go index 8829709283..2700380c5f 100644 --- a/components/payments/internal/app/models/bank_account.go +++ b/components/payments/internal/app/models/bank_account.go @@ -20,6 +20,8 @@ type BankAccount struct { IBAN string `bun:"decrypted_iban,scanonly"` SwiftBicCode string `bun:"decrypted_swift_bic_code,scanonly"` Country string `bun:"country"` + + AccountID *AccountID } func (a *BankAccount) Offuscate() error { diff --git a/components/payments/internal/app/storage/bank_accounts.go b/components/payments/internal/app/storage/bank_accounts.go index 0da8b4686e..41b32317f2 100644 --- a/components/payments/internal/app/storage/bank_accounts.go +++ b/components/payments/internal/app/storage/bank_accounts.go @@ -15,6 +15,7 @@ func (s *Storage) CreateBankAccount(ctx context.Context, bankAccount *models.Ban Country: bankAccount.Country, Provider: bankAccount.Provider, Name: bankAccount.Name, + AccountID: bankAccount.AccountID, } var id uuid.UUID @@ -42,11 +43,24 @@ func (s *Storage) updateBankAccountInformation(ctx context.Context, id uuid.UUID return nil } +func (s *Storage) LinkBankAccountWithAccount(ctx context.Context, id uuid.UUID, accountID *models.AccountID) error { + _, err := s.db.NewUpdate(). + Model(&models.BankAccount{}). + Set("account_id = ?", accountID). + Where("id = ?", id). + Exec(ctx) + if err != nil { + return e("update bank account information", err) + } + + return nil +} + func (s *Storage) ListBankAccounts(ctx context.Context, pagination PaginatorQuery) ([]*models.BankAccount, PaginationDetails, error) { var bankAccounts []*models.BankAccount query := s.db.NewSelect(). - Column("id", "name", "created_at", "country", "provider"). + Column("id", "name", "created_at", "country", "provider", "account_id"). Model(&bankAccounts) query = pagination.apply(query, "bank_account.created_at") @@ -98,7 +112,7 @@ func (s *Storage) GetBankAccount(ctx context.Context, id uuid.UUID, expand bool) var account models.BankAccount query := s.db.NewSelect(). Model(&account). - Column("id", "name", "created_at", "country", "provider") + Column("id", "name", "created_at", "country", "provider", "account_id") if expand { query = query.ColumnExpr("pgp_sym_decrypt(account_number, ?, ?) AS decrypted_account_number", s.configEncryptionKey, encryptionOptions). diff --git a/components/payments/internal/app/storage/migrations.go b/components/payments/internal/app/storage/migrations.go index 3d5e3f8eb4..c40d3cf902 100644 --- a/components/payments/internal/app/storage/migrations.go +++ b/components/payments/internal/app/storage/migrations.go @@ -579,6 +579,26 @@ func registerMigrations(migrator *migrations.Migrator) { return err } + return nil + }, + }, + migrations.Migration{ + Up: func(tx bun.Tx) error { + _, err := tx.Exec(` + ALTER TABLE accounts.bank_account ADD COLUMN account_id CHARACTER VARYING; + + ALTER TABLE accounts.bank_account ADD CONSTRAINT bank_account_account_id + FOREIGN KEY (account_id) + REFERENCES accounts.account (id) + ON DELETE CASCADE + NOT DEFERRABLE + INITIALLY IMMEDIATE + ; + `) + if err != nil { + return err + } + return nil }, }, diff --git a/components/payments/openapi.yaml b/components/payments/openapi.yaml index 9a54e5c934..10ff649850 100644 --- a/components/payments/openapi.yaml +++ b/components/payments/openapi.yaml @@ -51,7 +51,7 @@ paths: responses: '204': $ref: '#/components/responses/NoContent' - /transfer-initiation: + /transfer-initiations: get: summary: List Transfer Initiations operationId: listTransferInitiations @@ -76,7 +76,7 @@ paths: responses: '200': $ref: '#/components/responses/TransferInitiation' - /transfer-initiation/{transferId}: + /transfer-initiations/{transferId}: get: summary: Get a transfer initiation tags: @@ -98,7 +98,7 @@ paths: responses: '204': $ref: '#/components/responses/NoContent' - /transfer-initiation/{transferId}/status: + /transfer-initiations/{transferId}/status: post: summary: Update the status of a transfer initiation tags: @@ -112,7 +112,7 @@ paths: responses: '204': $ref: '#/components/responses/NoContent' - /transfer-initiation/{transferId}/retry: + /transfer-initiations/{transferId}/retry: post: summary: Retry a failed transfer initiation tags: diff --git a/docs/openapi/v2.json b/docs/openapi/v2.json index 60cd6a54bf..0caa6bf180 100644 --- a/docs/openapi/v2.json +++ b/docs/openapi/v2.json @@ -12,7 +12,7 @@ "url": "https://avatars.githubusercontent.com/u/84325077?s=200&v=4", "altText": "Formance" }, - "version": "v1.0.20231010" + "version": "v1.0.20231011" }, "servers": [ { @@ -1823,7 +1823,7 @@ } } }, - "/api/payments/transfer-initiation": { + "/api/payments/transfer-initiations": { "get": { "summary": "List Transfer Initiations", "operationId": "listTransferInitiations", @@ -1867,7 +1867,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}": { + "/api/payments/transfer-initiations/{transferId}": { "get": { "summary": "Get a transfer initiation", "tags": [ @@ -1904,7 +1904,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}/status": { + "/api/payments/transfer-initiations/{transferId}/status": { "post": { "summary": "Update the status of a transfer initiation", "tags": [ @@ -1927,7 +1927,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}/retry": { + "/api/payments/transfer-initiations/{transferId}/retry": { "post": { "summary": "Retry a failed transfer initiation", "tags": [ diff --git a/openapi/build/generate.json b/openapi/build/generate.json index 60cd6a54bf..0caa6bf180 100644 --- a/openapi/build/generate.json +++ b/openapi/build/generate.json @@ -12,7 +12,7 @@ "url": "https://avatars.githubusercontent.com/u/84325077?s=200&v=4", "altText": "Formance" }, - "version": "v1.0.20231010" + "version": "v1.0.20231011" }, "servers": [ { @@ -1823,7 +1823,7 @@ } } }, - "/api/payments/transfer-initiation": { + "/api/payments/transfer-initiations": { "get": { "summary": "List Transfer Initiations", "operationId": "listTransferInitiations", @@ -1867,7 +1867,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}": { + "/api/payments/transfer-initiations/{transferId}": { "get": { "summary": "Get a transfer initiation", "tags": [ @@ -1904,7 +1904,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}/status": { + "/api/payments/transfer-initiations/{transferId}/status": { "post": { "summary": "Update the status of a transfer initiation", "tags": [ @@ -1927,7 +1927,7 @@ } } }, - "/api/payments/transfer-initiation/{transferId}/retry": { + "/api/payments/transfer-initiations/{transferId}/retry": { "post": { "summary": "Retry a failed transfer initiation", "tags": [