129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
package sendico
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.sendico.io/tech/sxchnge/product/payments"
|
|
merrors "git.sendico.io/tech/sxchnge/shared/errors"
|
|
)
|
|
|
|
type moneyDTO struct {
|
|
Amount string `json:"amount"`
|
|
Currency string `json:"currency"`
|
|
}
|
|
type endpointDTO struct {
|
|
Type string `json:"type"`
|
|
Data map[string]string `json:"data"`
|
|
}
|
|
|
|
// Explicit wire fields avoid coupling local domain models to provider JSON.
|
|
type quoteRequestDTO struct {
|
|
Key string `json:"idempotencyKey"`
|
|
Intent paymentIntentDTO `json:"intent"`
|
|
}
|
|
type paymentIntentDTO struct {
|
|
Kind string `json:"kind"`
|
|
Source endpointDTO `json:"source"`
|
|
Destination endpointDTO `json:"destination"`
|
|
Amount moneyDTO `json:"amount"`
|
|
Subject string `json:"external_subject_ref"`
|
|
FeeTreatment string `json:"fee_treatment"`
|
|
}
|
|
type quoteAmountsDTO struct {
|
|
Principal moneyDTO `json:"sourcePrincipal"`
|
|
Debit moneyDTO `json:"sourceDebitTotal"`
|
|
Credit moneyDTO `json:"destinationSettlement"`
|
|
}
|
|
type paymentQuoteDTO struct {
|
|
ID string `json:"quoteRef"`
|
|
State string `json:"state"`
|
|
Executable bool `json:"executable"`
|
|
BlockReason string `json:"blockReason"`
|
|
Expires time.Time `json:"expiresAt"`
|
|
Amounts quoteAmountsDTO `json:"amounts"`
|
|
}
|
|
type paymentDTO struct {
|
|
ID string `json:"paymentRef"`
|
|
State string `json:"state"`
|
|
Quote paymentQuoteDTO `json:"lastQuote"`
|
|
Created time.Time `json:"createdAt"`
|
|
}
|
|
type accountDTO struct {
|
|
ID string `json:"ledgerAccountRef"`
|
|
ScopeRef string `json:"organizationRef"`
|
|
Owner string `json:"ownerRef"`
|
|
Currency string `json:"currency"`
|
|
Type string `json:"accountType"`
|
|
LedgerRole string `json:"role"`
|
|
Status string `json:"status"`
|
|
AllowNegative bool `json:"allowNegative"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
type currencyDTO struct {
|
|
Code string `json:"code"`
|
|
Scale *int `json:"accountingScale"`
|
|
}
|
|
type balanceDTO struct {
|
|
ID string `json:"ledgerAccountRef"`
|
|
Money moneyDTO `json:"balance"`
|
|
}
|
|
|
|
func (c *Client) paymentRequest(ctx context.Context, method, path string, payload, dst any, accepted ...int) error {
|
|
token, err := c.token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var body []byte
|
|
if payload != nil {
|
|
body, err = json.Marshal(payload)
|
|
if err != nil {
|
|
return merrors.OpaqueWrap(payments.Unavailable, err, "encode Sendico payment request")
|
|
}
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, method, c.base+path, bytes.NewReader(body))
|
|
if err != nil {
|
|
return merrors.OpaqueWrap(payments.Unavailable, err, "create Sendico payment request")
|
|
}
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := *c.http
|
|
client.Timeout = 30 * time.Second
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return merrors.OpaqueWrap(payments.Unavailable, err, "Sendico payment request failed")
|
|
}
|
|
defer func() { _ = res.Body.Close() }()
|
|
// Never forward upstream errors, credentials, or arbitrary error bodies.
|
|
expected := http.StatusOK
|
|
if len(accepted) != 0 {
|
|
expected = accepted[0]
|
|
}
|
|
if res.StatusCode != expected {
|
|
return payments.Unavailable
|
|
}
|
|
var envelope struct {
|
|
Status string `json:"status"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
d := json.NewDecoder(io.LimitReader(res.Body, 4*1024*1024+1))
|
|
if err = d.Decode(&envelope); err != nil {
|
|
return merrors.OpaqueWrap(payments.Unavailable, err, "decode Sendico payment envelope")
|
|
}
|
|
if envelope.Status != "success" {
|
|
return payments.Unavailable
|
|
}
|
|
var extra any
|
|
if d.Decode(&extra) != io.EOF {
|
|
return payments.Unavailable
|
|
}
|
|
if err = json.Unmarshal(envelope.Data, dst); err != nil {
|
|
return merrors.OpaqueWrap(payments.Unavailable, err, "decode Sendico payment payload")
|
|
}
|
|
return nil
|
|
}
|