ARTICLE DETAIL

资讯详情

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

Java开源支付系统Jeepay:聚合支付与四方支付技术实践指南

Java开源支付系统Jeepay:聚合支付与四方支付技术实践指南 简介这是一套基于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或11Maven用3.6以上数据库用MySQL 5.7或8.0Redis必须是可用状态因为验证码、登录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; # 导入初始化SQLjeepay.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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword redis: host: 127.0.0.1 port: 6379 jeepay: # 支付网关对外暴露的域名或IP回调地址会基于这个配置生成 pay-address: http://127.0.0.1:9216datasource那段是最容易出问题的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.activeprod java -jar jeepay-op/target/jeepay-op.jar --spring.profiles.activeprod java -jar jeepay-merchant/target/jeepay-merchant.jar --spring.profiles.activeprod启动完看日志里的端口监听。如果启动过程报“无法连接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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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_no20240901001;这些都是在真实环境里被验证过的顺序。我早年联调时先跑真实支付结果回调地址配错用户付了钱单子一直挂着后来把所有环节拆成单点验证之后就再没犯过同样的错。希望帮到你。本文还有配套的精品资源点击获取
返回列表