Files

137 lines
4.6 KiB
Go

package client
import (
"context"
"fmt"
"net/http"
"time"
"github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
"github.com/hanzo-fi/payments/pkg/domain/httpwrapper"
"github.com/hanzo-fi/payments/pkg/domain/metrics"
"github.com/hanzo-fi/payments/pkg/domain/plugins"
"github.com/hanzo-fi/payments/pkg/domain/models"
errorsutils "github.com/hanzo-fi/payments/pkg/domain/errors"
"github.com/stripe/stripe-go/v80"
"github.com/stripe/stripe-go/v80/account"
"github.com/stripe/stripe-go/v80/balance"
"github.com/stripe/stripe-go/v80/balancetransaction"
"github.com/stripe/stripe-go/v80/bankaccount"
"github.com/stripe/stripe-go/v80/payout"
"github.com/stripe/stripe-go/v80/transfer"
"github.com/stripe/stripe-go/v80/transferreversal"
"github.com/stripe/stripe-go/v80/webhookendpoint"
)
// https://github.com/stripe/stripe-go/blob/master/stripe.go#L1478
const StripeDefaultTimeout = 80 * time.Second
//go:generate mockgen -source client.go -destination client_generated.go -package client . Client
type Client interface {
GetRootAccountID() string
GetRootAccount() (*stripe.Account, error)
GetAccounts(ctx context.Context, timeline Timeline, pageSize int64) ([]*stripe.Account, Timeline, bool, error)
GetAccountBalances(ctx context.Context, accountID string) (*stripe.Balance, error)
GetExternalAccounts(ctx context.Context, accountID string, timeline Timeline, pageSize int64) ([]*stripe.BankAccount, Timeline, bool, error)
GetPayments(ctx context.Context, accountID string, timeline Timeline, pageSize int64) ([]*stripe.BalanceTransaction, Timeline, bool, error)
CreatePayout(ctx context.Context, createPayoutRequest *CreatePayoutRequest) (*stripe.Payout, error)
CreateTransfer(ctx context.Context, createTransferRequest *CreateTransferRequest) (*stripe.Transfer, error)
ReverseTransfer(ctx context.Context, reverseTransferRequest ReverseTransferRequest) (*stripe.TransferReversal, error)
CreateWebhookEndpoints(ctx context.Context, webhookBaseURL string) ([]*stripe.WebhookEndpoint, error)
DeleteWebhookEndpoints([]models.PSPWebhookConfig) error
}
type client struct {
logger logging.Logger
rootAccountID string
accountClient account.Client
balanceClient balance.Client
transferClient transfer.Client
transferReversalClient transferreversal.Client
payoutClient payout.Client
bankAccountClient bankaccount.Client
balanceTransactionClient balancetransaction.Client
webhookEndpointClient webhookendpoint.Client
}
func New(
name string,
logger logging.Logger,
backend stripe.Backend,
apiKey string,
) (Client, error) {
if backend == nil {
backends := stripe.NewBackends(metrics.NewHTTPClient(name, StripeDefaultTimeout))
backend = backends.API
}
accountClient := account.Client{B: backend, Key: apiKey}
result, err := accountClient.Get()
if err != nil {
return nil, fmt.Errorf("%w: %w", plugins.ErrInvalidClientRequest, err)
}
return &client{
logger: logger,
rootAccountID: result.ID,
accountClient: accountClient,
balanceClient: balance.Client{B: backend, Key: apiKey},
transferClient: transfer.Client{B: backend, Key: apiKey},
payoutClient: payout.Client{B: backend, Key: apiKey},
bankAccountClient: bankaccount.Client{B: backend, Key: apiKey},
balanceTransactionClient: balancetransaction.Client{B: backend, Key: apiKey},
webhookEndpointClient: webhookendpoint.Client{B: backend, Key: apiKey},
}, nil
}
func (c *client) GetRootAccountID() string {
return c.rootAccountID
}
func limit(wanted int64, have int) *int64 {
needed := wanted - int64(have)
return &needed
}
// wrap a public error for cases that we don't want to retry
// so that activities can classify this error for temporal
func wrapSDKErr(err error) error {
if err == nil {
return nil
}
stripeErr, ok := err.(*stripe.Error)
if !ok {
return err
}
if stripeErr.Code == stripe.ErrorCodeRateLimit {
return errorsutils.NewWrappedError(
err,
httpwrapper.ErrStatusCodeTooManyRequests,
)
}
switch stripeErr.HTTPStatusCode {
case http.StatusRequestTimeout:
return errorsutils.NewWrappedError(err, httpwrapper.ErrStatusCodeRequestTimeout)
case http.StatusMisdirectedRequest:
return errorsutils.NewWrappedError(err, httpwrapper.ErrStatusCodeMisdirectedRequest)
case http.StatusLocked:
return errorsutils.NewWrappedError(err, httpwrapper.ErrStatusCodeLocked)
case http.StatusTooEarly:
return errorsutils.NewWrappedError(err, httpwrapper.ErrStatusCodeTooEarly)
}
switch stripeErr.Type {
case stripe.ErrorTypeInvalidRequest, stripe.ErrorTypeIdempotency:
return errorsutils.NewWrappedError(
err,
httpwrapper.ErrStatusCodeClientError,
)
}
return err
}