All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
227 lines
9.8 KiB
Go
227 lines
9.8 KiB
Go
package model
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/pkg/db/storable"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mservice"
|
|
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
|
|
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
|
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
|
)
|
|
|
|
// PaymentKind captures the orchestrator intent type.
|
|
type PaymentKind string
|
|
|
|
const (
|
|
PaymentKindUnspecified PaymentKind = "unspecified"
|
|
PaymentKindPayout PaymentKind = "payout"
|
|
PaymentKindInternalTransfer PaymentKind = "internal_transfer"
|
|
PaymentKindFXConversion PaymentKind = "fx_conversion"
|
|
)
|
|
|
|
// PaymentState enumerates lifecycle phases.
|
|
type PaymentState string
|
|
|
|
const (
|
|
PaymentStateUnspecified PaymentState = "unspecified"
|
|
PaymentStateAccepted PaymentState = "accepted"
|
|
PaymentStateFundsReserved PaymentState = "funds_reserved"
|
|
PaymentStateSubmitted PaymentState = "submitted"
|
|
PaymentStateSettled PaymentState = "settled"
|
|
PaymentStateFailed PaymentState = "failed"
|
|
PaymentStateCancelled PaymentState = "cancelled"
|
|
)
|
|
|
|
// PaymentFailureCode captures terminal reasons.
|
|
type PaymentFailureCode string
|
|
|
|
const (
|
|
PaymentFailureCodeUnspecified PaymentFailureCode = "unspecified"
|
|
PaymentFailureCodeBalance PaymentFailureCode = "balance"
|
|
PaymentFailureCodeLedger PaymentFailureCode = "ledger"
|
|
PaymentFailureCodeFX PaymentFailureCode = "fx"
|
|
PaymentFailureCodeChain PaymentFailureCode = "chain"
|
|
PaymentFailureCodeFees PaymentFailureCode = "fees"
|
|
PaymentFailureCodePolicy PaymentFailureCode = "policy"
|
|
)
|
|
|
|
// PaymentEndpointType indicates how value should be routed.
|
|
type PaymentEndpointType string
|
|
|
|
const (
|
|
EndpointTypeUnspecified PaymentEndpointType = "unspecified"
|
|
EndpointTypeLedger PaymentEndpointType = "ledger"
|
|
EndpointTypeManagedWallet PaymentEndpointType = "managed_wallet"
|
|
EndpointTypeExternalChain PaymentEndpointType = "external_chain"
|
|
)
|
|
|
|
// LedgerEndpoint describes ledger routing.
|
|
type LedgerEndpoint struct {
|
|
LedgerAccountRef string `bson:"ledgerAccountRef" json:"ledgerAccountRef"`
|
|
ContraLedgerAccountRef string `bson:"contraLedgerAccountRef,omitempty" json:"contraLedgerAccountRef,omitempty"`
|
|
}
|
|
|
|
// ManagedWalletEndpoint describes managed wallet routing.
|
|
type ManagedWalletEndpoint struct {
|
|
ManagedWalletRef string `bson:"managedWalletRef" json:"managedWalletRef"`
|
|
Asset *chainv1.Asset `bson:"asset,omitempty" json:"asset,omitempty"`
|
|
}
|
|
|
|
// ExternalChainEndpoint describes an external address.
|
|
type ExternalChainEndpoint struct {
|
|
Asset *chainv1.Asset `bson:"asset,omitempty" json:"asset,omitempty"`
|
|
Address string `bson:"address" json:"address"`
|
|
Memo string `bson:"memo,omitempty" json:"memo,omitempty"`
|
|
}
|
|
|
|
// PaymentEndpoint is a polymorphic payment destination/source.
|
|
type PaymentEndpoint struct {
|
|
Type PaymentEndpointType `bson:"type" json:"type"`
|
|
Ledger *LedgerEndpoint `bson:"ledger,omitempty" json:"ledger,omitempty"`
|
|
ManagedWallet *ManagedWalletEndpoint `bson:"managedWallet,omitempty" json:"managedWallet,omitempty"`
|
|
ExternalChain *ExternalChainEndpoint `bson:"externalChain,omitempty" json:"externalChain,omitempty"`
|
|
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
// FXIntent captures FX conversion preferences.
|
|
type FXIntent struct {
|
|
Pair *fxv1.CurrencyPair `bson:"pair,omitempty" json:"pair,omitempty"`
|
|
Side fxv1.Side `bson:"side,omitempty" json:"side,omitempty"`
|
|
Firm bool `bson:"firm,omitempty" json:"firm,omitempty"`
|
|
TTLMillis int64 `bson:"ttlMillis,omitempty" json:"ttlMillis,omitempty"`
|
|
PreferredProvider string `bson:"preferredProvider,omitempty" json:"preferredProvider,omitempty"`
|
|
MaxAgeMillis int32 `bson:"maxAgeMillis,omitempty" json:"maxAgeMillis,omitempty"`
|
|
}
|
|
|
|
// PaymentIntent models the requested payment operation.
|
|
type PaymentIntent struct {
|
|
Kind PaymentKind `bson:"kind" json:"kind"`
|
|
Source PaymentEndpoint `bson:"source" json:"source"`
|
|
Destination PaymentEndpoint `bson:"destination" json:"destination"`
|
|
Amount *moneyv1.Money `bson:"amount" json:"amount"`
|
|
RequiresFX bool `bson:"requiresFx,omitempty" json:"requiresFx,omitempty"`
|
|
FX *FXIntent `bson:"fx,omitempty" json:"fx,omitempty"`
|
|
FeePolicy *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
|
|
Attributes map[string]string `bson:"attributes,omitempty" json:"attributes,omitempty"`
|
|
}
|
|
|
|
// PaymentQuoteSnapshot stores the latest quote info.
|
|
type PaymentQuoteSnapshot struct {
|
|
DebitAmount *moneyv1.Money `bson:"debitAmount,omitempty" json:"debitAmount,omitempty"`
|
|
ExpectedSettlementAmount *moneyv1.Money `bson:"expectedSettlementAmount,omitempty" json:"expectedSettlementAmount,omitempty"`
|
|
ExpectedFeeTotal *moneyv1.Money `bson:"expectedFeeTotal,omitempty" json:"expectedFeeTotal,omitempty"`
|
|
FeeLines []*feesv1.DerivedPostingLine `bson:"feeLines,omitempty" json:"feeLines,omitempty"`
|
|
FeeRules []*feesv1.AppliedRule `bson:"feeRules,omitempty" json:"feeRules,omitempty"`
|
|
FXQuote *oraclev1.Quote `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
|
|
NetworkFee *chainv1.EstimateTransferFeeResponse `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
|
|
FeeQuoteToken string `bson:"feeQuoteToken,omitempty" json:"feeQuoteToken,omitempty"`
|
|
}
|
|
|
|
// ExecutionRefs links to downstream systems.
|
|
type ExecutionRefs struct {
|
|
DebitEntryRef string `bson:"debitEntryRef,omitempty" json:"debitEntryRef,omitempty"`
|
|
CreditEntryRef string `bson:"creditEntryRef,omitempty" json:"creditEntryRef,omitempty"`
|
|
FXEntryRef string `bson:"fxEntryRef,omitempty" json:"fxEntryRef,omitempty"`
|
|
ChainTransferRef string `bson:"chainTransferRef,omitempty" json:"chainTransferRef,omitempty"`
|
|
}
|
|
|
|
// Payment persists orchestrated payment lifecycle.
|
|
type Payment struct {
|
|
storable.Base `bson:",inline" json:",inline"`
|
|
model.OrganizationBoundBase `bson:",inline" json:",inline"`
|
|
|
|
PaymentRef string `bson:"paymentRef" json:"paymentRef"`
|
|
IdempotencyKey string `bson:"idempotencyKey" json:"idempotencyKey"`
|
|
Intent PaymentIntent `bson:"intent" json:"intent"`
|
|
State PaymentState `bson:"state" json:"state"`
|
|
FailureCode PaymentFailureCode `bson:"failureCode,omitempty" json:"failureCode,omitempty"`
|
|
FailureReason string `bson:"failureReason,omitempty" json:"failureReason,omitempty"`
|
|
LastQuote *PaymentQuoteSnapshot `bson:"lastQuote,omitempty" json:"lastQuote,omitempty"`
|
|
Execution *ExecutionRefs `bson:"execution,omitempty" json:"execution,omitempty"`
|
|
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
|
}
|
|
|
|
// Collection implements storable.Storable.
|
|
func (*Payment) Collection() string {
|
|
return mservice.Payments
|
|
}
|
|
|
|
// PaymentFilter enables filtered queries.
|
|
type PaymentFilter struct {
|
|
States []PaymentState
|
|
SourceRef string
|
|
DestinationRef string
|
|
Cursor string
|
|
Limit int32
|
|
}
|
|
|
|
// PaymentList contains paginated results.
|
|
type PaymentList struct {
|
|
Items []*Payment
|
|
NextCursor string
|
|
}
|
|
|
|
// Normalize harmonises string fields for indexing and comparisons.
|
|
func (p *Payment) Normalize() {
|
|
p.PaymentRef = strings.TrimSpace(p.PaymentRef)
|
|
p.IdempotencyKey = strings.TrimSpace(p.IdempotencyKey)
|
|
p.FailureReason = strings.TrimSpace(p.FailureReason)
|
|
if p.Metadata != nil {
|
|
for k, v := range p.Metadata {
|
|
p.Metadata[k] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
normalizeEndpoint(&p.Intent.Source)
|
|
normalizeEndpoint(&p.Intent.Destination)
|
|
if p.Intent.Attributes != nil {
|
|
for k, v := range p.Intent.Attributes {
|
|
p.Intent.Attributes[k] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
if p.Execution != nil {
|
|
p.Execution.DebitEntryRef = strings.TrimSpace(p.Execution.DebitEntryRef)
|
|
p.Execution.CreditEntryRef = strings.TrimSpace(p.Execution.CreditEntryRef)
|
|
p.Execution.FXEntryRef = strings.TrimSpace(p.Execution.FXEntryRef)
|
|
p.Execution.ChainTransferRef = strings.TrimSpace(p.Execution.ChainTransferRef)
|
|
}
|
|
}
|
|
|
|
func normalizeEndpoint(ep *PaymentEndpoint) {
|
|
if ep == nil {
|
|
return
|
|
}
|
|
if ep.Metadata != nil {
|
|
for k, v := range ep.Metadata {
|
|
ep.Metadata[k] = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
switch ep.Type {
|
|
case EndpointTypeLedger:
|
|
if ep.Ledger != nil {
|
|
ep.Ledger.LedgerAccountRef = strings.TrimSpace(ep.Ledger.LedgerAccountRef)
|
|
ep.Ledger.ContraLedgerAccountRef = strings.TrimSpace(ep.Ledger.ContraLedgerAccountRef)
|
|
}
|
|
case EndpointTypeManagedWallet:
|
|
if ep.ManagedWallet != nil {
|
|
ep.ManagedWallet.ManagedWalletRef = strings.TrimSpace(ep.ManagedWallet.ManagedWalletRef)
|
|
if ep.ManagedWallet.Asset != nil {
|
|
ep.ManagedWallet.Asset.TokenSymbol = strings.TrimSpace(strings.ToUpper(ep.ManagedWallet.Asset.TokenSymbol))
|
|
ep.ManagedWallet.Asset.ContractAddress = strings.TrimSpace(strings.ToLower(ep.ManagedWallet.Asset.ContractAddress))
|
|
}
|
|
}
|
|
case EndpointTypeExternalChain:
|
|
if ep.ExternalChain != nil {
|
|
ep.ExternalChain.Address = strings.TrimSpace(strings.ToLower(ep.ExternalChain.Address))
|
|
ep.ExternalChain.Memo = strings.TrimSpace(ep.ExternalChain.Memo)
|
|
if ep.ExternalChain.Asset != nil {
|
|
ep.ExternalChain.Asset.TokenSymbol = strings.TrimSpace(strings.ToUpper(ep.ExternalChain.Asset.TokenSymbol))
|
|
ep.ExternalChain.Asset.ContractAddress = strings.TrimSpace(strings.ToLower(ep.ExternalChain.Asset.ContractAddress))
|
|
}
|
|
}
|
|
}
|
|
}
|