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

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)。当你调用 /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

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

LobeHub

LobeHub

LobeChat brings you the best user experience of ChatGPT, OLLaMA, Gemini, Claude

下载

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 的测试卡机制本身完全可靠——只要你的代码真正让它“参与”到支付流程中。

https://www.php.cn/faq/2012419.html

发表回复

Your email address will not be published. Required fields are marked *