Connect customer quotations and payment execution to Sendico (#50)
This commit is contained in:
1 parent
5d67c25c8e
commit
7a6510d1a8
45 files changed
+2696
-45
No files matched your search
@@ -0,0 +1,123 @@
|
||||
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"`
|
||||
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) 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.
|
||||
if res.StatusCode != http.StatusOK {
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user