From c6f375d3ab132add8425be7a20dbc0b9a838ffd9 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Mon, 22 Jun 2026 10:33:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(payment):=20=E8=AE=A2=E9=98=85=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=BA=94=E7=94=A8=E5=85=85=E5=80=BC=E6=B1=87=E7=8E=87?= =?UTF-8?q?=E6=8D=A2=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/service/payment_amounts.go | 8 +++ backend/internal/service/payment_order.go | 17 ++++- .../service/payment_order_result_test.go | 66 +++++++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/backend/internal/service/payment_amounts.go b/backend/internal/service/payment_amounts.go index a7f620d33e..f0c49905a4 100644 --- a/backend/internal/service/payment_amounts.go +++ b/backend/internal/service/payment_amounts.go @@ -23,6 +23,14 @@ func calculateCreditedBalance(paymentAmount, multiplier float64) float64 { InexactFloat64() } +func calculateGatewayPaymentAmount(orderAmount, multiplier float64, currency string) float64 { + fractionDigits := int32(payment.CurrencyMaxFractionDigits(currency)) + return decimal.NewFromFloat(orderAmount). + Div(decimal.NewFromFloat(normalizeBalanceRechargeMultiplier(multiplier))). + Round(fractionDigits). + InexactFloat64() +} + func calculateGatewayRefundAmount(orderAmount, payAmount, refundAmount float64, currency string) float64 { if orderAmount <= 0 || payAmount <= 0 || refundAmount <= 0 { return 0 diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go index 83edb9e163..6170819d48 100644 --- a/backend/internal/service/payment_order.go +++ b/backend/internal/service/payment_order.go @@ -67,7 +67,7 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest return nil, err } } - payAmountStr, payAmount, err := calculateCreateOrderPayAmount(limitAmount, feeRate, methodCurrency) + payAmountStr, payAmount, err := calculateCreateOrderPayAmountForOrder(req.OrderType, limitAmount, feeRate, cfg.BalanceRechargeMultiplier, methodCurrency) if err != nil { return nil, err } @@ -83,7 +83,7 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest selectedCurrency = paymentProviderConfigCurrency(sel.ProviderKey, sel.Config) } if selectedCurrency != methodCurrency { - payAmountStr, payAmount, err = calculateCreateOrderPayAmount(limitAmount, feeRate, selectedCurrency) + payAmountStr, payAmount, err = calculateCreateOrderPayAmountForOrder(req.OrderType, limitAmount, feeRate, cfg.BalanceRechargeMultiplier, selectedCurrency) if err != nil { return nil, err } @@ -612,6 +612,19 @@ func calculateCreateOrderPayAmount(limitAmount, feeRate float64, currency string return payAmountStr, payAmount, nil } +func calculateCreateOrderPayAmountForOrder(orderType string, limitAmount, feeRate, multiplier float64, currency string) (string, float64, error) { + paymentAmount := calculateCreateOrderPaymentAmount(orderType, limitAmount, multiplier, currency) + return calculateCreateOrderPayAmount(paymentAmount, feeRate, currency) +} + +func calculateCreateOrderPaymentAmount(orderType string, limitAmount, multiplier float64, currency string) float64 { + normalizedCurrency, err := payment.NormalizePaymentCurrency(currency) + if err != nil || normalizedCurrency != payment.DefaultPaymentCurrency || orderType != payment.OrderTypeSubscription { + return limitAmount + } + return calculateGatewayPaymentAmount(limitAmount, multiplier, normalizedCurrency) +} + func validateCreateOrderAmountCurrency(amount float64, currency string) error { amountStr := strconv.FormatFloat(amount, 'f', -1, 64) if _, err := payment.AmountToMinorUnit(amountStr, currency); err != nil { diff --git a/backend/internal/service/payment_order_result_test.go b/backend/internal/service/payment_order_result_test.go index bfe275481c..9ea3c38220 100644 --- a/backend/internal/service/payment_order_result_test.go +++ b/backend/internal/service/payment_order_result_test.go @@ -126,6 +126,72 @@ func TestCalculateCreateOrderPayAmountUsesCurrencyPrecision(t *testing.T) { } } +func TestCalculateCreateOrderPayAmountForSubscriptionAppliesCNYMultiplier(t *testing.T) { + t.Parallel() + + amountStr, amount, err := calculateCreateOrderPayAmountForOrder(payment.OrderTypeSubscription, 7.99, 0, 0.14, "CNY") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if amountStr != "57.07" || amount != 57.07 { + t.Fatalf("subscription CNY pay amount = (%q, %v), want (57.07, 57.07)", amountStr, amount) + } +} + +func TestCalculateCreateOrderPayAmountForSubscriptionDefaultMultiplierKeepsPrice(t *testing.T) { + t.Parallel() + + for _, multiplier := range []float64{0, 1} { + amountStr, amount, err := calculateCreateOrderPayAmountForOrder(payment.OrderTypeSubscription, 7.99, 0, multiplier, "CNY") + if err != nil { + t.Fatalf("unexpected error for multiplier %v: %v", multiplier, err) + } + if amountStr != "7.99" || amount != 7.99 { + t.Fatalf("multiplier %v pay amount = (%q, %v), want (7.99, 7.99)", multiplier, amountStr, amount) + } + } +} + +func TestCalculateCreateOrderPayAmountForSubscriptionDoesNotConvertNonCNY(t *testing.T) { + t.Parallel() + + amountStr, amount, err := calculateCreateOrderPayAmountForOrder(payment.OrderTypeSubscription, 7.99, 0, 0.14, "USD") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if amountStr != "7.99" || amount != 7.99 { + t.Fatalf("subscription USD pay amount = (%q, %v), want (7.99, 7.99)", amountStr, amount) + } +} + +func TestCalculateCreateOrderPayAmountForSubscriptionMatchesBalanceRechargeRatio(t *testing.T) { + t.Parallel() + + credited := calculateCreditedBalance(10, 0.14) + if credited != 1.4 { + t.Fatalf("credited balance = %v, want 1.4", credited) + } + amountStr, amount, err := calculateCreateOrderPayAmountForOrder(payment.OrderTypeSubscription, credited, 0, 0.14, "CNY") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if amountStr != "10.00" || amount != 10 { + t.Fatalf("subscription CNY pay amount = (%q, %v), want (10.00, 10)", amountStr, amount) + } +} + +func TestCalculateCreateOrderPayAmountForSubscriptionAppliesFeeAfterMultiplier(t *testing.T) { + t.Parallel() + + amountStr, amount, err := calculateCreateOrderPayAmountForOrder(payment.OrderTypeSubscription, 7.99, 2.5, 0.14, "CNY") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if amountStr != "58.50" || amount != 58.5 { + t.Fatalf("subscription CNY pay amount with fee = (%q, %v), want (58.50, 58.5)", amountStr, amount) + } +} + func TestCalculateCreateOrderPayAmountRejectsFractionalZeroDecimal(t *testing.T) { t.Parallel()