diff --git a/frontend/src/components/payment/__tests__/paymentFlow.spec.ts b/frontend/src/components/payment/__tests__/paymentFlow.spec.ts index 72dc61f1..aebec8e5 100644 --- a/frontend/src/components/payment/__tests__/paymentFlow.spec.ts +++ b/frontend/src/components/payment/__tests__/paymentFlow.spec.ts @@ -33,7 +33,7 @@ function createOrderResult(overrides: Partial = {}): CreateOr } describe('getVisibleMethods', () => { - it('filters hidden provider methods and normalizes aliases', () => { + it('normalizes provider aliases and keeps stripe as a top-level method', () => { const visible = getVisibleMethods({ alipay_direct: methodLimit({ single_min: 5 }), wxpay: methodLimit({ single_max: 100 }), @@ -43,6 +43,7 @@ describe('getVisibleMethods', () => { expect(visible).toEqual({ alipay: methodLimit({ single_min: 5 }), wxpay: methodLimit({ single_max: 100 }), + stripe: methodLimit({ fee_rate: 3 }), }) }) @@ -76,6 +77,19 @@ describe('decidePaymentLaunch', () => { expect(decision.recovery.outTradeNo).toBe('') }) + it('routes Stripe button click to the full Payment Element without a preselected sub-method', () => { + const decision = decidePaymentLaunch(createOrderResult({ + client_secret: 'cs_test', + }), { + visibleMethod: 'stripe', + orderType: 'balance', + isMobile: false, + }) + + expect(decision.kind).toBe('stripe_route') + expect(decision.stripeMethod).toBeUndefined() + }) + it('uses Stripe route flow for mobile WeChat client secret', () => { const decision = decidePaymentLaunch(createOrderResult({ client_secret: 'cs_test', diff --git a/frontend/src/components/payment/paymentFlow.ts b/frontend/src/components/payment/paymentFlow.ts index 2a5f93dc..318f3882 100644 --- a/frontend/src/components/payment/paymentFlow.ts +++ b/frontend/src/components/payment/paymentFlow.ts @@ -14,9 +14,10 @@ const VISIBLE_METHOD_ALIASES = { alipay_direct: 'alipay', wxpay: 'wxpay', wxpay_direct: 'wxpay', + stripe: 'stripe', } as const -export type VisiblePaymentMethod = 'alipay' | 'wxpay' +export type VisiblePaymentMethod = 'alipay' | 'wxpay' | 'stripe' export type StripeVisibleMethod = 'alipay' | 'wechat_pay' export type PaymentLaunchKind = | 'qr_waiting' @@ -144,7 +145,12 @@ export function decidePaymentLaunch( }, context.now) if (baseState.clientSecret) { - const stripeMethod: StripeVisibleMethod = visibleMethod === 'wxpay' ? 'wechat_pay' : 'alipay' + // visibleMethod === 'stripe' means the user clicked the dedicated Stripe button + // and should land on the full Payment Element to choose a sub-method themselves. + const isStripeButton = visibleMethod === 'stripe' + const stripeMethod: StripeVisibleMethod | undefined = isStripeButton + ? undefined + : visibleMethod === 'wxpay' ? 'wechat_pay' : 'alipay' const kind: PaymentLaunchKind = stripeMethod === 'alipay' && !context.isMobile ? 'stripe_popup' : 'stripe_route' diff --git a/frontend/src/views/user/PaymentView.vue b/frontend/src/views/user/PaymentView.vue index 7cb4343d..7bbdf708 100644 --- a/frontend/src/views/user/PaymentView.vue +++ b/frontend/src/views/user/PaymentView.vue @@ -693,14 +693,18 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n } } const visibleMethod = normalizeVisibleMethod(requestType) || requestType - const stripeMethod = visibleMethod === 'wxpay' ? 'wechat_pay' : 'alipay' + // When user clicks the dedicated Stripe button, leave method blank so the + // landing page renders Stripe's full Payment Element (card/link/alipay/wxpay). + const stripeMethod = visibleMethod === 'stripe' + ? '' + : visibleMethod === 'wxpay' ? 'wechat_pay' : 'alipay' const stripeRouteUrl = result.client_secret ? router.resolve({ path: '/payment/stripe', query: { order_id: String(result.order_id), client_secret: result.client_secret, - method: stripeMethod, + method: stripeMethod || undefined, resume_token: result.resume_token || undefined, }, }).href