Set up default currency accounts automatically (#72)
This commit is contained in:
1 parent
62df9eac5f
commit
5c255296d3
21 files changed
+675
-62
No files matched your search
@@ -0,0 +1,87 @@
|
||||
package sendico
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/product/payments"
|
||||
merrors "git.sendico.io/tech/sxchnge/shared/errors"
|
||||
)
|
||||
|
||||
func (c *Client) validCustomerAccount(a accountDTO, subject string) bool {
|
||||
return validRef(a.ID) && a.ScopeRef == c.config.OrganizationID && a.Owner == "" &&
|
||||
a.Type == "ACCOUNT_TYPE_ASSET" && a.LedgerRole == "ACCOUNT_ROLE_OPERATING" &&
|
||||
a.Status == "ACCOUNT_STATUS_ACTIVE" && !a.AllowNegative && a.Metadata["sxchnge_subject_ref"] == subject &&
|
||||
(a.Metadata["external_subject_ref"] == "" || a.Metadata["external_subject_ref"] == subject)
|
||||
}
|
||||
|
||||
// EnsureAccounts also repairs older customers, using provider-enforced
|
||||
// organization/subject/currency uniqueness for safe retries after lost responses.
|
||||
func (c *Client) EnsureAccounts(ctx context.Context, subject string) error {
|
||||
if !validRef(subject) {
|
||||
return payments.Unavailable
|
||||
}
|
||||
root := "/ledger_accounts/" + c.config.OrganizationID
|
||||
var data accountListDTO
|
||||
if err := c.paymentRequest(ctx, http.MethodGet, root, nil, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
existing := map[string]bool{}
|
||||
reconcile := map[string]bool{}
|
||||
for _, a := range data.Accounts {
|
||||
if a.Metadata["sxchnge_subject_ref"] != subject && a.Metadata["external_subject_ref"] != subject {
|
||||
continue
|
||||
}
|
||||
if !c.validCustomerAccount(a, subject) || existing[a.Currency] {
|
||||
return payments.Unavailable
|
||||
}
|
||||
existing[a.Currency] = true
|
||||
reconcile[a.Currency] = a.Metadata["external_subject_ref"] == subject
|
||||
}
|
||||
missing := []string{}
|
||||
for _, code := range []string{"RUB", "USDT", "USDC"} {
|
||||
if !existing[code] || reconcile[code] {
|
||||
missing = append(missing, code)
|
||||
}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Older providers can accept the metadata but cannot prevent duplicates.
|
||||
// Require the explicit capability before issuing the first create.
|
||||
if !data.ExternalSubjectAccounts {
|
||||
return payments.Unavailable
|
||||
}
|
||||
var catalog struct {
|
||||
Rows []currencyDTO `json:"currencies"`
|
||||
}
|
||||
if err := c.paymentRequest(ctx, http.MethodGet, "/payments/fx/currencies/"+c.config.OrganizationID, nil, &catalog); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, code := range missing {
|
||||
found := false
|
||||
for _, currency := range catalog.Rows {
|
||||
if currency.Code == code && currency.Scale != nil && *currency.Scale >= 0 && *currency.Scale <= 8 {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return merrors.OpaqueWrap(payments.Unavailable, payments.Unsupported, "default currency missing from provider catalog")
|
||||
}
|
||||
}
|
||||
// Replaying a bound create also resumes Sendico's durable wallet/route setup
|
||||
// if a previous response failed after the ledger itself had been committed.
|
||||
for _, code := range missing {
|
||||
var response struct {
|
||||
Account accountDTO `json:"account"`
|
||||
}
|
||||
request := createAccountDTO{Currency: code, Type: "asset", LedgerRole: "operating", Metadata: map[string]string{"external_subject_ref": subject, "sxchnge_subject_ref": subject}}
|
||||
if err := c.paymentRequest(ctx, http.MethodPost, root, request, &response, http.StatusCreated); err != nil {
|
||||
return err
|
||||
}
|
||||
if !c.validCustomerAccount(response.Account, subject) || response.Account.Currency != code || response.Account.Metadata["external_subject_ref"] != subject {
|
||||
return payments.Unavailable
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user