payment quotation v2 + payment orchestration v2 draft
This commit is contained in:
@@ -5,6 +5,7 @@ go 1.25.7
|
||||
replace github.com/tech/sendico/pkg => ../../pkg
|
||||
|
||||
require (
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/tech/sendico/pkg v0.1.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
go.uber.org/zap v1.27.1
|
||||
|
||||
@@ -91,6 +91,8 @@ github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/i
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
|
||||
224
api/payments/storage/model/gateway_eligibility.go
Normal file
224
api/payments/storage/model/gateway_eligibility.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type GatewayDirection int
|
||||
|
||||
const (
|
||||
GatewayDirectionAny GatewayDirection = iota
|
||||
GatewayDirectionOut
|
||||
GatewayDirectionIn
|
||||
)
|
||||
|
||||
func (d GatewayDirection) String() string {
|
||||
switch d {
|
||||
case GatewayDirectionOut:
|
||||
return "out"
|
||||
case GatewayDirectionIn:
|
||||
return "in"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
|
||||
func NoEligibleGatewayMessage(network, currency string, action RailOperation, dir GatewayDirection) string {
|
||||
return fmt.Sprintf(
|
||||
"plan builder: no eligible gateway found for %s %s %s for direction %s",
|
||||
strings.ToUpper(strings.TrimSpace(network)),
|
||||
strings.ToUpper(strings.TrimSpace(currency)),
|
||||
ParseRailOperation(string(action)),
|
||||
dir.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func IsGatewayEligible(
|
||||
gw *GatewayInstanceDescriptor,
|
||||
rail Rail,
|
||||
network, currency string,
|
||||
action RailOperation,
|
||||
dir GatewayDirection,
|
||||
amount decimal.Decimal,
|
||||
) error {
|
||||
if gw == nil {
|
||||
return gatewayIneligible(gw, "gateway instance is required")
|
||||
}
|
||||
if !gw.IsEnabled {
|
||||
return gatewayIneligible(gw, "gateway instance is disabled")
|
||||
}
|
||||
if gw.Rail != rail {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("rail mismatch: want %s got %s", rail, gw.Rail))
|
||||
}
|
||||
if network != "" && gw.Network != "" && !strings.EqualFold(gw.Network, network) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("network mismatch: want %s got %s", network, gw.Network))
|
||||
}
|
||||
if currency != "" && len(gw.Currencies) > 0 {
|
||||
found := false
|
||||
for _, c := range gw.Currencies {
|
||||
if strings.EqualFold(c, currency) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return gatewayIneligible(gw, "currency not supported: "+currency)
|
||||
}
|
||||
}
|
||||
|
||||
if !gatewayAllowsAction(gw.Operations, gw.Capabilities, action, dir) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("gateway does not allow action=%s dir=%s", action, dir.String()))
|
||||
}
|
||||
|
||||
if currency != "" {
|
||||
if err := amountWithinLimits(gw, gw.Limits, currency, amount, action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type gatewayIneligibleError struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
func (e gatewayIneligibleError) Error() string {
|
||||
return e.reason
|
||||
}
|
||||
|
||||
func gatewayIneligible(gw *GatewayInstanceDescriptor, reason string) error {
|
||||
if strings.TrimSpace(reason) == "" {
|
||||
reason = "gateway instance is not eligible"
|
||||
}
|
||||
instanceID := ""
|
||||
if gw != nil {
|
||||
instanceID = gw.InstanceID
|
||||
}
|
||||
return gatewayIneligibleError{reason: fmt.Sprintf("gateway %s eligibility check error: %s", instanceID, reason)}
|
||||
}
|
||||
|
||||
func gatewayAllowsAction(operations []RailOperation, cap RailCapabilities, action RailOperation, dir GatewayDirection) bool {
|
||||
normalized := NormalizeRailOperations(operations)
|
||||
if len(normalized) > 0 {
|
||||
return operationsAllowAction(normalized, action, dir)
|
||||
}
|
||||
return capabilityAllowsAction(cap, action, dir)
|
||||
}
|
||||
|
||||
func capabilityAllowsAction(cap RailCapabilities, action RailOperation, dir GatewayDirection) bool {
|
||||
switch action {
|
||||
case RailOperationSend:
|
||||
switch dir {
|
||||
case GatewayDirectionOut:
|
||||
return cap.CanPayOut
|
||||
case GatewayDirectionIn:
|
||||
return cap.CanPayIn
|
||||
default:
|
||||
return cap.CanPayIn || cap.CanPayOut
|
||||
}
|
||||
case RailOperationExternalDebit, RailOperationExternalCredit:
|
||||
switch dir {
|
||||
case GatewayDirectionOut:
|
||||
return cap.CanPayOut
|
||||
case GatewayDirectionIn:
|
||||
return cap.CanPayIn
|
||||
default:
|
||||
return cap.CanPayIn || cap.CanPayOut
|
||||
}
|
||||
case RailOperationFee:
|
||||
return cap.CanSendFee
|
||||
case RailOperationObserveConfirm:
|
||||
return cap.RequiresObserveConfirm
|
||||
case RailOperationBlock:
|
||||
return cap.CanBlock
|
||||
case RailOperationRelease:
|
||||
return cap.CanRelease
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func operationsAllowAction(operations []RailOperation, action RailOperation, dir GatewayDirection) bool {
|
||||
action = ParseRailOperation(string(action))
|
||||
if action == RailOperationUnspecified {
|
||||
return false
|
||||
}
|
||||
|
||||
if HasRailOperation(operations, action) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch action {
|
||||
case RailOperationSend:
|
||||
switch dir {
|
||||
case GatewayDirectionIn:
|
||||
return HasRailOperation(operations, RailOperationExternalDebit)
|
||||
case GatewayDirectionOut:
|
||||
return HasRailOperation(operations, RailOperationExternalCredit)
|
||||
default:
|
||||
return HasRailOperation(operations, RailOperationExternalDebit) ||
|
||||
HasRailOperation(operations, RailOperationExternalCredit)
|
||||
}
|
||||
case RailOperationExternalDebit:
|
||||
return HasRailOperation(operations, RailOperationSend)
|
||||
case RailOperationExternalCredit:
|
||||
return HasRailOperation(operations, RailOperationSend)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func amountWithinLimits(gw *GatewayInstanceDescriptor, limits Limits, currency string, amount decimal.Decimal, action RailOperation) error {
|
||||
min := firstLimitValue(limits.MinAmount, "")
|
||||
max := firstLimitValue(limits.MaxAmount, "")
|
||||
perTxMin := firstLimitValue(limits.PerTxMinAmount, "")
|
||||
perTxMax := firstLimitValue(limits.PerTxMaxAmount, "")
|
||||
maxFee := firstLimitValue(limits.PerTxMaxFee, "")
|
||||
|
||||
if override, ok := limits.CurrencyLimits[currency]; ok {
|
||||
min = firstLimitValue(override.MinAmount, min)
|
||||
max = firstLimitValue(override.MaxAmount, max)
|
||||
if action == RailOperationFee {
|
||||
maxFee = firstLimitValue(override.MaxFee, maxFee)
|
||||
}
|
||||
}
|
||||
|
||||
if min != "" {
|
||||
if val, err := decimal.NewFromString(min); err == nil && amount.LessThan(val) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("amount %s %s below min limit %s", amount.String(), currency, val.String()))
|
||||
}
|
||||
}
|
||||
if perTxMin != "" {
|
||||
if val, err := decimal.NewFromString(perTxMin); err == nil && amount.LessThan(val) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("amount %s %s below per-tx min limit %s", amount.String(), currency, val.String()))
|
||||
}
|
||||
}
|
||||
if max != "" {
|
||||
if val, err := decimal.NewFromString(max); err == nil && amount.GreaterThan(val) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("amount %s %s exceeds max limit %s", amount.String(), currency, val.String()))
|
||||
}
|
||||
}
|
||||
if perTxMax != "" {
|
||||
if val, err := decimal.NewFromString(perTxMax); err == nil && amount.GreaterThan(val) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("amount %s %s exceeds per-tx max limit %s", amount.String(), currency, val.String()))
|
||||
}
|
||||
}
|
||||
if action == RailOperationFee && maxFee != "" {
|
||||
if val, err := decimal.NewFromString(maxFee); err == nil && amount.GreaterThan(val) {
|
||||
return gatewayIneligible(gw, fmt.Sprintf("fee amount %s %s exceeds max fee limit %s", amount.String(), currency, val.String()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func firstLimitValue(primary, fallback string) string {
|
||||
val := strings.TrimSpace(primary)
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return strings.TrimSpace(fallback)
|
||||
}
|
||||
49
api/payments/storage/model/gateway_eligibility_test.go
Normal file
49
api/payments/storage/model/gateway_eligibility_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func TestIsGatewayEligible_AllowsMatchingGateway(t *testing.T) {
|
||||
gw := &GatewayInstanceDescriptor{
|
||||
ID: "gw-1",
|
||||
InstanceID: "inst-1",
|
||||
Rail: RailCrypto,
|
||||
Network: "TRON",
|
||||
Currencies: []string{"USDT"},
|
||||
Operations: []RailOperation{RailOperationSend, RailOperationExternalCredit},
|
||||
IsEnabled: true,
|
||||
}
|
||||
|
||||
err := IsGatewayEligible(gw, RailCrypto, "TRON", "USDT", RailOperationSend, GatewayDirectionOut, decimal.RequireFromString("10"))
|
||||
if err != nil {
|
||||
t.Fatalf("expected gateway to be eligible, got err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGatewayEligible_RejectsNetworkMismatch(t *testing.T) {
|
||||
gw := &GatewayInstanceDescriptor{
|
||||
ID: "gw-1",
|
||||
InstanceID: "inst-1",
|
||||
Rail: RailCrypto,
|
||||
Network: "ETH",
|
||||
Currencies: []string{"USDT"},
|
||||
Operations: []RailOperation{RailOperationSend},
|
||||
IsEnabled: true,
|
||||
}
|
||||
|
||||
err := IsGatewayEligible(gw, RailCrypto, "TRON", "USDT", RailOperationSend, GatewayDirectionOut, decimal.RequireFromString("10"))
|
||||
if err == nil {
|
||||
t.Fatalf("expected network mismatch error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoEligibleGatewayMessage(t *testing.T) {
|
||||
got := NoEligibleGatewayMessage("tron", "usdt", RailOperationSend, GatewayDirectionOut)
|
||||
want := "plan builder: no eligible gateway found for TRON USDT SEND for direction out"
|
||||
if got != want {
|
||||
t.Fatalf("unexpected message: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,7 @@ type GatewayInstanceDescriptor struct {
|
||||
Network string `bson:"network,omitempty" json:"network,omitempty"`
|
||||
InvokeURI string `bson:"invokeUri,omitempty" json:"invokeUri,omitempty"`
|
||||
Currencies []string `bson:"currencies,omitempty" json:"currencies,omitempty"`
|
||||
Operations []RailOperation `bson:"operations,omitempty" json:"operations,omitempty"`
|
||||
Capabilities RailCapabilities `bson:"capabilities,omitempty" json:"capabilities,omitempty"`
|
||||
Limits Limits `bson:"limits,omitempty" json:"limits,omitempty"`
|
||||
Version string `bson:"version,omitempty" json:"version,omitempty"`
|
||||
@@ -305,18 +306,18 @@ type PaymentPlan struct {
|
||||
|
||||
// ExecutionStep describes a planned or executed payment step for reporting.
|
||||
type ExecutionStep struct {
|
||||
Code string `bson:"code,omitempty" json:"code,omitempty"`
|
||||
Description string `bson:"description,omitempty" json:"description,omitempty"`
|
||||
Amount *paymenttypes.Money `bson:"amount,omitempty" json:"amount,omitempty"`
|
||||
NetworkFee *paymenttypes.Money `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
|
||||
SourceWalletRef string `bson:"sourceWalletRef,omitempty" json:"sourceWalletRef,omitempty"`
|
||||
DestinationRef string `bson:"destinationRef,omitempty" json:"destinationRef,omitempty"`
|
||||
TransferRef string `bson:"transferRef,omitempty" json:"transferRef,omitempty"`
|
||||
OperationRef string `bson:"operationRef,omitempty" json:"operationRef,omitempty"`
|
||||
ReportVisibility ReportVisibility `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"`
|
||||
Error string `bson:"error,omitempty" json:"error,omitempty"`
|
||||
State OperationState `bson:"state,omitempty" json:"state,omitempty"`
|
||||
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
||||
Code string `bson:"code,omitempty" json:"code,omitempty"`
|
||||
Description string `bson:"description,omitempty" json:"description,omitempty"`
|
||||
Amount *paymenttypes.Money `bson:"amount,omitempty" json:"amount,omitempty"`
|
||||
NetworkFee *paymenttypes.Money `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
|
||||
SourceWalletRef string `bson:"sourceWalletRef,omitempty" json:"sourceWalletRef,omitempty"`
|
||||
DestinationRef string `bson:"destinationRef,omitempty" json:"destinationRef,omitempty"`
|
||||
TransferRef string `bson:"transferRef,omitempty" json:"transferRef,omitempty"`
|
||||
OperationRef string `bson:"operationRef,omitempty" json:"operationRef,omitempty"`
|
||||
ReportVisibility ReportVisibility `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"`
|
||||
Error string `bson:"error,omitempty" json:"error,omitempty"`
|
||||
State OperationState `bson:"state,omitempty" json:"state,omitempty"`
|
||||
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (s *ExecutionStep) IsTerminal() bool {
|
||||
|
||||
93
api/payments/storage/model/rail_operations.go
Normal file
93
api/payments/storage/model/rail_operations.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package model
|
||||
|
||||
import "strings"
|
||||
|
||||
var supportedRailOperations = map[RailOperation]struct{}{
|
||||
RailOperationDebit: {},
|
||||
RailOperationCredit: {},
|
||||
RailOperationExternalDebit: {},
|
||||
RailOperationExternalCredit: {},
|
||||
RailOperationMove: {},
|
||||
RailOperationSend: {},
|
||||
RailOperationFee: {},
|
||||
RailOperationObserveConfirm: {},
|
||||
RailOperationFXConvert: {},
|
||||
RailOperationBlock: {},
|
||||
RailOperationRelease: {},
|
||||
}
|
||||
|
||||
// ParseRailOperation canonicalizes string values into a RailOperation token.
|
||||
func ParseRailOperation(value string) RailOperation {
|
||||
clean := strings.ToUpper(strings.TrimSpace(value))
|
||||
if clean == "" {
|
||||
return RailOperationUnspecified
|
||||
}
|
||||
return RailOperation(clean)
|
||||
}
|
||||
|
||||
// IsSupportedRailOperation reports whether op is recognized by payment planning.
|
||||
func IsSupportedRailOperation(op RailOperation) bool {
|
||||
_, ok := supportedRailOperations[ParseRailOperation(string(op))]
|
||||
return ok
|
||||
}
|
||||
|
||||
// NormalizeRailOperations trims, uppercases, deduplicates, and filters unknown values.
|
||||
func NormalizeRailOperations(values []RailOperation) []RailOperation {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]RailOperation, 0, len(values))
|
||||
seen := map[RailOperation]bool{}
|
||||
for _, value := range values {
|
||||
op := ParseRailOperation(string(value))
|
||||
if op == RailOperationUnspecified || !IsSupportedRailOperation(op) || seen[op] {
|
||||
continue
|
||||
}
|
||||
seen[op] = true
|
||||
result = append(result, op)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// NormalizeRailOperationStrings normalizes string operation values.
|
||||
func NormalizeRailOperationStrings(values []string) []RailOperation {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
ops := make([]RailOperation, 0, len(values))
|
||||
for _, value := range values {
|
||||
ops = append(ops, ParseRailOperation(value))
|
||||
}
|
||||
return NormalizeRailOperations(ops)
|
||||
}
|
||||
|
||||
// HasRailOperation checks whether ops includes action.
|
||||
func HasRailOperation(ops []RailOperation, action RailOperation) bool {
|
||||
want := ParseRailOperation(string(action))
|
||||
if want == RailOperationUnspecified {
|
||||
return false
|
||||
}
|
||||
for _, op := range ops {
|
||||
if ParseRailOperation(string(op)) == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RailCapabilitiesFromOperations derives legacy capability flags from explicit operations.
|
||||
func RailCapabilitiesFromOperations(ops []RailOperation) RailCapabilities {
|
||||
normalized := NormalizeRailOperations(ops)
|
||||
return RailCapabilities{
|
||||
CanPayIn: HasRailOperation(normalized, RailOperationExternalDebit),
|
||||
CanPayOut: HasRailOperation(normalized, RailOperationSend) || HasRailOperation(normalized, RailOperationExternalCredit),
|
||||
CanReadBalance: false,
|
||||
CanSendFee: HasRailOperation(normalized, RailOperationFee),
|
||||
RequiresObserveConfirm: HasRailOperation(normalized, RailOperationObserveConfirm),
|
||||
CanBlock: HasRailOperation(normalized, RailOperationBlock),
|
||||
CanRelease: HasRailOperation(normalized, RailOperationRelease),
|
||||
}
|
||||
}
|
||||
65
api/payments/storage/model/rail_operations_test.go
Normal file
65
api/payments/storage/model/rail_operations_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizeRailOperations(t *testing.T) {
|
||||
ops := NormalizeRailOperations([]RailOperation{
|
||||
"send",
|
||||
"SEND",
|
||||
" external_credit ",
|
||||
"unknown",
|
||||
"",
|
||||
})
|
||||
if len(ops) != 2 {
|
||||
t.Fatalf("unexpected operations count: got=%d want=2", len(ops))
|
||||
}
|
||||
if ops[0] != RailOperationSend {
|
||||
t.Fatalf("unexpected first operation: got=%q want=%q", ops[0], RailOperationSend)
|
||||
}
|
||||
if ops[1] != RailOperationExternalCredit {
|
||||
t.Fatalf("unexpected second operation: got=%q want=%q", ops[1], RailOperationExternalCredit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasRailOperation(t *testing.T) {
|
||||
ops := []RailOperation{RailOperationSend, RailOperationExternalCredit}
|
||||
if !HasRailOperation(ops, RailOperationSend) {
|
||||
t.Fatalf("expected send operation to be present")
|
||||
}
|
||||
if !HasRailOperation(ops, " external_credit ") {
|
||||
t.Fatalf("expected external credit operation to be present")
|
||||
}
|
||||
if HasRailOperation(ops, RailOperationObserveConfirm) {
|
||||
t.Fatalf("did not expect observe confirm operation to be present")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailCapabilitiesFromOperations(t *testing.T) {
|
||||
cap := RailCapabilitiesFromOperations([]RailOperation{
|
||||
RailOperationExternalDebit,
|
||||
RailOperationExternalCredit,
|
||||
RailOperationFee,
|
||||
RailOperationObserveConfirm,
|
||||
RailOperationBlock,
|
||||
RailOperationRelease,
|
||||
})
|
||||
|
||||
if !cap.CanPayIn {
|
||||
t.Fatalf("expected can pay in to be true")
|
||||
}
|
||||
if !cap.CanPayOut {
|
||||
t.Fatalf("expected can pay out to be true")
|
||||
}
|
||||
if !cap.CanSendFee {
|
||||
t.Fatalf("expected can send fee to be true")
|
||||
}
|
||||
if !cap.RequiresObserveConfirm {
|
||||
t.Fatalf("expected requires observe confirm to be true")
|
||||
}
|
||||
if !cap.CanBlock {
|
||||
t.Fatalf("expected can block to be true")
|
||||
}
|
||||
if !cap.CanRelease {
|
||||
t.Fatalf("expected can release to be true")
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
)
|
||||
|
||||
// Store implements storage.Repository backed by MongoDB.
|
||||
@@ -23,6 +24,7 @@ type Store struct {
|
||||
logger mlogger.Logger
|
||||
ping func(context.Context) error
|
||||
|
||||
database *mongo.Database
|
||||
payments storage.PaymentsStore
|
||||
methods storage.PaymentMethodsStore
|
||||
quotes quotestorage.QuotesStore
|
||||
@@ -71,17 +73,18 @@ func New(logger mlogger.Logger, conn *db.MongoConnection, opts ...Option) (*Stor
|
||||
plansRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentPlanTemplate{}).Collection())
|
||||
methodsRepo := repository.CreateMongoRepository(conn.Database(), mservice.PaymentMethods)
|
||||
|
||||
return newWithRepository(logger, conn.Ping, paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo, opts...)
|
||||
return newWithRepository(logger, conn.Ping, conn.Database(), paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo, opts...)
|
||||
}
|
||||
|
||||
// NewWithRepository constructs a payments repository using the provided primitives.
|
||||
func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, paymentsRepo repository.Repository, quotesRepo repository.Repository, routesRepo repository.Repository, plansRepo repository.Repository, opts ...Option) (*Store, error) {
|
||||
return newWithRepository(logger, ping, paymentsRepo, nil, quotesRepo, routesRepo, plansRepo, opts...)
|
||||
return newWithRepository(logger, ping, nil, paymentsRepo, nil, quotesRepo, routesRepo, plansRepo, opts...)
|
||||
}
|
||||
|
||||
func newWithRepository(
|
||||
logger mlogger.Logger,
|
||||
ping func(context.Context) error,
|
||||
database *mongo.Database,
|
||||
paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo repository.Repository,
|
||||
opts ...Option,
|
||||
) (*Store, error) {
|
||||
@@ -147,6 +150,7 @@ func newWithRepository(
|
||||
result := &Store{
|
||||
logger: childLogger,
|
||||
ping: ping,
|
||||
database: database,
|
||||
payments: paymentsStore,
|
||||
methods: methodsStore,
|
||||
quotes: quotesRepoStore.Quotes(),
|
||||
@@ -190,4 +194,12 @@ func (s *Store) PlanTemplates() storage.PlanTemplatesStore {
|
||||
return s.plans
|
||||
}
|
||||
|
||||
// MongoDatabase returns underlying Mongo database when available.
|
||||
func (s *Store) MongoDatabase() *mongo.Database {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return s.database
|
||||
}
|
||||
|
||||
var _ storage.Repository = (*Store)(nil)
|
||||
|
||||
Reference in New Issue
Block a user