Skip to content

Commit

Permalink
feat(payments): replace http.Client with httpwrapper.Client in curren…
Browse files Browse the repository at this point in the history
…cycloud
  • Loading branch information
laouji committed Sep 17, 2024
1 parent b5e3a4a commit 09dd17d
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package client

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/formancehq/payments/internal/connectors/httpwrapper"
)

type Account struct {
Expand Down Expand Up @@ -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"`
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"net/http"
"time"

"github.com/formancehq/payments/internal/connectors/httpwrapper"
)

type Balance struct {
Expand Down Expand Up @@ -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"`
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package client

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/formancehq/payments/internal/connectors/httpwrapper"
)

type Beneficiary struct {
Expand Down Expand Up @@ -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"`
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"time"

"github.com/formancehq/payments/internal/connectors/httpwrapper"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

Expand All @@ -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
Expand All @@ -33,46 +34,45 @@ 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),
}
}

// 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"net/http"
"time"

"github.com/formancehq/payments/internal/connectors/httpwrapper"
)

//nolint:tagliatelle // allow different styled tags in client
Expand Down Expand Up @@ -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"`
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 09dd17d

Please sign in to comment.