mece request / payment economics

This commit is contained in:
Stephan D
2026-02-24 18:02:20 +01:00
parent 2e08ec9b9b
commit 4c5677202a
32 changed files with 704 additions and 223 deletions

View File

@@ -6,6 +6,7 @@ import (
"github.com/tech/sendico/pkg/merrors"
pkgmodel "github.com/tech/sendico/pkg/model"
payecon "github.com/tech/sendico/pkg/payments/economics"
paymenttypes "github.com/tech/sendico/pkg/payments/types"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
@@ -32,9 +33,16 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
if err != nil {
return nil, err
}
settlementCurrency := strings.TrimSpace(intent.SettlementCurrency)
resolvedSettlementMode, resolvedFeeTreatment, err := payecon.ResolveSettlementAndFee(settlementMode, feeTreatment)
if err != nil {
return nil, err
}
if strings.TrimSpace(intent.SettlementCurrency) != "" {
return nil, merrors.InvalidArgument("settlement_currency must not be provided; it is derived from fx intent or amount currency")
}
settlementCurrency := resolveSettlementCurrency(intent)
if settlementCurrency == "" {
settlementCurrency = resolveSettlementCurrency(intent)
return nil, merrors.InvalidArgument("unable to derive settlement currency from intent")
}
source, err := mapQuoteEndpoint(intent.Source, "intent.source")
@@ -50,8 +58,8 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
Source: source,
Destination: destination,
Amount: mapMoney(intent.Amount),
SettlementMode: settlementMode,
FeeTreatment: feeTreatment,
SettlementMode: resolvedSettlementMode,
FeeTreatment: resolvedFeeTreatment,
SettlementCurrency: settlementCurrency,
}
if comment := strings.TrimSpace(intent.Attributes["comment"]); comment != "" {

View File

@@ -4,6 +4,7 @@ import (
"testing"
paymenttypes "github.com/tech/sendico/pkg/payments/types"
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
"github.com/tech/sendico/server/interface/api/srequest"
)
@@ -80,3 +81,121 @@ func TestMapQuoteIntent_InvalidFeeTreatmentFails(t *testing.T) {
t.Fatalf("expected error for invalid fee treatment")
}
}
func TestMapQuoteIntent_AcceptsIndependentSettlementAndFeeTreatment(t *testing.T) {
source, err := srequest.NewManagedWalletEndpointDTO(srequest.ManagedWalletEndpoint{
ManagedWalletRef: "wallet-source-1",
}, nil)
if err != nil {
t.Fatalf("failed to build source endpoint: %v", err)
}
destination, err := srequest.NewCardEndpointDTO(srequest.CardEndpoint{
Pan: "2200700142860161",
FirstName: "John",
LastName: "Doe",
ExpMonth: 3,
ExpYear: 2030,
}, nil)
if err != nil {
t.Fatalf("failed to build destination endpoint: %v", err)
}
intent := &srequest.PaymentIntent{
Kind: srequest.PaymentKindPayout,
Source: &source,
Destination: &destination,
Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
SettlementMode: srequest.SettlementModeFixReceived,
FeeTreatment: srequest.FeeTreatmentAddToSource,
}
got, err := mapQuoteIntent(intent)
if err != nil {
t.Fatalf("mapQuoteIntent returned error: %v", err)
}
if got.GetSettlementMode() != paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED {
t.Fatalf("unexpected settlement mode: got=%s", got.GetSettlementMode().String())
}
if got.GetFeeTreatment() != quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE {
t.Fatalf("unexpected fee treatment: got=%s", got.GetFeeTreatment().String())
}
}
func TestMapQuoteIntent_RejectsExplicitSettlementCurrency(t *testing.T) {
source, err := srequest.NewManagedWalletEndpointDTO(srequest.ManagedWalletEndpoint{
ManagedWalletRef: "wallet-source-1",
}, nil)
if err != nil {
t.Fatalf("failed to build source endpoint: %v", err)
}
destination, err := srequest.NewCardEndpointDTO(srequest.CardEndpoint{
Pan: "2200700142860161",
FirstName: "John",
LastName: "Doe",
ExpMonth: 3,
ExpYear: 2030,
}, nil)
if err != nil {
t.Fatalf("failed to build destination endpoint: %v", err)
}
intent := &srequest.PaymentIntent{
Kind: srequest.PaymentKindPayout,
Source: &source,
Destination: &destination,
Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
SettlementMode: srequest.SettlementModeFixSource,
FeeTreatment: srequest.FeeTreatmentAddToSource,
SettlementCurrency: "RUB",
}
if _, err := mapQuoteIntent(intent); err == nil {
t.Fatalf("expected error for explicit settlement_currency")
}
}
func TestMapQuoteIntent_DerivesSettlementCurrencyFromFX(t *testing.T) {
source, err := srequest.NewManagedWalletEndpointDTO(srequest.ManagedWalletEndpoint{
ManagedWalletRef: "wallet-source-1",
}, nil)
if err != nil {
t.Fatalf("failed to build source endpoint: %v", err)
}
destination, err := srequest.NewCardEndpointDTO(srequest.CardEndpoint{
Pan: "2200700142860161",
FirstName: "John",
LastName: "Doe",
ExpMonth: 3,
ExpYear: 2030,
}, nil)
if err != nil {
t.Fatalf("failed to build destination endpoint: %v", err)
}
intent := &srequest.PaymentIntent{
Kind: srequest.PaymentKindPayout,
Source: &source,
Destination: &destination,
Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
SettlementMode: srequest.SettlementModeFixSource,
FeeTreatment: srequest.FeeTreatmentAddToSource,
FX: &srequest.FXIntent{
Pair: &srequest.CurrencyPair{
Base: "USDT",
Quote: "RUB",
},
Side: srequest.FXSideSellBaseBuyQuote,
},
}
got, err := mapQuoteIntent(intent)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.GetSettlementCurrency() != "RUB" {
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
}
}