
本文解释为何 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)。当你调用 \Stripe\Charge::create() 并只传入 customer 参数时,Stripe 自动对客户默认支付方式(default_source)发起扣款——而该卡极大概率是你之前测试成功的卡(如 4242...4242),因此无论你前端输入什么测试卡(甚至无效卡号/过期年份/CVC),后端实际扣的都不是你刚输的那张卡。
真正应使用的参数是 source(或 payment_method,取决于 API 版本):
// ✅ 正确:使用前端实时提交的 token(代表本次输入的卡片)
try {
$charge = \Stripe\Charge::create([
'amount' => 1000,
'currency' => 'usd',
'source' => $_POST['stripeToken'], // ← 关键!必须传此值
'description' => "Single Credit Purchase"
]);
} catch (\Stripe\Exception\CardException $e) {
// 此处才能捕获 card_declined、insufficient_funds 等真实拒绝
$errors[] = $e->getError()->message;
} catch (\Stripe\Exception\StripeException $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)
\Stripe\Stripe::setApiKey('sk_test_...'); // 私钥
try {
$charge = \Stripe\Charge::create([
'amount' => 1000,
'currency' => 'usd',
'source' => $_POST['stripeToken'], // ✅ 使用本次 token
'description' => 'One Credit Purchase'
]);
echo json_encode(['success' => true]);
} catch (\Stripe\Exception\CardException $e) {
$error = $e->getError();
echo json_encode(['error' => $error->message]); // 如 "Your card was declined."
} catch (\Stripe\Exception\StripeException $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 的测试卡机制本身完全可靠——只要你的代码真正让它“参与”到支付流程中。










