mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/formancehq/payments/internal/models"
|
|
)
|
|
|
|
type Client interface {
|
|
FetchAccounts(ctx context.Context, startToken int, pageSize int) ([]models.PSPAccount, int, error)
|
|
FetchBalance(ctx context.Context, accountID string) (*models.PSPBalance, error)
|
|
CreatePayment(ctx context.Context, paymentType models.PaymentType, paymentInit models.PSPPaymentInitiation) (*models.PSPPayment, error)
|
|
ReversePayment(ctx context.Context, paymentType models.PaymentType, reversal models.PSPPaymentInitiationReversal) (models.PSPPayment, error)
|
|
}
|
|
|
|
type client struct {
|
|
directory string
|
|
}
|
|
|
|
func New(dir string) Client {
|
|
return &client{
|
|
directory: dir,
|
|
}
|
|
}
|
|
|
|
func (c *client) FetchAccounts(ctx context.Context, startToken int, pageSize int) ([]models.PSPAccount, int, error) {
|
|
b, err := c.readFile("accounts.json")
|
|
if err != nil {
|
|
return []models.PSPAccount{}, 0, fmt.Errorf("failed to fetch accounts: %w", err)
|
|
}
|
|
|
|
pspAccounts := make([]models.PSPAccount, 0, pageSize)
|
|
if len(b) == 0 {
|
|
return pspAccounts, -1, nil
|
|
}
|
|
|
|
accounts := make([]Account, 0)
|
|
err = json.Unmarshal(b, &accounts)
|
|
if err != nil {
|
|
return []models.PSPAccount{}, 0, fmt.Errorf("failed to unmarshal accounts: %w", err)
|
|
}
|
|
|
|
next := -1
|
|
for i := startToken; i < len(accounts); i++ {
|
|
if len(pspAccounts) >= pageSize {
|
|
if len(accounts)-startToken > len(pspAccounts) {
|
|
next = i
|
|
}
|
|
break
|
|
}
|
|
|
|
account := accounts[i]
|
|
pspAccounts = append(pspAccounts, models.PSPAccount{
|
|
Reference: account.ID,
|
|
CreatedAt: account.OpeningDate,
|
|
Name: &account.Name,
|
|
DefaultAsset: &account.Currency,
|
|
})
|
|
}
|
|
return pspAccounts, next, nil
|
|
}
|
|
|
|
func (c *client) FetchBalance(ctx context.Context, accountID string) (*models.PSPBalance, error) {
|
|
b, err := c.readFile("balances.json")
|
|
if err != nil {
|
|
return &models.PSPBalance{}, fmt.Errorf("failed to fetch balances: %w", err)
|
|
}
|
|
|
|
balances := make([]Balance, 0)
|
|
if len(b) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
err = json.Unmarshal(b, &balances)
|
|
if err != nil {
|
|
return &models.PSPBalance{}, fmt.Errorf("failed to unmarshal balances: %w", err)
|
|
}
|
|
|
|
for _, balance := range balances {
|
|
if balance.AccountID != accountID {
|
|
continue
|
|
}
|
|
return &models.PSPBalance{
|
|
AccountReference: balance.AccountID,
|
|
CreatedAt: time.Now().Truncate(time.Second),
|
|
Asset: balance.Currency,
|
|
Amount: big.NewInt(balance.AmountInMinors),
|
|
}, nil
|
|
}
|
|
return &models.PSPBalance{}, nil
|
|
}
|
|
|
|
func (c *client) CreatePayment(
|
|
ctx context.Context,
|
|
paymentType models.PaymentType,
|
|
paymentInit models.PSPPaymentInitiation,
|
|
) (*models.PSPPayment, error) {
|
|
balances := []Balance{
|
|
{
|
|
AccountID: paymentInit.SourceAccount.Reference,
|
|
AmountInMinors: paymentInit.Amount.Int64(),
|
|
Currency: paymentInit.Asset,
|
|
},
|
|
}
|
|
b, err := json.Marshal(&balances)
|
|
if err != nil {
|
|
return &models.PSPPayment{}, fmt.Errorf("failed to marshal new balance: %w", err)
|
|
}
|
|
|
|
err = c.writeFile("balances.json", b)
|
|
if err != nil {
|
|
return &models.PSPPayment{}, fmt.Errorf("failed to write balance: %w", err)
|
|
}
|
|
|
|
return &models.PSPPayment{
|
|
Reference: paymentInit.Reference,
|
|
CreatedAt: paymentInit.CreatedAt,
|
|
Amount: paymentInit.Amount,
|
|
Asset: paymentInit.Asset,
|
|
Type: paymentType,
|
|
Status: models.PAYMENT_STATUS_SUCCEEDED,
|
|
Scheme: models.PAYMENT_SCHEME_OTHER,
|
|
SourceAccountReference: &paymentInit.SourceAccount.Reference,
|
|
DestinationAccountReference: &paymentInit.DestinationAccount.Reference,
|
|
}, nil
|
|
}
|
|
|
|
func (c *client) ReversePayment(
|
|
ctx context.Context,
|
|
paymentType models.PaymentType,
|
|
reversal models.PSPPaymentInitiationReversal,
|
|
) (models.PSPPayment, error) {
|
|
b, err := c.readFile("balances.json")
|
|
if err != nil {
|
|
return models.PSPPayment{}, fmt.Errorf("failed to fetch balances: %w", err)
|
|
}
|
|
|
|
balances := make([]Balance, 0)
|
|
if len(b) == 0 {
|
|
return models.PSPPayment{}, fmt.Errorf("no balance data found")
|
|
}
|
|
|
|
err = json.Unmarshal(b, &balances)
|
|
if err != nil {
|
|
return models.PSPPayment{}, fmt.Errorf("failed to unmarshal balances: %w", err)
|
|
}
|
|
|
|
var balanceUpdated bool
|
|
for _, balance := range balances {
|
|
if balance.AccountID == reversal.RelatedPaymentInitiation.SourceAccount.Reference {
|
|
if balance.AmountInMinors-reversal.Amount.Int64() < 0 {
|
|
return models.PSPPayment{}, fmt.Errorf("balance will be negative if %d is subtracted", reversal.Amount.Int64())
|
|
}
|
|
balance.AmountInMinors = balance.AmountInMinors - reversal.Amount.Int64()
|
|
balanceUpdated = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !balanceUpdated {
|
|
return models.PSPPayment{}, fmt.Errorf("no reversable balance found")
|
|
}
|
|
|
|
b, err = json.Marshal(&balances)
|
|
if err != nil {
|
|
return models.PSPPayment{}, fmt.Errorf("failed to marshal new balance: %w", err)
|
|
}
|
|
|
|
err = c.writeFile("balances.json", b)
|
|
if err != nil {
|
|
return models.PSPPayment{}, fmt.Errorf("failed to write balance: %w", err)
|
|
}
|
|
|
|
return models.PSPPayment{
|
|
Reference: reversal.Reference,
|
|
CreatedAt: reversal.CreatedAt,
|
|
Amount: reversal.Amount,
|
|
Asset: reversal.Asset,
|
|
Type: paymentType,
|
|
Status: models.PAYMENT_STATUS_REFUNDED,
|
|
Scheme: models.PAYMENT_SCHEME_OTHER,
|
|
SourceAccountReference: &reversal.RelatedPaymentInitiation.SourceAccount.Reference,
|
|
DestinationAccountReference: &reversal.RelatedPaymentInitiation.DestinationAccount.Reference,
|
|
}, nil
|
|
}
|
|
|
|
func (c *client) writeFile(filename string, b []byte) error {
|
|
filePath := path.Join(c.directory, filename)
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open %q for write: %w", filePath, err)
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = file.Write(b)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read file %q: %w", filePath, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) readFile(filename string) (b []byte, err error) {
|
|
filePath := path.Join(c.directory, filename)
|
|
file, err := os.Open(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return b, nil
|
|
}
|
|
return b, fmt.Errorf("failed to open %q for read: %w", filePath, err)
|
|
}
|
|
defer file.Close()
|
|
|
|
fileInfo, err := file.Stat()
|
|
if err != nil {
|
|
return b, fmt.Errorf("failed to stat file %q: %w", filePath, err)
|
|
}
|
|
|
|
buf := make([]byte, fileInfo.Size())
|
|
_, err = file.Read(buf)
|
|
if err != nil {
|
|
return b, fmt.Errorf("failed to read file %q: %w", filePath, err)
|
|
}
|
|
return buf, nil
|
|
}
|