mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
Merge pull request #3747 from Wei-Shaw/revert-3738-fix/subscription-confirm-amount-and-affiliate-base
Revert "fix(payment): 订阅确认页显示换算后 CNY 金额 + 邀请返利按 USD price 计算"
This commit is contained in:
@@ -602,8 +602,8 @@ func TestExecuteSubscriptionFulfillmentAppliesAffiliateRebate(t *testing.T) {
|
||||
SetUserID(user.ID).
|
||||
SetUserEmail(user.Email).
|
||||
SetUserName(user.Username).
|
||||
SetAmount(9.99).
|
||||
SetPayAmount(71.36).
|
||||
SetAmount(120).
|
||||
SetPayAmount(120).
|
||||
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: "15",
|
||||
SettingKeyAffiliateRebateRate: "20",
|
||||
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.InDelta(t, 1.4985, affiliateRepo.accrueCalls[0].amount, 0.00000001)
|
||||
require.Equal(t, 24.0, affiliateRepo.accrueCalls[0].amount)
|
||||
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":9.99`)
|
||||
require.Contains(t, applied.Detail, `"rebateAmount":1.4985`)
|
||||
require.Contains(t, applied.Detail, `"baseAmount":120`)
|
||||
require.Contains(t, applied.Detail, `"rebateAmount":24`)
|
||||
}
|
||||
|
||||
func TestExecuteSubscriptionFulfillmentDoesNotDuplicateWorkAfterLegacySuccessAudit(t *testing.T) {
|
||||
|
||||
@@ -16,7 +16,6 @@ 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 ---
|
||||
@@ -68,7 +67,8 @@ func (s *PaymentService) CreateOrder(ctx context.Context, req CreateOrderRequest
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
payAmountStr, payAmount, err := calculateCreateOrderPayAmountForOrderType(limitAmount, feeRate, methodCurrency, req.OrderType, cfg.BalanceRechargeMultiplier)
|
||||
// 订阅套餐 price 是直付价,余额充值倍率只影响余额充值到账,不参与订阅 pay_amount 计算。
|
||||
payAmountStr, payAmount, err := calculateCreateOrderPayAmount(limitAmount, feeRate, methodCurrency)
|
||||
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 = calculateCreateOrderPayAmountForOrderType(limitAmount, feeRate, selectedCurrency, req.OrderType, cfg.BalanceRechargeMultiplier)
|
||||
payAmountStr, payAmount, err = calculateCreateOrderPayAmount(limitAmount, feeRate, selectedCurrency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -630,24 +630,6 @@ 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,39 +161,27 @@ func TestCalculateCreateOrderPayAmountUsesCurrencyPrecision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCreateOrderPayAmountForSubscriptionConvertsCNYPrice(t *testing.T) {
|
||||
func TestCalculateCreateOrderPayAmountForSubscriptionKeepsDirectPrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
amountStr, amount, err := calculateCreateOrderPayAmountForOrderType(9.99, 0, "CNY", payment.OrderTypeSubscription, 0.14)
|
||||
amountStr, amount, err := calculateCreateOrderPayAmount(5, 0, "CNY")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if amountStr != "71.36" || amount != 71.36 {
|
||||
t.Fatalf("subscription CNY pay amount = (%q, %v), want (71.36, 71.36)", amountStr, amount)
|
||||
if amountStr != "5.00" || amount != 5 {
|
||||
t.Fatalf("subscription CNY pay amount = (%q, %v), want (5.00, 5)", amountStr, amount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateCreateOrderPayAmountForSubscriptionAppliesFeeAfterCNYConversion(t *testing.T) {
|
||||
func TestCalculateCreateOrderPayAmountForSubscriptionAppliesFeeToDirectPrice(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
amountStr, amount, err := calculateCreateOrderPayAmountForOrderType(9.99, 2.5, "CNY", payment.OrderTypeSubscription, 0.14)
|
||||
amountStr, amount, err := calculateCreateOrderPayAmount(5, 2.5, "CNY")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
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)
|
||||
if amountStr != "5.13" || amount != 5.13 {
|
||||
t.Fatalf("subscription CNY pay amount with fee = (%q, %v), want (5.13, 5.13)", amountStr, amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ import { platformAccentBarClass, platformBadgeLightClass, platformBadgeClass, pl
|
||||
import SubscriptionPlanCard from '@/components/payment/SubscriptionPlanCard.vue'
|
||||
import PaymentStatusPanel from '@/components/payment/PaymentStatusPanel.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { DEFAULT_PAYMENT_CURRENCY, formatPaymentAmount, normalizePaymentCurrency } from '@/components/payment/currency'
|
||||
import { formatPaymentAmount, normalizePaymentCurrency } from '@/components/payment/currency'
|
||||
import type { PaymentMethodOption } from '@/components/payment/PaymentMethodSelector.vue'
|
||||
import { buildPaymentErrorToastMessage, describePaymentScenarioError } from './paymentUx'
|
||||
import { hasWechatResumeQuery, parseWechatResumeRoute, stripWechatResumeQuery } from './paymentWechatResume'
|
||||
@@ -579,17 +579,12 @@ function ceilPaymentAmount(value: number, currency: string): number {
|
||||
return Math.ceil(value * factor) / factor
|
||||
}
|
||||
|
||||
function subscriptionPaymentAmountForCurrency(value: number, currency: string): number {
|
||||
if (currency !== DEFAULT_PAYMENT_CURRENCY) return roundPaymentAmount(value, currency)
|
||||
return roundPaymentAmount(value / balanceRechargeMultiplier.value, currency)
|
||||
}
|
||||
|
||||
function formatSelectedPaymentAmount(value: number): string {
|
||||
return formatPaymentAmount(value, selectedCurrency.value, localeCode.value)
|
||||
}
|
||||
|
||||
function formatSelectedSubscriptionPaymentAmount(value: number): string {
|
||||
return formatSelectedPaymentAmount(subscriptionPaymentAmountForCurrency(value, selectedCurrency.value))
|
||||
return formatSelectedPaymentAmount(roundPaymentAmount(value, selectedCurrency.value))
|
||||
}
|
||||
|
||||
const methodOptions = computed<PaymentMethodOption[]>(() =>
|
||||
@@ -638,7 +633,7 @@ const canSubmit = computed(() =>
|
||||
|
||||
const subPaymentAmount = computed(() => {
|
||||
const price = selectedPlan.value?.price ?? 0
|
||||
return subscriptionPaymentAmountForCurrency(price, selectedCurrency.value)
|
||||
return roundPaymentAmount(price, selectedCurrency.value)
|
||||
})
|
||||
|
||||
const subFeeAmount = computed(() => {
|
||||
@@ -652,7 +647,7 @@ const subTotalAmount = computed(() => {
|
||||
})
|
||||
|
||||
function subscriptionTotalAmountForCurrency(value: number, currency: string): number {
|
||||
const paymentAmount = subscriptionPaymentAmountForCurrency(value, currency)
|
||||
const paymentAmount = roundPaymentAmount(value, currency)
|
||||
if (feeRate.value <= 0 || paymentAmount <= 0) return paymentAmount
|
||||
const fee = ceilPaymentAmount((paymentAmount * feeRate.value) / 100, currency)
|
||||
return roundPaymentAmount(paymentAmount + fee, currency)
|
||||
|
||||
@@ -236,28 +236,29 @@ async function mountSubscriptionConfirm(options: Parameters<typeof checkoutInfoW
|
||||
}
|
||||
|
||||
describe('PaymentView subscription confirmation amounts', () => {
|
||||
it('shows converted CNY pay amount for plan price, original price, and create button', async () => {
|
||||
it('keeps subscription plan price independent from balance recharge multiplier', async () => {
|
||||
const wrapper = await mountSubscriptionConfirm({
|
||||
checkout: {
|
||||
balance_recharge_multiplier: 0.14,
|
||||
balance_recharge_multiplier: 4,
|
||||
},
|
||||
method: {
|
||||
currency: 'CNY',
|
||||
},
|
||||
plan: {
|
||||
price: 9.99,
|
||||
original_price: 12.99,
|
||||
price: 200,
|
||||
original_price: 300,
|
||||
},
|
||||
})
|
||||
|
||||
const text = wrapper.text()
|
||||
const convertedPrice = formatPaymentAmount(71.36, 'CNY')
|
||||
const convertedOriginalPrice = formatPaymentAmount(92.79, 'CNY')
|
||||
const planPrice = formatPaymentAmount(200, 'CNY')
|
||||
const originalPrice = formatPaymentAmount(300, 'CNY')
|
||||
const convertedByRechargeMultiplier = formatPaymentAmount(50, 'CNY')
|
||||
|
||||
expect(text).toContain(convertedPrice)
|
||||
expect(text).toContain(convertedOriginalPrice)
|
||||
expect(text).not.toContain(formatPaymentAmount(9.99, 'CNY'))
|
||||
expect(wrapper.findAll('button').some(button => button.text().includes(convertedPrice))).toBe(true)
|
||||
expect(text).toContain(planPrice)
|
||||
expect(text).toContain(originalPrice)
|
||||
expect(text).not.toContain(convertedByRechargeMultiplier)
|
||||
expect(wrapper.findAll('button').some(button => button.text().includes(planPrice))).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps plan price when multiplier is not configured or payment currency is not CNY', async () => {
|
||||
@@ -293,26 +294,26 @@ describe('PaymentView subscription confirmation amounts', () => {
|
||||
expect(usdWrapper.text()).toContain(formatPaymentAmount(9.99, 'USD'))
|
||||
})
|
||||
|
||||
it('adds fee rate after CNY multiplier conversion to match backend pay_amount', async () => {
|
||||
it('adds fee rate to the direct subscription plan price to match backend pay_amount', async () => {
|
||||
const wrapper = await mountSubscriptionConfirm({
|
||||
checkout: {
|
||||
balance_recharge_multiplier: 0.14,
|
||||
balance_recharge_multiplier: 4,
|
||||
recharge_fee_rate: 2.5,
|
||||
},
|
||||
method: {
|
||||
currency: 'CNY',
|
||||
},
|
||||
plan: {
|
||||
price: 9.99,
|
||||
price: 7.99,
|
||||
},
|
||||
})
|
||||
|
||||
const text = wrapper.text()
|
||||
const convertedPrice = formatPaymentAmount(71.36, 'CNY')
|
||||
const fee = formatPaymentAmount(1.79, 'CNY')
|
||||
const total = formatPaymentAmount(73.15, 'CNY')
|
||||
const price = formatPaymentAmount(7.99, 'CNY')
|
||||
const fee = formatPaymentAmount(0.20, 'CNY')
|
||||
const total = formatPaymentAmount(8.19, 'CNY')
|
||||
|
||||
expect(text).toContain(convertedPrice)
|
||||
expect(text).toContain(price)
|
||||
expect(text).toContain(fee)
|
||||
expect(text).toContain(total)
|
||||
expect(wrapper.findAll('button').some(button => button.text().includes(total))).toBe(true)
|
||||
|
||||
Reference in New Issue
Block a user