fixed mntx payout sequence

This commit is contained in:
Stephan D
2026-03-04 02:46:51 +01:00
parent 8377b6b2af
commit 56bf49aa03
19 changed files with 385 additions and 121 deletions

View File

@@ -85,6 +85,7 @@ func (e *gatewayCardPayoutExecutor) ExecuteCardPayout(ctx context.Context, req s
if token := strings.TrimSpace(card.Token); token != "" {
resp, createErr := client.CreateCardTokenPayout(ctx, &mntxv1.CardTokenPayoutRequest{
ProjectId: projectID,
ParentPaymentRef: strings.TrimSpace(req.Payment.PaymentRef),
CustomerId: customer.id,
CustomerFirstName: customer.firstName,
CustomerMiddleName: customer.middleName,
@@ -122,6 +123,7 @@ func (e *gatewayCardPayoutExecutor) ExecuteCardPayout(ctx context.Context, req s
}
resp, createErr := client.CreateCardPayout(ctx, &mntxv1.CardPayoutRequest{
ProjectId: projectID,
ParentPaymentRef: strings.TrimSpace(req.Payment.PaymentRef),
CustomerId: customer.id,
CustomerFirstName: customer.firstName,
CustomerMiddleName: customer.middleName,
@@ -538,9 +540,6 @@ func cardPayoutMetadata(payment *agg.Payment, step xplan.Step) map[string]string
out = map[string]string{}
}
if payment != nil {
if parentPaymentRef := strings.TrimSpace(payment.PaymentRef); parentPaymentRef != "" {
out[settlementMetadataParentPaymentRef] = parentPaymentRef
}
if quoteRef := firstNonEmpty(
strings.TrimSpace(payment.QuotationRef),
strings.TrimSpace(quoteRefFromSnapshot(payment.QuoteSnapshot)),

View File

@@ -143,8 +143,8 @@ func TestGatewayCardPayoutExecutor_ExecuteCardPayout_SubmitsCardPayout(t *testin
if got, want := payoutReq.GetMetadata()[settlementMetadataOutgoingLeg], string(discovery.RailCardPayout); got != want {
t.Fatalf("outgoing_leg metadata mismatch: got=%q want=%q", got, want)
}
if got, want := payoutReq.GetMetadata()[settlementMetadataParentPaymentRef], "payment-1"; got != want {
t.Fatalf("parent_payment_ref metadata mismatch: got=%q want=%q", got, want)
if got, want := payoutReq.GetParentPaymentRef(), "payment-1"; got != want {
t.Fatalf("parent_payment_ref mismatch: got=%q want=%q", got, want)
}
if len(out.StepExecution.ExternalRefs) != 3 {
t.Fatalf("expected 3 external refs, got=%d", len(out.StepExecution.ExternalRefs))
@@ -275,8 +275,8 @@ func TestGatewayCardPayoutExecutor_ExecuteCardPayout_UsesStepMetadataOverrides(t
if got, want := payoutReq.GetMetadata()[batchmeta.MetaPayoutTargetRef], "recipient-2"; got != want {
t.Fatalf("target_ref metadata mismatch: got=%q want=%q", got, want)
}
if got, want := payoutReq.GetMetadata()[settlementMetadataParentPaymentRef], "payment-2"; got != want {
t.Fatalf("parent_payment_ref metadata mismatch: got=%q want=%q", got, want)
if got, want := payoutReq.GetParentPaymentRef(), "payment-2"; got != want {
t.Fatalf("parent_payment_ref mismatch: got=%q want=%q", got, want)
}
}
@@ -319,3 +319,140 @@ func TestGatewayCardPayoutExecutor_ExecuteCardPayout_RequiresGatewayRegistry(t *
t.Fatalf("unexpected error: %v", err)
}
}
func TestGatewayCardPayoutExecutor_ExecuteCardPayout_BatchChildrenUseDistinctOperationRefsAndSharedParent(t *testing.T) {
orgID := bson.NewObjectID()
var payoutReqs []*mntxv1.CardPayoutRequest
executor := &gatewayCardPayoutExecutor{
gatewayRegistry: &fakeGatewayRegistry{
items: []*model.GatewayInstanceDescriptor{
{
ID: paymenttypes.DefaultCardsGatewayID,
InstanceID: paymenttypes.DefaultCardsGatewayID,
Rail: discovery.RailCardPayout,
InvokeURI: "grpc://mntx-gateway:50051",
IsEnabled: true,
},
},
},
dialClient: func(_ context.Context, _ string) (mntxclient.Client, error) {
return &mntxclient.Fake{
CreateCardPayoutFn: func(_ context.Context, req *mntxv1.CardPayoutRequest) (*mntxv1.CardPayoutResponse, error) {
payoutReqs = append(payoutReqs, req)
return &mntxv1.CardPayoutResponse{
Payout: &mntxv1.CardPayoutState{
PayoutId: req.GetOperationRef(),
OperationRef: req.GetOperationRef(),
},
}, nil
},
}, nil
},
}
payment := &agg.Payment{
OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
PaymentRef: "payment-3",
IdempotencyKey: "idem-3",
QuotationRef: "quote-3",
IntentSnapshot: model.PaymentIntent{
Ref: "intent-3",
Destination: model.PaymentEndpoint{
Type: model.EndpointTypeCard,
Card: &model.CardEndpoint{
Pan: "2200700142860161",
ExpMonth: 3,
ExpYear: 2030,
},
},
Customer: &model.Customer{
ID: "cust-3",
FirstName: "Stephan",
LastName: "Deshevikh",
IP: "198.51.100.10",
},
Amount: &paymenttypes.Money{
Amount: "1.000000",
Currency: "USDT",
},
},
QuoteSnapshot: &model.PaymentQuoteSnapshot{
ExpectedSettlementAmount: &paymenttypes.Money{
Amount: "76.50",
Currency: "RUB",
},
QuoteRef: "quote-3",
},
}
firstReq := sexec.StepRequest{
Payment: payment,
Step: xplan.Step{
StepRef: "hop_4_card_payout_send",
StepCode: "hop.4.card_payout.send",
Action: discovery.RailOperationSend,
Rail: discovery.RailCardPayout,
Gateway: paymenttypes.DefaultCardsGatewayID,
InstanceID: paymenttypes.DefaultCardsGatewayID,
},
StepExecution: agg.StepExecution{
StepRef: "hop_4_card_payout_send",
StepCode: "hop.4.card_payout.send",
Attempt: 1,
},
}
secondReq := sexec.StepRequest{
Payment: payment,
Step: xplan.Step{
StepRef: "hop_4_card_payout_send_2",
StepCode: "hop.4.card_payout.send",
Action: discovery.RailOperationSend,
Rail: discovery.RailCardPayout,
Gateway: paymenttypes.DefaultCardsGatewayID,
InstanceID: paymenttypes.DefaultCardsGatewayID,
Metadata: map[string]string{
batchmeta.MetaPayoutTargetRef: "recipient-2",
batchmeta.MetaAmount: "150",
batchmeta.MetaCurrency: "RUB",
batchmeta.MetaCardPan: "2200700142860162",
batchmeta.MetaCardExpMonth: "4",
batchmeta.MetaCardExpYear: "2030",
},
},
StepExecution: agg.StepExecution{
StepRef: "hop_4_card_payout_send_2",
StepCode: "hop.4.card_payout.send",
Attempt: 1,
},
}
if _, err := executor.ExecuteCardPayout(context.Background(), firstReq); err != nil {
t.Fatalf("first ExecuteCardPayout returned error: %v", err)
}
if _, err := executor.ExecuteCardPayout(context.Background(), secondReq); err != nil {
t.Fatalf("second ExecuteCardPayout returned error: %v", err)
}
if got, want := len(payoutReqs), 2; got != want {
t.Fatalf("submitted request count mismatch: got=%d want=%d", got, want)
}
if got, want := payoutReqs[0].GetOperationRef(), "payment-3:hop_4_card_payout_send"; got != want {
t.Fatalf("first operation_ref mismatch: got=%q want=%q", got, want)
}
if got, want := payoutReqs[1].GetOperationRef(), "payment-3:hop_4_card_payout_send_2"; got != want {
t.Fatalf("second operation_ref mismatch: got=%q want=%q", got, want)
}
if payoutReqs[0].GetPayoutId() != "" || payoutReqs[1].GetPayoutId() != "" {
t.Fatalf("expected empty payout_id for both child operations")
}
if got, want := payoutReqs[0].GetParentPaymentRef(), "payment-3"; got != want {
t.Fatalf("first parent_payment_ref mismatch: got=%q want=%q", got, want)
}
if got, want := payoutReqs[1].GetParentPaymentRef(), "payment-3"; got != want {
t.Fatalf("second parent_payment_ref mismatch: got=%q want=%q", got, want)
}
if payoutReqs[0].GetCardPan() == payoutReqs[1].GetCardPan() {
t.Fatalf("expected different destination cards across child operations")
}
}

View File

@@ -15,9 +15,8 @@ import (
)
const (
settlementMetadataQuoteRef = "quote_ref"
settlementMetadataOutgoingLeg = "outgoing_leg"
settlementMetadataParentPaymentRef = "parent_payment_ref"
settlementMetadataQuoteRef = "quote_ref"
settlementMetadataOutgoingLeg = "outgoing_leg"
)
type gatewayProviderSettlementExecutor struct {