Merge pull request 'op payment info added' (#641) from bff-640 into main
Reviewed-on: #641
This commit was merged in pull request #641.
This commit is contained in:
@@ -79,16 +79,18 @@ type Payment struct {
|
||||
}
|
||||
|
||||
type PaymentOperation struct {
|
||||
StepRef string `json:"stepRef,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
OperationRef string `json:"operationRef,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
FailureReason string `json:"failureReason,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
StepRef string `json:"stepRef,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Amount *paymenttypes.Money `json:"amount,omitempty"`
|
||||
ConvertedAmount *paymenttypes.Money `json:"convertedAmount,omitempty"`
|
||||
OperationRef string `json:"operationRef,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
FailureReason string `json:"failureReason,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
}
|
||||
|
||||
type paymentQuoteResponse struct {
|
||||
@@ -287,7 +289,7 @@ func toPayment(p *orchestrationv2.Payment) *Payment {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
operations := toUserVisibleOperations(p.GetStepExecutions())
|
||||
operations := toUserVisibleOperations(p.GetStepExecutions(), p.GetQuoteSnapshot())
|
||||
failureCode, failureReason := firstFailure(operations)
|
||||
return &Payment{
|
||||
PaymentRef: p.GetPaymentRef(),
|
||||
@@ -312,7 +314,7 @@ func firstFailure(operations []PaymentOperation) (string, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOperation {
|
||||
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) []PaymentOperation {
|
||||
if len(steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -321,7 +323,7 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOp
|
||||
if step == nil || !isUserVisibleStep(step.GetReportVisibility()) {
|
||||
continue
|
||||
}
|
||||
ops = append(ops, toPaymentOperation(step))
|
||||
ops = append(ops, toPaymentOperation(step, quote))
|
||||
}
|
||||
if len(ops) == 0 {
|
||||
return nil
|
||||
@@ -329,17 +331,20 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOp
|
||||
return ops
|
||||
}
|
||||
|
||||
func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
|
||||
func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) PaymentOperation {
|
||||
operationRef, gateway := operationRefAndGateway(step.GetStepCode(), step.GetRefs())
|
||||
amount, convertedAmount := operationAmounts(step.GetStepCode(), quote)
|
||||
op := PaymentOperation{
|
||||
StepRef: step.GetStepRef(),
|
||||
Code: step.GetStepCode(),
|
||||
State: enumJSONName(step.GetState().String()),
|
||||
Label: strings.TrimSpace(step.GetUserLabel()),
|
||||
OperationRef: operationRef,
|
||||
Gateway: string(gateway),
|
||||
StartedAt: timestampAsTime(step.GetStartedAt()),
|
||||
CompletedAt: timestampAsTime(step.GetCompletedAt()),
|
||||
StepRef: step.GetStepRef(),
|
||||
Code: step.GetStepCode(),
|
||||
State: enumJSONName(step.GetState().String()),
|
||||
Label: strings.TrimSpace(step.GetUserLabel()),
|
||||
Amount: amount,
|
||||
ConvertedAmount: convertedAmount,
|
||||
OperationRef: operationRef,
|
||||
Gateway: string(gateway),
|
||||
StartedAt: timestampAsTime(step.GetStartedAt()),
|
||||
CompletedAt: timestampAsTime(step.GetCompletedAt()),
|
||||
}
|
||||
failure := step.GetFailure()
|
||||
if failure == nil {
|
||||
@@ -353,6 +358,54 @@ func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
|
||||
return op
|
||||
}
|
||||
|
||||
func operationAmounts(stepCode string, quote *quotationv2.PaymentQuote) (*paymenttypes.Money, *paymenttypes.Money) {
|
||||
if quote == nil {
|
||||
return nil, nil
|
||||
}
|
||||
operation := stepOperationToken(stepCode)
|
||||
|
||||
primary := firstValidMoney(
|
||||
toMoney(quote.GetDestinationAmount()),
|
||||
toMoney(quote.GetTransferPrincipalAmount()),
|
||||
toMoney(quote.GetPayerTotalDebitAmount()),
|
||||
)
|
||||
if operation != "fx_convert" {
|
||||
return primary, nil
|
||||
}
|
||||
|
||||
base := firstValidMoney(
|
||||
toMoney(quote.GetTransferPrincipalAmount()),
|
||||
toMoney(quote.GetPayerTotalDebitAmount()),
|
||||
toMoney(quote.GetFxQuote().GetBaseAmount()),
|
||||
)
|
||||
quoteAmount := firstValidMoney(
|
||||
toMoney(quote.GetDestinationAmount()),
|
||||
toMoney(quote.GetFxQuote().GetQuoteAmount()),
|
||||
)
|
||||
return base, quoteAmount
|
||||
}
|
||||
|
||||
func stepOperationToken(stepCode string) string {
|
||||
parts := strings.Split(strings.ToLower(strings.TrimSpace(stepCode)), ".")
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(parts[len(parts)-1])
|
||||
}
|
||||
|
||||
func firstValidMoney(values ...*paymenttypes.Money) *paymenttypes.Money {
|
||||
for _, value := range values {
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(value.GetAmount()) == "" || strings.TrimSpace(value.GetCurrency()) == "" {
|
||||
continue
|
||||
}
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
externalRefKindOperation = "operation_ref"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
@@ -33,7 +34,7 @@ func TestToUserVisibleOperationsFiltersByVisibility(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
ops := toUserVisibleOperations(steps)
|
||||
ops := toUserVisibleOperations(steps, nil)
|
||||
if len(ops) != 2 {
|
||||
t.Fatalf("operations count mismatch: got=%d want=2", len(ops))
|
||||
}
|
||||
@@ -149,7 +150,7 @@ func TestToPaymentOperation_MapsOperationRefAndGateway(t *testing.T) {
|
||||
Ref: "op-123",
|
||||
},
|
||||
},
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if got, want := op.OperationRef, "op-123"; got != want {
|
||||
t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want)
|
||||
@@ -164,7 +165,7 @@ func TestToPaymentOperation_InfersGatewayFromStepCode(t *testing.T) {
|
||||
StepRef: "step-2",
|
||||
StepCode: "edge.1_2.ledger.debit",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if got := op.OperationRef; got != "" {
|
||||
t.Fatalf("expected empty operation_ref, got=%q", got)
|
||||
@@ -187,7 +188,7 @@ func TestToPaymentOperation_DoesNotFallbackToCardPayoutRef(t *testing.T) {
|
||||
Ref: "payout-123",
|
||||
},
|
||||
},
|
||||
})
|
||||
}, nil)
|
||||
|
||||
if got := op.OperationRef; got != "" {
|
||||
t.Fatalf("expected empty operation_ref, got=%q", got)
|
||||
@@ -196,3 +197,57 @@ func TestToPaymentOperation_DoesNotFallbackToCardPayoutRef(t *testing.T) {
|
||||
t.Fatalf("gateway mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPaymentOperation_MapsAmount(t *testing.T) {
|
||||
op := toPaymentOperation(&orchestrationv2.StepExecution{
|
||||
StepRef: "step-4",
|
||||
StepCode: "hop.4.card_payout.send",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
||||
}, "ationv2.PaymentQuote{
|
||||
TransferPrincipalAmount: &moneyv1.Money{Amount: "110.00", Currency: "USDT"},
|
||||
DestinationAmount: &moneyv1.Money{Amount: "100.00", Currency: "EUR"},
|
||||
})
|
||||
|
||||
if op.Amount == nil {
|
||||
t.Fatal("expected amount to be mapped")
|
||||
}
|
||||
if got, want := op.Amount.Amount, "100.00"; got != want {
|
||||
t.Fatalf("amount.value mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := op.Amount.Currency, "EUR"; got != want {
|
||||
t.Fatalf("amount.currency mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got := op.ConvertedAmount; got != nil {
|
||||
t.Fatalf("expected no converted_amount for non-fx operation, got=%+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPaymentOperation_MapsFxTwoAmounts(t *testing.T) {
|
||||
op := toPaymentOperation(&orchestrationv2.StepExecution{
|
||||
StepRef: "step-5",
|
||||
StepCode: "hop.2.settlement.fx_convert",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
||||
}, "ationv2.PaymentQuote{
|
||||
TransferPrincipalAmount: &moneyv1.Money{Amount: "110.00", Currency: "USDT"},
|
||||
DestinationAmount: &moneyv1.Money{Amount: "100.00", Currency: "EUR"},
|
||||
})
|
||||
|
||||
if op.Amount == nil {
|
||||
t.Fatal("expected fx base amount to be mapped")
|
||||
}
|
||||
if got, want := op.Amount.Amount, "110.00"; got != want {
|
||||
t.Fatalf("base amount.value mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := op.Amount.Currency, "USDT"; got != want {
|
||||
t.Fatalf("base amount.currency mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if op.ConvertedAmount == nil {
|
||||
t.Fatal("expected fx converted amount to be mapped")
|
||||
}
|
||||
if got, want := op.ConvertedAmount.Amount, "100.00"; got != want {
|
||||
t.Fatalf("converted amount.value mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := op.ConvertedAmount.Currency, "EUR"; got != want {
|
||||
t.Fatalf("converted amount.currency mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ type grpcQuotationClient struct {
|
||||
callTimeout time.Duration
|
||||
}
|
||||
|
||||
func newQuotationClient(ctx context.Context, cfg quotationClientConfig, opts ...grpc.DialOption) (quotationClient, error) {
|
||||
func newQuotationClient(_ context.Context, cfg quotationClientConfig, opts ...grpc.DialOption) (quotationClient, error) {
|
||||
cfg.setDefaults()
|
||||
if strings.TrimSpace(cfg.Address) == "" {
|
||||
return nil, merrors.InvalidArgument("payment quotation: address is required")
|
||||
|
||||
@@ -397,8 +397,14 @@ components:
|
||||
label:
|
||||
description: Human-readable operation label.
|
||||
type: string
|
||||
amount:
|
||||
description: Primary money amount associated with the operation.
|
||||
$ref: ../common/money.yaml#/components/schemas/Money
|
||||
convertedAmount:
|
||||
description: Secondary amount for conversion operations (for example FX convert output amount).
|
||||
$ref: ../common/money.yaml#/components/schemas/Money
|
||||
operationRef:
|
||||
description: Internal operation reference identifier reported by the gateway.
|
||||
description: External operation reference identifier reported by the gateway.
|
||||
type: string
|
||||
gateway:
|
||||
description: Gateway microservice type handling the operation.
|
||||
|
||||
Reference in New Issue
Block a user