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:
@@ -83,6 +83,8 @@ type PaymentOperation struct {
|
|||||||
Code string `json:"code,omitempty"`
|
Code string `json:"code,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
|
Amount *paymenttypes.Money `json:"amount,omitempty"`
|
||||||
|
ConvertedAmount *paymenttypes.Money `json:"convertedAmount,omitempty"`
|
||||||
OperationRef string `json:"operationRef,omitempty"`
|
OperationRef string `json:"operationRef,omitempty"`
|
||||||
Gateway string `json:"gateway,omitempty"`
|
Gateway string `json:"gateway,omitempty"`
|
||||||
FailureCode string `json:"failureCode,omitempty"`
|
FailureCode string `json:"failureCode,omitempty"`
|
||||||
@@ -287,7 +289,7 @@ func toPayment(p *orchestrationv2.Payment) *Payment {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
operations := toUserVisibleOperations(p.GetStepExecutions())
|
operations := toUserVisibleOperations(p.GetStepExecutions(), p.GetQuoteSnapshot())
|
||||||
failureCode, failureReason := firstFailure(operations)
|
failureCode, failureReason := firstFailure(operations)
|
||||||
return &Payment{
|
return &Payment{
|
||||||
PaymentRef: p.GetPaymentRef(),
|
PaymentRef: p.GetPaymentRef(),
|
||||||
@@ -312,7 +314,7 @@ func firstFailure(operations []PaymentOperation) (string, string) {
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOperation {
|
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) []PaymentOperation {
|
||||||
if len(steps) == 0 {
|
if len(steps) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -321,7 +323,7 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOp
|
|||||||
if step == nil || !isUserVisibleStep(step.GetReportVisibility()) {
|
if step == nil || !isUserVisibleStep(step.GetReportVisibility()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ops = append(ops, toPaymentOperation(step))
|
ops = append(ops, toPaymentOperation(step, quote))
|
||||||
}
|
}
|
||||||
if len(ops) == 0 {
|
if len(ops) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -329,13 +331,16 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOp
|
|||||||
return ops
|
return ops
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
|
func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) PaymentOperation {
|
||||||
operationRef, gateway := operationRefAndGateway(step.GetStepCode(), step.GetRefs())
|
operationRef, gateway := operationRefAndGateway(step.GetStepCode(), step.GetRefs())
|
||||||
|
amount, convertedAmount := operationAmounts(step.GetStepCode(), quote)
|
||||||
op := PaymentOperation{
|
op := PaymentOperation{
|
||||||
StepRef: step.GetStepRef(),
|
StepRef: step.GetStepRef(),
|
||||||
Code: step.GetStepCode(),
|
Code: step.GetStepCode(),
|
||||||
State: enumJSONName(step.GetState().String()),
|
State: enumJSONName(step.GetState().String()),
|
||||||
Label: strings.TrimSpace(step.GetUserLabel()),
|
Label: strings.TrimSpace(step.GetUserLabel()),
|
||||||
|
Amount: amount,
|
||||||
|
ConvertedAmount: convertedAmount,
|
||||||
OperationRef: operationRef,
|
OperationRef: operationRef,
|
||||||
Gateway: string(gateway),
|
Gateway: string(gateway),
|
||||||
StartedAt: timestampAsTime(step.GetStartedAt()),
|
StartedAt: timestampAsTime(step.GetStartedAt()),
|
||||||
@@ -353,6 +358,54 @@ func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
|
|||||||
return op
|
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 (
|
const (
|
||||||
externalRefKindOperation = "operation_ref"
|
externalRefKindOperation = "operation_ref"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
|
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"
|
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
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 {
|
if len(ops) != 2 {
|
||||||
t.Fatalf("operations count mismatch: got=%d want=2", len(ops))
|
t.Fatalf("operations count mismatch: got=%d want=2", len(ops))
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,7 @@ func TestToPaymentOperation_MapsOperationRefAndGateway(t *testing.T) {
|
|||||||
Ref: "op-123",
|
Ref: "op-123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if got, want := op.OperationRef, "op-123"; got != want {
|
if got, want := op.OperationRef, "op-123"; got != want {
|
||||||
t.Fatalf("operation_ref mismatch: got=%q want=%q", 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",
|
StepRef: "step-2",
|
||||||
StepCode: "edge.1_2.ledger.debit",
|
StepCode: "edge.1_2.ledger.debit",
|
||||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if got := op.OperationRef; got != "" {
|
if got := op.OperationRef; got != "" {
|
||||||
t.Fatalf("expected empty operation_ref, got=%q", got)
|
t.Fatalf("expected empty operation_ref, got=%q", got)
|
||||||
@@ -187,7 +188,7 @@ func TestToPaymentOperation_DoesNotFallbackToCardPayoutRef(t *testing.T) {
|
|||||||
Ref: "payout-123",
|
Ref: "payout-123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}, nil)
|
||||||
|
|
||||||
if got := op.OperationRef; got != "" {
|
if got := op.OperationRef; got != "" {
|
||||||
t.Fatalf("expected empty operation_ref, got=%q", 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)
|
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
|
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()
|
cfg.setDefaults()
|
||||||
if strings.TrimSpace(cfg.Address) == "" {
|
if strings.TrimSpace(cfg.Address) == "" {
|
||||||
return nil, merrors.InvalidArgument("payment quotation: address is required")
|
return nil, merrors.InvalidArgument("payment quotation: address is required")
|
||||||
|
|||||||
@@ -397,8 +397,14 @@ components:
|
|||||||
label:
|
label:
|
||||||
description: Human-readable operation label.
|
description: Human-readable operation label.
|
||||||
type: string
|
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:
|
operationRef:
|
||||||
description: Internal operation reference identifier reported by the gateway.
|
description: External operation reference identifier reported by the gateway.
|
||||||
type: string
|
type: string
|
||||||
gateway:
|
gateway:
|
||||||
description: Gateway microservice type handling the operation.
|
description: Gateway microservice type handling the operation.
|
||||||
|
|||||||
Reference in New Issue
Block a user