Angular+Laravel Cashier 3DS支付未完成:如何用Stripe.js处理?
问题描述
使用Angular开发前端,Laravel搭配Sanctum令牌认证开发后端,搭建订阅模块时遇到以下场景:
- 前端发起支付时已完成Stripe 3DS验证,将
payment_method_id发送给Laravel后端 - 后端创建订阅时抛出
IncompletePayment异常,目前需通过Laravel返回的跳转URL完成订阅 - 希望直接通过Stripe.js处理这一二次3DS验证流程,无需跳转页面
现有前端代码:
this.paymentService.getSecret().subscribe(async (res: any) => { const paymentIntent = await this.paymentService.stripe.confirmCardSetup(res.intent.client_secret, { payment_method: { card: this.paymentService.cardElement, billing_details: { name: 'John', } }, }); if (paymentIntent.error) { console.error(paymentIntent.error); } else { if (paymentIntent.setupIntent.status === "succeeded") { let test = { plan: res.plan.id, payment_method_id: paymentIntent.setupIntent.payment_method } this.paymentService.confirmPayment(test).subscribe(async (res1: any) => { }, err => { console.log(err) }) } } });
现有Laravel后端代码:
try{ $subscription = $request->user()->newSubscription(PLAN_NAME, PLAN_ID) ->create($request->payment_method_id); return response()->json([ 'status' => true, 'message' => "subscription_success" ]); } catch(IncompletePayment $exception){ return response()->json([ 'url' => route('cashier.payment', [$exception->payment->id, 'redirect' => route('subscription.done')]), 'status' => false, 'message' => "subscription_incomplete", 'payment_id' => $exception->payment->id ]); }
解决方案
步骤1:后端补充返回Payment Intent的Client Secret
修改Laravel的异常捕获逻辑,新增返回payment_intent_client_secret,这是Stripe.js处理验证必需的参数:
catch(IncompletePayment $exception){ return response()->json([ 'url' => route('cashier.payment', [$exception->payment->id, 'redirect' => route('subscription.done')]), 'status' => false, 'message' => "subscription_incomplete", 'payment_id' => $exception->payment->id, 'payment_intent_client_secret' => $exception->payment->client_secret // 新增参数 ]); }
步骤2:前端用Stripe.js处理二次3DS验证
修改前端confirmPayment的错误回调逻辑,捕获subscription_incomplete异常后,调用Stripe.js完成3DS验证,验证成功后再次触发订阅确认:
this.paymentService.confirmPayment(test).subscribe(async (res1: any) => { // 订阅成功后的业务逻辑 }, async err => { // 捕获订阅不完整的异常 if (err.error?.message === "subscription_incomplete" && err.error?.payment_intent_client_secret) { try { // 调用Stripe.js完成Payment Intent的3DS验证 const { error, paymentIntent } = await this.paymentService.stripe.confirmCardPayment( err.error.payment_intent_client_secret, { payment_method: test.payment_method_id } ); if (error) { console.error('3DS验证失败:', error.message); // 处理验证失败的用户提示 } else if (paymentIntent.status === 'succeeded') { // 验证成功后,再次调用后端接口完成订阅 this.paymentService.confirmPayment(test).subscribe(res2 => { // 订阅完成后的业务逻辑(如跳转成功页面) }, err2 => { console.error('订阅确认失败:', err2); }); } } catch (stripeErr) { console.error('Stripe验证流程出错:', stripeErr); } } else { console.log('其他错误:', err); } });
关键说明
- 二次3DS验证触发原因:即使完成了Setup Intent的3DS,Stripe可能因实时风险评估要求再次验证,此时Laravel Cashier会生成一个状态为
requires_action的Payment Intent - 无需跳转页面:通过Stripe.js的
confirmCardPayment方法可在当前页面内完成3DS验证流程,提升用户体验 - 可选优化:可配置Stripe Webhook监听
payment_intent.succeeded事件,后端自动完成订阅,避免前端二次调用接口的冗余流程
内容的提问来源于stack exchange,提问作者user1456131
相关产品推荐
相关产品推荐

