Stripe 测试卡在旧版 Checkout 中始终成功:原因与解决方案

13次阅读

Stripe 测试卡在旧版 Checkout 中始终成功:原因与解决方案

本文解释为何 stripe 旧版 checkout(modal 弹窗)无法触发测试卡拒绝行为,并指出根本原因是未使用前端提交的 `stripeToken`,而是错误地复用了已有客户默认卡;同时提供迁移至现代支付流程的完整方案。

你遇到的问题非常典型:Stripe 测试卡(如 4000000000000002)在旧版 Checkout 集成中始终显示“succeeded”,完全不触发拒绝逻辑。这不是测试环境失效,而是集成方式存在关键缺陷。

? 根本原因:未使用实时生成的支付凭证

你的前端代码通过 checkout.js 正确弹出支付弹窗并生成了 stripeToken(例如 tok_1P…),但后端 php 处理逻辑中却完全忽略了它:

// ❌ 错误:仅使用 customer_id,复用客户已绑定的「成功卡」 'customer' => $_POST['customer_id'], // ← 这里跳过了本次输入的卡片!

$_POST[‘customer_id’] 对应的是 Stripe Customer 对象 ID(如 cus_XXX)。当你调用 StripeCharge::create() 并只传入 customer 参数时,Stripe 自动对客户默认支付方式(default_source)发起扣款——而该卡极大概率是你之前测试成功的卡(如 4242…4242),因此无论你前端输入什么测试卡(甚至无效卡号/过期年份/CVC),后端实际扣的都不是你刚输的那张卡。

真正应使用的参数是 source(或 payment_method,取决于 API 版本):

// ✅ 正确:使用前端实时提交的 token(代表本次输入的卡片) try {     $charge = StripeCharge::create([         'amount' => 1000,         'currency' => 'usd',         'source' => $_POST['stripeToken'], // ← 关键!必须传此值         'description' => "Single Credit Purchase"     ]); } catch (StripeExceptionCardException $e) {     // 此处才能捕获 card_declined、insufficient_funds 等真实拒绝     $errors[] = $e->getError()->message; } catch (StripeExceptionStripeException $e) {     $errors[] = 'Payment failed: ' . $e->getMessage(); }

⚠️ 注意:$_POST[‘stripeToken’] 是 Checkout.js 在用户完成填写后自动注入表单并提交的隐藏字段值。请确认你的表单 确实接收并提交了该字段(可通过浏览器开发者工具 Network → Form Data 验证)。

? 为什么旧版 Checkout 已不可靠?

Stripe 官方已于 2019 年正式弃用(deprecated)checkout.js(v2),并停止对其新增功能支持:

  • 不支持 SCA(Strong Customer Authentication)和 3D Secure 2 —— 欧盟/英国等地区强制要求,否则交易将被拒;
  • 无动态错误反馈(如实时 CVC/日期校验);
  • 无法处理订阅、多币种、发票等现代支付场景;
  • 测试卡行为不一致(正如你所见),因底层逻辑绕过真实卡路由

✅ 推荐方案:迁移到 Stripe Elements + Confirm Card Payment

以下是轻量级、安全且兼容测试卡的现代实现(无需重写全部前端):

1. 前端(html + JS)

   

2. 后端(charge.php

StripeStripe::setApiKey('sk_test_...'); // 私钥  try {   $charge = StripeCharge::create([     'amount' => 1000,     'currency' => 'usd',     'source' => $_POST['stripeToken'], // ✅ 使用本次 token     'description' => 'One Credit Purchase'   ]);   echo json_encode(['success' => true]); } catch (StripeExceptionCardException $e) {   $error = $e->getError();   echo json_encode(['error' => $error->message]); // 如 "Your card was declined." } catch (StripeExceptionStripeException $e) {   echo json_encode(['error' => 'Payment failed']); }

✅ 此方案下,输入 4000000000000002 将明确返回 card_declined 错误;输入 4000000000009995 返回 insufficient_funds —— 完全符合 Stripe 测试卡文档预期。

? 总结

问题环节 正确做法
前端 确保 checkout.js 表单提交 stripeToken,或改用 stripe.elements() 获取实时 token
后端 必须使用 source => $_POST[‘stripeToken’],禁止仅依赖 customer 参数
长期 立即弃用 checkout.js,采用 Stripe Elements + confirmCardPayment,满足 SCA 合规与测试卡可靠性

迁移成本远低于维护一个已停更、不合规且行为异常的旧集成。Stripe 的测试卡机制本身完全可靠——只要你的代码真正让它“参与”到支付流程中。

text=ZqhQzanResources