跳到主要内容

结账表单的卡种识别怎么测全?六大卡组织测试卡号实战

Visa、Mastercard、Amex、Discover、JCB、Diners 的前缀和位数各不相同,卡种识别的分支很容易漏测。这篇文章用 Luhn 有效的测试卡号把六个分支一次跑完,并给出 Mastercard 2 系列 BIN 等三个最常漏掉的坑。

发布于 作者 李雷
#developer #payment #qa #testing

结账表单的卡种识别怎么测全?六大卡组织测试卡号实战

很多结账页都有这个交互:用户敲入卡号的前几位,输入框右侧实时亮起 Visa 或 Mastercard 的图标,CVV 字段的占位符跟着从 3 位变 4 位。这段逻辑写起来不难(按 IIN 前缀做个分支判断),但要测全却出乎意料地麻烦:六大卡组织的前缀区间加起来有十几个,位数还分 14、15、16 三种。手头没有覆盖所有前缀的号码,分支就只能靠代码评审"目测"。

这篇文章讲我自己测这类表单的流程:先搞清楚每个卡组织的结构规则,再用测试信用卡号生成器批量生成各前缀的 Luhn 有效号码,最后把三个最容易漏的坑单独过一遍。

六大卡组织的前缀和位数,一张表说清

卡种识别的全部依据是号码开头的发卡机构识别码(IIN)和总位数:

| 卡组织 | 前缀区间 | 位数 | |---|---|---| | Visa | 4 | 16 | | Mastercard | 51–55 以及 2221–2720 | 16 | | American Express | 34、37 | 15 | | Discover | 6011、65、644–649 | 16 | | JCB | 3528–3589 | 16 | | Diners Club | 36、38、300–305 | 14 |

注意 Mastercard 那行有两个区间。Mastercard 自 2017 年 6 月起正式启用 2 系列 BIN(2221–2720),并要求所有收单机构在该日期前完成支持(per Mastercard 2-Series BIN 公告)。也就是说,一个以 2345 开头的 16 位卡号是合法的 Mastercard,而我见过不止一个识别函数只写了 /^5[1-5]/,把整个 2 系列识别成"未知卡种"。这正是需要批量测试号码的原因:没有 2 开头的样本,这个分支永远不会被踩到。

校验位是怎么算出来的:一个手算例子

生成器产出的每个号码都通过 Luhn 校验。Luhn 算法(美国专利 2,950,048,1960 年授权)的规则是:从右往左,偶数位置的数字乘以 2,乘积满 10 则减 9,把所有数字加总,总和能被 10 整除即有效。它能检出全部单个数字的输入错误。

拿一个 Visa 号码的前 15 位走一遍:

输入4539 1488 0343 646?(校验位待定)

前 15 位按上述规则加总得 73,要让总和凑成 10 的倍数,校验位必须是 7。

输出:完整号码 4539 1488 0343 6467,Luhn 校验通过。

你可以把这个号码贴进 Luhn 校验器反向验证。这也是我推荐的工作流:生成器负责"造正例",校验器负责"确认你的实现和标准答案一致"。

实操:一次生成覆盖所有分支的测试集

在生成器里选"随机混合"、数量 50、勾选附带 CVV 和有效期,点生成,再导出 CSV。导出格式是每行一个号码加卡种、CVV、有效期,结构如下(号码每次随机,这里用几枚公开测试号示意列结构):

number,scheme,cvv,expiry
4539 1488 0343 6467,visa,914,12/28
5105 1051 0510 5100,mastercard,402,07/29
3782 822463 10005,amex,3754,03/30
3056 930902 5904,diners,188,09/28

拿到 CSV 后,我的断言清单是三条:

  1. 每个号码喂进表单后,亮起的图标和 CSV 第二列的卡种一致;
  2. Amex 行的 CVV 字段要求 4 位,其余要求 3 位;
  3. Diners 的 14 位和 Amex 的 15 位不被"必须 16 位"的长度校验误杀。

第三条值得展开。不少表单写死 pattern="\d{16}",Amex 用户永远填不进合法卡号:没有 15 位的测试样本,这个 bug 在内部测试阶段是隐形的。

我踩过的坑:随机生成 50 个还是漏了 JCB

第一次做这类测试时,我以为"随机混合生成 50 个"就万事大吉,结果跑完发现 JCB 分支的断言一次都没执行:随机混合按六个卡种均匀抽样,但我的断言脚本里把 JCB 的图标类名拼错了,样本全被跳过,而测试框架对"零次执行的断言"默认静默通过。后来我改成每个卡种单独生成 10 个、分六组跑,并在每组结尾断言"本组至少执行了 10 次比较"。教训是:测试集的覆盖和断言的执行是两件事,前者靠生成器保证,后者得自己数。

另外提醒一句这类号码的边界:它们通过格式和 Luhn 校验、带正确前缀,但没有在任何支付网关的沙盒注册。要触发 Stripe 的 3DS 挑战或特定拒付码,必须用 Stripe 文档里的固定测试卡;这个生成器解决的是"我需要很多个互不相同、格式合法的号码"的场景:fuzz 表单、灌测试数据库、单测自己的校验函数。如果你的演示环境还需要配套的假姓名和地址,可以用模拟数据生成器一起生成,整套夹具里没有一个字节是真实持卡人数据。

收尾:把识别逻辑也单独抽出来测

表单层面测完后,建议把识别函数本身也做成纯函数单测:正例用生成器按卡种各生成 20 个粘进测试文件,反例把其中几个改掉一位数字。想对照一个现成实现的话,卡种识别器就是按上面那张前缀表写的,把同一批号码贴进去比对结果即可。

整条流程都在浏览器本地完成:生成器用 crypto.getRandomValues 填随机位、本地算 Luhn 校验位,不发网络请求,号码也不会写进分享链接。


Made by Toolora · Updated 2026-06-11