Merge pull request 'fixed quotation calculation logic' (#103) from fees-102 into main
Some checks failed
ci/woodpecker/push/chain_gateway Pipeline is pending
ci/woodpecker/push/db Pipeline is pending
ci/woodpecker/push/frontend Pipeline is pending
ci/woodpecker/push/fx_ingestor Pipeline is pending
ci/woodpecker/push/fx_oracle Pipeline is pending
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/mntx_gateway Pipeline is pending
ci/woodpecker/push/nats Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline failed
ci/woodpecker/push/bff Pipeline failed

Reviewed-on: #103
This commit was merged in pull request #103.
This commit is contained in:
2025-12-12 12:42:13 +00:00
3 changed files with 43 additions and 11 deletions

View File

@@ -145,7 +145,7 @@ func resolveTradeAmounts(intentAmount *moneyv1.Money, fxQuote *oraclev1.Quote, s
}
}
func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.EstimateTransferFeeResponse, fxQuote *oraclev1.Quote) (*moneyv1.Money, *moneyv1.Money) {
func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.EstimateTransferFeeResponse, fxQuote *oraclev1.Quote, mode orchestratorv1.SettlementMode) (*moneyv1.Money, *moneyv1.Money) {
if pay == nil {
return nil, nil
}
@@ -166,7 +166,7 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
}
}
adjustDebit := func(m *moneyv1.Money) {
applyChargeToDebit := func(m *moneyv1.Money) {
converted, err := ensureCurrency(m, pay.GetCurrency(), fxQuote)
if err != nil || converted == nil {
return
@@ -176,7 +176,7 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
}
}
adjustSettlement := func(m *moneyv1.Money) {
applyChargeToSettlement := func(m *moneyv1.Money) {
converted, err := ensureCurrency(m, settlementCurrency, fxQuote)
if err != nil || converted == nil {
return
@@ -186,12 +186,22 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
}
}
adjustDebit(fee)
adjustSettlement(fee)
switch mode {
case orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED:
// Sender pays the fee: keep settlement fixed, increase debit.
applyChargeToDebit(fee)
default:
// Recipient pays the fee (default): reduce settlement, keep debit fixed.
applyChargeToSettlement(fee)
}
if network != nil && network.GetNetworkFee() != nil {
adjustDebit(network.GetNetworkFee())
adjustSettlement(network.GetNetworkFee())
switch mode {
case orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED:
applyChargeToDebit(network.GetNetworkFee())
default:
applyChargeToSettlement(network.GetNetworkFee())
}
}
return makeMoney(pay.GetCurrency(), debitDecimal), makeMoney(settlementCurrency, settlementDecimal)

View File

@@ -7,6 +7,7 @@ import (
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
)
func TestResolveTradeAmountsBuyBase(t *testing.T) {
@@ -47,11 +48,32 @@ func TestComputeAggregatesConvertsCurrencies(t *testing.T) {
},
}
debit, settlement := computeAggregates(pay, settle, fee, network, fxQuote)
debit, settlement := computeAggregates(pay, settle, fee, network, fxQuote, orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED)
if debit.GetCurrency() != "USD" || debit.GetAmount() != "115" {
t.Fatalf("expected debit 115 USD, got %s %s", debit.GetCurrency(), debit.GetAmount())
}
if settlement.GetCurrency() != "EUR" || settlement.GetAmount() != "42.5" {
t.Fatalf("expected settlement 42.5 EUR, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
if settlement.GetCurrency() != "EUR" || settlement.GetAmount() != "50" {
t.Fatalf("expected settlement 50 EUR, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
}
}
func TestComputeAggregatesRecipientPaysFee(t *testing.T) {
pay := &moneyv1.Money{Currency: "USDT", Amount: "100"}
settle := &moneyv1.Money{Currency: "RUB", Amount: "7932"} // 100 * 79.32
fee := &moneyv1.Money{Currency: "USDT", Amount: "7"} // 7% of 100
fxQuote := &oraclev1.Quote{
Pair: &fxv1.CurrencyPair{Base: "USDT", Quote: "RUB"},
Side: fxv1.Side_SELL_BASE_BUY_QUOTE,
Price: &moneyv1.Decimal{
Value: "79.32",
},
}
debit, settlement := computeAggregates(pay, settle, fee, nil, fxQuote, orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_SOURCE)
if debit.GetCurrency() != "USDT" || debit.GetAmount() != "100" {
t.Fatalf("expected debit 100 USDT, got %s %s", debit.GetCurrency(), debit.GetAmount())
}
if settlement.GetCurrency() != "RUB" || settlement.GetAmount() != "7376.76" {
t.Fatalf("expected settlement 7376.76 RUB, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
}
}

View File

@@ -65,7 +65,7 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *orc
s.logger.Debug("network fee estimated", zap.String("org_ref", orgRef))
}
debitAmount, settlementAmount := computeAggregates(payAmount, settlementAmountBeforeFees, feeTotal, networkFee, fxQuote)
debitAmount, settlementAmount := computeAggregates(payAmount, settlementAmountBeforeFees, feeTotal, networkFee, fxQuote, intent.GetSettlementMode())
quote := &orchestratorv1.PaymentQuote{
DebitAmount: debitAmount,