You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+Laravel Cashier 3DS支付未完成:如何用Stripe.js处理?

问题描述

使用Angular开发前端,Laravel搭配Sanctum令牌认证开发后端,搭建订阅模块时遇到以下场景:

  1. 前端发起支付时已完成Stripe 3DS验证,将payment_method_id发送给Laravel后端
  2. 后端创建订阅时抛出IncompletePayment异常,目前需通过Laravel返回的跳转URL完成订阅
  3. 希望直接通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:07:24