QPBOC非接触交易POS与卡片APDU交互全流程解析
发布时间:2026/11/3 14:23:11 锦皓数字建站

拨通一次非接触交易只要零点几秒可在这几百毫秒里POS 与卡片之间来回传递的 APDU 报文少则七八条、多则十几条中间任何一个字段填错、任何一个步骤顺序颠倒都会以请重新挥卡或者干脆一张红叉收场。这篇内容围绕QPBOC 交易流程中 POS 与卡片的数据交互展开把一次挥卡从射频场建立、PPSE 目录发现、应用选择、GPO、读记录、终端风险管理、脱机认证一直到 GENERATE AC 出结果的完整链路拆开逐条链路给出报文样例、字段含义、判断依据以及我在联调现场反复踩到的坑。适合正在做非接触终端开发、支付应用移植、或者需要排查同一张卡在 A 机器能过、在 B 机器过不去这类问题的从业者参考也适合刚接触这套规范、看不懂 APDU 流水的新人从头捋一遍。1. 从挥卡到出票QPBOC这条链路上到底跑了多少次交互1.1 一次嘀卡背后被压缩的六七个回合很多人对非接触交易的直觉是卡片贴上去终端读一下卡号就完事了。实际完全不是这么回事。终端上真正的流程是先建立射频场给卡片供电卡片上电后回一个类似接触式 ATR 的应答终端随后用 PPSE2PAY.SYS.DDF01去问卡片你支持哪些应用拿到应用列表后选一个 AID选完应用发 GPO 命令卡片这时会返回 AIP 和 AFL等于告诉终端我支持哪些能力、你要去哪儿读哪些记录终端按 AFL 去 READ RECORD 把 PAN、有效期、CDOL、CVM 列表这些数据元捞出来接着在本地跑一遍终端风险管理和脱机数据认证最后才发 GENERATE AC让卡片用交易数据算一个密文回来。把这套流程压缩进几百毫秒靠的是两个前提一是非接触卡片不需要触点复位那种物理握手射频场一建立就能通信二是 qPBOC 对经典的借贷记流程做了裁剪把一些可以放到联机侧判断的东西挪出去了。但裁剪不等于可以随便跳步规范里哪些命令必须发、哪些顺序不能换卡片的内部状态机是认死理的。你少发一条 GPO 直接去 READ RECORD绝大多数卡会甩你一个6985因为它的当前文件还没切过去。我见过最典型的新手写法是拿到 AID 之后直接发 READ RECORD 读00 B2 01 0C 00然后一脸疑惑地问为什么返回 6A82。原因很简单READ RECORD 读的是当前已选应用下的文件应用都没选SFI 无从谈起。所以这条链路上的每一步都不是可选项而是状态机的必要转移。1.2 qPBOC与标准借贷记流程的取舍差异这里必须把 qPBOC 和接触式那套经典流程的区别讲清楚因为很多人是拿着接触式的代码改非接触改完发现行为对不上。两者在命令层面看起来很像都是 SELECT、GPO、READ RECORD、GENERATE AC但语义上有几处关键差别。第一处是目录发现方式。接触式交易通常是终端枚举自己的 AID 列表逐个 SELECT 试过去试到哪个能用就用哪个。非接触下因为时间紧张主流做法是先 SELECT PPSE 拿到卡片自报的应用列表和优先级再按优先级选一个这一来一回省掉了大量无效试探。第二处是终端交易属性。qPBOC 里有一个 9F66描述终端对这套快速流程的支持能力和小额脱机策略接触式流程里没有这个数据元。它的存在意味着卡片会根据终端是个什么样的终端来调整自己的行为比如是否允许在脱机状态下直接批准小额交易。写代码时如果把 9F66 的长度搞错了整个 GPO 就会直接崩。第三处是流程刚性。接触式交易里终端有比较大的自由裁量空间可以先读记录再补 GPO 之外的数据非接触这边因为卡片供电时间有限规范对必须在多长时间内完成哪一步有要求终端如果把 AFL 里所有记录不分青红皂白全读一遍很容易在最后一步超时卡片掉电GENERATE AC 返回6985。对比项接触式经典流程qPBOC 快速流程应用发现终端枚举 AID 逐个尝试优先 SELECT PPSE 取应用列表终端属性无 9F66必须组包 9F66记录读取可较宽松地多读严格按 AFL尽量少读时间预算相对宽松从场建立到 GAC 完成有硬约束联机决策终端风控为主终端风控 小额脱机策略叠加理解这张表的意义在于你在非接触链路上遇到的失败很大一部分不是命令写错了而是时序和预算没管住。后面几节我会把每一步拆到报文级别方便你对照抓包定位。2. 上电与目录发现PPSE这一步做错后面全是白费2.1 射频场建立后卡片回的第一个ATR能看出什么非接触卡片进入射频场后会返回一个长度较短的应答形式上等价于接触式的 ATR。这个应答不是用来做协商的终端基本上不需要从中解析出什么业务信息但它在排错时非常有用。如果终端连这个应答都拿不到问题一定在射频层不用往下查 APDU 了。我在现场排查时习惯先分三层看第一层终端能不能检测到卡片存在也就是场内的负载调制有没有被识别第二层能不能收到完整的上电应答第三层才是 APDU 交互。很多偶发失败其实是第一层的问题比如终端天线匹配没调好卡片稍微偏一点就掉场表现出来却是交易超时。这时候你去改 APDU 代码是南辕北辙。上电应答拿到之后终端要做的第一件事是确定后续所有命令的传输参数。非接触这边通常使用默认的块传输参数不需要像接触式那样做 PPS 协商这也是一处简化。但要注意有些卡片在第一次交互时返回的应答偏短终端如果按固定长度去读会读到超时正确做法是按协议层给出的长度字段来收收够就停。顺带说一个容易忽略的点上电应答拿到之后立刻发 SELECT PPSE中间不要插入任何自定义的命令。我见过有人为了探测卡片类型先发一条私有的探测指令结果卡片状态被搞乱后续 PPSE 直接返回6A82。非接触卡片的状态机比接触式更脆非标准命令能不发就不发。2.2 SELECT PPSE的报文拆解与常见失败PPSE 的名字是2PAY.SYS.DDF01转成十六进制是32 50 41 59 2E 53 59 53 2E 44 44 46 30 31共 14 字节所以 Lc 是0x0E。完整命令00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00拆开看00是 CLAA4是 SELECT04表示按名称选择00是 P100是 P2首次出现返回 FCI0E是名称长度后面是名称最后00是 Le表示把你能给的都给我。这里 Le 给00是有讲究的非接触下用00让卡片自己决定返回长度比算一个精确值更稳因为 FCI 的长度随卡片不同差异很大。成功时返回的是一个 FCI 模板大致长这样6F 2A 84 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 A5 18 BF0C 15 61 0B 4F 07 A0 00 00 00 03 10 10 50 0A ...应用标签 61 06 4F 04 A0 00 00 00 0384是专用文件名称就是刚才选的 PPSE 名A5是 FCI 专有模板BF0C是 FCI 发行者自定义数据里面每一个61是一个目录条目包含4FADF 名称也就是 AID、50应用标签给用户看的名字、87应用优先级指示值越小优先级越高。失败的情况我遇到过三类。第一类返回6A82含义是文件未找到通常意味着这张卡根本不支持 PPSE 目录方式属于老卡或者私有卡这时候要退回终端枚举 AID 的老路子。第二类返回6D00指令不支持那基本是命令字节写错了比如把A4写成了别的。第三类是没有响应直接超时多数是卡片在命令发出前就已经移开或者掉场了。解析这一段的关键是不要假设只有一个目录条目。有些双应用卡片会同时返回借贷记两个条目终端必须按87的优先级排序后再选。我见过实现里直接取第一个4F的在某些卡上永远只跑借记贷记金额超限时才会暴露问题排查起来非常绕。3. 应用选择与GPOAIP和AFL把交易路线定死了3.1 SELECT AID之后先看清FCI里给了什么选定 AID 之后发 SELECT00 A4 04 00 07 A0 00 00 00 03 10 10 00返回的 FCI 里最重要的不是应用名字而是9F38——PDOL。这是卡片告诉终端你发 GPO 的时候请按这个列表把数据填给我。PDOL 的格式是标签加长度的三元组序列注意标签可能是两字节也可能是三字节长度是一字节描述的是终端要提供的每个数据元占多少字节。比如一段典型的 PDOL9F 66 04 终端交易属性4 字节 9F 02 06 授权金额6 字节 9F 03 06 其他金额6 字节 9F 1A 02 终端国家代码2 字节 9F 35 01 终端类型1 字节 9F 33 03 终端能力3 字节 9F 40 05 附加终端能力5 字节 9F 09 02 应用版本号2 字节 9F 1B 04 终端最低限额4 字节 9F 34 03 偏置随机选择3 字节这里第一个坑就来了9F66 必须是 4 字节。qPBOC 里 9F66 是终端交易属性四个字节某些厂商的内部文档写成了两字节照着抄的代码就会在拼 PDOL 数据时整体错位。错位的后果是卡片收到的数据长度和声明不符直接返回6700长度错误或者6F00。解析 PDOL 的正确姿势是写一个通用的三元组遍历器按标签实际长度推进指针而不是硬编码标签顺序。因为不同卡片的 PDOL 内容、顺序、甚至数据元集合都可能不一样硬编码在测试卡上能跑换一张卡就废。3.2 PDOL拼装规则与GPO响应解析拼装 PDOL 数据的核心原则是严格按 PDOL 声明的顺序、用 PDOL 声明的长度一个字节都不能多也不能少。对于终端不认识的数据元按长度填全00不能跳过去跳过去后面的数据全部错位。几个字段的填写要点值得单独说9F02 授权金额是 6 字节压缩 BCD单位是分。100 块钱要填成00 00 00 01 00 00。这里最常错的是把金额当成 4 字节或者忘了补前导零导致金额被解读成天文数字终端风控直接拒绝。9F03 其他金额同理没有现金返还就全填00。9F66 的取值取决于终端的业务策略。如果这台终端支持 qPBOC 且允许小额脱机就按要求把对应位置起来如果不支持小额脱机也要按规范填一个合法值不能全00全00在某些卡上会被判为不认识这个终端。9F35 终端类型一字节常见取值区分 attended/unattended、商户/持卡人操作等具体取值按规范表查别自己编。9F33 终端能力三字节和 9F40 附加终端能力五字节描述终端支持哪些 CVM、是否有打印、是否支持某些特殊能力这几个字段填错会直接影响卡片决定的 CVM 路径。GPO 命令本身是80 AE 00 00 Lc PDOL数据 0080是专有 CLAAE是 GET PROCESSING OPTIONSP1P2 都是00。成功响应一般是一个77模板里面至少有两个关键数据元77 14 82 02 39 00 94 08 08 01 01 00 10 01 03 0182是 AIP两字节描述卡片的能力。第一字节的位大致对应最低位表示是否支持 CDA往上依次是设备端 CVM、终端风险管理、持卡人认证、DDA、SDA 等。我这里举例的0x39展开成二进制是0011 1001含义是支持 CDA、要求终端执行风险管理、支持持卡人认证、支持 DDA不支持 SDA。这个值直接决定了后面脱机数据认证走哪条路。94是 AFL四字节一组第一字节是短文件标识符 SFI第二字节是起始记录号第三字节是结束记录号第四字节是参与脱机数据认证的记录数。上例里08 01 01 00表示 SFI 8 的记录 1 到 1参与认证的记录数为 010 01 03 01表示 SFI 16 的记录 1 到 3其中参与认证的记录数是 1。AFL 的那个参与认证记录数是最容易被忽略的字段。它的含义是从起始记录开始往后的最后 N 条记录参与脱机数据认证具体方向按规范定义终端在做签名的时候要按这个规则把记录内容拼进签名数据里。拼错了签名一定验不过但交易可能仍然成功——如果终端没做签名校验或者把校验结果当成软失败处理这个错误会一直藏着直到某天风控策略收紧了才爆出来。4. 读记录TLV解析里最容易埋雷的一段4.1 AFL驱动的读取顺序与SFI换算READ RECORD 的命令格式是00 B2 记录号 SFI3 | 4 00P1 是记录号P2 的高 5 位是 SFI低 3 位固定100表示按记录号读。所以 SFI 8 对应 P2 是(83)|4 0x44SFI 16 对应0x84。命令长这样00 B2 01 44 00 读 SFI 8 的记录 1 00 B2 01 84 00 读 SFI 16 的记录 1 00 B2 02 84 00 读 SFI 16 的记录 2 00 B2 03 84 00 读 SFI 16 的记录 3这里有个细节AFL 里给的 SFI 是内部 SFI和 P2 里的编码不是一回事必须做左移三位的换算。写死0x0CSFI 1去读的代码在只返回 SFI 1 的卡上能跑换一张返回 SFI 8 的卡就6A82。另一个细节是读取顺序。规范建议按 AFL 给定的顺序读因为卡片内部可能对读取顺序有优化乱序读会拖慢速度。非接触场景下时间就是命我实测过按 AFL 顺序读和倒序读在慢卡上能差出几十毫秒而这几十毫秒有时候就决定了最后一次 GAC 能不能在超时前发出去。还有一个实操技巧不是所有 AFL 里的记录都必须读。AFL 的第四字节如果是 0说明这一组记录不参与脱机数据认证但通常仍然需要读——因为 PAN、有效期、CDOL 这些业务数据往往就在里面。真正可以省的是那些既不在参与认证范围内、又不含终端需要的数据元的记录。不过这种判断需要你对卡片数据布局有把握保守做法还是全读毕竟6A82比慢几十毫秒更难处理。4.2 70模板里的关键数据元与容错处理READ RECORD 返回的是一个70模板EMV 专有记录模板里面是一串 TLV。需要捞出来的核心数据元标签名称长度用途5A应用主账号 PAN变长交易主键联机报文必带5F24应用失效日期3有效期校验5F34PAN 序列号1区分同 PAN 不同卡8CCDOL1变长第一次 GAC 的数据元列表8DCDOL2变长第二次 GAC 的数据元列表5F28发卡行国家代码2路由参考9F07应用用途控制2境内/境外、是否允许某些交易8ECVM 列表变长持卡人认证规则9F0D发卡行行为代码默认值5风控默认动作9F0E发卡行行为代码拒绝5风控拒绝动作9F0F发卡行行为代码联机5风控联机动作解析这段的坑集中在 TLV 的标签长度判断上。标签的第一个字节如果低 5 位全是 1即0x1F说明标签还有后续字节要继续读否则就是单字节标签。比如5A是单字节低 5 位0x1A不全 15F24是两字节0x5F低 5 位全 19F07也是两字节。长度字段同理如果最高位是 1说明后面还有字节表示长度但卡片数据里一般不会用这么长的长度遇到就当异常处理。我见过最典型的解析 bug 是把5F24当成单字节标签5F加长度0x24然后往后读 36 字节整个报文全乱。写解析器的时候一定要用递归下降遇到构造位标签第一个字节的第 6 位为 1就往下展开一层比如70、77、A5、BF0C都是构造型其余是原始型。一段可以直接抄的 TLV 遍历逻辑Python 伪代码只示意核心判断def parse_tlv(data, pos0, endNone): end len(data) if end is None else end out {} while pos end: tag data[pos]; pos 1 if tag 0x1F 0x1F: # 多字节标签 while data[pos] 0x80: tag (tag 8) | data[pos]; pos 1 tag (tag 8) | data[pos]; pos 1 ln data[pos]; pos 1 if ln 0x80: # 长度超过 127 n ln 0x7F ln int.from_bytes(data[pos:posn], big); pos n body data[pos:posln]; pos ln if tag 0x20 ((tag.bit_length()-1)//8)*8: # 构造型展开 out[tag] parse_tlv(body) else: out[tag] body return out容错方面还有一条经验不要假设某条记录一定存在某个数据元。有的卡片把9F07放在 SFI 16 的记录 2有的放在记录 1还有的干脆不给。代码里对每个数据元都要有默认值策略缺9F07时按规范默认值处理缺8D时第二次 GAC 用 CDOL1 的数据缺 CVM 列表时按终端默认路径走。硬编码取值遇到野生卡必挂。5. 终端侧决策与脱机认证交易走到哪个岔路口5.1 终端风险管理参数如何把交易推向联机数据捞齐之后终端要在本地做一轮判断决定这笔交易是脱机批准、脱机拒绝还是转联机。这套判断的结果最终体现在两个数据元里TVR终端验证结果标签 955 字节和 TSI交易状态信息标签 9B3 字节。这两个东西会作为 GAC 的输入数据发给卡片卡片据此决定自己回什么。参与判断的终端侧参数主要有9F1B 终端最低限额4 字节单位分。金额超过这个值某些脱机策略就不适用了。这个值配错是现场最常见的问题之一配成 0 会导致所有交易都往联机走配得太大又会让大额交易脱机放行风控上不可接受。9F34 偏置随机选择3 字节控制终端在什么情况下做随机选择。它的作用是让同一张卡在同一个终端上不会永远走同一条路径避免被摸出规律。9F35 终端类型和 9F33 终端能力决定了卡片认为这台机器能不能做某些操作比如能不能输入 PIN、能不能打印、是不是有人值守。9F09 应用版本号必须和卡片 FCI 里返回的版本号兼容不兼容的话终端应该拒绝继续。这个字段很多实现是写死的遇到新版本卡片会出问题。判断的过程大致是先看金额是否超过终端最低限额超过就打上对应的 TVR 位再看卡片是否要求终端做风险管理AIP 里那一位置了然后检查有效期、检查 PAN 是否在黑名单如果有最后根据发卡行行为代码9F0D/9F0E/9F0F决定动作。整个过程的结果不是过或不过而是一组位标记最后交给卡片结合自己的策略综合判断。这里有个反直觉的点终端判断联机不代表卡片一定回 ARQC。终端只是把 TVR 和 TSI 作为输入卡片有最终裁量权。我遇到过 TVR 里联机位打上了卡片依然返回 TC 的情况原因是卡片自己的风控认为金额在它的脱机限额内且它的策略优先级高于终端。所以实现里不能写TVR 有联机位就直接跳过 GAC 判 CID必须老老实实读 9F27。5.2 SDA/DDA/CDA三种认证路径的实测差异脱机数据认证的目的是让终端相信这些数据确实是这张卡给的没被中间人篡改。三条路径的强度递增SDA静态数据认证用卡片里一张固定的公钥证书验证静态数据只能证明卡片是真的不能证明这次交易是这张卡现场做的。它对应 AIP 里 SDA 位。DDA动态数据认证让卡片用私钥对终端给的动态数据签名能证明卡片在场但签名覆盖的数据范围有限。它对应 AIP 里 DDA 位需要用到 DDOL。CDA复合动态数据认证把签名和 GENERATE AC 合并成一步签名覆盖了交易数据本身强度最高。它对应 AIP 里 CDA 位需要用到9F4B签名动态应用数据。实际实现里选择路径的逻辑是先看终端自己支持什么9F33 和 9F40 里对应的能力位再看卡片 AIP 支持什么取交集按 CDA DDA SDA 的优先级选。如果交集为空这笔交易就不能脱机做认证只能走联机或者拒绝。CDA 的验签是最容易出问题的一环。签名数据的拼装顺序是有严格规定的交易数据元按规范给定的顺序拼然后是 AIP、然后是被签名的动态数据9F4B再往下接。顺序错一个字节公钥验签就失败。而失败的表现为9F27里 CID 的最低位是 0或者干脆终端自己算出来验不过。这时候不要去怀疑卡片先把自己的拼装顺序打印出来逐字节对照规范。我在一个项目里卡了整整两天最后发现是9F37不可预测数的长度被写成了 2 字节而不是 4 字节。因为终端生成随机数的时候用了一个返回短值的函数日志里看不出来只有把拼装缓冲区 dump 出来数字节才看见。这个教训是验签失败先查长度再查顺序最后才怀疑密钥。6. GENERATE AC一次还是两次P1决定卡片干什么6.1 第一次GAC的组包与CID判读第一次 GENERATE AC 的命令是80 AE P1 00 Lc CDOL1数据 00P1 的取值决定了终端请求卡片做什么P1 值含义使用场景0x00请求 AAC拒绝终端已决定拒绝0x40请求 TC批准终端认为可脱机批准0x80请求 ARQC联机需要联机授权0xC0请求带 CDA 签名的 AC支持 CDA 时注意 P1 是请求卡片可以不同意。比如你请求 TC卡片判断风险高可能回 AAC。所以必须读响应里的9F27CID来确认真实结果。CDOL1 数据的拼装规则和 PDOL 一样按8C里声明的标签和长度顺序填。CDOL1 一般包含授权金额、其他金额、终端国家代码、TVR、交易日期、交易时间、交易类型、不可预测数、终端类型、终端能力等。这里数据元比 PDOL 多得多漏填一个后面全错位所以拼装函数一定要用同一套逻辑不能两处各写一遍。响应通常是77模板或80模板关键数据元77 1E 9F 27 01 80 9F 36 02 00 2A 9F 26 08 3A 5B 7C 8D 9E 0F 12 34 9F 10 07 06 01 0A 03 A0 00 00 9F 37 04 12 34 56 789F27是 CID0x80表示 ARQC9F36是应用交易计数器 ATC9F26是应用密文9F10是发卡行应用数据长度和内容由发卡行自定义里面可能包含 CVN 版本、卡片验证结果等9F37是不可预测数要看它是否和你发出去的一致不一致说明卡片没按规矩来。如果支持 CDA响应里还会有9F4B这是签名动态应用数据长度不定验签时要用。6.2 联机成功后第二次GAC的关键点拿到 ARQC 之后终端把联机报文发出去收到授权结果再用第二次 GENERATE AC 把结果告诉卡片80 AE P1 00 Lc CDOL2数据 00这里的 P1 一般用0x40TC表示联机批准用0x00AAC表示联机拒绝。CDOL2 数据来自记录里的8D如果卡片没给8D按规范用 CDOL1 的数据代替。第二次 GAC 的坑主要有三个。第一ATC 必须递增如果终端重发第二次 GAC 而卡片已经处理过一次卡片可能返回6985或者拒绝。第二联机报文里的授权码、响应码要正确填进 CDOL2 对应位置填错了卡片记录的交易结果是错的对账时会出现终端说成功、后台说失败。第三第二次 GAC 之后交易就算结束了卡片会锁定状态此时再发任何命令都是无意义的。还有一个容易忽略的流程如果第一次 GAC 返回的是 TC脱机批准就不需要第二次 GAC如果返回 AAC拒绝也不需要。只有 ARQC 才走联机。有些实现不看 CID 一律发两次在某些卡片上会触发异常。7. 抓包复现一次完整交易以及那些不按剧本走的响应7.1 成功交易的APDU流水还原把前面几节串起来一次完整的、走联机的 qPBOC 交易报文流水大致是这样# 1. 目录发现 - 00 A4 04 00 0E 32 50 41 59 2E 53 59 53 2E 44 44 46 30 31 00 - 6F 2A 84 0E ... A5 18 BF0C 15 61 0B 4F 07 ... 87 01 01 ... # 2. 选应用 - 00 A4 04 00 07 A0 00 00 00 03 10 10 00 - 6F 3C 84 07 ... A5 31 50 0A ... 87 01 01 9F 38 0C ... # 3. GPO - 80 AE 00 00 1B PDOL数据 00 - 77 10 82 02 39 00 94 08 08 01 01 00 10 01 03 01 # 4. 读记录 - 00 B2 01 44 00 - 70 32 5A 08 ... 5F 24 03 ... 5F 34 01 ... - 00 B2 01 84 00 - 70 45 8C 15 ... 8D 15 ... 5F 28 02 ... - 00 B2 02 84 00 - 70 3A 9F 07 02 ... 8E 0C ... # 5. 第一次 GAC请求联机 - 80 AE 80 00 2B CDOL1数据 00 - 77 1E 9F 27 01 80 9F 36 02 00 2A 9F 26 08 ... 9F 10 07 ... # 6. 联机授权终端与后台之间非 APDU # 7. 第二次 GAC联机批准 - 80 AE 40 00 0F CDOL2数据 00 - 77 0A 9F 27 01 40 9F 36 02 00 2B注意最后一步的响应里9F27是0x40表示卡片接受了联机批准并以 TC 结束。这时候终端可以打印小票了。抓包工具方面我一般用两种手段一种是终端侧的 APDU 日志把发送和接收的十六进制完整打出来带时间戳另一种是如果有条件用非接触嗅探设备抓空口数据两者对照着看。终端日志的优势是能看到每一步的耗时空口数据能看到更底层的重传情况。对于偶发失败时间戳比内容更重要——你要看的是哪一步耗时异常而不是哪一步返回了错误。7.2 常见错误码与对应排查链路返回码含义常见原因排查方向6A82文件未找到AID/PSE 写错、未选应用就读记录检查 SELECT 的 AID 和 PPSE 名称6A81功能不支持卡片不支持该操作、应用被锁换卡验证确认卡片状态6985使用条件不满足流程顺序错、卡片移开、超时查时序和每步耗时6700长度错误PDOL/CDOL 数据长度与声明不符逐字节核对 PDOL 顺序和长度6C xxLe 不正确给了精确 Le 但卡片返回更长改用 Le00 让卡片自主返回6D00指令不支持CLA/INS 写错核对命令字节6F00无具体诊断卡片内部异常、状态机混乱复位重来检查前面命令是否越界9000成功正常继续下一步排查顺序我总结成一句话先看时序再看状态最后看内容。时序上看每一步的耗时和各步之间有没有异常间隔状态上看是不是漏了哪条必要命令、有没有在不该发命令的时候发命令内容上才是核对 TLV 字段。这个顺序之所以有效是因为非接触交易里时序问题占的比例远高于内容问题而内容问题一旦出现往往是稳定复现的反而好查。8. 几个我在联调里反复踩的坑第一个坑是把接触式的代码直接搬到非接触。接触式那边 PPS 协商、ATR 解析、超时重试都有一套成熟逻辑非接触这边这些环节大部分被简化掉了直接搬过来会带进大量无用逻辑还会因为超时设置太长导致整体超时。我的做法是从零写一个精简的流程控制器只保留 SELECT/GPO/READ/GAC 四条主干每条命令的超时按实测值设不要用默认值。第二个坑是日志打得太少。早期我调试的时候只打命令名和返回码遇到6F00完全无从下手。后来改成把完整的发送和接收十六进制都打出来加上时间戳和步骤名排查效率提升了不止一个量级。日志量确实大但可以分级调试期全开量产时关掉或者只留错误级别。第三个坑是对 9F66 和 9F34 的理解偏差。我一开始以为 9F66 只是个能力声明填什么都行结果在某张卡上所有交易都往联机走。后来才发现卡片会根据 9F66 里的策略位决定要不要脱机批准。9F34 也是它影响随机选择的结果配错会导致同一张卡在同一台机器上行为不一致测试时以为是偶发 bug其实是配置问题。第四个坑是卡片移开导致的假失败。用户在 GAC 之前把卡拿走了终端拿到6985日志看起来像交易被拒绝。这种失败必须在终端上做区分给用户提示请勿移动卡片而不是交易失败。判断方法是看失败发生在哪一步如果前面的 SELECT 和 READ RECORD 都正常只在 GAC 或之后失败大概率是移卡。这种情况重试时要从头开始不能从中断的那一步续因为卡片状态已经不确定了。第五个坑是忘记处理 6C xx。某些卡片在收到精确 Le 时如果实际返回数据比 Le 长会返回6C xx告诉你正确长度然后你必须用这个长度重发一次。非接触场景下多一次往返就是多几十毫秒所以统一用 Le00是最省事的做法能规避掉这一整类问题。第六个坑是多应用卡片的优先级处理。我见过实现里拿到 PPSE 返回的第一个应用就直接用在只有单应用的卡上没问题遇到借贷双应用的卡如果第一个是借记而交易是贷记场景后面金额校验、限额判断全部对不上。正确做法是按87排序然后结合交易类型9C和终端支持的 AID 列表来选。最后一个值得说的经验永远不要相信这张卡在我这儿是好的。非接触卡片的实现差异极大同一家发卡行不同批次、同一批次不同芯片厂商行为都可能有细微差别。我的做法是维护一个自己的测试卡池覆盖不同的 AID、不同的 AIP 组合、不同的 PDOL 结构每次改完代码全跑一遍跑不通的卡把报文存档。这套卡池后来救过我很多次尤其是在别人报某张卡不能用的时候能快速判断是我的问题还是卡片的问题。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。