Stripe 的测试卡看似只要把卡号填进去就能得到文档里写的失败结果,实际集成时却经常出现页面没有报错、回调里没有失败事件、订单状态也没有改变的情况。使用 4000000000000002 这张最基础的拒绝卡时,常见的做法是直接把卡号填入 Payment Element,然后观察客户端抛出的异常,结果往往是什么都没有发生。这里最容易被忽略的一点是:Stripe 创建 PaymentMethod 时只检查卡号格式、Luhn 校验、卡类型是否被启用,并不会向发卡行发起授权,因此也根本不会返回余额不足、卡片丢失、盗卡之类的业务失败。

真正触发测试卡失败的位置在确认支付意图之后。也就是说,在服务端调用 paymentIntents.confirm 或客户端调用 confirmCardPayment 时,Stripe 才会向测试银行发起模拟授权,并根据卡号返回对应的拒绝码。如果不理解这个时序,就很容易把 PaymentMethod 创建成功误判为支付成功,或者把失败原因归结为测试卡不生效。下面从 Stripe 的确认流程、常见配置错误、正确代码实现以及自动验证方案几个角度展开。
一、先理解失败发生在 PaymentIntent 的哪个阶段
Stripe 的 PaymentIntent 生命周期里,刚创建时通常是 requires_payment_method 或 requires_confirmation。当用户输入卡片并提交后,系统先创建 PaymentMethod,这是一个卡片信息的容器对象,创建成功只代表卡号格式正确。随后进入确认阶段,Stripe 会根据你选择的确认方式向卡组织发送授权请求。对于测试卡来说,这一步才是模拟银行返回拒绝码的地方。
如果使用服务端确认,必须监听 stripe.paymentIntents.create 或 stripe.paymentIntents.confirm 的异常。注意,某些拒绝会直接抛出异常,而某些异步失败事件会通过 webhook 发送,具体取决于支付方式。对于卡支付,普通的 card_declined 通常会在同步确认时抛出错误,并且 PaymentIntent 会被设置为 requires_payment_method 状态。可以通过读取异常里的 code 和 decline_code 来区分失败类型。
举例来说,4000000000000002 的 code 是 card_declined,decline_code 是 generic_decline;4000000000009995 的 code 也是 card_declined,但 decline_code 会变成 insufficient_funds。如果在客户端只判断了 error.message,没有打印 decline_code,就难以确认测试是否真的命中预期规则。
二、测试卡不按预期失败的几个常见配置问题
第一种是测试模式与生产模式混用。测试卡的特殊行为只存在于测试模式。若前端使用 pk_live_xxx 或者服务端使用 sk_live_xxx,测试卡号会被当作普通银行卡处理,轻则返回 invalid_card,重则无法通过前端校验。排查时可以先确认 Stripe Dashboard 右上角是否显示 Test mode,以及请求头里的密钥前缀。
第二种是只创建 SetupIntent 而不是 PaymentIntent。SetupIntent 用于保存卡片并生成可复用的 PaymentMethod,默认不会产生扣款授权。像余额不足、卡片丢失这类需要向发卡行验证余额的失败,可能不会在保存卡片时出现。测试卡虽然看起来填进去了,但卡片被合法保存,页面自然没有任何失败提示。要触发业务拒绝,应使用 PaymentIntent 并实际确认扣款。
第三种是客户端没有处理确认操作返回的 Promise。比如调用 stripe.confirmCardPayment 后没有 await,或者只监听了表单提交却没接收 resolve 结果。Stripe 的确认是异步过程,失败信息会通过 Promise 返回,也会体现在服务端异常里。如果前端跳过了这个步骤,测试结果就会看似成功。
第四种是 Radar 风控规则或 3DS 规则干扰。某些测试卡本身会触发 3DS 验证,例如以 3220 结尾的卡会进入 requires_action 状态,而不是返回拒绝错误。如果代码没有处理这个状态,就可能等到超时或者页面卡住。做失败测试时,建议先关闭非必要的 Radar 规则,或者使用不会触发 3DS 的拒绝卡。
三、用 PaymentIntents 正确实现失败测试
最稳定的做法是让服务端来创建并确认 PaymentIntent,前端只负责收集卡信息并提交 PaymentMethod。这样失败信息会首先落在服务端异常中,再统一返回给前端展示。下面是一个简单的 Node.js 与 Express 示例。
客户端先通过 stripe.createPaymentMethod 得到卡片标识,这一步不会触发拒绝:
const stripe = Stripe('pk_test_your_publishable_key');
const cardElement = elements.getElement('card');
const { paymentMethod, error } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (error) {
console.log(error);
return;
}
const response = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentMethodId: paymentMethod.id }),
});
const result = await response.json();
if (result.error) {
console.log(result.error.code, result.error.decline_code);
}
服务端收到 paymentMethodId 后,创建 PaymentIntent 并直接确认。确认时使用测试卡,就会在 catch 中拿到预期失败:
const express = require('express');
const Stripe = require('stripe');
const app = express();
app.use(express.json());
const stripe = Stripe('sk_test_your_secret_key');
app.post('/pay', async (req, res) => {
const { paymentMethodId } = req.body;
try {
const intent = await stripe.paymentIntents.create({
amount: 2500,
currency: 'usd',
payment_method: paymentMethodId,
confirm: true,
return_url: 'https://ipipp.com/return',
});
res.json({ status: intent.status });
} catch (err) {
res.status(402).json({
code: err.code,
decline_code: err.decline_code,
status: err.payment_intent ? err.payment_intent.status : null,
last_payment_error: err.payment_intent
? err.payment_intent.last_payment_error
: null,
});
}
});
app.listen(3000);
上面的代码重点在于 confirm: true。如果只创建 PaymentIntent 而没有确认,那么当用户离开页面后,服务端根本不会发起授权,测试卡自然也不会失败。另一个要注意的是 err.payment_intent 可能在异常抛出时已经挂载,里面的 last_payment_error 会保留失败详情,这比仅依赖异常对象更完整。
对于需要处理 3DS 的卡,不能简单地在服务端直接确认并期待同步结果。此时应把 confirm 交给客户端,在 requires_action 状态下调用 stripe.handleCardAction。但如果你只是要验证拒绝卡,上面的同步方式已经足够可靠。
四、借助 Stripe CLI 与 Radar 规则做可重复的失败验证
除了手动输入测试卡,Stripe CLI 可以在不启动浏览器的情况下触发支付失败事件。像下面这条命令会向本地 webhook 地址发送一个 payment_intent.payment_failed 事件,适合用来检查订单系统是否正确处理失败状态:
stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.payment_failed
这种方式虽然不会经过完整的银行卡验证流程,但能快速验证服务端在失败事件到达后的更新逻辑。和真实确认失败配合使用,可以覆盖同步异常和异步 webhook 两条路径。
如果希望更细粒度地控制失败条件,可以在 Stripe Dashboard 的 Radar 规则中创建测试规则。例如可以基于邮箱、金额、卡号前缀等条件,将交易指定为 insufficient_funds 或 expired_card。对于复杂业务,这比依赖固定测试卡更有弹性。不过要注意,Radar 规则只影响测试模式,上线前需要确认生产环境不会出现相同的拦截条件,否则正常交易也可能被误伤。
五、一份失败测试检查清单
最后汇总排查测试卡问题的顺序。先打开 Stripe Dashboard 确认当前处于测试模式,再检查前端的 pk_test 与服务端的 sk_test。然后确认使用的是 PaymentIntent 而不是 SetupIntent,并且确认动作已经真正发生。接着在服务端 catch 中打印 err.code、err.decline_code 和 err.payment_intent.status,与测试卡预期值逐一比对。
如果你看到 card_declined 但 decline_code 不是预期值,可能是卡号输入错误,或者这张卡被 Stripe 更新过规则。如果页面上没有报错但服务端却返回了 402,那说明前端没有正确消费服务端返回的错误。如果一切正确但仍然没有失败,先换一张最基础的 4000000000000002 重新测试,因为它不需要任何附加条件,只要确认扣款就会返回 generic_decline。
稳定触发测试卡失败的关键不是卡号本身,而是把代码路径走到确认支付那一步。只有在这一步拿到同步异常或 webhook 事件,才能确认你的失败处理链路真正工作。把上面的流程补进本地测试和 CI 后,后续再遇到测试卡不生效的问题,就能快速定位是环境、密钥、流程阶段还是错误消费逻辑出了问题。
Stripe 测试卡支付失败测试PaymentIntents修改时间:2026-09-23 13:41:27