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

Stripe中3DS卡片用户认证实现问题求助

问题分析

你当前的3DS认证逻辑存在两个核心问题:

  • 后端直接通过header()跳转至银行认证页,这种方式无法实现弹窗/iframe的交互体验,且可能因前端表单提交后的页面状态问题,导致跳转逻辑未被正确触发。
  • 订阅创建后状态为incomplete,说明支付流程因缺少用户认证步骤中断,但后端未将认证需求正确传递给前端。

Stripe的3DS认证(及其他需用户交互的支付验证)必须通过前端SDK处理,才能实现弹窗/内嵌iframe的交互流程,而非后端直接跳转。

解决方案

1. 后端代码调整

创建订阅后,若检测到支付需要用户动作,不要直接跳转,而是返回payment_intent的client_secret给前端,由前端触发认证流程:

if($_SERVER['REQUEST_METHOD'] == "POST"){
    set_value($_POST);
    
    $token = (isset($_POST['stripeToken'])) ? $_POST['stripeToken'] : null;
    $crad_number = (isset($_POST['crad_number'])) ? $_POST['crad_number'] : null;
    $month = (isset($_POST['month'])) ? $_POST['month'] : null;
    $year = (isset($_POST['year'])) ? $_POST['year'] : null;
    $cvv = (isset($_POST['cvv'])) ? $_POST['cvv'] : null;
    
    // Validation
    $errors = array();
    if(empty($token)){
        $errors[] = 'Token is required';
    }
    if(empty($crad_number)){
        $errors[] = 'Crad Number is required';
    }
    if(empty($month)){
        $errors[] = 'MONTH name is required';
    }
    if(empty($year)){
        $errors[] = 'YEAR is required';
    }
    if(empty($cvv)){
        $errors[] = 'CVV is required';
    }
    
    if(empty($errors)){
        try{
            $stripe = new \Stripe\StripeClient('sk_test key');
            
            // 注意:Stripe当前推荐使用payment_method而非source,建议后续迁移
            $customer = \Stripe\Customer::create(array(
                "email" => $email,
                "name" => $membernamedata,
                'source'  => $token,
            ));

            $paydesc = "Members Directory";

            // 创建订阅
            $subscription = $stripe->subscriptions->create([
                'customer' => $customer->id,
                'items' => [['price' => $product_id]],
                "currency" => $_currency,
                "description" => $paydesc,
                'expand' => ['latest_invoice.payment_intent'],
            ]);

            $payment_intent = $subscription->latest_invoice->payment_intent;
            
            // 检查是否需要用户交互认证
            if ($payment_intent->status === 'requires_action' &&
                $payment_intent->next_action->type === 'redirect_to_url'
            ) {
                // 返回JSON给前端,传递认证所需参数
                header('Content-Type: application/json');
                echo json_encode([
                    'status' => 'requires_action',
                    'client_secret' => $payment_intent->client_secret
                ]);
                exit();
            } else {
                // 非3DS卡支付成功,执行后续业务逻辑
                // ...
                echo json_encode(['status' => 'success']);
                exit();
            }
        } catch (\Stripe\Exception\ApiErrorException $e) {
            // 捕获Stripe接口错误并返回
            header('Content-Type: application/json');
            echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
            exit();
        }
    } else {
        header('Content-Type: application/json');
        echo json_encode(['status' => 'error', 'message' => implode(', ', $errors)]);
        exit();
    }
}

2. 前端代码调整

引入Stripe官方v3版本前端SDK,在表单提交后接收后端返回的结果,调用SDK触发弹窗式认证:

<!-- 引入Stripe官方v3版本前端SDK -->
<script>
// 初始化Stripe,替换为你的publishable key
const stripe = Stripe('pk_test_your_key');

// 监听表单提交事件
document.getElementById('your-form-id').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const form = e.target;
    const formData = new FormData(form);
    
    // 提交表单数据到后端接口
    const response = await fetch(form.action, {
        method: 'POST',
        body: formData
    });
    
    const result = await response.json();
    
    if (result.status === 'requires_action') {
        // 触发Stripe认证弹窗(自动适配弹窗/iframe形式)
        const { error, paymentIntent } = await stripe.handleCardAction(result.client_secret);
        
        if (error) {
            // 认证失败提示
            alert('认证失败:' + error.message);
        } else {
            // 认证成功后,通知后端验证支付状态
            const verifyResponse = await fetch('verify-payment.php', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ payment_intent_id: paymentIntent.id })
            });
            
            const verifyResult = await verifyResponse.json();
            if (verifyResult.status === 'success') {
                // 跳转至成功页面或更新当前页面状态
                window.location.href = 'success-page.php';
            } else {
                alert('支付验证失败:' + verifyResult.message);
            }
        }
    } else if (result.status === 'success') {
        // 非3DS卡支付成功,直接跳转
        window.location.href = 'success-page.php';
    } else {
        // 通用错误提示
        alert('错误:' + result.message);
    }
});
</script>

3. 新增支付验证后端(verify-payment.php)

认证完成后,后端需验证支付意图的最终状态,确保支付成功后再执行后续业务逻辑:

<?php
header('Content-Type: application/json');

// 获取前端传递的支付意图ID
$input = json_decode(file_get_contents('php://input'), true);
$payment_intent_id = $input['payment_intent_id'] ?? null;

if (!$payment_intent_id) {
    echo json_encode(['status' => 'error', 'message' => 'Payment Intent ID缺失']);
    exit();
}

try {
    $stripe = new \Stripe\StripeClient('sk_test key');
    $payment_intent = $stripe->paymentIntents->retrieve($payment_intent_id);
    
    if ($payment_intent->status === 'succeeded') {
        // 支付成功,执行后续业务逻辑(如更新用户会员状态)
        // ...
        
        echo json_encode(['status' => 'success']);
    } else {
        echo json_encode(['status' => 'error', 'message' => '支付未完成']);
    }
} catch (\Stripe\Exception\ApiErrorException $e) {
    echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
?>
关键说明
  • 必须使用Stripe前端SDK的handleCardAction方法,该方法会自动适配银行的认证流程(弹窗或iframe),无需手动处理跳转逻辑。
  • 后端仅负责传递认证所需参数,所有用户交互均由前端SDK处理,避免页面跳转导致的体验断裂。
  • 认证完成后务必验证支付意图状态,确保支付成功后再执行业务逻辑,避免出现订阅状态与实际支付结果不一致的问题。

内容的提问来源于stack exchange,提问作者Mid9it Coder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 09:26:00