unified gateway interface
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/payments/rail"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -16,6 +18,10 @@ import (
|
||||
|
||||
// Client exposes typed helpers around the ledger gRPC API.
|
||||
type Client interface {
|
||||
ReadBalance(ctx context.Context, accountID string) (*moneyv1.Money, error)
|
||||
CreateTransaction(ctx context.Context, tx rail.LedgerTx) (string, error)
|
||||
HoldBalance(ctx context.Context, accountID string, amount string) error
|
||||
|
||||
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
PostCreditWithCharges(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error)
|
||||
@@ -95,6 +101,80 @@ func (c *ledgerClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ledgerClient) ReadBalance(ctx context.Context, accountID string) (*moneyv1.Money, error) {
|
||||
if strings.TrimSpace(accountID) == "" {
|
||||
return nil, merrors.InvalidArgument("ledger: account_id is required")
|
||||
}
|
||||
resp, err := c.GetBalance(ctx, &ledgerv1.GetBalanceRequest{LedgerAccountRef: strings.TrimSpace(accountID)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil || resp.GetBalance() == nil {
|
||||
return nil, merrors.Internal("ledger: balance response missing")
|
||||
}
|
||||
return cloneMoney(resp.GetBalance()), nil
|
||||
}
|
||||
|
||||
func (c *ledgerClient) CreateTransaction(ctx context.Context, tx rail.LedgerTx) (string, error) {
|
||||
orgRef := strings.TrimSpace(tx.OrganizationRef)
|
||||
if orgRef == "" {
|
||||
return "", merrors.InvalidArgument("ledger: organization_ref is required")
|
||||
}
|
||||
accountRef := strings.TrimSpace(tx.LedgerAccountRef)
|
||||
if accountRef == "" {
|
||||
return "", merrors.InvalidArgument("ledger: ledger_account_ref is required")
|
||||
}
|
||||
money := &moneyv1.Money{
|
||||
Currency: strings.TrimSpace(tx.Currency),
|
||||
Amount: strings.TrimSpace(tx.Amount),
|
||||
}
|
||||
if money.GetCurrency() == "" || money.GetAmount() == "" {
|
||||
return "", merrors.InvalidArgument("ledger: amount is required")
|
||||
}
|
||||
|
||||
description := strings.TrimSpace(tx.Description)
|
||||
metadata := ledgerTxMetadata(tx.Metadata, tx)
|
||||
|
||||
switch {
|
||||
case isLedgerRail(tx.FromRail) && !isLedgerRail(tx.ToRail):
|
||||
resp, err := c.PostDebitWithCharges(ctx, &ledgerv1.PostDebitRequest{
|
||||
IdempotencyKey: strings.TrimSpace(tx.IdempotencyKey),
|
||||
OrganizationRef: orgRef,
|
||||
LedgerAccountRef: accountRef,
|
||||
Money: money,
|
||||
Description: description,
|
||||
Charges: tx.Charges,
|
||||
Metadata: metadata,
|
||||
ContraLedgerAccountRef: strings.TrimSpace(tx.ContraLedgerAccountRef),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(resp.GetJournalEntryRef()), nil
|
||||
case isLedgerRail(tx.ToRail) && !isLedgerRail(tx.FromRail):
|
||||
resp, err := c.PostCreditWithCharges(ctx, &ledgerv1.PostCreditRequest{
|
||||
IdempotencyKey: strings.TrimSpace(tx.IdempotencyKey),
|
||||
OrganizationRef: orgRef,
|
||||
LedgerAccountRef: accountRef,
|
||||
Money: money,
|
||||
Description: description,
|
||||
Charges: tx.Charges,
|
||||
Metadata: metadata,
|
||||
ContraLedgerAccountRef: strings.TrimSpace(tx.ContraLedgerAccountRef),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(resp.GetJournalEntryRef()), nil
|
||||
default:
|
||||
return "", merrors.InvalidArgument("ledger: unsupported transaction direction")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ledgerClient) HoldBalance(ctx context.Context, accountID string, amount string) error {
|
||||
return merrors.NotImplemented("ledger: hold balance not supported")
|
||||
}
|
||||
|
||||
func (c *ledgerClient) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||
ctx, cancel := c.callContext(ctx)
|
||||
defer cancel()
|
||||
@@ -156,3 +236,57 @@ func (c *ledgerClient) callContext(ctx context.Context) (context.Context, contex
|
||||
}
|
||||
return context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
func isLedgerRail(value string) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(value), "LEDGER")
|
||||
}
|
||||
|
||||
func cloneMoney(input *moneyv1.Money) *moneyv1.Money {
|
||||
if input == nil {
|
||||
return nil
|
||||
}
|
||||
return &moneyv1.Money{
|
||||
Currency: input.GetCurrency(),
|
||||
Amount: input.GetAmount(),
|
||||
}
|
||||
}
|
||||
|
||||
func cloneMetadata(input map[string]string) map[string]string {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(input))
|
||||
for k, v := range input {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ledgerTxMetadata(base map[string]string, tx rail.LedgerTx) map[string]string {
|
||||
meta := cloneMetadata(base)
|
||||
if meta == nil {
|
||||
meta = map[string]string{}
|
||||
}
|
||||
if val := strings.TrimSpace(tx.PaymentPlanID); val != "" {
|
||||
meta["payment_plan_id"] = val
|
||||
}
|
||||
if val := strings.TrimSpace(tx.FromRail); val != "" {
|
||||
meta["from_rail"] = val
|
||||
}
|
||||
if val := strings.TrimSpace(tx.ToRail); val != "" {
|
||||
meta["to_rail"] = val
|
||||
}
|
||||
if val := strings.TrimSpace(tx.ExternalReferenceID); val != "" {
|
||||
meta["external_reference_id"] = val
|
||||
}
|
||||
if val := strings.TrimSpace(tx.FXRateUsed); val != "" {
|
||||
meta["fx_rate_used"] = val
|
||||
}
|
||||
if val := strings.TrimSpace(tx.FeeAmount); val != "" {
|
||||
meta["fee_amount"] = val
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
return nil
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
@@ -3,13 +3,18 @@ package client
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tech/sendico/pkg/payments/rail"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
)
|
||||
|
||||
// Fake implements Client for tests.
|
||||
type Fake struct {
|
||||
CreateAccountFn func(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccountsFn func(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
ReadBalanceFn func(ctx context.Context, accountID string) (*moneyv1.Money, error)
|
||||
CreateTransactionFn func(ctx context.Context, tx rail.LedgerTx) (string, error)
|
||||
HoldBalanceFn func(ctx context.Context, accountID string, amount string) error
|
||||
CreateAccountFn func(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccountsFn func(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
PostCreditWithChargesFn func(ctx context.Context, req *ledgerv1.PostCreditRequest) (*ledgerv1.PostResponse, error)
|
||||
PostDebitWithChargesFn func(ctx context.Context, req *ledgerv1.PostDebitRequest) (*ledgerv1.PostResponse, error)
|
||||
TransferInternalFn func(ctx context.Context, req *ledgerv1.TransferRequest) (*ledgerv1.PostResponse, error)
|
||||
@@ -20,6 +25,27 @@ type Fake struct {
|
||||
CloseFn func() error
|
||||
}
|
||||
|
||||
func (f *Fake) ReadBalance(ctx context.Context, accountID string) (*moneyv1.Money, error) {
|
||||
if f.ReadBalanceFn != nil {
|
||||
return f.ReadBalanceFn(ctx, accountID)
|
||||
}
|
||||
return &moneyv1.Money{}, nil
|
||||
}
|
||||
|
||||
func (f *Fake) CreateTransaction(ctx context.Context, tx rail.LedgerTx) (string, error) {
|
||||
if f.CreateTransactionFn != nil {
|
||||
return f.CreateTransactionFn(ctx, tx)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (f *Fake) HoldBalance(ctx context.Context, accountID string, amount string) error {
|
||||
if f.HoldBalanceFn != nil {
|
||||
return f.HoldBalanceFn(ctx, accountID, amount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fake) CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||
if f.CreateAccountFn != nil {
|
||||
return f.CreateAccountFn(ctx, req)
|
||||
|
||||
Reference in New Issue
Block a user