简介:这份资源面向需要在Java项目中集成农行Web端网银支付功能的开发者,提供接口文件与可运行Demo,帮助解决支付请求构造、签名验证、响应解析与回调通知等对接难题。压缩包共147个文件,约5.1MB,以class编译类、jsp页面、html页面、jar依赖包为主,另含properties配置、cer证书与truststore密钥库等,覆盖商户参数配置、签名服务、数据校验、批量代付请求等模块,便于直接导入工程调试。已有2076人学习下载。通过研读Demo与接口文档,读者可掌握交易请求发送、公私钥签名、银行响应处理、异常捕获及回调验证的完整链路,并参考升级版接口包中的安全改进与目录结构,快速完成二次开发与联调排错。
1. 从一份“能跑通的支付对接包”说起
做过支付对接的同学大概都有这种体验:文档给了一堆字段,签名规则写得云里雾里,回调地址配好了却死活收不到通知,最后只能对着日志一行行猜。这次拆的是一份农行 web 端网银支付的 Java 接口文件和 demo,说白了就是一套“能直接跑起来”的对接参考包——里面有接口文档、有 Java 侧的调用示例、有签名验签的代码骨架,也有回调处理的逻辑。它适合两类人:一类是第一次接网银支付、想先跑通再说的新手;另一类是接过别的渠道、想快速摸清农行这套报文和签名差异的老手。我拿到之后先把 demo 跑了一遍,又对着文档把关键字段和签名流程捋清楚,下面按“能干什么 → 怎么跑 → 怎么改”的顺序讲。
2. 项目定位与技术选型:这套接口包到底解决什么问题
2.1 它覆盖的支付场景
农行 web 端网银支付,核心是商户系统把订单信息按约定格式组装、签名后跳转到银行侧完成付款,银行再把结果同步或异步通知回来。这份接口包围绕的就是这条链路:下单请求的组装、签名、跳转,以及支付结果的通知接收和验签。demo 里通常会把“发起支付”和“接收通知”两个入口都写出来,方便你对照着改。它不涉及移动端 SDK,也不管退款、对账这些后续环节,定位很明确——先把支付主流程跑通。
2.2 技术栈与依赖
从 Java 侧看,这类对接包基本是 Servlet 或 Spring MVC 的写法,依赖很轻。常见做法是用 JDK 自带的java.security做签名验签,用HttpURLConnection或 HttpClient 发请求,报文格式多为键值对拼接或 XML。demo 里一般会带一个MerchantConfig之类的配置类,把商户号、证书路径、密钥这些集中管理。下面这张表是我按常见实现整理的依赖清单,具体以你拿到的包为准:
| 组件 | 作用 | 常见版本/形式 |
|---|---|---|
| JDK | 运行环境 | 1.8 及以上 |
| 签名工具 | RSA/MD5 签名验签 | JDK 自带 security 包 |
| HTTP 客户端 | 发送支付请求 | HttpURLConnection / HttpClient |
| Web 容器 | 跑 demo | Tomcat 7/8 |
| 证书文件 | 商户私钥、银行公钥 | .pfx / .cer |
提示:证书和密钥文件不要提交到代码仓库,demo 里如果带了示例证书,跑通后记得换成自己的。
2.3 目录结构长什么样
一份典型的对接包,目录大致是这样组织的:
payment-demo/ ├── src/main/java/ │ ├── config/ # 商户配置、证书加载 │ ├── sign/ # 签名、验签工具类 │ ├── request/ # 支付请求组装 │ ├── notify/ # 回调通知处理 │ └── servlet/ # demo 入口 ├── src/main/resources/ │ ├── merchant.properties │ └── cert/ # 证书存放 ├── doc/ # 接口文档 └── pom.xml / lib/config管配置,sign是核心,request和notify分别对应支付发起和结果接收。你二次开发时,改动基本集中在这几个包,不用大动结构。
3. 跑起来:从配置到发起第一笔支付
3.1 环境准备与配置项
先把 JDK 和 Tomcat 装好,把 demo 导入 IDE。接着改merchant.properties,这是跑通的前提。关键配置项一般有这些:
# 商户号,银行分配 merchant.id=103xxxxxxxxx # 商户证书路径(私钥) merchant.cert.path=/cert/merchant.pfx # 证书密码 merchant.cert.password=123456 # 银行公钥证书,用于验签 bank.cert.path=/cert/bank.cer # 支付请求地址 pay.url=https://xxx.abchina.com/pay # 异步通知地址,必须是公网可访问 notify.url=http://your-domain/notifymerchant.id和证书是银行给的,notify.url必须公网可达,否则收不到异步通知。本地调试时常见做法是用内网穿透工具把本地端口映射出去,或者直接部署到有公网 IP 的测试机。
3.2 组装支付请求并签名
支付请求的核心是把订单字段按规则拼接,再签名。下面这段是常见的组装逻辑:
// 组装支付参数 Map<String, String> params = new TreeMap<>(); params.put("MerchantID", config.getMerchantId()); params.put("OrderNo", orderNo); // 商户订单号,唯一 params.put("Amount", amount); // 金额,单位分 params.put("Currency", "156"); // 人民币 params.put("NotifyURL", config.getNotifyUrl()); // 按 key 排序后拼接成待签名字符串 String signStr = buildSignString(params); // 用商户私钥签名 String sign = SignUtil.sign(signStr, config.getMerchantCertPath(), config.getMerchantCertPassword()); params.put("Sign", sign);TreeMap保证字段按字典序排列,这是签名一致性的关键——顺序错了签名必然对不上。buildSignString一般按key=value&key=value拼接,空值字段是否参与签名要看文档约定。SignUtil.sign内部用私钥对摘要做加密,输出通常是 Base64 或十六进制字符串。金额单位、币种代码这些字段最容易填错,对着文档逐个核对。
3.3 跳转与同步返回
参数组装签名完成后,把表单以 POST 方式提交到银行支付地址,或者拼成 URL 重定向。demo 里一般会生成一个自动提交的 HTML 表单:
// 生成自动提交表单,跳转到银行支付页 StringBuilder form = new StringBuilder(); form.append("<form id='payForm' action='").append(config.getPayUrl()) .append("' method='post'>"); for (Map.Entry<String, String> e : params.entrySet()) { form.append("<input type='hidden' name='").append(e.getKey()) .append("' value='").append(e.getValue()).append("'/>"); } form.append("</form><script>document.getElementById('payForm').submit();</script>"); response.getWriter().write(form.toString());用户付款完成后,银行会同步跳回商户页面,同时异步通知后台。同步返回只用来展示结果,不能作为发货依据——这是血泪经验,同步返回的参数可能被篡改,必须以异步通知为准。
3.4 接收异步通知并验签
异步通知是发货的唯一依据。收到通知后先验签,再处理业务:
// 接收异步通知 Map<String, String> notifyParams = parseRequest(request); String bankSign = notifyParams.remove("Sign"); // 用银行公钥验签 boolean valid = SignUtil.verify(buildSignString(notifyParams), bankSign, config.getBankCertPath()); if (!valid) { response.getWriter().write("fail"); return; } // 验签通过,处理订单 if ("Success".equals(notifyParams.get("Result"))) { orderService.markPaid(notifyParams.get("OrderNo")); } // 必须返回约定字符串,否则银行会重复通知 response.getWriter().write("success");验签用的是银行公钥,和签名用的商户私钥是两套。处理完业务后必须返回银行约定的成功标识,否则银行会按策略重复推送通知。业务处理要做幂等,同一笔订单重复通知不能重复发货。
4. 避坑与排查:对接网银支付最容易翻车的几个点
4.1 签名一直验不过
现象是本地算出来的签名和银行返回的对不上,或者银行说验签失败。原因通常是待签名字符串拼接规则不一致:字段顺序、空值处理、编码格式、是否包含某些字段,任何一处和文档不符都会导致签名失败。解决方法是把待签名字符串打印出来,逐字符和文档示例比对,重点看编码是不是 UTF-8、有没有多余的空格或换行。
4.2 收不到异步通知
现象是用户付款成功了,但后台一直没收到通知,订单状态不更新。原因可能是notify.url不是公网地址、被防火墙拦截、或者返回内容不符合银行要求导致银行认为通知失败。解决方法是先用公网工具测一下地址可达性,再检查返回的字符串是不是银行约定的成功标识,最后看服务器日志有没有收到请求。
4.3 证书加载报错
现象是启动时报Keystore was tampered with或找不到证书。原因多是证书路径写错、密码不对、或者证书格式不匹配(pfx 和 cer 混用)。解决方法是确认证书文件确实存在、密码和银行给的一致,pfx 用私钥加载方式,cer 用公钥加载方式,别搞反。
4.4 金额和订单号出错
现象是银行返回参数错误,或者订单金额对不上。原因是金额单位搞错(元还是分)、订单号重复或超长。解决方法是严格按文档的单位传值,订单号保证全局唯一且长度合规,下单前先做校验。
4.5 重复通知导致重复发货
现象是同一笔订单被处理了多次。原因是银行在没收到成功响应时会重复通知,而业务侧没做幂等。解决方法是用订单号做唯一约束,处理前先查状态,已处理的直接返回成功,不再执行业务逻辑。
5. 二次开发:把这套 demo 改成能上生产的代码
5.1 配置外置与多环境隔离
demo 里的配置常是写死的,上生产第一步是把配置外置。我一般会把商户号、证书路径、支付地址这些放到配置中心或环境变量,按开发、测试、生产分环境管理。证书文件单独挂载,不打包进 jar。这样换环境不用改代码,也避免密钥泄露。
5.2 签名模块的封装与复用
签名验签是核心,建议单独抽成一个工具类或服务,别散落在各处。下面是我常用的封装思路:
public class PaySignService { // 统一入口:组装待签名字符串 public String buildSignContent(Map<String, String> params) { return params.entrySet().stream() .filter(e -> e.getValue() != null && !e.getValue().isEmpty()) .sorted(Map.Entry.comparingByKey()) .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining("&")); } // 签名 public String sign(Map<String, String> params) { ... } // 验签 public boolean verify(Map<String, String> params, String sign) { ... } }把拼接、签名、验签收敛到一个类里,字段过滤规则只写一处,后续文档变更只改这里。这样也方便写单元测试,用固定的参数和预期签名做回归。
5.3 回调处理的幂等与重试
回调处理要解决两个问题:幂等和可靠。幂等靠订单号加状态判断,可靠靠本地消息表或队列。我的做法是收到通知验签通过后,先落一条通知记录,再用订单号做唯一键更新订单状态,更新成功才执行业务。如果业务处理失败,把通知记录标记为待重试,由定时任务补偿。这样即使银行重复通知或业务临时异常,也不会丢单或重复发货。
5.4 日志与对账
支付对接最怕出问题查不到原因。关键节点都要打日志:请求参数(脱敏后)、待签名字符串、签名结果、通知原文、处理结果。日志里带上订单号,方便串联。每天再跑一次对账,把银行流水和本地订单比对,发现差异及时处理。这套组合拳下来,大部分问题都能定位。
5.5 改造后的验证方法
改完别急着上生产,先在测试环境跑完整链路:正常支付、支付失败、重复通知、验签失败、网络超时,每种情况都验证一遍。我习惯用固定的测试订单号反复跑,确认幂等生效。上线后先小额试一笔,确认通知和状态都正常,再放开额度。从那以后我每次对接新的支付渠道,都强制走一遍“配置外置 → 签名封装 → 幂等回调 → 对账”这套流程,少一步都可能埋雷。希望帮到你。
本文还有配套的精品资源,点击获取