十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

农行95交易码接口对接全解析:从报文组装到生产上线

农行95交易码接口对接全解析:从报文组装到生产上线 前一阵子产品经理丢过来一张需求单标题只有一行字“95 农业银行接口对接”备注栏写着“客户要求走农行接口业务编号95尽快拉通联调”。没有文档链接没有字段说明也没有测试账号。银行接口对接的需求开局基本都长这样——业务侧只负责传递客户的一句话剩下所有技术细节都要自己去渠道侧问、去文档中心翻、去联调环境踩没人能替你走完这条路。先说结论这个“95”不是农行客服短号的某种缩写而是农行接口报文里的交易码TxnCode代表“企业批量付款”这条业务链路。农行开放平台把不同业务能力拆成不同交易码有的编号对应余额查询有的编号对应单笔转账而95这一组负责的是批量交易从报文组装、签名上送、渠道转发、核心记账到结果通知的完整闭环。项目组后来都顺口叫它“95接口”但你要清楚它不是一个简单的Restful URL而是一整套接口约定请求地址、消息头、报文结构、签名算法、同步应答、异步通知每一个环节都由交易码95串起来。这篇文章就基于这次的实操经历把从准备到联调、再从上线的关键节点都梳理一遍给后面接农行接口的同学做个参考。1. 接到这个需求时我第一反应是95到底是什么一个需求单上只有一个编号第一件要做的事不是写代码而是把“95”这个编号的完整业务含义从农行文档里捞出来。这一步如果搞错了后面所有配置都是空中楼阁。1.1 需求单上的“95”从文档里挖出来的交易码农行的对公接口体系里交易码是识别业务类型的第一把钥匙。有的交易码代表账户余额查询有的代表交易明细下载有的代表单笔支付而我们对接的95在我拿到的《企业网银开放接口说明书》里对应的是“批量付款及结果通知”。也就是说客户那边一次性发一批付款指令农行接收后进核心系统处理处理完成后再通过异步通知把逐笔结果推回来。这个编号并不算常见很多做单笔支付的同行直到项目结束都没碰过它。但也正因为是批量场景它的报文设计比普通查询类接口多了一大块批量总笔数、总金额、明细清单、同批次的批次号、以及后续的批量结果通知。在字段层面它和单笔接口完全不同在调用方式上它又保留了Restful接口的通用约定——POST一个JSON报文到固定URL用证书做签名服务端同步返回受理结果真正的业务结果走异步回调。所以在开始联调前我先把交易码95对应的文档页面完整读了三遍把“请求报文”“响应报文”“通知报文”这三段字段全部摘出来按照必填和非必填整理成字段映射表。这一步虽然枯燥但能避免后面测试时反复因为少字段被拒。1.2 农行对接的整体链路从企业系统到核心系统农行的接口链路可以粗略分成四段企业侧系统、农行的前置或开放平台网关、农行内部渠道系统、核心账务系统。企业侧系统就是我们自己的ERP、财务系统或者资金管理系统负责组装报文、维护商户号/证书、接收回调农行网关负责验签、鉴权、合法性检查通过后转发到内部渠道渠道系统根据交易码把请求路由到核心系统核心系统完成记账后再沿原路把结果返回。理解这条链路有一个直接好处你能快速判断一个报错到底发生在哪一层。比如“验签失败”通常是企业侧证书或签名逻辑的问题“渠道不存在”多半是渠道号配置错了“交易币种与账户不一致”则是核心系统校验出来的账户问题。联调时最怕的就是不知道错误是哪一层返回的然后瞎改一气。把链路画清楚哪怕是画在纸上对接效率会高非常多。2. 先把对接四件套备齐渠道号、密钥、证书、测试环境银行接口不同于互联网开放平台不是注册账号、拿个token就能调。农行侧那套准入流程本质是双向身份认证加业务权限控制。你在生产环境能调哪些交易码完全取决于农行给你开的渠道参数而这套参数从申请到下发的周期往往比写代码还长。2.1 渠道号、商户号、网点编号先理清这三者的关系有开发第一次对接看到“商户号”就直接填了客户的企业信用代码然后一直报“渠道不存在”。其实农行参数体系分三层渠道号标识接入方式比如银企直连、开放平台、前置机网银各自有独立的渠道标识商户号也叫单位编号或客户号是客户在农行侧开立账户时生成的关联标识报文里通常用它定位是哪个企业在发起交易网点编号开户行的行政管理编码一般不进报文但申请接口权限时会在工单里用到。三者关系可以粗略类比成渠道号是你进哪个门商户号是你在门后挂哪个账户网点编号是账户落在哪个柜台。报文里核心用的是渠道号加商户号的组合其他字段按文档要求填不要自作主张。尤其注意一个常见坑有人把农行网银登录页上那串客户号和接口报文里的商户号混在一起用格式都对不上白折腾半天。2.2 双向证书农行验你、你验农行农行接口走的是HTTPS加双向证书认证。所谓双向就是企业侧要用农行签发的证书证明“我是我”同时农行服务端的证书也要能被企业侧信任。我们这次用的是RSA算法密钥长度2048位证书格式是PKCS#12即.pfx文件里面同时包含公钥和私钥。农行侧还有一个CA根证书用于校验服务端身份。实操中容易忽略的是私钥证书和签名证书可能不是同一个东西。有的渠道会发两个文件一个.pfx用于HTTPS建连时做客户端证书一个.cer或.txt形式的公钥字符串用于报文签名验签。我这次一开始就吃了亏拿.pfx里的私钥去签报文体虽然签名能生成但农行服务端验不过日志一直报“签名校验失败”。后来仔细看文档才发现报文签名要用单独的RSA私钥HTTPS客户端证书是另外一套。这两个如果不分开联调第一关就卡死。2.3 测试环境参数清单建一个配置类而不是写死农行联调环境通常和生产的接入地址不同证书也不同。我在项目里用配置类统一管理这些参数而不是把值散落在代码里这样后面切生产时只改配置不动代码。以我们这次的情况为例整理成表大概是这样的配置项联调环境值示例生产环境值示例说明接口基础地址https://ibsbjstar.ccb.com.cn/xxxhttps://ibsbjstar.abchina.com.cn/xxx不同交易的路径前缀可能相同靠交易码区分渠道号联调渠道号正式渠道号渠道号错误会直接报“渠道不存在”商户号测试商户号生产商户号对应客户实际开立的账户客户端证书test_client.pfxprod_client.pfx由农行渠道侧下发签名私钥test_private.keyprod_private.key与农行留存公钥配对连接超时3000ms5000ms交易提交接口不建议太长读取超时10000ms15000ms避免线程长时间挂住配置类写好后再补一个环境枚举切环境时只改一个参数。我习惯把联调和生产的配置分开成两个yaml/profile文件防止联调时手滑把测试报文发到生产去。3. 拆解95号接口的Restful协议请求头、报文体与签名地基建好了接下来才是真正干活的阶段把Restful接口请求组装出来。农行很多渠道已经把接口规格统一成了“HTTPS POST JSON”的形式跟传统的XML报文或定长报文比起来友好不少但签名、字段类型这些细节仍然决定成败。3.1 请求地址与消息头的写法95接口的调用地址在文档里是一个固定URL比如以/api/gateway结尾。你只需要往这个URL POST一个JSON字符串不需要把交易码拼在路径里交易码在请求体里面。这样做的好处是网关入口统一新交易上来基本不用运维改路由。请求头有几个字段建议每次都显式设置Content-Type: application/json;charsetUTF-8Accept: application/json如果有Http请求头扩展字段用来透传AppId或客户号按文档要求加这里最容易出问题的是字符集。有些农行网关默认按ISO-8859-1解析如果你代码里没指定UTF-8中文户名、用途字段到了服务端就变乱码。为了彻底规避我通常在代码里把所有String类型都统一编码为UTF-8并且在HTTP客户端层面设置Entity内容为UTF-8格式而不是只改Content-Type头。3.2 报文体的核心字段交易码、商户号、批次号95号接口的请求体分两部分公共报文头和业务报文。公共报文头包含交易码、渠道号、商户号、请求流水号、请求时间、签名值等业务报文部分则包含批次号、总笔数、总金额、付款账户、以及每条明细的收款账号、收款户名、金额、用途。这里挑几个关键字段重点说明交易码固定填95这个字段决定农行内部路由请求流水号企业侧生成的唯一流水建议用“日期随机数”的组合农行侧会拿它做去重批次号批量交易的全局唯一批次标识建议规则可读性强一点比如yyyyMMddHHmmss4位随机数总金额单位通常是分必须用字符串类型传输。Java里如果用Long类型拼JSON没问题如果用了浮点数精度就直接废了明细列表最多支持多少笔、单笔金额上限以具体签约为准批量接口通常都有笔数限制。以JSON格式看大致是这个结构{ txnCode: 95, channelNo: 渠道号, merchantNo: 商户号, reqSeqNo: 202506131030001234, reqTime: 2025-06-13 10:30:00, batchNo: 202506131030000001, totalCount: 2, totalAmount: 10000, payAccountNo: 开户账号, detailList: [ { detailSeq: 1, recvAccountNo: 收款账号1, recvAccountName: 收款户名1, amount: 6000, purpose: 货款 }, { detailSeq: 2, recvAccountNo: 收款账号2, recvAccountName: 收款户名2, amount: 4000, purpose: 服务费 } ] }字段名前后顺序在文档中有严格定义因为它直接关系到签名串的拼接。不要用JSON.stringify硬拼对象建议严格按照文档列出的字段顺序构造LinkedHashMap避免顺序不一致导致验签失败。3.3 签名生成步骤先拼字符串再做SHA256withRSA签名是整个对接环节里最容易出问题的部分也是银行接口区别于普通开放平台的关键。农行Restful接口的签名逻辑通常可以概括为三步第一步把请求报文里的核心字段按文档定义的顺序拼接成一个待签名字符串。这个顺序不一定是JSON里的顺序有的渠道要求按“商户号请求流水号请求时间批次号总笔数总金额”这样的顺序拼有的渠道要求把整个业务报文JSON原样当作一个字段参与签名。没有统一规则必须以文档为准。第二步用企业侧私钥对这个待签名字符串做SHA256withRSA签名。签名结果绝大多数情况是Base64编码的字符串放入请求报文中的sign字段。第三步农行服务端用企业侧的公钥验签同时农行用农行自己的私钥对响应做签名企业侧用农行公钥去验。我用Java手写过签名工具核心逻辑大概长这样// 待签名字符串先按文档拼接 String content merchantNo reqSeqNo reqTime batchNo totalCount totalAmount; // 加载私钥 byte[] keyBytes Base64.getDecoder().decode(privateKeyBase64); PKCS8EncodedKeySpec keySpec new PKCS8EncodedKeySpec(keyBytes); PrivateKey privateKey KeyFactory.getInstance(RSA).generatePrivate(keySpec); // 使用SHA256withRSA算法签名 Signature signature Signature.getInstance(SHA256withRSA); signature.initSign(privateKey); signature.update(content.getBytes(StandardCharsets.UTF_8)); byte[] signed signature.sign(); // 输出Base64 String signValue Base64.getEncoder().encodeToString(signed);这一步的教训是待签名字符串里绝对不允许带空格、换行等不可见字符。我排查过一个问题两个系统用的同一套字段值一边拼出来的字符串中间多了个\r\n导致签名对不上。查了很久最后用文本比对工具看了十六进制才发现。所以在对签名前先把参与签名的原文在日志里原样打出来和农行文档示例做对比能省下大量排查时间。3.4 同步响应与异步通知两个都要解析95接口和其他交易一样调用后立即得到一个同步响应但这个响应只在告诉你农行“收到你的请求了”不代表交易成功。它的状态可能有两个常见值受理成功、数据校验失败。拿到受理成功之后真正的结果要靠异步通知获取。异步通知是农行服务端主动往企业侧预留的回调URL发送POST请求携带批次号、总笔数、成功笔数、失败笔数以及每笔明细的处理结果。企业侧必须对通知报文做两件事验签、记录。如果验签不通过宁可丢到异常表也不要落库否则后续对账会对不上。很多团队第一次做批量接口容易犯一个错只重视请求端签名忽略了回调验签。农行推送来的通知报文同样有签名需要用农行公钥验。如果漏了这一步理论上任何人都可以伪造一笔“成功”的批量结果风险很大。这个环节必须像上送请求一样认真对待。4. 联调阶段最容易翻车的五个细节联调是银行接口对接中最耗精力的阶段。农行联调环境一般给到夜间或限时段开放而且一次报错不一定立刻能定位到原因。下面几个问题几乎每个项目都会遇到提前知道能少走很多弯路。4.1 时间戳漂移网关时钟校验比想象中严格农行报文中的reqTime参与签名同时网关服务端会校验这个时间和本机时间差。我们遇到过请求时间写的服务器本地时间但两台机器时钟差了五六分钟结果网关直接拒绝返回“请求时间无效”。后来在代码里做了时钟同步方案对接前用农行联调环境返回的时间戳校准本机时间偏移量在生成reqTime时动态加上偏移值。更简单一点的方案是直接对接NTP服务器做系统级时间同步。但要注意云服务器内部时钟可能因为没有配置NTP或者被安全策略屏蔽而偏差很大开发机尤其严重。在生成报文前先打印一行当前时间和UTC时间确认无误再请求比盯着看不懂的错误码瞎猜靠谱很多。4.2 金额精度传输用分、计算用Long、显示用元这算不上新鲜事但95接口金额字段更多总共涉及总金额、单笔金额、可能还有手续费字段任何一个地方用浮点数处理都会埋雷。比如10000分乘以0.2作为手续费浮点数算出1999.9999传给农行就报金额异常。我的习惯是全链路用Long表示“分”只在展示层转成“元”。JSON序列化时金额字段是数字类型但如果是Java BigDecimal更好如果文档里金额字段被定义成字符串那就一律用字符串不参与任何数字运算。关键是所有金额字段在一开始定义DTO时就统一成同一类型不要请求体用Long、回调体里用BigDecimal然后互相转换很容易出现精度丢位。4.3 NULL字段不能省略空串和缺字段含义不同互联网接口设计时缺字段往往代表不传。农行接口在部分报文里有完全相反的语义必填字段如果没值要传空字符串但不能不传非必填字段如果不参与签名可以不传。混用会导致签名串和网关解析后的字段集合不匹配。我们联调时遇到一个报错叫“报文格式错误缺少[remark]字段”。文档里remark描述是“备注可空”按理解不传应该没问题。但农行网关对请求体的JSON模板要求字段必须完整哪怕是空字符串也要占位。后来在DTO上加了JsonInclude(Include.ALWAYS)注解让空值也参与序列化问题解决。每个接口这种隐性要求不一样联调前最好先通读一遍JSON样例把样例里的字段当最小集合来对待。4.4 返回码要和状态字段一起看95接口的通知报文里可能同时存在两套状态一套是整体批次状态如成功、部分成功、失败另一套是每笔明细状态。只看批次状态就以为全部成功会让对账逻辑出现漏洞。比如批次状态是“部分成功”你必须再遍历detailList找出那些失败明细记录失败原因码。同步响应里也有类似情况外层可能返回“0000”表示受理成功但业务字段里又有个resultCode表示数据校验是否通过。两个码一个都不能少看。我在日志工具里专门把外层响应码、内部业务码、错误信息三个字段拼成一行这样过滤日志时一眼就能看出问题归属。4.5 证书链更新根证书过期比私钥失效更隐蔽证书问题很烦因为它不常发生但一旦发生就让人摸不着头脑。我们联调到第二周突然所有请求都报“证书链校验失败”代码没改、私钥没换。排查了很久发现是本机JDK的信任库没问题但农行那边更新了服务端证书链我们的请求客户端没有加载新的CA根证书导致服务端无法建立可信链路。后来我在代码里把农行提供的根证书文件单独加载到SSLContext中不依赖JDK默认信任库并在配置里留好根证书的路径。这样一来农行更新证书链时只需要替换文件、重启应用不用重新部署整个服务。证书过期这种事建议在监控系统里配置有效期告警提前30天提醒别等生产请求大面积失败再到处问。5. 生产切流前要处理的事白名单、幂等、重试和监控联调环境跑通只是第一步真正上线前还有一堆运营侧工作。银行接口的生产权限和网络策略都比较重提前规划能避免“代码写完了却上不了线”的尴尬。5.1 IP白名单与生产参数变更工单农行生产环境通常会对企业侧出口IP做白名单限制。如果你用的是专线那就是专线出口IP如果走互联网其实就是公网出口IP。最怕的是公司出口有多个或者后续更换了网络出口导致生产某天突然全挂。建议在工单里一次性把正式环境的所有出口IP都提交完整并且说明未来可能的变更窗口。另外生产环境的证书、渠道号、商户号等配置很多银行要求通过线下申请特定格式的变更单即使已经拿到下线文档也可能有个审批周期。不要等联调结束才去提项目启动时就应该把申请流程跑起来否则等待时间比写代码还长。5.2 异步通知的幂等表宁可重复不能丢失95接口的异步通知有一个特性农行侧为了保证送达会根据配置重发通知。企业侧如果处理逻辑不幂等同一笔付款结果被处理两遍可能造成业务重复入账或重复通知。我在库表里建了一张notify_record表唯一键就用“批次号明细序号通知流水号”收到通知先做insert如果主键冲突说明已经处理过直接忽略。幂等表还有一个好处方便排查。农行重发通知时你可以通过查表看到第一次处理时间、处理结果、是否异常不用靠日志猜。如果第一次处理时程序报错了也可以额外设计一个状态字段把失败的通知标记出来人工或后台任务重新消费。5.3 重试策略和超时阈值的取舍请求农行接口有同步等待时间。连接超时设太短网络抖动时容易误判失败读超时设太长应用线程又被占住拖垮整体性能。我通常的经验是连接超时3000ms到5000ms读取超时10000ms到15000ms业务重试次数日间交易重试不超过3次且必须有退避间隔重试前提仅当请求没有成功拿到同步响应比如超时、连接中断时才重试如果同步响应明确返回业务码错误不重试异步通知场景下后台任务消费失败的消息同样要做退避重试比如第一次延迟30秒第二次延迟2分钟第三次延迟10分钟超过三次进人工处理队列。5.4 日志要留全但绝不能出现完整卡号银行接口涉及资金交易日志记录水平直接影响排障效率。我的习惯是把请求流水号、批次号、业务码、错误信息、请求耗时、签名结果打全但敏感字段如卡号、户名必须脱敏只保留前四位后四位。这里并不是单纯为了合规而是防止日志文件泄露后被恶意利用。日志格式建议统一采用“traceId 业务流水号 阶段描述 关键参数”的模式把一次95交易从发起、上送、受理、回调的几个阶段全部串起来。排障时用traceId一查整条链路状态全出来了比一个个日志文件翻找快得多。6. 对接完成之后我复盘出的几点建议整个95农业银行接口对接的项目收尾后我复盘出了一些可以提前落地的方法虽然不涉及具体代码但作用不亚于修任何一个Bug。第一先写Mock服务不要死等农行联调环境。银行联调环境往往要排队申请与其干等不如先把农行返回的JSON样例存成Mock文件用WireMock或者普通的本地Controller模拟同步响应和异步通知把整套流程先跑通。等到联调环境下来重点只验证真实签名、真实证书、真实网关行为效率会高很多。第二把字段映射表提前交给业务确认。95接口里很多字段并不是纯技术字段比如用途、摘要、会计科目这些字段的值会直接打到客户的银行回单上。如果技术组自己拍脑袋填了一个用途客户拿到回单后肯定会不满意后续又要改。提前让业务确认好每个字段的取值规则做好字段映射表能省掉上线后返工。第三留一个手动触发重发的管理入口。生产环境总有极少数通知因为网络或程序问题没有送达或者送达了没处理成功。你在后台管理页面里做一个“按批次号重发查询/重新消费”的按钮让运维人员可以手动触发比每次临时改数据库字段要安全可靠得多。这次对接下来我觉得银行接口并没有想象中那么“神秘”它更像是一套规则极度严谨的API证书、签名、报文、回调每一步都有固定套路。只要你把文档吃到足够细把链路图搞明白剩下的就是耐心联调。尤其像95这种批量交易接口字段多、依赖异步通知只要提前把幂等、日志、监控、重试这些生产必备项想清楚上线后其实比很多互联网接口还稳定。
返回列表