Orchestration / payments v2 #554
@@ -19,6 +19,7 @@ func requiredIndexes() []*indexDefinition {
|
||||
Keys: []ri.Key{
|
||||
{Field: "paymentRef", Sort: ri.Asc},
|
||||
},
|
||||
Unique: true,
|
||||
},
|
||||
{
|
||||
Keys: []ri.Key{
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestNewWithStore_EnsuresRequiredIndexes(t *testing.T) {
|
||||
}
|
||||
|
||||
assertIndex(t, store.indexes[0], []string{"organizationRef", "paymentRef"}, true)
|
||||
assertIndex(t, store.indexes[1], []string{"paymentRef"}, false)
|
||||
assertIndex(t, store.indexes[1], []string{"paymentRef"}, true)
|
||||
assertIndex(t, store.indexes[2], []string{"organizationRef", "idempotencyKey"}, true)
|
||||
assertIndex(t, store.indexes[3], []string{"organizationRef", "quotationRef", "createdAt"}, false)
|
||||
assertIndex(t, store.indexes[4], []string{"organizationRef", "state", "createdAt"}, false)
|
||||
|
||||
@@ -472,6 +472,25 @@ func (s *Service) pollObserveCandidate(ctx context.Context, payment *agg.Payment
|
||||
}
|
||||
|
||||
func (s *Service) resolveObserveGateway(ctx context.Context, payment *agg.Payment, candidate runningObserveCandidate) (*model.GatewayInstanceDescriptor, error) {
|
||||
if gatewayID := strings.TrimSpace(candidate.gatewayInstanceID); gatewayID != "" {
|
||||
items, err := s.gatewayRegistry.List(ctx)
|
||||
if err == nil {
|
||||
for i := range items {
|
||||
item := items[i]
|
||||
if item == nil || !item.IsEnabled {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(item.ID), gatewayID) && !strings.EqualFold(strings.TrimSpace(item.InstanceID), gatewayID) {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(item.InvokeURI) == "" {
|
||||
continue
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executor := gatewayCryptoExecutor{
|
||||
gatewayRegistry: s.gatewayRegistry,
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/erecon"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
pm "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/payments/rail"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
@@ -271,5 +273,59 @@ func TestRunningObserveCandidates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveObserveGateway_UsesExternalRefGatewayInstanceAcrossRails(t *testing.T) {
|
||||
svc := &Service{
|
||||
gatewayRegistry: &fakeGatewayRegistry{
|
||||
items: []*model.GatewayInstanceDescriptor{
|
||||
{
|
||||
ID: "payment_gateway_settlement",
|
||||
InstanceID: "payment_gateway_settlement",
|
||||
Rail: model.RailProviderSettlement,
|
||||
InvokeURI: "grpc://tgsettle-gateway",
|
||||
IsEnabled: true,
|
||||
},
|
||||
{
|
||||
ID: "crypto_rail_gateway_tron_nile",
|
||||
InstanceID: "crypto_rail_gateway_tron_nile",
|
||||
Rail: model.RailCrypto,
|
||||
InvokeURI: "grpc://tron-gateway",
|
||||
IsEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payment := &agg.Payment{
|
||||
QuoteSnapshot: &model.PaymentQuoteSnapshot{
|
||||
Route: &paymenttypes.QuoteRouteSpecification{
|
||||
Hops: []*paymenttypes.QuoteRouteHop{
|
||||
{
|
||||
Index: 1,
|
||||
Rail: "CRYPTO",
|
||||
Gateway: "crypto_rail_gateway_tron_nile",
|
||||
InstanceID: "crypto_rail_gateway_tron_nile",
|
||||
Role: paymenttypes.QuoteRouteHopRoleSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
gateway, err := svc.resolveObserveGateway(context.Background(), payment, runningObserveCandidate{
|
||||
stepRef: "hop_2_settlement_observe",
|
||||
transferRef: "trf-1",
|
||||
gatewayInstanceID: "payment_gateway_settlement",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveObserveGateway returned error: %v", err)
|
||||
}
|
||||
if gateway == nil {
|
||||
t.Fatal("expected gateway")
|
||||
}
|
||||
if got, want := gateway.ID, "payment_gateway_settlement"; got != want {
|
||||
t.Fatalf("gateway id mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
var _ prepo.Repository = (*fakeExternalRuntimeRepo)(nil)
|
||||
var _ psvc.Service = (*fakeExternalRuntimeV2)(nil)
|
||||
|
||||
@@ -35,9 +35,9 @@ func newOrchestrationV2Service(logger mlogger.Logger, repo storage.Repository, r
|
||||
return nil, nil, merrors.Internal("No repo for orchestrator v2 provided")
|
||||
}
|
||||
|
||||
paymentRepo := buildPaymentRepositoryV2(repo, logger)
|
||||
if paymentRepo == nil {
|
||||
logger.Error("Orchestration v2 disabled: database not available")
|
||||
paymentRepo, err := buildPaymentRepositoryV2(repo, logger)
|
||||
if paymentRepo == nil || err != nil {
|
||||
logger.Error("Orchestration v2 disabled: database not available", zap.Error(err))
|
||||
return nil, nil, merrors.Internal("database is not available")
|
||||
}
|
||||
|
||||
@@ -81,6 +81,10 @@ func buildOrchestrationV2Executors(logger mlogger.Logger, runtimeDeps v2RuntimeD
|
||||
gatewayRegistry: runtimeDeps.GatewayRegistry,
|
||||
cardGatewayRoutes: cloneCardGatewayRoutes(runtimeDeps.CardGatewayRoutes),
|
||||
}
|
||||
providerSettlementExecutor := &gatewayProviderSettlementExecutor{
|
||||
gatewayInvokeResolver: runtimeDeps.GatewayInvokeResolver,
|
||||
gatewayRegistry: runtimeDeps.GatewayRegistry,
|
||||
}
|
||||
guardExecutor := &gatewayGuardExecutor{
|
||||
logger: execLogger.Named("guard"),
|
||||
gatewayInvokeResolver: runtimeDeps.GatewayInvokeResolver,
|
||||
@@ -88,30 +92,28 @@ func buildOrchestrationV2Executors(logger mlogger.Logger, runtimeDeps v2RuntimeD
|
||||
}
|
||||
return psvc.NewDefaultExecutors(execLogger, sexec.Dependencies{
|
||||
Crypto: cryptoExecutor,
|
||||
ProviderSettlement: providerSettlementExecutor,
|
||||
Guard: guardExecutor,
|
||||
})
|
||||
}
|
||||
|
||||
func buildPaymentRepositoryV2(repo storage.Repository, logger mlogger.Logger) prepo.Repository {
|
||||
func buildPaymentRepositoryV2(repo storage.Repository, logger mlogger.Logger) (prepo.Repository, error) {
|
||||
if repo == nil {
|
||||
return nil
|
||||
return nil, merrors.InvalidArgument("repo must be provided")
|
||||
}
|
||||
provider, ok := repo.(v2MongoDBProvider)
|
||||
if !ok {
|
||||
return nil
|
||||
return nil, merrors.Internal("Failed to fetch correct repository interface")
|
||||
}
|
||||
db := provider.MongoDatabase()
|
||||
if db == nil {
|
||||
return nil
|
||||
return nil, merrors.Internal("Failed to fetch database")
|
||||
}
|
||||
paymentRepo, err := prepo.NewMongo(
|
||||
db.Collection(mservice.Payments),
|
||||
prepo.Dependencies{Logger: logger},
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return paymentRepo
|
||||
return paymentRepo, err
|
||||
}
|
||||
|
||||
type v2GRPCServer struct {
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
settlementMetadataQuoteRef = "quote_ref"
|
||||
settlementMetadataOutgoingLeg = "outgoing_leg"
|
||||
)
|
||||
|
||||
type gatewayProviderSettlementExecutor struct {
|
||||
gatewayInvokeResolver GatewayInvokeResolver
|
||||
gatewayRegistry GatewayRegistry
|
||||
}
|
||||
|
||||
func (e *gatewayProviderSettlementExecutor) ExecuteProviderSettlement(ctx context.Context, req sexec.StepRequest) (*sexec.ExecuteOutput, error) {
|
||||
if req.Payment == nil {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: payment is required")
|
||||
}
|
||||
if model.ParseRailOperation(string(req.Step.Action)) != model.RailOperationFXConvert {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: unsupported action")
|
||||
}
|
||||
|
||||
gateway, err := e.resolveGateway(ctx, req.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := e.gatewayInvokeResolver.Resolve(ctx, gateway.InvokeURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sourceWalletRef, err := sourceManagedWalletRef(req.Payment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destination := &chainv1.TransferDestination{
|
||||
Destination: &chainv1.TransferDestination_ManagedWalletRef{
|
||||
ManagedWalletRef: sourceWalletRef,
|
||||
},
|
||||
}
|
||||
|
||||
amount, err := settlementAmount(req.Payment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stepRef := strings.TrimSpace(req.Step.StepRef)
|
||||
operationRef := strings.TrimSpace(req.Payment.PaymentRef) + ":" + stepRef
|
||||
idempotencyKey := strings.TrimSpace(req.Payment.IdempotencyKey)
|
||||
if idempotencyKey == "" {
|
||||
idempotencyKey = operationRef
|
||||
}
|
||||
idempotencyKey += ":" + stepRef
|
||||
|
||||
resp, err := client.SubmitTransfer(ctx, &chainv1.SubmitTransferRequest{
|
||||
IdempotencyKey: idempotencyKey,
|
||||
OrganizationRef: req.Payment.OrganizationRef.Hex(),
|
||||
SourceWalletRef: sourceWalletRef,
|
||||
Destination: destination,
|
||||
Amount: amount,
|
||||
OperationRef: operationRef,
|
||||
IntentRef: strings.TrimSpace(req.Payment.IntentSnapshot.Ref),
|
||||
PaymentRef: strings.TrimSpace(req.Payment.PaymentRef),
|
||||
Metadata: settlementTransferMetadata(req.Payment, req.Step),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil || resp.GetTransfer() == nil {
|
||||
return nil, merrors.Internal("settlement fx_convert: transfer response is missing")
|
||||
}
|
||||
|
||||
step := req.StepExecution
|
||||
refs, refsErr := transferExternalRefs(resp.GetTransfer(), firstNonEmpty(
|
||||
strings.TrimSpace(req.Step.InstanceID),
|
||||
strings.TrimSpace(gateway.InstanceID),
|
||||
strings.TrimSpace(req.Step.Gateway),
|
||||
strings.TrimSpace(gateway.ID),
|
||||
))
|
||||
if refsErr != nil {
|
||||
return nil, refsErr
|
||||
}
|
||||
step.ExternalRefs = refs
|
||||
step.State = agg.StepStateCompleted
|
||||
step.FailureCode = ""
|
||||
step.FailureMsg = ""
|
||||
return &sexec.ExecuteOutput{StepExecution: step}, nil
|
||||
}
|
||||
|
||||
func (e *gatewayProviderSettlementExecutor) resolveGateway(ctx context.Context, step xplan.Step) (*model.GatewayInstanceDescriptor, error) {
|
||||
if e.gatewayRegistry == nil {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: gateway registry is required")
|
||||
}
|
||||
items, err := e.gatewayRegistry.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stepGateway := strings.TrimSpace(step.Gateway)
|
||||
stepInstance := strings.TrimSpace(step.InstanceID)
|
||||
|
||||
var byInstance *model.GatewayInstanceDescriptor
|
||||
var byGateway *model.GatewayInstanceDescriptor
|
||||
var single *model.GatewayInstanceDescriptor
|
||||
settlementCount := 0
|
||||
for i := range items {
|
||||
item := items[i]
|
||||
if item == nil || model.ParseRail(string(item.Rail)) != model.RailProviderSettlement || !item.IsEnabled {
|
||||
continue
|
||||
}
|
||||
settlementCount++
|
||||
single = item
|
||||
if stepInstance != "" && (strings.EqualFold(strings.TrimSpace(item.InstanceID), stepInstance) || strings.EqualFold(strings.TrimSpace(item.ID), stepInstance)) {
|
||||
byInstance = item
|
||||
break
|
||||
}
|
||||
if stepGateway != "" && (strings.EqualFold(strings.TrimSpace(item.ID), stepGateway) || strings.EqualFold(strings.TrimSpace(item.InstanceID), stepGateway)) {
|
||||
byGateway = item
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case byInstance != nil:
|
||||
if strings.TrimSpace(byInstance.InvokeURI) == "" {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: gateway invoke uri is missing")
|
||||
}
|
||||
return byInstance, nil
|
||||
case byGateway != nil:
|
||||
if strings.TrimSpace(byGateway.InvokeURI) == "" {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: gateway invoke uri is missing")
|
||||
}
|
||||
return byGateway, nil
|
||||
case stepGateway == "" && stepInstance == "" && settlementCount == 1:
|
||||
if strings.TrimSpace(single.InvokeURI) == "" {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: gateway invoke uri is missing")
|
||||
}
|
||||
return single, nil
|
||||
default:
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: gateway instance not found")
|
||||
}
|
||||
}
|
||||
|
||||
func settlementAmount(payment *agg.Payment) (*moneyv1.Money, error) {
|
||||
if payment == nil {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: payment is required")
|
||||
}
|
||||
|
||||
money := sourceAmountForSettlement(payment)
|
||||
if money == nil {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: debit amount is required")
|
||||
}
|
||||
amount := strings.TrimSpace(money.Amount)
|
||||
currency := strings.TrimSpace(money.Currency)
|
||||
if amount == "" || currency == "" {
|
||||
return nil, merrors.InvalidArgument("settlement fx_convert: debit amount is invalid")
|
||||
}
|
||||
return &moneyv1.Money{
|
||||
Amount: amount,
|
||||
Currency: currency,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sourceAmountForSettlement(payment *agg.Payment) *moneyv1.Money {
|
||||
if payment != nil && payment.QuoteSnapshot != nil && payment.QuoteSnapshot.DebitAmount != nil {
|
||||
return &moneyv1.Money{
|
||||
Amount: strings.TrimSpace(payment.QuoteSnapshot.DebitAmount.Amount),
|
||||
Currency: strings.TrimSpace(payment.QuoteSnapshot.DebitAmount.Currency),
|
||||
}
|
||||
}
|
||||
if payment != nil && payment.IntentSnapshot.Amount != nil {
|
||||
return &moneyv1.Money{
|
||||
Amount: strings.TrimSpace(payment.IntentSnapshot.Amount.Amount),
|
||||
Currency: strings.TrimSpace(payment.IntentSnapshot.Amount.Currency),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func settlementTransferMetadata(payment *agg.Payment, step xplan.Step) map[string]string {
|
||||
out := transferMetadata(step)
|
||||
if out == nil {
|
||||
out = map[string]string{}
|
||||
}
|
||||
if payment != nil {
|
||||
if quoteRef := firstNonEmpty(
|
||||
strings.TrimSpace(payment.QuotationRef),
|
||||
strings.TrimSpace(quoteRefFromSnapshot(payment.QuoteSnapshot)),
|
||||
); quoteRef != "" {
|
||||
out[settlementMetadataQuoteRef] = quoteRef
|
||||
}
|
||||
}
|
||||
if outgoingLeg := strings.TrimSpace(string(step.Rail)); outgoingLeg != "" {
|
||||
out[settlementMetadataOutgoingLeg] = outgoingLeg
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func quoteRefFromSnapshot(snapshot *model.PaymentQuoteSnapshot) string {
|
||||
if snapshot == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(snapshot.QuoteRef)
|
||||
}
|
||||
|
||||
var _ sexec.ProviderSettlementExecutor = (*gatewayProviderSettlementExecutor)(nil)
|
||||
@@ -0,0 +1,191 @@
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
chainclient "github.com/tech/sendico/gateway/chain/client"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/erecon"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/sexec"
|
||||
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/xplan"
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
pm "github.com/tech/sendico/pkg/model"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
func TestGatewayProviderSettlementExecutor_ExecuteProviderSettlement_SubmitsTransfer(t *testing.T) {
|
||||
orgID := bson.NewObjectID()
|
||||
|
||||
var submitReq *chainv1.SubmitTransferRequest
|
||||
client := &chainclient.Fake{
|
||||
SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
|
||||
submitReq = req
|
||||
return &chainv1.SubmitTransferResponse{
|
||||
Transfer: &chainv1.Transfer{
|
||||
TransferRef: "trf-settlement-1",
|
||||
OperationRef: "op-settlement-1",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
resolver := &fakeGatewayInvokeResolver{client: client}
|
||||
registry := &fakeGatewayRegistry{
|
||||
items: []*model.GatewayInstanceDescriptor{
|
||||
{
|
||||
ID: "payment_gateway_settlement",
|
||||
InstanceID: "payment_gateway_settlement",
|
||||
Rail: model.RailProviderSettlement,
|
||||
InvokeURI: "grpc://tgsettle-gateway",
|
||||
IsEnabled: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
executor := &gatewayProviderSettlementExecutor{
|
||||
gatewayInvokeResolver: resolver,
|
||||
gatewayRegistry: registry,
|
||||
}
|
||||
|
||||
req := sexec.StepRequest{
|
||||
Payment: &agg.Payment{
|
||||
OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
|
||||
PaymentRef: "payment-1",
|
||||
IdempotencyKey: "idem-1",
|
||||
QuotationRef: "quote-1",
|
||||
IntentSnapshot: model.PaymentIntent{
|
||||
Ref: "intent-1",
|
||||
Source: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeManagedWallet,
|
||||
ManagedWallet: &model.ManagedWalletEndpoint{
|
||||
ManagedWalletRef: "wallet-src",
|
||||
},
|
||||
},
|
||||
Destination: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeCard,
|
||||
Card: &model.CardEndpoint{Pan: "4111111111111111"},
|
||||
},
|
||||
Amount: &paymenttypes.Money{Amount: "1", Currency: "USDT"},
|
||||
},
|
||||
QuoteSnapshot: &model.PaymentQuoteSnapshot{
|
||||
DebitAmount: &paymenttypes.Money{Amount: "1.000000", Currency: "USDT"},
|
||||
ExpectedSettlementAmount: &paymenttypes.Money{Amount: "76.63", Currency: "RUB"},
|
||||
QuoteRef: "quote-1",
|
||||
},
|
||||
},
|
||||
Step: xplan.Step{
|
||||
StepRef: "hop_2_settlement_fx_convert",
|
||||
StepCode: "hop.2.settlement.fx_convert",
|
||||
Action: model.RailOperationFXConvert,
|
||||
Rail: model.RailProviderSettlement,
|
||||
Gateway: "payment_gateway_settlement",
|
||||
InstanceID: "payment_gateway_settlement",
|
||||
},
|
||||
StepExecution: agg.StepExecution{
|
||||
StepRef: "hop_2_settlement_fx_convert",
|
||||
StepCode: "hop.2.settlement.fx_convert",
|
||||
Attempt: 1,
|
||||
},
|
||||
}
|
||||
|
||||
out, err := executor.ExecuteProviderSettlement(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("ExecuteProviderSettlement returned error: %v", err)
|
||||
}
|
||||
if out == nil {
|
||||
t.Fatal("expected output")
|
||||
}
|
||||
if out.StepExecution.State != agg.StepStateCompleted {
|
||||
t.Fatalf("expected completed state, got=%q", out.StepExecution.State)
|
||||
}
|
||||
if submitReq == nil {
|
||||
t.Fatal("expected transfer submission request")
|
||||
}
|
||||
if got, want := resolver.lastInvokeURI, "grpc://tgsettle-gateway"; got != want {
|
||||
t.Fatalf("invoke uri mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetSourceWalletRef(), "wallet-src"; got != want {
|
||||
t.Fatalf("source wallet mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetDestination().GetManagedWalletRef(), "wallet-src"; got != want {
|
||||
t.Fatalf("destination mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetAmount().GetAmount(), "1.000000"; got != want {
|
||||
t.Fatalf("amount mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetAmount().GetCurrency(), "USDT"; got != want {
|
||||
t.Fatalf("currency mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetMetadata()[settlementMetadataQuoteRef], "quote-1"; got != want {
|
||||
t.Fatalf("quote_ref metadata mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := submitReq.GetMetadata()[settlementMetadataOutgoingLeg], string(model.RailProviderSettlement); got != want {
|
||||
t.Fatalf("outgoing_leg metadata mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if len(out.StepExecution.ExternalRefs) != 2 {
|
||||
t.Fatalf("expected two external refs, got=%d", len(out.StepExecution.ExternalRefs))
|
||||
}
|
||||
if out.StepExecution.ExternalRefs[0].Kind != erecon.ExternalRefKindOperation {
|
||||
t.Fatalf("unexpected first external ref kind: %q", out.StepExecution.ExternalRefs[0].Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGatewayProviderSettlementExecutor_ExecuteProviderSettlement_MissingSettlementAmount(t *testing.T) {
|
||||
orgID := bson.NewObjectID()
|
||||
|
||||
executor := &gatewayProviderSettlementExecutor{
|
||||
gatewayInvokeResolver: &fakeGatewayInvokeResolver{
|
||||
client: &chainclient.Fake{},
|
||||
},
|
||||
gatewayRegistry: &fakeGatewayRegistry{
|
||||
items: []*model.GatewayInstanceDescriptor{
|
||||
{
|
||||
ID: "payment_gateway_settlement",
|
||||
InstanceID: "payment_gateway_settlement",
|
||||
Rail: model.RailProviderSettlement,
|
||||
InvokeURI: "grpc://tgsettle-gateway",
|
||||
IsEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := executor.ExecuteProviderSettlement(context.Background(), sexec.StepRequest{
|
||||
Payment: &agg.Payment{
|
||||
OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
|
||||
PaymentRef: "payment-2",
|
||||
IdempotencyKey: "idem-2",
|
||||
IntentSnapshot: model.PaymentIntent{
|
||||
Ref: "intent-2",
|
||||
Source: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeManagedWallet,
|
||||
ManagedWallet: &model.ManagedWalletEndpoint{
|
||||
ManagedWalletRef: "wallet-src",
|
||||
},
|
||||
},
|
||||
},
|
||||
QuoteSnapshot: nil,
|
||||
},
|
||||
Step: xplan.Step{
|
||||
StepRef: "hop_2_settlement_fx_convert",
|
||||
StepCode: "hop.2.settlement.fx_convert",
|
||||
Action: model.RailOperationFXConvert,
|
||||
Rail: model.RailProviderSettlement,
|
||||
Gateway: "payment_gateway_settlement",
|
||||
InstanceID: "payment_gateway_settlement",
|
||||
},
|
||||
StepExecution: agg.StepExecution{
|
||||
StepRef: "hop_2_settlement_fx_convert",
|
||||
StepCode: "hop.2.settlement.fx_convert",
|
||||
Attempt: 1,
|
||||
},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "debit amount is required") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user