From 876375de8bc19fcd07f25550150488fd83f51f0b Mon Sep 17 00:00:00 2001 From: Crimson Thompson Date: Wed, 18 Sep 2024 17:04:47 +0200 Subject: [PATCH] feat(payments): replace http.Client with httpwrapper.Client in bankingcircle --- .../public/bankingcircle/client/accounts.go | 65 ++++++------------ .../public/bankingcircle/client/auth.go | 58 +++++----------- .../public/bankingcircle/client/client.go | 28 ++++---- .../public/bankingcircle/client/payments.go | 66 ++++++------------- .../bankingcircle/client/transfer_payouts.go | 33 ++++------ 5 files changed, 78 insertions(+), 172 deletions(-) diff --git a/components/payments/internal/connectors/plugins/public/bankingcircle/client/accounts.go b/components/payments/internal/connectors/plugins/public/bankingcircle/client/accounts.go index ff6ab9f45c..7f8a99af22 100644 --- a/components/payments/internal/connectors/plugins/public/bankingcircle/client/accounts.go +++ b/components/payments/internal/connectors/plugins/public/bankingcircle/client/accounts.go @@ -4,9 +4,10 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Account struct { @@ -58,24 +59,6 @@ func (c *Client) GetAccounts(ctx context.Context, page int, pageSize int, fromOp req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read accounts response body: %w", err) - } - type response struct { Result []Account `json:"result"` PageInfo struct { @@ -84,13 +67,16 @@ func (c *Client) GetAccounts(ctx context.Context, page int, pageSize int, fromOp } `json:"pageInfo"` } - var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal accounts response: %w", err) + res := response{Result: make([]Account, 0)} + statusCode, err := c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return res.Result, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, fmt.Errorf("received status code %d for get accounts", statusCode) } - - return res.Result, nil + return nil, fmt.Errorf("failed to get accounts: %w", err) } func (c *Client) GetAccount(ctx context.Context, accountID string) (*Account, error) { @@ -109,27 +95,14 @@ func (c *Client) GetAccount(ctx context.Context, accountID string) (*Account, er } req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get accounts: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("wrong status code: %d", resp.StatusCode) - } - var account Account - if err := json.NewDecoder(resp.Body).Decode(&account); err != nil { - return nil, fmt.Errorf("failed to decode account response: %w", err) + statusCode, err := c.httpClient.Do(req, &account, nil) + switch err { + case nil: + return &account, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, fmt.Errorf("received status code %d for get account", statusCode) } - - return &account, nil + return nil, fmt.Errorf("failed to get account: %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/bankingcircle/client/auth.go b/components/payments/internal/connectors/plugins/public/bankingcircle/client/auth.go index d1db7f0b34..7280091fc7 100644 --- a/components/payments/internal/connectors/plugins/public/bankingcircle/client/auth.go +++ b/components/payments/internal/connectors/plugins/public/bankingcircle/client/auth.go @@ -2,12 +2,12 @@ package client import ( "context" - "encoding/json" "fmt" - "io" "net/http" "strconv" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) func (c *Client) login(ctx context.Context) error { @@ -24,60 +24,36 @@ func (c *Client) login(ctx context.Context) error { req.SetBasicAuth(c.username, c.password) - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("failed to login: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read login response body: %w", err) - } - - if resp.StatusCode != http.StatusOK { - type responseError struct { - ErrorCode string `json:"errorCode"` - ErrorText string `json:"errorText"` - } - var errors []responseError - if err = json.Unmarshal(responseBody, &errors); err != nil { - return fmt.Errorf("failed to unmarshal login response: %w", err) - } - if len(errors) > 0 { - return fmt.Errorf("failed to login: %s %s", errors[0].ErrorCode, errors[0].ErrorText) - } - return fmt.Errorf("failed to login: %s", resp.Status) - } - //nolint:tagliatelle // allow for client-side structures type response struct { AccessToken string `json:"access_token"` ExpiresIn string `json:"expires_in"` } + type responseError struct { + ErrorCode string `json:"errorCode"` + ErrorText string `json:"errorText"` + } var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return fmt.Errorf("failed to unmarshal login response: %w", err) + var errors []responseError + statusCode, err := c.httpClient.Do(req, &res, &errors) + switch err { + case nil: + // fallthrough + case httpwrapper.ErrStatusCodeUnexpected: + if len(errors) > 0 { + return fmt.Errorf("failed to login: %s %s", errors[0].ErrorCode, errors[0].ErrorText) + } + return fmt.Errorf("failed to login: %s", statusCode) } + return fmt.Errorf("failed make login request: %w", err) c.accessToken = res.AccessToken - expiresIn, err := strconv.Atoi(res.ExpiresIn) if err != nil { return fmt.Errorf("failed to convert expires_in to int: %w", err) } - c.accessTokenExpiresAt = time.Now().Add(time.Duration(expiresIn) * time.Second) - return nil } diff --git a/components/payments/internal/connectors/plugins/public/bankingcircle/client/client.go b/components/payments/internal/connectors/plugins/public/bankingcircle/client/client.go index 12b04d1ac1..d7af45f623 100644 --- a/components/payments/internal/connectors/plugins/public/bankingcircle/client/client.go +++ b/components/payments/internal/connectors/plugins/public/bankingcircle/client/client.go @@ -5,11 +5,11 @@ import ( "net/http" "time" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type Client struct { - httpClient *http.Client + httpClient httpwrapper.Client username string password string @@ -21,8 +21,12 @@ type Client struct { accessTokenExpiresAt time.Time } -func newHTTPClient(userCertificate, userCertificateKey string) (*http.Client, error) { - cert, err := tls.X509KeyPair([]byte(userCertificate), []byte(userCertificateKey)) +func New( + username, password, + endpoint, authorizationEndpoint, + uCertificate, uCertificateKey string, +) (*Client, error) { + cert, err := tls.X509KeyPair([]byte(uCertificate), []byte(uCertificateKey)) if err != nil { return nil, err } @@ -32,18 +36,10 @@ func newHTTPClient(userCertificate, userCertificateKey string) (*http.Client, er Certificates: []tls.Certificate{cert}, } - return &http.Client{ - Timeout: 10 * time.Second, - Transport: otelhttp.NewTransport(tr), - }, nil -} - -func New( - username, password, - endpoint, authorizationEndpoint, - uCertificate, uCertificateKey string, -) (*Client, error) { - httpClient, err := newHTTPClient(uCertificate, uCertificateKey) + config := &httpwrapper.Config{ + Transport: tr, + } + httpClient, err := httpwrapper.NewClient(config) if err != nil { return nil, err } diff --git a/components/payments/internal/connectors/plugins/public/bankingcircle/client/payments.go b/components/payments/internal/connectors/plugins/public/bankingcircle/client/payments.go index 1146f32ddb..26db5469b9 100644 --- a/components/payments/internal/connectors/plugins/public/bankingcircle/client/payments.go +++ b/components/payments/internal/connectors/plugins/public/bankingcircle/client/payments.go @@ -4,9 +4,10 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) //nolint:tagliatelle // allow for client-side structures @@ -102,24 +103,6 @@ func (c *Client) GetPayments(ctx context.Context, page int, pageSize int) ([]Pay req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payments: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read payments response body: %w", err) - } - type response struct { Result []Payment `json:"result"` PageInfo struct { @@ -128,13 +111,16 @@ func (c *Client) GetPayments(ctx context.Context, page int, pageSize int) ([]Pay } `json:"pageInfo"` } - var res response - - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal payments response: %w", err) + res := response{Result: make([]Payment, 0)} + statusCode, err := c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return res.Result, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, fmt.Errorf("received status code %d for get payments", statusCode) } - - return res.Result, nil + return nil, fmt.Errorf("failed to get payments: %w", err) } type StatusResponse struct { @@ -157,28 +143,14 @@ func (c *Client) GetPaymentStatus(ctx context.Context, paymentID string) (*Statu } req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to get payments: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - responseBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read payments response body: %w", err) - } - var res StatusResponse - if err = json.Unmarshal(responseBody, &res); err != nil { - return nil, fmt.Errorf("failed to unmarshal payments response: %w", err) + statusCode, err := c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return &res, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, fmt.Errorf("received status code %d for get payment status", statusCode) } - - return &res, nil + return nil, fmt.Errorf("failed to get payments status: %w", err) } diff --git a/components/payments/internal/connectors/plugins/public/bankingcircle/client/transfer_payouts.go b/components/payments/internal/connectors/plugins/public/bankingcircle/client/transfer_payouts.go index b29cb8ade6..e2decf2e82 100644 --- a/components/payments/internal/connectors/plugins/public/bankingcircle/client/transfer_payouts.go +++ b/components/payments/internal/connectors/plugins/public/bankingcircle/client/transfer_payouts.go @@ -7,6 +7,8 @@ import ( "fmt" "net/http" "time" + + "github.com/formancehq/payments/internal/connectors/httpwrapper" ) type PaymentAccount struct { @@ -56,27 +58,14 @@ func (c *Client) InitiateTransferOrPayouts(ctx context.Context, transferRequest req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+c.accessToken) - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("failed to make transfer: %w", err) - } - - defer func() { - err = resp.Body.Close() - if err != nil { - _ = err - // TODO(polo): log error - } - }() - - if resp.StatusCode != http.StatusCreated { - return nil, fmt.Errorf("failed to make transfer: %w", err) - } - - var transferResponse PaymentResponse - if err := json.NewDecoder(resp.Body).Decode(&transferResponse); err != nil { - return nil, fmt.Errorf("failed to unmarshal wallets response body: %w", err) + var res PaymentResponse + statusCode, err := c.httpClient.Do(req, &res, nil) + switch err { + case nil: + return &res, nil + case httpwrapper.ErrStatusCodeUnexpected: + // TODO(polo): retryable errors + return nil, fmt.Errorf("received status code %d for make payout", statusCode) } - - return &transferResponse, nil + return nil, fmt.Errorf("failed to make payout: %w", err) }