From 09dd17d6f20d3174a07e7a7fa78e2fdb2926c29d Mon Sep 17 00:00:00 2001 From: Crimson Thompson Date: Tue, 17 Sep 2024 16:05:57 +0200 Subject: [PATCH] feat(payments): replace http.Client with httpwrapper.Client in currencycloud --- .../public/currencycloud/client/accounts.go | 28 ++++++------- .../public/currencycloud/client/auth.go | 4 +- .../public/currencycloud/client/balances.go | 27 ++++++------ .../currencycloud/client/beneficiaries.go | 28 ++++++------- .../public/currencycloud/client/client.go | 42 +++++++++---------- .../public/currencycloud/client/contacts.go | 35 +++++++--------- .../currencycloud/client/transactions.go | 28 ++++++------- .../plugins/public/currencycloud/plugin.go | 2 +- 8 files changed, 87 insertions(+), 107 deletions(-) diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/accounts.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/accounts.go index b15c50dd9c..fdb9cd667c 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/accounts.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/accounts.go @@ -2,10 +2,11 @@ package client import ( "context" - "encoding/json" "fmt" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Account struct { @@ -36,16 +37,6 @@ func (c *Client) GetAccounts(ctx context.Context, page int, pageSize int) ([]*Ac req.Header.Add("Accept", "application/json") - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - //nolint:tagliatelle // allow for client code type response struct { Accounts []*Account `json:"accounts"` @@ -54,10 +45,15 @@ func (c *Client) GetAccounts(ctx context.Context, page int, pageSize int) ([]*Ac } `json:"pagination"` } - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err + res := response{Accounts: make([]*Account, 0)} + var errRes currencyCloudError + _, err = c.httpClient.Do(req, &res, &errRes) + switch err { + case nil: + return res.Accounts, res.Pagination.NextPage, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, 0, errRes.Error() } - - return res.Accounts, res.Pagination.NextPage, nil + return nil, 0, fmt.Errorf("failed to get accounts: %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/auth.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/auth.go index 32cf21bc35..5dba981387 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/auth.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/auth.go @@ -9,7 +9,7 @@ import ( "strings" ) -func (c *Client) authenticate(ctx context.Context) (string, error) { +func (c *Client) authenticate(ctx context.Context, httpClient *http.Client) (string, error) { // TODO(polo): metrics // f := connectors.ClientMetrics(ctx, "currencycloud", "authenticate") // now := time.Now() @@ -29,7 +29,7 @@ func (c *Client) authenticate(ctx context.Context) (string, error) { req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Accept", "application/json") - resp, err := c.httpClient.Do(req) + resp, err := httpClient.Do(req) if err != nil { return "", fmt.Errorf("failed to do get request: %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/balances.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/balances.go index cf7a7f9395..b2545108e5 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/balances.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/balances.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Balance struct { @@ -38,16 +40,6 @@ func (c *Client) GetBalances(ctx context.Context, page int, pageSize int) ([]*Ba req.Header.Add("Accept", "application/json") - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - //nolint:tagliatelle // allow for client code type response struct { Balances []*Balance `json:"balances"` @@ -56,10 +48,15 @@ func (c *Client) GetBalances(ctx context.Context, page int, pageSize int) ([]*Ba } `json:"pagination"` } - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err + res := response{Balances: make([]*Balance, 0)} + var errRes currencyCloudError + _, err = c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return res.Balances, res.Pagination.NextPage, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, 0, errRes.Error() } - - return res.Balances, res.Pagination.NextPage, nil + return nil, 0, fmt.Errorf("failed to get balances %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/beneficiaries.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/beneficiaries.go index 2a6cd08043..7cb8ea3562 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/beneficiaries.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/beneficiaries.go @@ -2,10 +2,11 @@ package client import ( "context" - "encoding/json" "fmt" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Beneficiary struct { @@ -38,16 +39,6 @@ func (c *Client) GetBeneficiaries(ctx context.Context, page int, pageSize int) ( req.Header.Add("Accept", "application/json") - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - //nolint:tagliatelle // allow for client code type response struct { Beneficiaries []*Beneficiary `json:"beneficiaries"` @@ -56,10 +47,15 @@ func (c *Client) GetBeneficiaries(ctx context.Context, page int, pageSize int) ( } `json:"pagination"` } - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err + res := response{Beneficiaries: make([]*Beneficiary, 0)} + var errRes currencyCloudError + _, err = c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return res.Beneficiaries, res.Pagination.NextPage, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, 0, errRes.Error() } - - return res.Beneficiaries, res.Pagination.NextPage, nil + return nil, 0, fmt.Errorf("failed to get beneficiaries %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/client.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/client.go index 2293339266..5aac60ad68 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/client.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/client.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/formancehq/payments/internal/connectors/httpwrapper" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -21,7 +22,7 @@ func (t *apiTransport) RoundTrip(req *http.Request) (*http.Response, error) { } type Client struct { - httpClient *http.Client + httpClient httpwrapper.Client endpoint string loginID string apiKey string @@ -33,15 +34,6 @@ func (c *Client) buildEndpoint(path string, args ...interface{}) string { const DevAPIEndpoint = "https://devapi.currencycloud.com" -func newAuthenticatedHTTPClient(authToken string) *http.Client { - return &http.Client{ - Transport: &apiTransport{ - authToken: authToken, - underlying: otelhttp.NewTransport(http.DefaultTransport), - }, - } -} - func newHTTPClient() *http.Client { return &http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport), @@ -49,30 +41,38 @@ func newHTTPClient() *http.Client { } // New creates a new client for the CurrencyCloud API. -func New(loginID, apiKey, endpoint string) (*Client, error) { +func New(ctx context.Context, loginID, apiKey, endpoint string) (*Client, error) { if endpoint == "" { endpoint = DevAPIEndpoint } + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + c := &Client{ - httpClient: newHTTPClient(), - endpoint: endpoint, - loginID: loginID, - apiKey: apiKey, + endpoint: endpoint, + loginID: loginID, + apiKey: apiKey, } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - // Tokens expire after 30 minutes of inactivity which should not be the case // for us since we're polling the API frequently. // TODO(polo): add refreh - authToken, err := c.authenticate(ctx) + authToken, err := c.authenticate(ctx, newHTTPClient()) if err != nil { return nil, err } - c.httpClient = newAuthenticatedHTTPClient(authToken) - + config := &httpwrapper.Config{ + Transport: &apiTransport{ + authToken: authToken, + underlying: otelhttp.NewTransport(http.DefaultTransport), + }, + } + httpClient, err := httpwrapper.NewClient(config) + if err != nil { + return nil, err + } + c.httpClient = httpClient return c, nil } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/contacts.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/contacts.go index e9cd26f9f0..bd295c6671 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/contacts.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/contacts.go @@ -2,11 +2,12 @@ package client import ( "context" - "encoding/json" "fmt" "net/http" "net/url" "strings" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Contact struct { @@ -28,28 +29,22 @@ func (c *Client) GetContactID(ctx context.Context, accountID string) (*Contact, } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, unmarshalError(resp.StatusCode, resp.Body).Error() - } - type Contacts struct { Contacts []*Contact `json:"contacts"` } - var res Contacts - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, err - } - - if len(res.Contacts) == 0 { - return nil, fmt.Errorf("no contact found for account %s", accountID) + res := Contacts{Contacts: make([]*Contact, 0)} + var errRes currencyCloudError + _, err = c.httpClient.Do(req, &res, nil) + switch err { + case nil: + if len(res.Contacts) == 0 { + return nil, fmt.Errorf("no contact found for account %s", accountID) + } + return res.Contacts[0], nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, errRes.Error() } - - return res.Contacts[0], nil + return nil, fmt.Errorf("failed to get contacts %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/client/transactions.go b/components/payments/internal/connectors/plugins/public/currencycloud/client/transactions.go index bdf546cd4a..d45425c5b3 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/client/transactions.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/client/transactions.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) //nolint:tagliatelle // allow different styled tags in client @@ -50,17 +52,6 @@ func (c *Client) GetTransactions(ctx context.Context, page int, pageSize int, up req.Header.Add("Accept", "application/json") - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, 0, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, 0, unmarshalError(resp.StatusCode, resp.Body).Error() - } - //nolint:tagliatelle // allow for client code type response struct { Transactions []Transaction `json:"transactions"` @@ -69,10 +60,15 @@ func (c *Client) GetTransactions(ctx context.Context, page int, pageSize int, up } `json:"pagination"` } - var res response - if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { - return nil, 0, err + res := response{Transactions: make([]Transaction, 0)} + var errRes currencyCloudError + _, err = c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return res.Transactions, res.Pagination.NextPage, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, 0, errRes.Error() } - - return res.Transactions, res.Pagination.NextPage, nil + return nil, 0, fmt.Errorf("failed to get transactions %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/currencycloud/plugin.go b/components/payments/internal/connectors/plugins/public/currencycloud/plugin.go index f147e5e558..47f987b44a 100644 --- a/components/payments/internal/connectors/plugins/public/currencycloud/plugin.go +++ b/components/payments/internal/connectors/plugins/public/currencycloud/plugin.go @@ -18,7 +18,7 @@ func (p *Plugin) Install(ctx context.Context, req models.InstallRequest) (models return models.InstallResponse{}, err } - client, err := client.New(config.LoginID, config.APIKey, config.Endpoint) + client, err := client.New(ctx, config.LoginID, config.APIKey, config.Endpoint) if err != nil { return models.InstallResponse{}, err }