简介:JSON格式银行卡BIN码大全是一份面向Web与移动端开发者的常用银行卡信息匹配工具。它利用银行卡前六位数字即银行识别码的唯一标识特性,把发卡机构、卡种和货币等关键字段组织成结构清晰的JSON数据集。开发者在用户输入卡号时,可先截取前六位并在此数据中快速匹配,进而即时展示发卡银行、卡片类型等信息,也能用于支付前的卡片有效性校验。资源压缩包为RAR格式,内部仅含1个JSON文件,整体大小约33KB;数据以键值对形式存放,便于直接集成到前端项目或作为离线对照表,也方便后续自行更新维护。目前已有1742人学习或下载,适合需要快速实现银行卡识别、支付表单优化、聚合支付场景或相关演示项目的开发者,可省去从零收集与清洗BIN码数据的时间,提升功能交付效率。 做支付相关的开发久了,你会发现一个特别不起眼却又绕不开的东西:银行卡BIN码。拿一串卡号判断发卡行、卡组织、卡种,靠的就是这块数据;而把它整理成JSON格式,前后端都能直接拿去匹配及验证,省掉了无数张Excel表来回传的麻烦。这篇文章就来聊聊JSON格式的银行卡BIN码大全从整理到落地这件事,适合做支付、电商、账务系统或前端收银台的同学参考。我会把数据来源、字段设计、匹配代码、性能优化和真实踩坑都过一遍,尽量让你看完就能直接用起来。顺便说一句,这里面很多坑是我自己上线之后才撞见的,网上那些示例代码不会告诉你,我尽量都摊开讲。
1. 一个卡号引发的“查表”需求:BIN码与JSON格式的组合价值
1.1 BIN码的底层逻辑:卡号前6位意味着什么
银行卡号不是一串随机的数字,它遵循ISO/IEC 7812标准。前6位是发卡行标识码,也就是大家常说的BIN(Bank Identification Number);第7位到倒数第2位是个人账户标识,最后一位是校验位。前6位一旦确定,就能定位到具体的发卡机构,比如622848开头基本指向中国农业银行,622845开头也是农行的另一个产品段,而4开头多数属于Visa、5开头多数属于Mastercard、62开头是银联。这些规律都是BIN表的核心内容,也是后续匹配验证的数据基础。
为什么要关注BIN?因为大量业务逻辑都建立在它上面。前端收银台在用户输入卡号的瞬间要展示卡组织图标,后端在交易路由时要判断这张卡是借记卡还是贷记卡、能不能走某个支付通道,风控在绑卡环节要根据发卡行信息做初步判断。没有一份能直接查的BIN数据,这些需求都只能靠写死if-else或人工查表完成,维护成本高得吓人。我见过不少团队把卡组织判断散落在各个服务里,最后数据对不上,排查起来非常痛苦。
1.2 为什么是JSON而不是表格或CSV
一开始我也见过很多人用Excel管理BIN表,后来换成CSV,再后来才统一改成JSON。为什么是JSON而不是表格或CSV?三个原因:第一,JSON是前后端天然通用的格式,前端拉一个静态文件就能用,后端也能直接解析进内存;第二,BIN信息是典型的键值结构,用JSON表达非常直观,一条记录对应一个BIN前缀;第三,JSON没有CSV那种转义和编码困扰,中文银行名、带逗号的机构名都能干净地存放。对于大多数业务系统来说,JSON的简单和可审计性比极致性能更重要。
需要强调的是,JSON并不是性能最优的序列化格式,如果单条数据几十万、QPS又高,Protocol Buffers或MessagePack确实更快,但换来的复杂度不值得。一份整理干净的JSON BIN库,可以直接沉淀成npm包、Go包或接口数据,团队内复用成本极低;如果要改字段,在JSON里加一个属性就行,老系统不感知。尤其是配合Git管理,数据变更记录全在提交历史里,比Excel的“最终版v3.8”不知道靠谱多少倍。
还有一点很多人没意识到:JSON格式的数据特别好做工程化校验。写个脚本读取文件,先用JSON.parse确认没有语法错误,再对bin字段做去重、排序,最后抽查几十条记录和官方资料比对。这些检查全可以在发布前跑完,比在生产环境里发现查不到结果要舒服太多。我用这套流程之后,数据更新基本不用人工review,脚本过了就发,省下大量时间。
2. 动手前先想清楚:数据来源、字段设计与合规边界
2.1 可用的公开数据源与使用边界
想要一份能上生产的BIN库,第一件事不是写代码,而是搞清楚数据从哪来。常见的来源包括binlist-data、payine/BIN-LIST这类开源仓库,Visa、Mastercard、银联等卡组织的公开文档,以及银行官网披露的银行卡产品信息。开源仓库通常已经整理成JSON或CSV,字段相对齐全,但覆盖范围和更新频率参差不齐。卡组织文档准确度高,但通常需要申请或只提供部分摘录,银行官网的信息分散,适合做人工核实。
这里必须提醒一句合规问题。BIN码是发卡机构公开分配的信息,用于识别发卡行和卡种完全没问题,但不要拿着它去做伪造卡号、批量碰撞或任何越界的事情。如果是把整理后的数据集商用或公开发布,先看数据源的License:很多开源项目是CC-BY或Open Data许可,要求保留署名;有些来源的数据是抓取来的,授权不明,这种情况宁可不用,也不要给自己埋雷。产品上线后数据合规问题比技术问题难处理得多。
2.2 JSON结构设计:字段取舍和两种存法
拿到原始数据后别急着塞进项目,先设计字段。我常用的核心字段是这些:bin(6位或8位BIN)、bank(发卡行名称)、bank_code(银行代码)、card_type(借记卡/贷记卡)、card_brand(卡组织)、country(国家或地区代码)。另外还会留一个source字段记录数据来源,方便后面追溯和定期更新。字段不是越多越好,太细的卡等级、产品名通常变化快,维护成本高,反而容易出错。
JSON存法有两种。第一种是直接用BIN做key的对象,查询时可以直接取key,速度最快,但对象key顺序不可靠,且数据量大时JSON.parse一次性载入内存的占用比较夸张。对于只有几千条BIN的小库,这种结构很省事,适合做成静态配置文件丢给前端;但库一旦扩展到几十万条,维护起来就会有点别扭。下面这个片段就是典型的对象结构:
{ "622848": { "bank": "中国农业银行", "bank_code": "ABC", "card_type": "借记卡", "card_brand": "UnionPay", "country": "CN" } }第二种是数组,每条记录一个对象,天然支持排序、去重、合并,也方便后面做二分查找和流式处理。我在最终项目里选了数组,并在构建时按bin字段排好序。两种结构没有绝对优劣,如果只做简单查询,对象更好;如果要持续更新和维护,数组的优势明显得多。如果数据量继续膨胀,还可以改成NDJSON(JSON Lines),每行一条记录,支持按行读取,不用一次性载入内存,这个方案我在处理超过20万条BIN数据时实际用过,效果不错。
3. 匹配与验证的代码实操:从最简到可用
3.1 “匹配”的第一步:前6位前缀查询
匹配的核心逻辑其实很简单:输入完整卡号,清洗掉空格和横线,截取前6位,然后去BIN库查询。最直接的实现是把数组转成Map,用BIN作为key,这样查询时间复杂度是O(1),代码也只有几行。下面这个函数接受卡号字符串,返回BIN记录或null,调用方只需要判断一下结果是否存在即可,不需要关心内部是用数组还是Map:
const binMap = new Map(binArray.map(item => [item.bin, item])); function getBinInfo(cardNumber) { const clean = String(cardNumber).replace(/\D/g, ''); return binMap.get(clean.slice(0, 6)) || null; }这段代码已经能应对绝大多数场景。用Map而不是普通对象做映射,主要是为了避免原型链污染和__proto__这类特殊key带来的潜在问题;同时Map对key的语义更清晰,6位字符串就是6位字符串,不会出现自动转换。如果只是写一次性脚本,用Array.find也能跑通,但数据量超过几万条后,线性查找的劣势会越来越明显。我建议一开始就把查询函数封装好,后面换成二分或Trie时只需要改内部实现,外部调用方完全无感。查询函数返回null时,调用方要做降级处理,不要直接抛异常,因为真实卡号列表里出现未知BIN是常态。
3.2 “验证”并不等于Luhn校验:两层逻辑要分开
说完匹配,再单独聊验证。很多人把BIN匹配和卡号校验混在一起,其实它们是两件事。卡号合法性通常用Luhn算法,它是个简单的模10算法:从右往左,偶数位数字乘以2,乘完大于9就减9,全部累加,最后取模10,结果为0说明卡号在格式上是合法的。
function luhnCheck(cardNumber) { const digits = String(cardNumber).replace(/\D/g, '').split('').reverse().map(Number); const sum = digits.reduce((acc, d, idx) => { if (idx % 2 === 1) { const doubled = d * 2; return acc + (doubled > 9 ? doubled - 9 : doubled); } return acc + d; }, 0); return sum % 10 === 0; }这段代码返回true,只能说明卡号没有计算错误,不能证明卡真实存在,更不能代表卡里有余额。BIN匹配负责的是另一个问题:这个卡号前缀对应的发卡行信息是什么。正确的验证顺序应该是:先清洗卡号,再检查长度(常见13到19位),再做Luhn校验,最后查BIN。如果Luhn都没过,就别浪费一次BIN查询了。还有一个边界问题:部分新卡BIN已经是8位,如果库里同时存在6位和8位BIN,查询策略要先试8位,再退到6位,否则8位卡会被错误归到另一个6位BIN上。这个细节在下面踩坑部分还会展开。
4. 数据量上来之后的性能优化与命中率问题
4.1 别再遍历了:排序二分与Trie树的选型
当BIN数量到几十万条、查询QPS又高时,Map虽然快,但内存和GC压力会变大;find这类线性查找则完全不可取。第一个优化是把数组按bin排序,然后做二分查找:
function binarySearchByBin(sortedArray, bin) { let low = 0; let high = sortedArray.length - 1; while (low <= high) { const mid = (low + high) >> 1; const midBin = sortedArray[mid].bin; if (midBin === bin) return sortedArray[mid]; if (midBin < bin) low = mid + 1; else high = mid - 1; } return null; }这里有个容易踩的细节:bin字段如果存成字符串,排序和比较必须都按字符串来,不能一会儿用数字、一会儿用字符串,否则相邻大小关系会错乱。二分查找的时间复杂度是O(log n),几十万条数据下一次查询也就十几次比较,完全够用。
比二分更贴合BIN场景的是前缀树,也就是Trie。BIN查询本质是定长前缀匹配,用Trie可以把查询时间压缩到与卡号长度成正比,和库总大小无关。构建时把每个BIN的6个字符依次挂到树上,叶子节点存记录:
class TrieNode { constructor() { this.next = new Map(); this.info = null; } } function buildBinTrie(binArray) { const root = new TrieNode(); for (const item of binArray) { let node = root; for (const ch of item.bin) { if (!node.next.has(ch)) node.next.set(ch, new TrieNode()); node = node.next.get(ch); } node.info = item; } return root; } function queryBin(root, cardNumber) { let node = root; const bin = String(cardNumber).replace(/\D/g, '').slice(0, 6); for (const ch of bin) { if (!node.next.has(ch)) return null; node = node.next.get(ch); } return node.info; }Trie的缺点是多存储了一些Map结构,内存占用比数组大,所以在内存充足、查询极其频繁的服务里更划算。如果既要快又要省内存,可以退而求其次,只对前2位或前3位建索引,再在分组内做线性查找,这种折中方案在中等数据量下表现也不错。
4.2 命中率上不去,多半是数据覆盖问题
性能解决之后,下一个更重要的问题是命中率。如果你发现大量卡号在库中查不到,多半不是代码问题,而是数据覆盖不够。中国银联实际分配的BIN非常多,开源数据源往往只覆盖常见银行,城商行、农商行、外资银行的记录特别容易缺失。这时候先别急着怪数据源,先把未命中的BIN收集起来,看看是哪一类缺失,再针对性补数据。
针对查不到的情况,比较稳妥的做法是分层降级:先精确查6位,查不到就查5位,再不行查4位,但结果里必须加一个confidence字段表示置信度,避免业务方把模糊匹配结果当成确定信息。我在实际项目里会把“未知BIN”单独记录下来,每个星期导出一次,人工核对后补进库。跑了两个月之后,命中率从85%慢慢提到97%,这个过程比一开始找一份“完美大全”靠谱得多。
5. 我在真实项目中踩过的坑与这套BIN库的扩展玩法
5.1 匹配顺序和“脏数据”能坑到什么程度
这套库看起来简单,真正上线后我踩过的坑一个都不少。第一个坑是匹配顺序。不同来源的BIN数据长度并不一样,有的库全收6位,有的还混着4位、5位的旧BIN。如果直接把短BIN先放进查找表,长BIN的卡会在降级搜索时被提前匹配到错误信息。正确做法是永远优先精确匹配最长BIN,比如先试8位,再试6位、5位、4位,而不是先到先得。这个顺序写错,轻则展示错银行名,重则交易路由走错通道。
第二个坑是银行合并和历史改名。比如某些银行经过重组后名字变了,但旧BIN还在市场上流通。如果直接把记录改成新银行名,老用户历史账单里的卡号就会对不上。我在数据文件里保留了old_name和new_name两个字段,并在更新脚本里改成“新增版本”而不是“覆盖旧记录”,这样每次调整都能追溯,不会被自己改坏。类似的还有同一BIN在不同数据源对应银行不一致的情况,处理策略是以卡组织官方数据为最高优先级,开源数据做补充,冲突时记录下来而不是静默覆盖。
5.2 扩展玩法:卡组织识别、测试卡号生成与风控辅助
如果觉得BIN库只能做匹配和验证,那就有点浪费了。前端可以根据卡号前缀实时判断Visa、Mastercard、银联等卡组织,在用户还没输完卡号时就展示对应图标,支付表单的体验会明显提升。测试同学也可以基于BIN前缀加Luhn算法一键生成合法格式的测试卡号,不用手工编造,联调和自动化用例都方便很多。下面这个函数就是我从这套BIN库派生出来的小工具,给定一个BIN前缀和卡号长度,就能生成一张通过Luhn校验的测试卡,注意它只能用于测试环境:
function generateTestCard(bin, length = 16) { let card = bin.padEnd(length - 1, '0'); for (let i = card.length; i < length - 1; i++) { card += Math.floor(Math.random() * 10); } const digits = (card + '0').split('').reverse().map(Number); const sum = digits.reduce((acc, d, idx) => { if (idx % 2 === 1) { const doubled = d * 2; return acc + (doubled > 9 ? doubled - 9 : doubled); } return acc + d; }, 0); const check = (10 - (sum % 10)) % 10; return card + check; }需要注意,测试卡号只能在测试环境用,不要拿它去真实支付通道试。生成的时候也要避免使用真实用户的完整卡号作为种子,否则即使经过脱敏也可能带来不必要的麻烦。风控场景也能用到BIN,比如同一BIN在短时间内关联大量账号绑卡,或者某个BIN段的交易集中在深夜小金额试探,都可以作为初筛规则。但BIN只能作为辅助信号,不能拿它替代真正的风控模型,否则误杀率会很吓人。把BIN库做成独立依赖包或独立服务,数据更新只换数据文件,业务代码完全不用动,是我在项目里最推荐的落地方式。每个季度跑一次去重、排序、Luhn校验和冲突检查,确认没问题再发布,配置类数据跟业务代码解耦后,下一个接手的人也不会因为一次数据更新被迫重新上线整个服务。
本文还有配套的精品资源,点击获取