ARTICLE DETAIL

资讯详情

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

Jeepay开源Java聚合支付系统:架构部署与二次开发实战

Jeepay开源Java聚合支付系统:架构部署与二次开发实战 简介Jeepay是一款采用Java开发的开源三方支付系统定位为互联网企业的聚合支付解决方案支持服务商与普通商户模式已对接微信、支付宝、云闪付官方接口并提供聚合码收款能力。资源包共1503个文件以924个Java后端源码和126个Vue前端页面为主配套SQL初始化脚本、YML配置、Docker部署文件以及项目说明文档体积仅4.06MB结构清晰适合直接导入开发工具进行二次开发或学习。系统基于Spring Boot与Ant Design Vue构建集成Spring Security权限管理覆盖商户管理、支付渠道、订单处理等核心模块适合支付开发入门者、需要私有化部署支付平台的技术团队以及研究开源架构的程序员。当前已有175人学习下载借由开源代码可深入理解支付对接流程、聚合支付实现与安全设计思路是兼具实用性与学习价值的支付系统源码。1. Jeepay到底是什么它解决的是商户和支付渠道之间的最后一公里做支付系统最痛苦的不是写代码而是你发现微信、支付宝、银联、聚合支付各有各的协议、密钥和回调规则每接一个渠道就要重写一遍对接逻辑。Jeepay就是冲着这个痛点来的一套Java语言开发的开源三方支付系统把「商户、平台、渠道」三层关系收拢到一个统一网关里让商户只需要对接你这一家剩下的路由、对账、分账、退款都由系统去跟上游渠道打交道。这个标题里的“三方支付”指的不是微信支付宝那种持牌支付机构而是站在商户和底层渠道之间的技术服务方。如果你正在选型一个能快速落地的支付中台或者公司准备自建一套聚合支付能力Jeepay是值得花时间研究的对象。它自带运营后台、商户后台和支付网关从部署到跑通一笔真实支付路径比从零写短得多。接下来我会按自己实际落地时的思路从架构拆解到部署、配置、二次开发再讲到那些会让新手翻车的细节尽量做到看完能动手。2. 拆开Jeepay一个Java支付系统应该长成什么样2.1 先理解它的分层网关、服务、后台不是一回事Jeepay不是一个单体应用而是按职责拆成多个可独立部署的模块。我第一次接触时最容易混淆的是“支付网关”和“支付后台”其实它们是完全不同的两个东西。支付网关jeepay-gateway是面向商户API的入口所有下单、查单、退款请求都打到这一层它负责验签、鉴权、转发到核心服务。支付服务jeepay-service才是真正干重活的地方它处理订单状态机、调用上游渠道、接收渠道回调、生成对账单。运营后台jeepay-manager是给平台运营人员用的管商户、管渠道、管费率。商户后台jeepay-merchant是给接入方看的查订单、配密钥、看报表。这个分层带来的直接好处是网关可以水平扩展扛流量后台服务可以单独调整订单处理的线程池而后台页面挂了也不影响线上支付链路。如果你在中小公司做架构千万别把这三个东西塞进一个进程里否则一次后台查询导致Full GC都可能拖垮下单接口。2.2 技术栈选型为什么是Spring Boot MyBatis而不是别家Jeepay的技术栈是典型的Java后端组合Spring Boot做微服务框架、MyBatisPlus做ORM、MySQL存业务数据、Redis管缓存和分布式锁定时任务用的是XXL-Job。这套组合在Java生态里属于“中庸但皮实”。Spring Boot自带嵌入式Tomcat和配置管理团队上手快MyBatis的SQL可控性在支付这种强事务场景里很重要——因为支付订单的查询条件非常复杂要按商户号、渠道、时间范围、订单状态各种维度组合用MyBatis的XML写动态SQL比JPA更直观。Redis在这里不是简单当缓存还承担了幂等键和分布式锁的职责。比如下单接口防重、回调处理防并发都是通过Redis的setnx实现的。如果你是非Java团队想接手这个项目门槛在于理解它的Spring容器管理方式和MyBatis的Mapper分层而不是Java语法本身。但话说回来既然标题写明“java语言开发”选型时就应该默认团队具备Java维护能力。2.3 核心表结构订单、商户、渠道的关系一眼看穿Jeepay的数据库设计值得花半天时间理清楚因为它决定了你二次开发时要动哪些表。最关键的三张表是t_mch_info商户信息表存储商户号、商户密钥、费率、状态。t_pay_order支付订单表是核心中的核心记录订单号、商户订单号、渠道订单号、支付金额、状态、回调状态。t_pay_channel支付渠道表记录每个商户开通了哪些渠道比如某个商户只开了微信支付没开支付宝那张表里就只会有对应记录。实际看代码时你会发现在t_pay_order里有个字段叫channel_order_no这个是上游渠道返回的单号。踩坑点在于有些渠道的回调里不保证channel_order_no跟下单请求时一模一样联调时一定要以渠道侧的最终返回为准而不是拿本地存的去比对。3. 本地跑通Jeepay从下载到收到第一笔回调3.1 环境准备JDK、MySQL、Redis一个都不能少Jeepay的部署依赖不算复杂但它对版本是有底线的。JDK要求1.8及以上我用的是1.8再高也没问题。MySQL建议5.7或8.0需要注意8.0以上默认的认证插件是caching_sha2_password而一些老版本驱动不认建议连接串里加上allowPublicKeyRetrievaltrue。Redis要求3.2以上没有特殊模块一个单机实例就够了。我一般会在本地用Docker起这些中间件省得污染宿主机。如果你是第一次跑建议直接用项目里的doc目录下的SQL脚本初始化数据库别手动建库因为里面的表结构和初始化数据是一体的。# 拉取项目源码以Git方式 git clone https://gitee.com/jeequan/jeepay.git cd jeepay # 初始化数据库先创建库再导入脚本 mysql -uroot -p -e CREATE DATABASE jeepay DEFAULT CHARACTER SET utf8mb4; mysql -uroot -p jeepay doc/jeepay.sql这段命令的逻辑是先拉源码再建一个utf8mb4字符集的库最后导入项目自带的初始化脚本。注意字符集必须用utf8mb4因为支付回调里可能带emoji之类的特殊字符用utf8会直接报错。3.2 启动服务四个进程按顺序起别颠倒Jeepay的启动顺序是有讲究的。数据库和Redis起来后先起jeepay-service等服务里定时任务注册完成再起jeepay-gateway然后起jeepay-manager和jeepay-merchant两个后台。颠倒顺序的后果是网关启动时检查数据库配置失败会直接退出而后台如果先起了登录时会报“服务未就绪”。每个服务都是标准的Spring Boot jar包用java -jar启动。建议加上JVM参数控制堆大小不然本地跑四个进程很容易把8G内存吃满。# 分别在不同终端启动示例为service和gateway java -Xms256m -Xmx512m -jar jeepay-service/target/jeepay-service.jar --spring.profiles.activedev java -Xms128m -Xmx256m -jar jeepay-gateway/target/jeepay-gateway.jar --spring.profiles.activedev这里的--spring.profiles.activedev是让Spring读取application-dev.yml里的配置。你需要在配置里把数据源地址、Redis地址改成自己本地的。最容易漏的是application-dev.yml里有个addons段里面是短信、邮件等扩展组件配置如果没用到可以直接置空不然启动时连不上邮件服务器会报错。3.3 配置商户和渠道跑通一笔真实的模拟支付服务起来后先用运营后台创建商户拿到商户号和应用ID再去商户后台配置API密钥。Jeepay的密钥是一对RSA公私钥商户私钥用来签名请求商户公钥上传给平台验证签名。渠道配置这一步要慎重。Jeepay支持模拟支付通道mock这个通道不会真扣钱非常适合本地联调。在运营后台的渠道列表里找到模拟支付绑定到商户上然后拿着商户号去调用网关的下单接口。# 模拟下单请求用curl演示注意替换参数 curl -X POST http://localhost:9216/api/pay/close \ -H Content-Type: application/json \ -d { mchNo: M1621873647, appId: 60cc09bce4b0a1a6a44e1c9a, reqTime: 1710000000000, version: 1.0, sign: 用商户私钥生成的签名字符串, mchOrderNo: TEST20240301001, wayCode: MOCK, amount: 100, subject: 测试商品, body: 本地联调 }上面这段是下单的关键请求。wayCode指定支付方式模拟通道就填MOCKamount单位是分100就是1块钱sign字段不是随便写的要用商户私钥对参数按ASCII排序后拼接、再做SHA256withRSA签名。如果签名不对网关会返回签名验签失败这是新手最常见的第一道坎。下单成功后模拟通道会直接返回支付成功并且触发回调。你可以检查t_pay_order表里的state字段变成2支付成功以及回调记录表里是否写了回调日志。到这里本地链路就算闭环了。4. 对接真实支付渠道参数、回调与状态机4.1 渠道接入的本质配置上游参数而不是写死代码很多人误以为接微信支付要改Java代码其实Jeepay把渠道适配器做成了可配置的。你只需要在运营后台添加渠道参数选好渠道类型填上上游分配的AppID、商户号、API密钥等网关就能把请求路由到对应渠道。以微信支付为例Jeepay的渠道配置项里需要填appId应用ID、mchId微信商户号、apiKey2APIv3密钥、serialNo商户证书序列号、privateKeyContent商户私钥内容。这些参数在微信商户平台都能找到但要注意apiKey2是APIv3的密钥不是旧版APIv2的32位密钥填错的话下单会报“签名错误”。支付宝渠道类似需要填appId、privateKey应用私钥、alipayPublicKey支付宝公钥。这里有个经典坑支付宝的公钥是“支付宝公钥”不是“应用公钥”很多人在开放平台复制错了。另外支付宝的sign_type默认是RSA2如果你从旧项目迁移过来用了RSAJeepay默认配置下会验签失败。4.2 回调验签为什么别人的回调能通你的总是验签失败回调是支付系统的命门。Jeepay收到上游渠道的回调通知后第一步是验签验签过了才更新本地订单状态。微信的支付回调是在HTTP body里放XML签名放在payInfo字段里需要按规则拼接后解密支付宝是在POST表单里返回sign字段需要拿公钥验签。我排查最多的回调问题是“支付成功但订单没变”。这时候先别急着查代码去t_pay_order表看state字段。如果渠道回调已经进入系统但验签失败订单会停留在“支付中”同时在t_mch_notify表里能看到回调记录。重点检查签名串的拼接顺序微信要求amount、mch_id、out_trade_no等字段按字典序排列但字段名必须以微信文档为准少了任何一环都会导致验签不通过。另一个高频坑是回调地址配置。Jeepay的网关地址决定了回调通知从哪里接收配置时一定要填网关的/api/chan/notify/{channelId}路径而且这个路径是公网可访问的。如果你只做本地测试模拟通道可以本地收但真实渠道必须保证网络可达否则上游根本通知不到你订单永远卡在那。4.3 订单状态机从下单到关闭每个状态怎么流转Jeepay的订单状态用数字表示对着源码看更清楚0是订单生成1是支付中2是支付成功3是支付失败4是已关闭。刚调用下单接口返回后订单是1支付中等渠道回调验签通过后变成2支付成功超时未支付在定时任务里触发关闭变成4。这里要特别提醒不要自己在业务系统里改状态。Jeepay的后台页面上有“关闭订单”功能但那是给异常情况兜底的。正常流程里订单关闭由定时任务扫描超时订单完成你在业务代码里如果也想搞一个定时关单逻辑很容易跟Jeepay的任务并发操作同一条记录导致状态回跳。正确的做法是监听Jeepay的回调通知来更新自己的业务订单而不是自己再去关一遍。分账和退款的状态也是一套独立逻辑。退款单有单独的t_refund_order表状态也是数字表示但退款回调与支付回调是分开的。如果你的业务涉及退款要确认你用的渠道在Jeepay里是否实现了退款接口——因为有些渠道适配默认没开退款需要在渠道参数里加allowRefund: true。5. 二次开发避坑从编译到上线的五个翻车点5.1 坑一源码编译报错原因是Maven仓库缺依赖Jeepay的某些依赖是放在私有仓库的直接从中央仓库拉会少几个包导致编译失败。现象是mvn compile报错提示找不到com.jeequan:jeepay-service之类的内部依赖。原因是项目里部分公共模块没有发到中央仓库需要你先本地安装。解决方法是先编译安装根目录下的jeepay-core和jeepay-components模块。# 先安装公共模块到本地仓库再编业务模块 mvn clean install -pl jeepay-core,jeepay-components -DskipTests mvn clean package -pl jeepay-service -DskipTests-pl指定模块列表-DskipTests跳过测试不然单测里连不上数据库就会挂。我遇到过有人跳过这一步直接mvn package结果整个项目报错还以为代码有问题实际上是依赖顺序没理清。5.2 坑二回调接口被上游访问不到了现象是生产环境订单支付成功但系统一直显示“支付中”日志里没有回调记录。排查下来多半是回调地址的端口没对外开放或者网关服务的部署环境没有配公网路由。原因是微信、支付宝的回调是上游服务器主动发起的必须能通过公网访问到你的网关端口。解决方法是确认网关服务的端口在安全组、防火墙层面是放行的并且回调地址用的是https://你的域名/api/chan/notify/xxx不能是内网IP。如果你在Nginx后面还要注意proxy_set_header Host和X-Real-IP要正确传递否则有些渠道校验回调IP时会拒绝。5.3 坑三金额精度问题——为什么99.99元变成9999分又变不回去Jeepay所有金额字段在数据库里以分为单位的整数存储这是对的但踩坑的人往往是在自己的业务系统里用double存了一笔金额然后转换时产生浮点误差。现象是下单时传了1000分但渠道回调后订单金额变成999分或1001分。原因可能是你在业务侧做了除法或乘法没有用BigDecimal或者在下单请求里把元转换成分时直接写了(int)(amount * 100)。解决方法是统一用BigDecimal而且在下单请求前就确保单位是分。Jeepay的SDK里有金额工具类能不用自己写就别自己写。5.4 坑四定时任务重复执行导致订单被错误关闭Jeepay用XXL-Job跑定时任务如果你没有配分布式锁或者把任务调度配置搞错了会出现两个任务同时扫描订单把刚支付的订单误关闭。现象是日志里出现同一条订单被关闭两次或者支付中的订单突然变成关闭状态。原因是Jeepay的任务通过Redis分布式锁控制并发但如果你用了多个实例却共用同一个Redis库且任务名配置不一致锁就会失效。解决方法是确保所有实例连同一个Redis任务调度中心的名字也要一致。另外关闭订单的任务应该延迟执行建议把超时时间设置成“当前时间-支付超时时间-5分钟”给支付回调留出缓冲。5.5 坑五数据库连接耗尽网关接口全部超时现象是压测时一开始每秒500笔下单很顺利突然全部超时看后台日志全是“Connection pool exhausted”。原因是默认连接池配置太小或者代码里在事务内跑了一次远程调用把连接占住了。Jeepay默认用的HikariCP配置在application.yml里的spring.datasource.hikari.maximum-pool-size字段。解决方法是把生产环境的maximum-pool-size调到50以上connection-timeout调到30000毫秒。更重要的是排查自己的代码如果在事务里调了钉钉通知、短信验证码这类外部接口要把它挪到事务外面否则一个请求卡了整个连接池就被拖垮。6. 进阶用法把Jeepay从demo变成生产可用系统的验证清单我看到很多人部署完Jeepay能跑通模拟支付就以为上线了其实离生产可用还差很远。我自己做过的检查清单里第一项就是对账任务。Jeepay自带每日对账单但默认是用SQL聚合生成的跟上游渠道的账单做比对时需要额外写一个对账脚本把t_pay_order表和渠道账单拉平逐笔核对金额和状态差异记录到异常表里。这一步不做等月底发现钱对不上再翻历史数据就太被动了。第二项是支付密码和密钥轮换。Jeepay商户端的API密钥支持重置但要注意重置后旧的实时回调签名会失效。生产环境建议每三个月轮换一次商户密钥轮换之前先切换双密钥配置确保旧回调还能验签通过再过期旧密钥。第三项是网关层的幂等。Jeepay自己的网关已经对商户请求做了幂等但如果你在网关前面又加了一层自己的API聚合那层也需要做同样的防重。用的是mchOrderNo作为幂等键Redis里存一份请求状态重复请求直接返回原状态而不是重新发起支付。这样做的好处是商户侧重试不会把订单搞乱。第四项是监控告警。支付系统最不能等的就是静默失败。我习惯对t_pay_order表做两个监控支付成功率低于98%时告警以及超过5分钟没有支付单时的空转告警。告警通道用钉钉机器人或企业微信代码很简单但那几次深夜被喊起来看问题的经历让我深知这比任何架构优化都值钱。如果只让我给一条经验那就是永远不要在生产环境让商户后台和运营后台共用一个域名。线上商户后台可以放公网但运营后台必须限制IP白名单或者直接放到内网。否则一旦后台账号泄露有人改一下商户费率损失可能比一次渠道故障还大。我见过不止一次因为后台权限边界没做好而出现资损的事故。Jeepay是一个把支付系统骨架做得相当完整的开源项目但生产环境的稳定性靠的是运维细节不是代码本身。先把本地链路跑通再按这四步去加固你会少踩很多坑。希望这篇笔记能帮你把Jeepay真正落地到业务里祝顺利。本文还有配套的精品资源点击获取
返回列表