Jeepay开源聚合支付系统:微信/支付宝/云闪付三端统一接入
发布时间:2026/10/9 7:56:18 锦皓数字建站

简介这是一套全开源的Java聚合支付系统Jeepay面向中高级Java开发者、支付平台架构师及金融科技领域技术团队用于快速构建支持多渠道接入的四方支付平台。资源包含387个文件以322个Java核心业务与配置类、28个XML配置文件、7个YML环境配置及2个SQL数据库脚本为主覆盖前后端分离架构下的网关路由、签名验签、MQ订单通知、Spring Security权限控制等关键模块压缩包仅6.8MB轻量易部署。已有816人学习下载适合需要深度理解支付系统安全设计、分布式高并发实践及多支付渠道微信V2/V3、支付宝RSA/RSA2、云闪付集成方案的开发者。读者可直接运行管理后台与商户系统复用HTTP接口与多语言SDK参考自动化参数配置界面与消息可靠性保障机制快速开展二次开发与生产级适配。1. Jeepay 聚合支付系统一个能真正在生产环境跑通微信/支付宝/云闪付的 Java 全开源支付中台你手头有个 SaaS 多商户系统刚上线就被商户追问“能不能用微信扫码支付宝直连云闪付碰一碰”——别急着找外包或买商业 SDK。Jeepay 就是那个你翻遍 GitHub、Gitee 和各大技术论坛后真正能 clone 下来、改两行配置、十分钟内跑通真实支付回调的 Java 全开源聚合支付系统。它不是教学 Demo也不是“仅支持模拟”而是已在线上稳定运行超 3 年、支撑日均 20 万笔交易的生产级支付网关。核心价值在于把微信服务商 V3、支付宝 RSA2、云闪付多机构路由这些黑匣子接口封装成统一 HTTP API 自动化参数配置界面 MQ 可靠通知链路。适合中小团队自建支付能力、独立站开发者对接多渠道、Java 工程师练手分布式事务与高并发支付场景。如果你正被“支付回调验签失败”“渠道参数配错导致订单飞单”“消息丢失导致商户收不到通知”这类问题卡住Jeepay 的源码结构和工程实践就是一份血泪经验整理版教科书。2. 从源码结构到核心模块为什么 Jeepay 能稳住微信/支付宝/云闪付三端流量Jeepay 不是简单拼凑几个 SDK它的架构设计直指聚合支付三大痛点渠道异构性、安全一致性、通知可靠性。我们拆开jeepay.zip看真实目录结构非.gitkeep占位符而是实际生效模块jeepay/ ├── jeepay-admin/ # 运营后台Vue3 Spring Boot ├── jeepay-merchant/ # 商户系统前后端分离商户自助管理 ├── jeepay-gateway/ # 支付网关核心Spring Boot MyBatis-Plus │ ├── controller/ # 统一入口/api/pay/create、/api/pay/notify │ ├── service/ # 渠道路由逻辑根据商户配置自动选择微信/支付宝/云闪付 │ ├── channel/ # 各渠道实现wxpay-v2/、wxpay-v3/、alipay-rsa2/、unionpay-cloud/ │ └── mq/ # 基于 RocketMQ/RabbitMQ 的订单通知模块 ├── jeepay-common/ # 公共工具签名生成器、AES/RSA 加解密、JSON 序列化适配 └── jeepay-db/ # 初始化 SQL含商户表、渠道配置表、订单表、MQ 消息表2.1 渠道自动路由机制不是 if-else而是策略工厂 配置驱动Jeepay 的路由不写死在代码里而是通过数据库配置动态加载。关键逻辑在PayChannelService.java中// 根据商户号 支付方式 金额区间查询匹配的渠道配置 PayChannelConfig config payChannelConfigMapper.selectByMerchantAndType( merchantNo, payOrder.getPayWay(), payOrder.getAmount() ); // 实例化对应渠道处理器Spring Bean 名称由配置决定 String beanName channelHandler config.getChannelCode().toUpperCase(); ChannelHandler handler (ChannelHandler) applicationContext.getBean(beanName);提示channelCode字段值为WX_JSAPI、ALIPAY_WAP、UNIONPAY_QR等对应WxJsapiChannelHandler、AlipayWapChannelHandler等具体实现类。这种设计让新增渠道只需新增 Handler 类 插入配置记录无需改核心路由逻辑。2.2 微信 V3 接口深度适配解决证书加载、平台证书轮换、敏感字段加密三大坑微信 V3 要求使用平台证书解密回调内容且证书每 30 天轮换。Jeepay 在WxV3NotifyController.java中做了完整闭环// 1. 从数据库读取当前有效平台证书支持多证书并存 PlatformCert platformCert platformCertMapper.selectLatestByMchId(mchId); // 2. 使用证书解密回调 body 中的 resource.encrypted_message String plainText AesUtil.decryptToString( platformCert.getAesKey(), platformCert.getAssociatedData(), platformCert.getNonce(), notifyData.getResource().getEncryptedMessage() ); // 3. 解析 JSON 并校验签名微信要求对原始 JSON 字符串验签非解析后对象 boolean valid WxV3Signature.verify( plainText, notifyData.getSign(), platformCert.getPublicKey() );参数说明AesUtil.decryptToString()是 Jeepay 封装的 AES-GCM 解密工具WxV3Signature.verify()严格按微信文档要求对未解析的原始字符串做 SHA256withRSA 验签。很多项目翻车就栽在“先 JSON.parse 再验签”导致验签失败。2.3 支付宝 RSA2 签名与验签兼容老商户RSA与新商户RSA2的双模式支付宝接口同时存在 RSASHA1withRSA和 RSA2SHA256withRSA两种签名算法。Jeepay 在AlipayConfig.java中通过signType字段区分// 根据数据库配置的 sign_type 动态选择签名器 if (RSA2.equals(config.getSignType())) { return new DefaultAlipayClient( config.getGatewayUrl(), config.getAppId(), config.getPrivateKey(), // 商户私钥PKCS8格式 json, UTF-8, config.getAlipayPublicKey(), // 支付宝公钥 RSA2 // 关键指定算法 ); } else { // RSA 模式SHA1withRSA ... }注意config.getPrivateKey()必须是 PKCS8 格式私钥OpenSSL 生成命令openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt直接用 PKCS1 会报InvalidKeyException: IOException: ObjectIdentifier not found。2.4 云闪付多机构路由不是只接一家而是可配置切换银联、平安、拉卡拉等通道云闪付并非单一接口而是接入不同收单机构如银联商务、平安付、拉卡拉。Jeepay 在UnionpayChannelHandler.java中抽象出UnionpayInstitution接口public interface UnionpayInstitution { String getInstitutionCode(); // 机构编码UNIONPAY、PINGAN、LAKALA String buildQrCodeUrl(PayOrder order); // 生成不同机构的二维码 URL boolean verifyNotify(String notifyBody, String sign); // 各机构验签规则不同 } // 数据库配置中指定 institution_code运行时注入对应实现 Bean ConditionalOnProperty(name unionpay.institution.code, havingValue PINGAN) public UnionpayInstitution pinganInstitution() { return new PinganUnionpayInstitution(); }价值点当某家机构风控收紧或费率上调运营人员只需在后台修改商户配置中的institution_code无需发版即可切换通道真正实现“渠道热插拔”。3. 部署与启动从零搭建一个可对接真实支付的 Jeepay 环境Jeepay 是标准 Spring Boot 项目但生产部署有若干关键依赖需提前确认。以下步骤基于 CentOS 7 JDK 11 MySQL 5.7 Redis 6 RocketMQ 4.9 环境官方推荐组合。3.1 数据库初始化执行建表 基础配置数据含默认测试商户Jeepay 使用jeepay-db/src/main/resources/sql/jeepay.sql初始化全量表结构。必须执行以下三步创建数据库jeepay字符集 utf8mb4排序规则 utf8mb4_unicode_ci执行jeepay.sql含 28 张表重点t_pay_order、t_mch_info、t_channel_config、t_mq_msg手动插入一条测试商户记录否则后台无法登录INSERT INTO t_mch_info ( mch_id, mch_name, mch_type, state, app_id, app_secret, notify_url, return_url, created_at, updated_at ) VALUES ( MCH_202400000001, 测试商户, 1, 1, APP_202400000001, test_app_secret_123, https://your-domain.com/api/notify, https://your-domain.com/return, NOW(), NOW() );注意mch_id是全局唯一商户号app_id和app_secret用于商户系统调用网关 API 的身份认证务必记牢。3.2 修改核心配置文件application.yml重点改这 5 处jeepay-gateway/src/main/resources/application.yml是启动成败关键。以下字段必须按实际环境修改配置项示例值说明spring.datasource.urljdbc:mysql://127.0.0.1:3306/jeepay?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseserverTimezoneAsia/ShanghaiMySQL 连接地址必须加serverTimezoneAsia/Shanghai否则时间字段写入异常jeepay.redis.host127.0.0.1Redis 地址用于分布式锁和缓存jeepay.mq.typerocketmq可选rocketmq或rabbitmq对应jeepay-mq-rocketmq或jeepay-mq-rabbitmq模块jeepay.wxpay.v3.mchId1900000100微信服务商商户号非普通商户号jeepay.alipay.appId20881021745XXXXXX支付宝应用 AppID提示微信 V3 私钥和平台证书路径需指向绝对路径如/opt/jeepay/cert/wx_v3_key.pem不能用classpath:因证书需实时读取。3.3 启动网关服务验证 HTTP 接口是否就绪编译并启动jeepay-gateway模块cd jeepay-gateway mvn clean package -Dmaven.test.skiptrue java -jar target/jeepay-gateway-1.0.0.jar启动成功后访问http://localhost:8080/actuator/health应返回{status:UP}。再测试基础支付接口curl -X POST http://localhost:8080/api/pay/create \ -H Content-Type: application/json \ -d { mchId: MCH_202400000001, appId: APP_202400000001, payWay: WX_JSAPI, amount: 1, subject: 测试商品, body: 测试商品详情, notifyUrl: https://your-domain.com/api/notify, clientIp: 127.0.0.1 }预期响应返回{code:0,msg:success,data:{payOrderId:PAY_202405201023456789,payData:{...}}}其中payData包含微信 JSAPI 所需的timeStamp、nonceStr、package、signType、paySign五要素。若返回code1且msg含“渠道配置不存在”说明t_channel_config表未配置微信渠道。3.4 启动管理后台配置渠道参数与商户信息jeepay-admin是 Vue3 前端需单独构建cd jeepay-admin npm install npm run build # 构建产物 dist/ 目录复制到 nginx html/ 下Nginx 配置示例反向代理网关location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }访问http://your-domain.com用默认账号admin/admin123登录。进入【渠道管理】→【添加渠道】填写微信服务商信息AppID、MCH_ID、APIv3密钥、证书路径进入【商户管理】→【编辑商户】绑定已配置的渠道。关键动作在商户编辑页勾选“启用渠道”并保存。否则该商户调用支付接口时会路由失败。4. 避坑指南微信回调验签失败、MQ 消息丢失、渠道参数配错的 5 个真实翻车现场Jeepay 开箱即用但生产环境踩坑率极高。以下是我在三个客户项目中复现并定位的典型问题按“现象 → 原因 → 解决”结构整理拒绝玄学排查。4.1 现象微信支付回调返回{code:1,msg:验签失败}但本地用相同参数验签成功原因微信回调 Body 是原始 JSON 字符串而 Jeepay 默认将请求体转为 Map 后再验签导致 JSON 键值顺序改变、空格增删破坏原始字符串完整性。解决在WxV3NotifyController.java中禁用 Spring Boot 的自动 JSON 解析改用RequestBody byte[]获取原始字节流PostMapping(value /wx/v3/notify, consumes MediaType.APPLICATION_JSON_VALUE) public ResponseEntityString wxV3Notify(RequestBody byte[] rawBody) { String body new String(rawBody, StandardCharsets.UTF_8); // 直接对 body 字符串验签不 parse if (!WxV3Signature.verify(body, request.getHeader(Wechatpay-Signature), ...)) { return ResponseEntity.status(401).build(); } // 后续解析 body 为对象 WxV3NotifyData data JSON.parseObject(body, WxV3NotifyData.class); }4.2 现象支付成功后商户系统收不到订单通知t_mq_msg表中状态为WAITING原因RocketMQ NameServer 地址配置错误或t_mq_msg表中max_wait_time最大等待时间设置过短默认 300 秒消息重试超时后被丢弃。解决检查application.yml中jeepay.mq.rocketmq.namesrvAddr是否可达telnet namesrv_ip 9876将t_mq_msg.max_wait_time改为8640024 小时并在jeepay-mq-rocketmq模块中调整重试策略// 修改 RetryMessageListener.java Override public void onException(Throwable e) { if (e instanceof MQClientException e.getMessage().contains(No route info)) { // 网络不通时延迟 60 秒后重试而非立即失败 try { Thread.sleep(60000); } catch (InterruptedException ignored) {} } }4.3 现象支付宝回调验签通过但out_trade_no在 Jeepay 订单表中查不到原因支付宝回调参数out_trade_no是商户订单号但 Jeepay 默认从notify_data.out_trade_no提取而部分支付宝版本回调中该字段名为out_trade_no正确或out_trade_no文档错误实际字段名是out_trade_no。解决在AlipayNotifyController.java中兼容两种字段名String outTradeNo notifyData.getOutTradeNo(); if (StringUtils.isBlank(outTradeNo)) { // 兼容旧版支付宝回调字段名 outTradeNo (String) notifyData.get(out_trade_no); } PayOrder order payOrderMapper.selectByMchIdAndOutTradeNo(mchId, outTradeNo);4.4 现象云闪付扫码支付返回{code:1,msg:机构配置不存在}原因t_channel_config表中channel_code字段值为UNIONPAY_QR但t_mch_info表中该商户的unionpay_institution_code为空或非法值如UNIONPAY拼错为UNION_PAY。解决查询商户配置SELECT unionpay_institution_code FROM t_mch_info WHERE mch_id MCH_XXXX;确保值为UNIONPAY、PINGAN或LAKALA全大写无下划线若为空执行UPDATE t_mch_info SET unionpay_institution_codeUNIONPAY WHERE mch_idMCH_XXXX;4.5 现象启动时报Caused by: java.lang.NoClassDefFoundError: org/apache/rocketmq/client/producer/DefaultMQProducer原因jeepay-gateway的pom.xml中jeepay-mq-rocketmq模块 scope 为runtime但 IDEA 默认不加载 runtime 依赖导致编译期找不到类。解决Maven 命令行启动mvn clean package -Dmaven.test.skiptrue正确IDEA 启动点击右上角Edit Configurations→Modify options→ 勾选Include dependencies with Provided scope或临时改为compilescope上线前切回runtime。血泪经验所有涉及 MQ、Redis、MySQL 的连接务必在application.yml中配置testWhileIdle: true和validationQuery: SELECT 1否则连接池空闲后失效首笔支付必失败。5. 生产级加固分布式锁防重复支付、幂等性设计、敏感信息脱敏的落地技巧Jeepay 开箱提供基础功能但要扛住秒杀、高并发、恶意刷单必须做三件事锁住创建、锁住通知、锁住查询。这不是理论是我在某电商大促中压测 5000 TPS 后沉淀的硬核技巧。5.1 支付订单创建Redis 分布式锁 唯一索引双重保险Jeepay 默认用数据库唯一索引uk_mch_id_out_trade_no防重但高并发下仍可能因网络重试导致重复插入。我在PayOrderService.java中加了一层 Redis 锁// 生成锁 KeymchId outTradeNo 的 MD5 String lockKey DigestUtils.md5Hex(mchId outTradeNo); Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofSeconds(30)); if (!locked) { throw new BizException(订单创建中请勿重复提交); } try { // 1. 检查数据库是否存在同商户同订单号 if (payOrderMapper.selectByMchIdAndOutTradeNo(mchId, outTradeNo) ! null) { return buildSuccessResult(...); // 直接返回已有订单 } // 2. 创建新订单 payOrderMapper.insert(order); } finally { redisTemplate.delete(lockKey); // 必须 finally 释放 }参数说明Duration.ofSeconds(30)是锁过期时间必须大于数据库事务执行时间通常 5s避免业务未完成锁已释放。5.2 支付回调通知MQ 消息 数据库状态机 幂等 Key 校验Jeepay 的 MQ 通知已很可靠但为防极端情况如 MQ 重复投递我在NotifyService.java中加入幂等控制// 幂等 Key渠道 商户号 支付订单号 通知时间戳精确到秒 String idempotentKey String.format(%s_%s_%s_%s, channelCode, mchId, payOrderId, LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS) ); // 使用 Redis SETNX 原子操作校验 Boolean isProcessed redisTemplate.opsForValue() .setIfAbsent(idempotent: idempotentKey, 1, Duration.ofHours(24)); if (!isProcessed) { log.warn(重复通知已处理过{}, idempotentKey); return; // 直接返回不执行后续逻辑 } // 执行更新订单状态、通知商户等业务 updateOrderStatus(payOrderId, PayStatus.SUCCESS); notifyMerchant(mchId, payOrderId);价值点truncatedTo(ChronoUnit.SECONDS)确保同一秒内多次通知只处理一次既防重放又不误杀正常重试。5.3 敏感信息脱敏日志中自动过滤银行卡号、身份证号、手机号Jeepay 日志默认打印全部请求参数存在合规风险。我在LogAspect.java中定义脱敏规则Around(annotation(org.springframework.web.bind.annotation.PostMapping)) public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args joinPoint.getArgs(); if (args.length 0 args[0] instanceof HttpServletRequest) { HttpServletRequest request (HttpServletRequest) args[0]; String body IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); // 脱敏正则银行卡号16-19位数字、手机号11位、身份证号18位 String safeBody body.replaceAll(\\d{16,19}, **** **** **** ****) .replaceAll(1[3-9]\\d{9}, 1** **** ****) .replaceAll(\\d{17}[0-9Xx], *****************); log.info(Request Body: {}, safeBody); } return joinPoint.proceed(); }注意此方案仅适用于调试日志。生产环境应配置 Logback 的MaskingPatternLayout在日志落盘前脱敏避免内存中明文留存。5.4 渠道参数配置自动化从数据库字段生成前端表单省去 80% 配置页面开发Jeepay 的渠道配置界面不是硬编码 HTML而是读取t_channel_param表动态渲染。表结构如下字段示例值说明channel_codeWX_JSAPI渠道编码param_keymchId参数名对应 Java Bean 字段param_name微信商户号前端显示名称param_typestring类型string / number / boolean / file证书上传required1是否必填default_value默认值前端 Vue3 组件ChannelParamForm.vue通过GET /api/channel/param?channelCodeWX_JSAPI获取配置列表循环生成el-input v-modelform[paramKey] /或el-upload。新增渠道时只需往表中插入几行配置界面自动生成。实战效果接入某地方银行快捷支付时从拿到接口文档到上线配置界面仅用 2 小时——因为不用写一行前端代码只填数据库。从那以后我每次给新渠道做对接都强制走一遍“建表 → 插配置 → 测接口 → 写 Handler”的四步流程绝不跳过数据库配置环节。因为 Jeepay 的强大不在代码多炫酷而在它把支付这个黑盒子拆成了可配置、可替换、可审计的标准化模块。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。