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
相关产品推荐
相关产品推荐

