fix(payment): convert subscription CNY pay amount

This commit is contained in:
wucm667
2026-07-06 10:56:43 +08:00
parent f0f1f59a44
commit b408edf97b
5 changed files with 73 additions and 39 deletions
@@ -602,8 +602,8 @@ func TestExecuteSubscriptionFulfillmentAppliesAffiliateRebate(t *testing.T) {
SetUserID(user.ID).
SetUserEmail(user.Email).
SetUserName(user.Username).
SetAmount(120).
SetPayAmount(120).
SetAmount(9.99).
SetPayAmount(71.36).
SetFeeRate(0).
SetRechargeCode("PAY-SUB-AFFILIATE").
SetOutTradeNo("sub2_subscription_affiliate").
@@ -636,7 +636,7 @@ func TestExecuteSubscriptionFulfillmentAppliesAffiliateRebate(t *testing.T) {
}
settingSvc := NewSettingService(&paymentFulfillmentSettingRepoStub{values: map[string]string{
SettingKeyAffiliateEnabled: "true",
SettingKeyAffiliateRebateRate: "20",
SettingKeyAffiliateRebateRate: "15",
SettingKeyAffiliateRebateFreezeHours: "0",
}}, nil)
subRepo := newSubscriptionUserSubRepoStub()
@@ -659,7 +659,7 @@ func TestExecuteSubscriptionFulfillmentAppliesAffiliateRebate(t *testing.T) {
require.Len(t, affiliateRepo.accrueCalls, 1)
require.Equal(t, inviterID, affiliateRepo.accrueCalls[0].inviterID)
require.Equal(t, user.ID, affiliateRepo.accrueCalls[0].inviteeUserID)
require.Equal(t, 24.0, affiliateRepo.accrueCalls[0].amount)
require.InDelta(t, 1.4985, affiliateRepo.accrueCalls[0].amount, 0.00000001)
require.NotNil(t, affiliateRepo.accrueCalls[0].sourceOrderID)
require.Equal(t, order.ID, *affiliateRepo.accrueCalls[0].sourceOrderID)
require.Equal(t, 1, subRepo.createCalls)
@@ -668,8 +668,8 @@ func TestExecuteSubscriptionFulfillmentAppliesAffiliateRebate(t *testing.T) {
Where(paymentauditlog.OrderIDEQ(strconv.FormatInt(order.ID, 10)), paymentauditlog.ActionEQ("AFFILIATE_REBATE_APPLIED")).
Only(ctx)
require.NoError(t, err)
require.Contains(t, applied.Detail, `"baseAmount":120`)
require.Contains(t, applied.Detail, `"rebateAmount":24`)
require.Contains(t, applied.Detail, `"baseAmount":9.99`)
require.Contains(t, applied.Detail, `"rebateAmount":1.4985`)
}
func TestExecuteSubscriptionFulfillmentDoesNotDuplicateWorkAfterLegacySuccessAudit(t *testing.T) {
+21 -3
View File
@@ -16,6 +16,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/Wei-Shaw/sub2api/internal/payment/provider"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/shopspring/decimal"
)
// --- Order Creation ---
@@ -67,8 +68,7 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest
return nil, err
}
}
// 订阅套餐 price 是直付价,余额充值倍率只影响余额充值到账,不参与订阅 pay_amount 计算。
payAmountStr, payAmount, err := calculateCreateOrderPayAmount(limitAmount, feeRate, methodCurrency)
payAmountStr, payAmount, err := calculateCreateOrderPayAmountForOrderType(limitAmount, feeRate, methodCurrency, req.OrderType, cfg.BalanceRechargeMultiplier)
if err != nil {
return nil, err
}
@@ -84,7 +84,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 = calculateCreateOrderPayAmountForOrderType(limitAmount, feeRate, selectedCurrency, req.OrderType, cfg.BalanceRechargeMultiplier)
if err != nil {
return nil, err
}
@@ -630,6 +630,24 @@ func calculateCreateOrderPayAmount(limitAmount, feeRate float64, currency string
return payAmountStr, payAmount, nil
}
func calculateCreateOrderPayAmountForOrderType(limitAmount, feeRate float64, currency, orderType string, multiplier float64) (string, float64, error) {
paymentAmount := limitAmount
if orderType == payment.OrderTypeSubscription {
paymentAmount = calculateSubscriptionGatewayBaseAmount(limitAmount, multiplier, currency)
}
return calculateCreateOrderPayAmount(paymentAmount, feeRate, currency)
}
func calculateSubscriptionGatewayBaseAmount(amount, multiplier float64, currency string) float64 {
if currency != payment.DefaultPaymentCurrency {
return amount
}
return decimal.NewFromFloat(amount).
Div(decimal.NewFromFloat(normalizeBalanceRechargeMultiplier(multiplier))).
Round(int32(payment.CurrencyMaxFractionDigits(currency))).
InexactFloat64()
}
func validateCreateOrderAmountCurrency(amount float64, currency string) error {
amountStr := strconv.FormatFloat(amount, 'f', -1, 64)
if _, err := payment.AmountToMinorUnit(amountStr, currency); err != nil {
@@ -161,27 +161,39 @@ func TestCalculateCreateOrderPayAmountUsesCurrencyPrecision(t *testing.T) {
}
}
func TestCalculateCreateOrderPayAmountForSubscriptionKeepsDirectPrice(t *testing.T) {
func TestCalculateCreateOrderPayAmountForSubscriptionConvertsCNYPrice(t *testing.T) {
t.Parallel()
amountStr, amount, err := calculateCreateOrderPayAmount(5, 0, "CNY")
amountStr, amount, err := calculateCreateOrderPayAmountForOrderType(9.99, 0, "CNY", payment.OrderTypeSubscription, 0.14)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if amountStr != "5.00" || amount != 5 {
t.Fatalf("subscription CNY pay amount = (%q, %v), want (5.00, 5)", amountStr, amount)
if amountStr != "71.36" || amount != 71.36 {
t.Fatalf("subscription CNY pay amount = (%q, %v), want (71.36, 71.36)", amountStr, amount)
}
}
func TestCalculateCreateOrderPayAmountForSubscriptionAppliesFeeToDirectPrice(t *testing.T) {
func TestCalculateCreateOrderPayAmountForSubscriptionAppliesFeeAfterCNYConversion(t *testing.T) {
t.Parallel()
amountStr, amount, err := calculateCreateOrderPayAmount(5, 2.5, "CNY")
amountStr, amount, err := calculateCreateOrderPayAmountForOrderType(9.99, 2.5, "CNY", payment.OrderTypeSubscription, 0.14)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if amountStr != "5.13" || amount != 5.13 {
t.Fatalf("subscription CNY pay amount with fee = (%q, %v), want (5.13, 5.13)", amountStr, amount)
if amountStr != "73.15" || amount != 73.15 {
t.Fatalf("subscription CNY pay amount with fee = (%q, %v), want (73.15, 73.15)", amountStr, amount)
}
}
func TestCalculateCreateOrderPayAmountForSubscriptionKeepsNonCNYPrice(t *testing.T) {
t.Parallel()
amountStr, amount, err := calculateCreateOrderPayAmountForOrderType(9.99, 0, "USD", payment.OrderTypeSubscription, 0.14)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if amountStr != "9.99" || amount != 9.99 {
t.Fatalf("subscription USD pay amount = (%q, %v), want (9.99, 9.99)", amountStr, amount)
}
}