
本文解释为何 stripe 旧版 checkout(弹窗模式)中使用官方测试卡(如 4000000000000002)仍会成功扣款,并指出根本原因在于未正确使用前端生成的 `stripetoken`,而是错误地复用了已有客户默认卡片;同时提供迁移至现代支付流程的必要性说明与代码修正方案。
您遇到的问题非常典型:Stripe 测试卡在旧版 Checkout 中“全部通过”,并非测试卡失效,而是后端根本没有使用用户刚输入的卡片信息。
关键问题出在您的服务端代码中:
'customer' => $_POST['customer_id'],
这段逻辑意味着:Stripe 后端直接对指定 customer_id 的默认支付方式(即该 Customer 对象下已绑定并设为 default 的 Card 或 PaymentMethod)发起扣款。如果该 Customer 之前用 4242424242424242(成功卡)创建过订阅或保存过卡,那么无论前端输入什么测试卡(哪怕是 4000000000000002),实际扣款的仍是那张成功的卡——自然永远“succeeded”。
✅ 正确做法是:从前端获取 Checkout 生成的单次有效 token(stripeToken),并在 Charge 创建时显式使用它,而非依赖 Customer 默认卡。
✅ 修复旧版 Checkout(仅作临时兼容,不推荐长期使用)
确保前端提交 stripeToken
Stripe Checkout 会自动将生成的 token 作为 stripeToken 字段 POST 到您的服务端 URL(无需手动收集)。请确认表单提交目标(如 /charge.php)能接收该参数:修改后端 Charge 创建逻辑
替换 customer 参数为 source,并传入 $_POST['stripeToken']:
try {
$charge = \Stripe\Charge::create([
'amount' => 1000,
'currency' => 'usd',
'source' => $_POST['stripeToken'], // ✅ 关键:使用本次输入的 token
'description' => "Single Credit Purchase"
// 注意:移除 'customer' 参数!避免复用旧卡
]);
} catch (\Stripe\Exception\CardException $e) {
$errors[] = $e->getError()->message;
} catch (\Stripe\Exception\RateLimitException $e) {
$errors[] = 'Too many requests. Please try again later.';
} catch (\Stripe\Exception\InvalidRequestException $e) {
$errors[] = 'Invalid parameters: ' . $e->getMessage();
} catch (\Stripe\Exception\AuthenticationException $e) {
$errors[] = 'Authentication failed. Check your secret key.';
} catch (\Stripe\Exception\ApiConnectionException $e) {
$errors[] = 'Network error. Please try again.';
} catch (\Stripe\Exception\ApiErrorException $e) {
$errors[] = 'Stripe API error: ' . $e->getMessage();
} catch (Exception $e) {
$errors[] = 'Unexpected error: ' . $e->getMessage();
}⚠️ 注意事项:source 参数必须是前端实时生成的 token(如 tok_1P...),不可复用 cus_... 或 card_... ID;若需保存客户信息,请先用 source 创建 Customer + Card,再扣款(但测试场景无需保存);所有 Stripe_* 类名在 Stripe PHP SDK v7+ 已弃用,应使用 \Stripe\Exception\* 命名空间(如上例)。
? 强烈建议:立即迁移到 Stripe Elements + ConfirmPayment(现代标准)
Stripe 官方已于 2019 年正式废弃 Checkout.js(v2),且该方案存在严重缺陷:
- ❌ 不支持 SCA/3D Secure 强制认证(欧盟、英国等地区交易将被拒);
- ❌ 无 PCI 合规保障(敏感卡信息经您服务器中转);
- ❌ 无法自定义 UI,移动端体验差;
- ❌ 测试卡行为不一致(因绕过实时卡验证流程)。
✅ 推荐替代方案:
使用 Stripe Elements + Confirm Payment(服务端配合 PaymentIntent):
// 前端(简略)
const stripe = Stripe('pk_test_...');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
document.getElementById('payment-form').addEventListener('submit', async (e) => {
e.preventDefault();
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
alert(error.message);
} else {
// 发送 payment_method.id 至后端
fetch('/create-payment-intent.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_method: paymentMethod.id })
});
}
});// 后端 create-payment-intent.php
\Stripe\Stripe::setApiKey('sk_test_...');
$intent = \Stripe\PaymentIntent::create([
'amount' => 1000,
'currency' => 'usd',
'payment_method_types' => ['card'],
'confirm' => true,
'payment_method' => $_POST['payment_method'],
'return_url' => 'https://yoursite.com/success',
]);✅ 优势:
- 测试卡(4000000000000002 等)100% 触发对应错误(card_declined、insufficient_funds);
- 自动处理 SCA 认证(3D Secure);
- PCI-DSS Level 1 合规(卡号不触达您的服务器);
- 支持多币种、多支付方式(Apple Pay、Link、SOFA 等);
- 官方长期维护,文档与错误提示更精准。
总结
- ? 根本原因:您未使用 stripeToken,而是复用了 Customer 的默认卡,导致测试卡被完全绕过;
- ✅ 短期修复:改用 'source' => $_POST['stripeToken'],并移除 customer 参数;
- ? 长期方案:立即弃用 Checkout.js,迁移到 Payment Intents + Elements —— 这不仅是技术升级,更是合规性与用户体验的必需选择。Stripe 的测试卡机制只在真实、合规的集成路径中才按设计工作。










