Stripe订阅+试用期+3DS SCA问题:如何实现自动扣款?
Stripe订阅3DS验证问题解决方案
核心结论
- 主流网站均在用户首次提交支付方式时完成3DS验证,确保后续自动扣款无需用户介入;
- 完全可以在用户输入卡信息时完成3DS验证,避免试用期结束后出现逾期未付的情况。
问题分析
你当前的代码逻辑存在两个关键问题:
- 使用
payment_behavior: 'default_incomplete'会在创建订阅时生成待支付发票,而试用期内本应优先验证支付方式而非立即扣费; - 3DS验证触发逻辑不明确,导致支付方式未经过预验证,试用期结束后扣款时需要用户再次验证。
代码修改方案
后端订阅创建逻辑修改
将订阅创建时的支付行为改为pending_incomplete,强制触发Setup Intent完成3DS预验证:
export const stripeSubscriptionCreate = async ( subscriptionReq: StripeSubscriptionCreateReq, ): Promise<StripeSubscriptionCreateRes> => { try { const { customerId, priceId } = subscriptionReq; if (!customerId || !priceId) throw new Error('Missing data'); const client = await stripeClient(); const subscription = await client.subscriptions.create({ customer: customerId, items: [{ price: priceId }], // 试用期内触发支付方式验证,而非立即生成待支付发票 payment_behavior: 'pending_incomplete', payment_settings: { payment_method_options: { card: { // 强制要求完成3DS验证,确保支付方式可用于后续自动扣款 request_three_d_secure: 'required', }, }, payment_method_types: ['card'], save_default_payment_method: 'on_subscription', }, // 若需要12小时试用期,替换为trial_end: Math.floor(Date.now() / 1000) + 43200 trial_period_days: 1, expand: ['pending_setup_intent'], }); const pendingSetupIntent = subscription.pending_setup_intent as Stripe.SetupIntent | null; let resData: StripeSubscriptionCreateRes = { success: true, subscriptionId: subscription.id, type: '', clientSecret: '', }; if (pendingSetupIntent !== null) { resData.type = 'setup'; resData.clientSecret = pendingSetupIntent.client_secret; } return resData; } catch { return { success: false }; } };
前端验证逻辑优化
聚焦处理Setup Intent的3DS验证流程,确保用户提交卡信息时完成验证:
const PaymentContainer: FunctionComponent<PaymentContainerProps> = ({ type }) => { const stripe = useStripe(); const stripeElements = useElements(); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); if (!stripe || !stripeElements || type !== "setup") { return; } const confirmOptions = { elements: stripeElements, confirmParams: { return_url: `${window.location.origin}${buildRoute("ACCOUNT.WELCOMECOMPLETE")}`, }, }; const result = await stripe.confirmSetup(confirmOptions); if (result.error) { console.error('验证失败:', result.error.message); // 替换为用户友好的错误提示 } else { // 验证完成后,Stripe会自动激活订阅,可直接跳转至成功页面 } }; return ( <form onSubmit={handleSubmit}> <PaymentElement /> <Button type="submit" disabled={!stripe}> 提交并完成验证 </Button> </form> ); };
补充说明
- 试用期时长:Stripe的
trial_period_days最小为1天,若需要12小时试用期,可使用trial_end参数设置时间戳(当前时间+43200秒); - 测试验证:使用测试卡
4000000000003220可触发3DS验证,用于测试流程; - 订阅状态:用户完成验证后,订阅会从
pending_incomplete变为active,试用期结束后自动扣款,无需用户再次操作。
内容的提问来源于stack exchange,提问作者crivella
相关产品推荐
相关产品推荐

