结账页面上填完卡号和有效期,通常还剩一个贴着安全码标签的小输入框。它就是本文要讲的东西:一串刻意印在卡片上、看起来平平无奇的短数字。CVV 是什么?一句话回答,它是用来证明「卡确实在你手上」的凭据。它不参与卡号的编号规则,也无法由卡号推导出来——这两点正是它存在的全部理由。
理解它的价值在于分清两类数据的不同命运:卡号需要被妥善保存以便后续扣款,安全码则被明确要求用完即弃。把这两者混为一谈,是很多团队在支付合规上踩的第一个坑。
名字有好几个,说的是同一个东西
安全码在不同资料里有不同叫法,看到它们时不必困惑:
- 卡验证值,英文缩写常写作 CVV,这是最通用的叫法。
- 卡验证码,缩写 CVC,用在一些卡组织的文档里。
- 卡识别码,缩写 CID,美国运通习惯用这个称呼。
三个缩写的技术含义基本一致:一串由发卡行在制卡时生成、印在卡面上、不经过任何编号公式的短数字。有些资料还会区分「磁条里那一版」和「卡面上那一版」,前者由发卡行在授权时核验,后者由持卡人在下单时填写。本文讲的是后者,也就是你需要问用户要的那几位。
它印在卡面哪个位置?
位置和位数由卡组织决定,两者常常是对应的:
| 卡组织 | 位数 | 位置 |
|---|---|---|
| 多数卡组织 | 3 位 | 签名条右端 |
| 美国运通 | 4 位 | 卡面正面的卡号上方 |
| 部分借记卡 | 3 位 | 签名条右端 |
一个常见的界面问题由此而来:表单默认只给三格,运通卡的用户就不知道该往哪里填第四位。较稳妥的做法是先根据号码首位判断卡组织,再决定输入框是三格还是四格。不要用「多留一格也无所谓」的方式敷衍——多出来的空位会让用户以为还有一位没填。
值得注意的是,这几位数字通常不与卡号连在一起印刷,而是单独出现在签名条附近或卡面正面的角落。制卡时它就被固定下来,之后不会变化,与一次一次生成的一次性验证码是两回事。有些银行的应用会为线上交易生成临时的验证码,那类数字由应用动态产生、有有效期,与印在卡片上的安全码完全不同,不要混为一谈。
反过来,如果用户填了四位而你只接受三位,应当在提交前就提示,而不是把它当成校验失败。
关于位置还有一个实际影响:要求用户翻看卡片去读签名条上的数字,本身就是一道体验门槛。有相当一部分人分不清卡号后四位与安全码后四位,尤其是当两者恰好有几位相同时。比较稳妥的界面做法是在输入框旁放一句简短说明,指出它在卡片上的位置,而不是只写一个缩写。位数与卡组织对应关系的完整整理见各卡组织的测试号段。
顺带说明一个常被问到的细节:有些借记卡也有安全码,位置与位数通常与同一卡组织的信用产品一致。因此按卡组织判断输入框宽度是可行的,不需要再按卡片类型细分。
为什么授权之后不允许保存
这条规则是硬性的。支付行业的安全标准把安全码列为敏感认证数据,明确要求在授权完成之后不得保存,即使是加密保存也不行。原因不难理解:安全码的唯一用途就是证明卡在手上,一旦它和卡号一起被长期存在数据库里,泄露之后攻击者就同时拿到了「账户标识」和「在场证明」,这道额外的验证步骤就失效了。
对比一下就很清楚。卡号在业务上必须保存,否则你无法对同一张卡发起后续扣款;安全码在业务上完全不需要保存,因为每次授权都可以重新向用户索取。一个必须留,一个必须扔,规则正是按这个逻辑划出来的。
所以当有人问「能不能把安全码存下来方便下次扣款」,正确的回答不是「技术上可以但要注意加密」,而是「这条路径本身就不该存在」。
这几位数字能被算出来吗?
不能。校验位可以算,因为它本来就是为了被算出来而设计的;安全码不行,它在制卡时由发卡行生成后直接印在卡上,任何公式都推不出来。
这个区别对测试很重要。你可以自己合成一个格式合法的卡号,因为结构规则是公开的;但一个「正确的安全码」只存在于真实卡片上,测试环境里出现的任何安全码都只是占位值。
因此,测试时随便填三位数字是完全合理的做法,服务商的测试环境本来就不校验它的真伪。但请务必记住:这只在测试环境成立。真实环境里,安全码填错会直接导致授权被拒。
在本站生成卡号时怎么处理这一栏
用信用卡号生成器生成号码时,结果里会附带一栏安全码。它的作用是让整张表单一填写完整,方便你复制进联调页面或测试脚本,而不是某个账户的真实凭据。
需要注意的是位数:选择美国运通号段时,生成的三位数字与实际规则不符。真实场景下运通卡的安全码是四位,如果你的表单按卡组织切换输入框宽度,测试时应当确认这条分支能走到。更完整的差异整理见各卡组织的测试号段。
所有生成内容都是合成数据:结构上合法,但从未发行给任何人,也无法用于任何真实交易。
给开发者:不落库怎么落实
「不保存」是一条很容易在代码里被无意破坏的规则,需要落到具体动作上:
- 不进数据库:安全码字段不应出现在任何持久化模型里。哪怕只是「临时存一下方便排查」,也会让这张表进入合规审计范围。
- 不进日志:请求体整体打日志是常见的排查手段,但它会把安全码一并写进日志文件,而这些文件往往比数据库更难管控。建议在日志中间件里对这类字段做固定替换。
- 不进错误上报:异常堆栈里常常带着完整的请求参数。上线前确认上报工具做了字段过滤。
- 不进缓存与队列:为了重试而把整个支付请求塞进消息队列,同样等于长期保存。
- 不留测试快照:用真实请求做过一次测试之后,那些抓包记录和快照文件里就会有敏感认证数据。开发与测试环境本来就不应该使用真实的持卡人数据,相关要求见PCI DSS 与测试数据。
另一个容易忽略的点是表单本身:如果页面把用户输入的安全码存在浏览器本地存储或自动填充里,那就等于把保管责任交给了用户的设备。这类方便功能在支付场景下通常不值得保留。
还有一条与合规无关但值得权衡的事:既然安全码每次都要重新索取,就不要把它设计成「记住我」的选项。用户不会因为多打三位数字而放弃付款,却可能因为你把它的保管方式改成了长期留存而付出代价。区分哪些字段应当方便、哪些字段应当克制,是支付表单设计里最实际的一课。需要保留的字段应当加密并掩码展示,需要丢弃的字段则连日志都不该进入,两者的处理方式没有任何可以互相借鉴的地方。
下一步
把安全码当成一次性凭据来对待,是这篇文章唯一希望留下的印象。设计表单时,先确认你的字段不会以任何形式留下来,再去考虑体验细节。
需要一批完整的测试表单数据,用信用卡号生成器生成卡号、有效期与安全码;要理清它与卡号、有效期三者的分工,接着读信用卡号格式;想知道上线前该测哪些分支,看支付表单测试清单。