
简介chinapay-java-new 是一套面向 Java Web 开发者的银联 ChinaPay 支付接口对接示例工程适合正在接入或调试银联支付功能的初中级开发者参考。资源包共 72 个文件约 5.05MB以 17 个 java 源码与 17 个 class 编译文件为核心配合 15 个 jar 依赖库、10 个 jsp 页面及 properties、xml 等配置文件构成一个可直接导入 Eclipse 的完整 Web 项目。目录中 src 存放业务源码WebContent 下含 index.jsp 与 WEB-INF 配置res、test、chinapay 等模块分别承载资源、测试与支付相关逻辑工程结构清晰便于按模块定位支付请求、签名与回调处理代码。目前已有 431 人学习下载读者可借助该工程快速理解银联支付接口的调用流程与参数组织方式对照源码完成本地环境搭建与联调排错减少从零摸索的成本。1. 从一份 chinapay-java-new 源码包说起它到底能跑通什么如果你手上正好有一份名为chinapay-java-new的 Java 源码包第一反应大概率是这是不是某个支付渠道的对接示例能不能直接跑跑起来之后能验证什么我拿到这类包的习惯是先看目录结构再看pom.xml或build.gradle最后才去翻具体业务代码。因为一个支付相关的 Java 工程能不能用、好不好用往往在依赖和配置层就已经决定了。chinapay-java-new从命名上看核心指向的是 ChinaPay银联电子支付相关的 Java 接入实现。它不是一个通用框架也不是一个业务中台而是一个偏渠道对接的工程包。适合谁适合正在做支付网关对接、需要参考签名验签流程、或者想找一个可运行的 Java 示例来理解支付报文交互的工程师。如果你只是想做普通 Web 开发这个包对你帮助有限但如果你要接支付通道它里面的加解密、报文组装、回调处理逻辑就是实打实能抄作业的东西。我见过太多人拿到源码包之后直接mvn spring-boot:run然后报一堆错就说“跑不起来”。问题往往不在代码本身而在于这类支付工程对证书、商户号、密钥路径有强依赖。所以这篇笔记不打算泛泛讲支付原理而是围绕这份chinapay-java-new资源把环境配置、依赖梳理、核心流程拆解、常见报错排查以及怎么把它改造成自己能用的对接骨架一步步写清楚。2. 拆开 chinapay-java-new工程结构、依赖与运行前提2.1 先看目录一个支付对接工程通常长什么样拿到chinapay-java-new之后不要急着导入 IDE。我一般先在终端里跑一遍tree或者find把顶层结构看清楚。一个典型的 ChinaPay Java 对接工程目录大致会包含以下几类内容src/main/java下按包名区分config、controller、service、util、dto或vosrc/main/resources下放配置文件application.yml或application.properties以及证书文件、日志配置pom.xml里声明核心依赖HTTP 客户端、JSON 库、加解密库、Spring Boot 父工程可能还有一个doc或README目录放接口文档或对接说明你可以用下面这行命令快速看结构find chinapay-java-new -maxdepth 3 -type f | sort逻辑说明-maxdepth 3限制递归深度避免输出太多-type f只看文件sort让结果按路径排序方便定位。参数上如果你拿到的是压缩包先解压再执行如果是 Git 仓库直接进根目录跑。这一步的目的是判断这个包是“完整可运行工程”还是“代码片段集合”。如果pom.xml存在且src/main/java下有启动类那基本可以按 Spring Boot 项目处理如果只有零散 Java 文件那就得自己搭壳。2.2 依赖梳理pom.xml 里哪些是必须的哪些可以换打开pom.xml重点看三块Spring Boot 版本、HTTP 客户端、加解密相关依赖。常见做法是parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.x.x/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.x.x/version /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.x/version /dependency /dependencies逻辑说明spring-boot-starter-web提供内嵌 Tomcat 和 MVC 能力hutool-all常用于签名、验签、文件读取fastjson或 Jackson 用于报文序列化。参数上Spring Boot 版本不要盲目升到 3.x因为 3.x 要求 JDK 17 且 Jakarta 包名变更老支付工程很多还停留在javax.*升上去会直接编译失败。如果你本地有多个 JDK记得先确认当前java -version和mvn -v指向的是同一个。常见坑是IDE 里配了 JDK 8终端却用 JDK 17 跑 Maven结果编译报“不支持发行版本”。我一般会显式设置export JAVA_HOME/path/to/jdk8 export PATH$JAVA_HOME/bin:$PATH mvn -v逻辑说明JAVA_HOME决定 Maven 用哪个 JDKPATH确保java命令也走同一个。参数上把/path/to/jdk8换成你实际的 JDK 安装路径。这一步不做后面报错会非常玄学。2.3 运行前提证书、商户号、回调地址一个都不能少支付类工程和普通 CRUD 项目最大的区别是它依赖外部配置才能启动或调用。chinapay-java-new里通常会有类似merchantId、certPath、certPwd、notifyUrl这样的配置项。你需要在application.yml里补齐chinapay: merchant-id: 你的商户号 cert-path: /data/certs/chinapay.pfx cert-pwd: 证书密码 notify-url: https://your-domain.com/notify gateway-url: https://gateway.chinapay.com/...逻辑说明merchant-id是渠道分配的唯一标识cert-path指向 PKCS12 或 JKS 证书cert-pwd是证书密码notify-url是异步通知地址必须是公网可访问的 HTTPS 地址gateway-url是渠道网关地址。参数上证书路径建议用绝对路径避免相对路径在不同启动目录下解析不一致。如果只是本地跑通流程没有真实商户号常见做法是用渠道提供的测试环境参数或者把签名验签逻辑单独抽出来做单元测试不发起真实 HTTP 请求。这一点后面会展开。3. 把 chinapay-java-new 跑起来配置、启动与最小验证3.1 配置文件怎么改从占位符到可运行参数很多源码包里的application.yml写的是占位符比如your-merchant-id、your-cert-path。你要做的是逐项替换。我一般会先列一个配置清单确认每一项的来源配置项作用从哪里获取本地测试替代方案merchant-id商户身份标识渠道开户后分配用测试商户号cert-path签名证书路径渠道下载或自行生成生成自签名证书cert-pwd证书密码生成时设置自定义notify-url异步通知地址自己的公网服务用内网穿透或本地 mockgateway-url渠道网关渠道文档测试环境网关替换完成后不要急着启动。先跑一次编译mvn clean compile -DskipTests逻辑说明clean清理旧产物compile只编译不打包-DskipTests跳过测试避免因为测试用例依赖外部环境而失败。参数上如果你只想验证依赖是否完整这一步足够了。如果编译报cannot find symbol大概率是某个依赖没下载全检查pom.xml里的仓库配置。3.2 启动类与端口怎么确认服务真的起来了如果工程有Application启动类直接运行mvn spring-boot:run或者打包后运行mvn clean package -DskipTests java -jar target/chinapay-java-new-0.0.1-SNAPSHOT.jar逻辑说明spring-boot:run适合开发阶段改代码后重启快package生成可执行 jar适合部署验证。参数上如果端口被占用可以在启动命令后加--server.port8081。启动成功后控制台会打印Started Application in x.x seconds。这时候不要以为万事大吉支付工程的核心不是“启动”而是“调用链路能不能通”。我一般会先找一个健康检查接口或者最简单的查询接口用curl打一下curl -X POST http://localhost:8080/api/query \ -H Content-Type: application/json \ -d {orderId:TEST20260101001}逻辑说明-X POST指定方法-H设置请求头-d传 JSON 体。参数上orderId换成你实际要查的订单号。如果返回签名错误说明证书配置有问题如果返回连接超时说明网关地址不通如果返回业务错误码说明链路通了只是业务参数不对。3.3 最小验证不发起真实请求先验签名逻辑支付对接最核心的不是 HTTP而是签名和验签。我通常会把util包里的签名工具类单独拿出来跑一个main方法或者写一个 JUnit 测试Test public void testSign() throws Exception { String plainText merchantId123orderIdTEST001amount100; String sign ChinapaySignUtil.sign(plainText, certPath, certPwd); System.out.println(签名结果: sign); boolean valid ChinapaySignUtil.verify(plainText, sign, certPath); Assert.assertTrue(valid); }逻辑说明sign方法用私钥对明文签名verify方法用公钥或证书验签。参数上plainText要严格按照渠道要求的字段顺序拼接顺序错了签名必错。这一步能过说明证书加载、签名算法、编码格式都没问题再去调 HTTP 接口就少了一层不确定性。常见做法是先用测试商户号和测试证书跑通签名验签再换成生产参数。不要一上来就拿生产证书在本地乱试容易触发渠道风控。4. 避坑与排查chinapay-java-new 最容易翻车的五个地方4.1 现象启动报java.lang.OutOfMemoryError: Java heap space原因工程里可能加载了较大的证书文件或日志配置默认堆内存不够。热词里有人提到“进程堆大小调整为 8000 还是报错”说明不是单纯调大堆就能解决。解决先看是不是死循环或大对象泄漏。如果是启动阶段加载证书检查证书文件是否损坏或路径指向了一个巨大文件。调整堆参数java -Xms512m -Xmx2048m -jar target/chinapay-java-new-0.0.1-SNAPSHOT.jar逻辑说明-Xms初始堆-Xmx最大堆。参数上不要盲目设成 8000先确认物理内存够不够。如果调大后仍报错用jmap或jstack看堆栈。4.2 现象签名验签一直失败返回“验签不通过”原因常见有三种——字段顺序不对、编码不是 UTF-8、证书不匹配。支付渠道对签名原文的拼接顺序有严格要求少一个或多一个空格都会导致签名不一致。解决把签名原文打印出来和渠道文档逐字对比。确认Charset是UTF-8。确认使用的证书和商户号是一对。我一般会在签名工具里加一行日志log.info(待签名原文: [{}], plainText);逻辑说明方括号包住原文方便看出首尾空格。参数上日志级别调到DEBUG或INFO生产环境注意脱敏。4.3 现象回调通知收不到或者收到后处理失败原因notify-url不是公网地址或者回调接口返回的不是渠道要求的格式。很多渠道要求回调返回OK或特定 JSON返回 404 或 500 都会导致渠道重试。解决先用curl模拟渠道回调确认接口能通curl -X POST https://your-domain.com/notify \ -H Content-Type: application/x-www-form-urlencoded \ -d orderIdTEST001statusSUCCESSsignxxx逻辑说明-d传表单参数模拟渠道回调格式。参数上sign换成真实签名。如果本地没有公网地址常见做法是用内网穿透工具做临时映射但注意不要用于生产。4.4 现象mvn编译报Cannot resolve symbol javax.servlet原因Spring Boot 3.x 把javax.servlet换成了jakarta.servlet而老支付工程还在用javax。解决要么把 Spring Boot 降到 2.7.x要么全局替换包名。我一般选降版本因为支付工程依赖的第三方库不一定支持 Jakarta。version2.7.18/version逻辑说明2.7.x 是 Spring Boot 2 的最后一个稳定版本兼容javax.*。参数上如果必须用 3.x就要做好改包名的准备。4.5 现象本地跑正常部署到服务器后连接网关超时原因服务器没有配置外网访问白名单或者 DNS 解析有问题。支付网关通常有 IP 白名单限制。解决先在服务器上telnet网关地址和端口telnet gateway.chinapay.com 443逻辑说明telnet测试 TCP 连通性。参数上端口换成渠道文档里的实际端口。如果不通检查安全组、防火墙、DNS。如果通但应用仍超时检查 JVM 的https.proxyHost等参数是否被错误设置。5. 进阶用法把 chinapay-java-new 改造成可复用的支付对接骨架5.1 抽离签名模块做成独立 Starter如果你不止接一个支付渠道建议把签名验签、证书加载、报文组装抽成一个独立模块。chinapay-java-new里的util包可以直接拿来改public class ChinapaySignTemplate { private final String certPath; private final String certPwd; public ChinapaySignTemplate(String certPath, String certPwd) { this.certPath certPath; this.certPwd certPwd; } public String sign(String plainText) { // 加载证书、构造签名、返回 Base64 } public boolean verify(String plainText, String sign) { // 加载证书、验签、返回布尔值 } }逻辑说明把证书路径和密码作为构造参数避免静态方法到处读配置。参数上certPath和certPwd从application.yml注入。这样其他渠道只要实现同样的接口就能复用上层业务逻辑。5.2 用策略模式管理多个支付渠道支付对接做多了你会发现每个渠道的签名方式、报文格式、回调处理都不一样。常见做法是定义一个PaymentChannel接口public interface PaymentChannel { String pay(PayRequest request); boolean verifyNotify(MapString, String params); String query(String orderId); }逻辑说明pay发起支付verifyNotify验签回调query查单。参数上PayRequest封装订单号、金额、商品描述等公共字段。然后为 ChinaPay 写一个实现类为其他渠道写各自的实现类。这样新增渠道时不用改调用方。5.3 验证方法用单元测试覆盖签名和报文组装支付工程最怕“改一行代码签名全错”。我一般会写三类测试测试类型测试内容断言目标签名测试固定明文 固定证书签名结果与预期一致验签测试固定明文 固定签名返回 true报文组装测试固定请求对象生成的表单字段顺序正确Test public void testBuildForm() { PayRequest request new PayRequest(); request.setOrderId(TEST001); request.setAmount(100); MapString, String form ChinapayFormBuilder.build(request); Assert.assertEquals(TEST001, form.get(orderId)); Assert.assertEquals(100, form.get(amount)); }逻辑说明断言字段值确保组装逻辑没被改坏。参数上amount注意单位是分还是元支付渠道通常要求分。5.4 一个具体技巧用日志脱敏保留排查能力支付日志不能明文打印卡号、密钥、完整签名。但排查问题时又需要看原文。我的习惯是写一个脱敏工具public static String mask(String text) { if (text null || text.length() 8) return ***; return text.substring(0, 4) **** text.substring(text.length() - 4); }逻辑说明保留前四位和后四位中间用星号代替。参数上长度小于 8 的直接全掩。这样日志里既能看出是哪个商户、哪个订单又不会泄露完整敏感信息。从那以后我每次对接新的支付渠道都强制走一遍“签名单测 → 本地 mock 回调 → 测试环境联调 → 生产灰度”的流程不再直接拿生产参数在本地跑。希望这份拆解能帮到你少踩几个签名和证书的坑。本文还有配套的精品资源点击获取