☰
Java开源支付系统Jeepay:聚合支付与四方支付技术实践指南
2026/9/29 18:12:59 网站建设 项目流程

简介:这是一套基于Java的全开源聚合支付系统,面向有支付通道对接需求的开发团队与独立开发者,可解决多渠道支付网关统一路由、交易签名安全及商户/运营管理等问题,适用于电商、金融等需要统一收银台的业务场景。压缩包内含387个文件,以322个Java源码文件为主,配合XML配置、YML环境配置、SQL脚本及说明文档,覆盖微信、支付宝、云闪付服务商与普通商户接口,支持V2/V3及RSA/RSA2签名。资源包大小约7.05MB,目录结构完整,附带开发文档,从代码结构到部署说明均有清晰呈现,适合需要快速搭建支付平台或学习支付系统设计的中高级Java工程师。已有94人浏览学习。通过学习可掌握支付网关自动路由、MQ订单通知、Spring Security权限管理及前后端分离架构,还可参考其参数配置界面自动化生成等实战设计,便于二次开发与分布式部署。

1. 全开源Java支付系统Jeepay:四方支付平台到底能帮你省多少事

接到支付需求的时候,最烦的不是写业务代码,而是每个渠道一套文档、一套签名、一套回调协议。微信扫码还没调通,支付宝H5又在问你要应用私钥;等这些都配完,商户又问你能不能接云闪付。全开源Java支付系统Jeepay解决的就是这个场景:它把微信、支付宝这一类第三方支付渠道统一成一套接口,你做的是聚合支付,也就是行业里常说的四方支付系统,站在持牌支付机构之上做统一入口。对Java团队来说,最大的价值是代码看得见、改得动,不用被商业支付平台的费率和服务条款绑死。适合需要快速搭建支付中台的技术部门,也适合用来做支付系统的二次开发学习。

2. 聚合支付与四方支付的技术定位:为什么选Jeepay而不是自研

2.1 聚合支付、四方支付和渠道网关的关系

先把概念理顺。支付宝、微信、银联这类持牌机构是第三方支付,它们直接面对商户和消费者。聚合支付做的事情是在这些第三方支付之上再加一层,商户只需要对接聚合平台一个接口,就能在同一个订单里选择微信、支付宝、云闪付等多个渠道。由于聚合平台本身不直接清算资金,行业里习惯称它为四方支付,也就是第四方服务商。

Jeepay的价值在于,它把这个四方的技术底座开源出来了。你不需要自己从零设计回调协议、签名机制、订单状态机,而是拿到一套已经在生产环境验证过的Java实现。自研支付系统最常见的坑是:订单状态乱、回调重复入账、渠道切换困难。Jeepay用一套固定的状态流转和渠道适配层把这些问题兜住了。

这里要说明白一个边界:Jeepay是开源技术框架,技术上可以做聚合、做四方平台,但能不能对外运营、怎么收费、资金怎么结算,取决于你的企业有没有相关资质、合作的持牌机构怎么约定。后面讲的所有内容都是技术落地层面的事,别拿着代码套壳就对外宣称是支付机构。

2.2 Jeepay的模块划分:三个后台与一条交易主链路

我拿到Jeepay第一件事是看它的模块边界。典型的Jeepay项目会分成三个后端服务加两个前端工程,分别是支付网关、商户后台、运营后台,以及配套的商户端页面和运营端页面。支付网关是核心,它负责接收商户的下单请求、调用渠道接口、处理异步回调、更新订单状态;商户后台给接入进来的商户看订单、查退款、配支付方式;运营后台是平台管理员用的,管理渠道参数、审核商户、配置费率。

这三个服务的分工,决定了你在部署时不能只启动一个jar包。很多第一次接触的人,启动完支付网关就去下单,结果发现商户后台登录不了,因为商户后台和运营后台没起来。正确的理解是:支付网关是交易心脏,另外两个是管理侧,它们共用同一个数据库,但运行逻辑是分开的。

一条交易主链路大致长这样:商户前端请求你的后端,你的后端调用Jeepay支付网关的统一支付接口;支付网关把订单落库,根据支付方式编码找出对应的渠道适配器,去请求微信或支付宝;渠道返回支付链接或二维码参数,网关再返回给你的后端;用户完成支付后,渠道主动回调支付网关,网关验签、更新订单状态,再向你的后端回调。这条链路里最容易被忽略的是最后一步:网关向商户端回调时,商户端必须返回SUCCESS字符串,否则网关会认为通知失败,继续重试。

2.3 订单状态机与数据一致性:支付系统最核心的设计

Java后端面试八股文里讲了无数遍的幂等、分布式事务、数据一致性,在支付系统里全部会真实遇到。Jeepay的订单表里,状态不是随便一个字段,而是严格按照状态机在流转。常见状态包括下单成功、支付中、支付成功、关闭、退款中、已退款,每个状态只能朝特定方向前进,不能从支付成功跳回支付中。

为什么状态机这么重要?因为支付渠道的回调不是一次性的。微信、支付宝为了保证通知到达,会按一定策略多次推送同一笔订单结果。如果你在回调处理里不做幂等,直接把金额入账,同一笔订单会被入账两次。Jeepay的做法是:每次回调先校验订单当前状态,如果已经是支付成功,就直接返回成功通知,不再重复处理业务逻辑。

数据库层面也会有约束配合。订单号必须唯一,回调更新时用状态条件做原子更新,比如UPDATE订单表SET状态=支付成功WHERE订单号=? AND状态=支付中。这样即使回调并发到达,也只有一条能更新成功。Java聚合支付系统里讲的数据一致性,落到实现上就是状态机加数据库条件更新,再加Redis分布式锁兜底,三件事缺一不可。

3. 本地跑通Jeepay:环境准备、初始化与最小启动步骤

3.1 基础环境与初始化数据库

先把环境捋干净。Jeepay是标准Java项目,JDK建议用8或11,Maven用3.6以上,数据库用MySQL 5.7或8.0,Redis必须是可用状态,因为验证码、登录token、部分缓存都依赖Redis。如果你拿到的是一份zip压缩包,而不是git仓库,第一步不是急着编译,而是检查压缩包结构是否完整。

压缩包常见的坑是缺文件。比如只打包了后端代码,没有前端dist目录;或者SQL脚本被拆分但只放了一半;最怕的是别人改过配置后重新打包,里面带着某个生产环境的数据库地址和密钥。我一般拿到包先看三处:pom.xml是否存在、docs目录下SQL脚本是否完整、application系列配置里有没有明显改过的痕迹。确认无误后,再动手建库导表。

# 建库,字符集必须用utf8mb4,支付回调里会有emoji和生僻字 mysql -uroot -p -e "CREATE DATABASE jeepay DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入初始化SQL,jeepay.sql通常是全量脚本 mysql -uroot -p jeepay < docs/jeepay.sql

导入后验证一下表数量和数据。重点不是表全不全,而是看底层核心表有没有初始化数据,比如支付渠道表、权限表、运营管理员账号。如果这些表是空的,后面前端登录和渠道管理都会异常。初始管理员账号一般在SQL脚本里有注释,或者存在sys_user表里,登录后第一件事是改密码。

3.2 修改配置并启动三个服务

Jeepay的配置集中在每个服务自己的application.yml里,生产环境常用application-prod.yml。第一次本地跑,我一般直接改默认配置。核心要改的是数据源、Redis连接、各服务端口。支付网关、商户后台、运营后台的默认端口分别不同,官方常见配置是9216、9217、9218,但如果你拿到的是二次修改过的包,端口可能完全不同,启动前先确认。

server: port: 9216 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/jeepay?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: yourpassword redis: host: 127.0.0.1 port: 6379 jeepay: # 支付网关对外暴露的域名或IP,回调地址会基于这个配置生成 pay-address: http://127.0.0.1:9216

datasource那段是最容易出问题的:MySQL 8默认开启了SSL和时区校验,不带useSSL和serverTimezone这两个参数,Spring Boot启动会直接报连接错误。Redis如果设了密码,spring.redis.password必须填,否则启动不报错,但登录验证码接口会全部500。

配置改完后,分别编译启动。三个服务共用同一个数据源,所以只要数据库正常,启动顺序其实不挑,但我习惯先支付网关、再运营后台、再商户后台,观察日志更清楚。

mvn clean package -DskipTests java -jar jeepay-payment/target/jeepay-payment.jar --spring.profiles.active=prod java -jar jeepay-op/target/jeepay-op.jar --spring.profiles.active=prod java -jar jeepay-merchant/target/jeepay-merchant.jar --spring.profiles.active=prod

启动完看日志里的端口监听。如果启动过程报“无法连接Redis”或“BeanCreationException”,基本是配置没对上,先把数据源和Redis单独测一遍再启动服务。这里顺带说一句:如果是从zip包解压出来的代码,源码目录里可能还带着target残留,建议mvn clean先删掉再打包,避免class污染带来的各种玄学问题。

3.3 用沙箱渠道完成第一笔订单

服务启动成功只是第一步,真正证明系统跑通的是完成一笔订单。最推荐的方式是配置一个沙箱支付渠道,而不是直接用真实商户号。支付宝开放平台有沙箱环境,微信支付也有测试商户号,特意用来联调。

先登录运营后台,创建一个商户,记下商户号mchNo。然后在商户后台创建一个应用,得到appId和商户API密钥。接着在运营后台配置支付渠道,把沙箱环境的应用私钥、支付宝公钥、网关地址填进去,关联到对应的支付方式。Jeepay用wayCode来标识渠道场景,比如WX_NATIVE是微信扫码,ALI_WAP是支付宝手机网站支付。渠道配好后,用curl直接调用支付网关统一下单接口。

curl -X POST 'http://127.0.0.1:9216/api/pay/unifiedorder' \ -H 'Content-Type: application/json' \ -d '{ "mchNo": "M00001", "appId": "6688123456789101", "mchOrderNo": "20240901001", "amount": 100, "currency": "cny", "wayCode": "ALI_SANDBOX", "returnUrl": "http://localhost:8080/pay-result", "notifyUrl": "http://localhost:8080/notify", "subject": "测试商品", "body": "沙箱支付测试" }'

amount字段的单位是分,100就是1元。mchOrderNo必须保证唯一,它是商户侧的订单号,回调结果里也会带这个字段。返回JSON里会包含支付链接或二维码内容,浏览器打开就能看到支付宝沙箱的收银台。用户支付成功后,渠道会把结果异步通知到notifyUrl,这个地址必须是公网可达的,本地联调时可以用内网穿透工具临时暴露,否则回调永远进不来。

4. 对接真实支付渠道:应用创建、参数配置与接口联调

4.1 渠道参数从哪来:商户平台与证书的关系

沙箱和真实环境最大的区别在参数来源和证书体系。微信支付走的是APIv3协议,你需要准备商户号、APIv3密钥、商户API证书序列号、商户私钥。APIv3密钥是在微信商户平台设置的,不是支付密钥,很多新手把AppSecret当成APIv3密钥填进去,导致下单时报签名错误。

支付宝相对简单一些,核心参数是应用AppID、应用私钥、支付宝公钥,加一个签名类型。私钥是在支付宝开放平台生成RSA密钥对时保存的,支付宝公钥可以在开放平台的密钥管理里看到。注意,应用私钥永远不要泄露给前端,也不应该出现在Nginx配置或静态页面里。

Jeepay的运营后台一般会有一个支付渠道管理页面,每个渠道对应一组参数配置。常见的做法是把证书文件上传到服务器指定目录,配置里填证书路径,而不是把证书内容直接贴进数据库。微信支付还涉及平台证书,平台证书用于校验渠道回调的请求真伪,和商户证书不是一回事。如果平台证书配置不对,支付能下单,但回调验签必然失败,甚至回调根本进不了你的系统。

4.2 创建支付应用与配置支付方式

接入方视角下,商户和应用是两个层级。运营后台创建商户,商户后台创建应用,一个商户可以创建多个应用,比如一个App用一套appId,一个PC网站用另一套。每个应用绑定自己的回调域名和支付方式集合。

支付方式是通过wayCode关联的。Jeepay里支付方式不只是微信或支付宝,而是精确到具体场景,比如WX_NATIVE对应微信扫码,WX_JSAPI对应微信公众号支付,ALI_PC对应支付宝PC网站支付。一个应用要支持哪些场景,需要在应用详情里把对应的支付方式勾选并启用。如果下单时传的wayCode没有开通,系统会直接提示支付方式不存在,而不是友好的渠道报错。

这里有个容易忽略的细节:应用和渠道参数之间的绑定关系。同样一个微信商户号,可以同时给多个应用用,但渠道参数必须在运营后台先配置成启用状态,商户后台才能选到。很多团队在测试环境配好了渠道参数,切生产环境时只改了证书,忘了在运营后台重新关联渠道,导致下单报“渠道未配置”。上线前一定要按照商户、应用、渠道、支付方式这条链路逐个核对状态。

4.3 统一下单与异步回调验签

真实渠道联调时,统一下单的报文结构基本不变,变化的是wayCode、渠道参数和回调地址。微信扫码支付返回的是code_url,你需要前端拿着这个链接生成二维码;支付宝PC支付返回的是一个表单页面。Jeepay把这些差异封装在了返回参数里,你的业务后端只需要拿到支付参数,原样透传给前端。

回调验签是整个流程里最不能糊弄的一步。支付网关收到渠道回调后,会先验签,确认这笔通知确实来自微信或支付宝,再更新订单状态,然后向商户回调。商户端收到回调后,同样需要验签。很多商户系统只判断订单号一致就改单,这是严重的隐患,因为只要有回调地址就能伪造通知。

// 商户端回调处理伪代码,验签逻辑必须放在第一步 public String payNotify(HttpServletRequest request) { // 1. 判断回调来源,验签失败直接返回FAIL boolean signValid = PayKit.verifySign(request.getParameterMap(), apiKey); if (!signValid) { return "FAIL"; } // 2. 根据商户订单号找到本地订单 PayOrder order = payOrderService.getByOrderNo(request.getParameter("mchOrderNo")); if (order == null) { return "FAIL"; } // 3. 状态机更新,只有支付中才能更新为成功 boolean updated = payOrderService.markSuccess(order, request); return updated ? "SUCCESS" : "FAIL"; }

注意回调接口一定要返回SUCCESS,而且要返回成功后再更新本地业务状态。如果先更新业务状态再返回SUCCESS,万一网络超时,渠道会重新回调,你的状态机重复判断一次没关系,但数据库和外部依赖的顺序必须保证幂等。Jeepay回调场景中,验签、幂等、状态机三者是一套完整流程,缺一个都会在后续对账时露出马脚。

5. 避坑:Jeepay部署与联调里最容易翻车的5个细节

5.1 回调地址不通,订单永远停留在下单成功

现象:用户在微信或支付宝里付了钱,渠道侧显示交易成功,但Jeepay商户后台里订单状态一直是支付中,没收到任何回调日志。

原因:回调URL不可达是最常见的问题。本地联调时notifyUrl填了192开头的局域网地址,支付渠道的服务器不可能访问到;或者填了localhost,那只有你自己机器能解析。另一个原因是网关应用jeepay-payment里配置的回调基础地址不是公网域名,系统自动拼接出的回调URL本身就是错的。

解决:联调阶段用内网穿透工具把支付网关的端口映射到一个公网临时域名,notifyUrl填这个域名,并在网关应用里把jeepay.pay-address也改成公网地址,保证回调能找回来;生产环境则必须用备案域名并且配置好Nginx反向代理。改完配置之后,先手动模拟一笔回调测试通道通不通,再真实验证。

5.2 MySQL 8时区与SSL连接导致启动失败

现象:服务启动时报Communications link failure,或者长时间卡在数据库连接初始化,最后抛Access denied for user。

原因:MySQL 8对连接URL的校验比5.7严格,时区信息不明确、SSL握手失败都会直接中断连接。很多人拿到项目里的连接串就改个用户名密码,漏了serverTimezone和useSSL这两个参数。

解决:把JDBC URL写完整,时代码已经给过:jdbc:mysql://127.0.0.1:3306/jeepay?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai。如果还报时区异常,在MySQL里执行set global time_zone = '+8:00'并重启服务。注意配置文件里不要出现转义错误,yaml里的&符号有时需要处理,用单引号包住整个URL最稳妥。

5.3 微信平台证书过期或未配置,下单直接报错

现象:调用统一下单接口,Jeepay返回“渠道配置异常”,查看支付网关日志,微信侧报错提示证书相关错误,比如CERTIFICATE_NOT_FOUND或private key error。

原因:微信支付APIv3要求使用商户证书和平台证书。常见错误是填了APIv3密钥但没配置平台证书,或者证书文件路径不对,程序读不到私钥。平台证书不是一成不变的,微信会定期轮换,如果系统长时间没更新,也会突然报错。

解决:到微信商户平台下载最新平台证书,把证书路径配到运营后台的渠道参数里,并同步更新商户私钥。建议把证书放在服务器固定目录,不要和代码打在一个包里,这样证书轮换时只需要替换文件,不需要重新发版。上线前确认证书序列号和私钥是匹配的一套,证书序列号可以在证书文件里查看。

5.4 商户API密钥与IP白名单配置失误

现象:商户后台能正常登录,商户系统调用Jeepay接口时却一直报“签名错误”或“无权限访问”。签名算法看起来没问题,密钥也核对过。

原因:Jeepay的商户API密钥和后台登录密码是两套凭证。调接口用的签名key是创建应用时生成的appSecret或API Key,很多人在商户后台改了个登录密码,以为API密钥也一起变了;另外,Jeepay这类系统通常在商户应用配置里带IP白名单,如果你的服务器出口IP没加进去,签名再正确也会被拦。

解决:登录商户后台,在应用管理里找到API密钥,重新生成或复制原值,确认你的签名代码用的是这个值。再检查应用配置里的IP白名单,把业务服务器的公网出口IP加进去,如果用云函数或负载均衡调用,要把所有可能的出口IP都加全,否则上线后某个容器实例会间歇性鉴权失败。

5.5 重复回调与幂等处理:不要把入账写在回调最前面

现象:用户支付一笔100元的订单,数据库里多了两条入账记录,订单金额翻倍。排查时发现渠道侧确实发起了多次回调。

原因:支付渠道为了保证通知不丢,会在一段时间内多次重试。如果业务系统在回调处理里没做幂等,每次收到回调就执行入账逻辑,重复入账就会发生。更隐蔽的是,Jeepay本身处理渠道回调和处理商户回调是两层,两层都要有幂等逻辑。

解决:订单表对mchOrderNo加唯一索引;回调处理第一步验签,第二步查订单状态,只有状态为支付中时才更新为成功并执行后续业务;更新语句用WHERE订单号=? AND状态=支付中,影响行数为0则说明已经被处理过,直接返回SUCCESS。入账、发送通知这类副作用操作,放到订单状态更新成功之后,并且依靠状态机保证只执行一次。

6. 进阶:从跑通到可用,Jeepay的扩展与上线前检查

6.1 自定义支付渠道的最小实现

如果你要接入一个Jeepay内置列表里没有的渠道,不要改它的核心支付流程,而是走渠道扩展点。常见做法是新增一个渠道服务类,实现支付、退款、回调验证这几个方法,再把wayCode映射进支付方式表。以渠道适配器为例,核心代码骨架类似这样:

@Component public class MyCustomPayService extends AbstractPaymentService { @Override public String getWayCode() { return "MY_PAY"; } @Override public PayResultWrapper pay(PayOrder order) { // 在这里调用第三方渠道的下单接口 // 返回支付链接或二维码内容 } @Override public String verifyNotify(String params) { // 验签通过返回SUCCESS,失败返回FAIL } }

最关键的是getWayCode返回值必须和数据库中支付方式表的wayCode一致,否则商户端勾选支付方式后仍然路由不到这个服务。扩展完成后,在运营后台配置对应渠道参数,再向商户应用开放这个支付方式,流程和微信、支付宝完全一样。

6.2 上线前验证顺序与常用检查命令

我自己的习惯是按逆向链路验收:先确认数据库订单状态机正常,再模拟渠道回调,最后才走真实支付。上线清单里至少要有四项:回调地址公网可达、证书路径和序列号匹配、商户API密钥与IP白名单生效、重复回调幂等验证。也可以用常用命令做快速检查:

# 检查支付网关日志中的回调记录 tail -f logs/payment.log | grep notif # 查看订单状态,确认状态机流转 mysql -uroot -p jeepay -e "SELECT order_id, state FROM t_pay_order WHERE mch_order_no='20240901001';"

这些都是在真实环境里被验证过的顺序。我早年联调时先跑真实支付,结果回调地址配错,用户付了钱单子一直挂着,后来把所有环节拆成单点验证之后,就再没犯过同样的错。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询