H5十四合一代付系统源码详解:架构、部署与二次开发指南

发布时间:2026/10/26 9:40:08
H5十四合一代付系统源码详解:架构、部署与二次开发指南

简介:全开源可二次开发的H5十四合一代付系统源码,适用PHP7.4与MySQL5.7环境,面向需要快速搭建聚合代付平台的开发者或运营者。程序内置美团、京东、拼多多、滴滴、携程、抖音、淘宝闪购、得物等14套模板,支持账号密码登录、代理分成、用户提现、商品审核上架、后台运营大屏及多支付接口对接。近期更新修复了分享卡片显示、本地化JS/CSS加载、商品展示逻辑等问题,并增强订单筛选、后台手动添加用户、卡片图片自定义等能力。资源共112个文件,以25个PHP业务脚本为主,辅以HTML页面、JS/CSS前端样式、图片素材、SQL数据库文件及部署说明文档,压缩包仅9.46MB,部署便捷。已有231人学习使用。源码完全开放,便于按自身业务定制模板、支付流程与分成规则,适合学习支付系统架构或直接搭建运营。

1. H5十四合一代付系统是什么:它解决的是结算侧的最后几公里

做了这么多年支付相关的业务系统,我最怕听到的需求就是“给用户批量打款”。如果你也接过类似“每日结算佣金、退款、分成”这类开发单,大概率会搜到“H5十四合一代付系统源码”这个方向。它不是支付网关,而是一套把十四种代付渠道聚合到同一个后台、对外提供统一 H5 页面和接口的资金结算系统;全套源码开源,附搭建教程,能按自己业务改,这正好切中“要可二次开发”的团队。

这套方案解决的最后一公里问题是:业务方手里有钱,但需要把零碎资金打到用户银行卡或钱包,逐笔手工操作不现实,接一家渠道又怕被单一接口限制住。它适合内部研发资源有限、又要同时对接多个支付通道的小型业务方,也适合做聚合支付的开发团队拿来做底座。先说清楚边界:它定位是企业自有业务的资金结算工具,不是牌照业务,别拿去做两清或公共收款,合规这关过不去,技术再好也白搭。

2. 源码结构先看这四个地方:通道层、路由层、回调层与对账任务

拿到一套这类项目的源码,别急着打包运行,先看目录里那套代码的组织方式。代付系统比普通 CRUD 敏感,它动的是真金白银,架构设计直接决定你后续能不能安稳睡好觉。常见的项目包无论叫什么名字,核心模块基本都绕不开四个:通道层、路由层、回调层和支撑任务层。前三个决定资金流是否顺畅,第四个决定账目是否可靠。

2.1 通道层:每个支付渠道都是一个独立适配器

十四合一的“十四”,在代码里通常体现为十四个类,而不是十四段散落的 if-else。每一个支付渠道被封装成一个适配器,对外暴露统一接口。这样做的理由很直接:不同渠道的接口报文、签名方式、同步返回格式完全不一样,你把差异局部封装后,上层业务感知不到渠道区别。

这类系统的通道接口通常长这样:

public interface PayChannelAdapter { /** * 渠道编码,要全局唯一,比如 alipay、wxpay、unionpay */ String channelCode(); /** * 提交一笔代付申请 * order 里至少要包含订单号、金额(单位:分)、收款账号、真实姓名 */ PayResult submit(WithdrawOrder order); /** * 主动查单,用于定时任务补偿 */ QueryResult query(String channelOrderNo); /** * 回调验签,不同渠道验签逻辑差异很大 */ boolean verifyNotify(NotifyRequest request); }

submit 是提交代付,query 是主动查单,verifyNotify 是回调验签。注意金额统一用“分”做整数,这是代付系统最常见的设计约束,后面会专门展开。接口里不写任何渠道特有字段,渠道参数从配置文件或数据库字典里取,避免渠道差异污染核心流程。

每个渠道实现一个 Adapter 类,例如 AlipayAdapter、WxpayAdapter。新增渠道时你只需要新增类并注册,不需要改动提现代付和回调处理的公共逻辑。这是“可二次开发”在这个项目里最重要的体现:做加法,不改核心。

2.2 路由层:十四合一不是十四个接口各调各的

有了通道层,接下来就是路由层。路由层解决的是“这笔代付应该走哪个渠道”的问题。系统里配置了十二个可用渠道,可不代表每笔单子都可以随便挑一个。有的渠道单笔限额五万,有的单笔限额五百;有的渠道费率低但清算慢,有的费率贵但秒到。路由层把这些差异变成规则,而不是靠人在后台手工选。

常见做法是建一张路由配置表,字段包含渠道编码、适用的金额区间、优先级、时段、状态。系统提交代付前按路由规则过滤和排序,逐个尝试:

List<ChannelPriority> routes = channelRouteService.getRoutes(merchantId, order.getAmount()); for (ChannelPriority priority : routes) { PayChannelAdapter adapter = adapterFactory.get(priority.getChannelCode()); PayResult result = adapter.submit(order); if (result.isSuccess()) { return result; } }

这段代码是路由的骨架逻辑。getRoutes 先把不可用渠道过滤掉,再按优先级排列;submit 失败就尝试下一个渠道。这里有个细节很容易被忽略:渠道返回失败不代表一定失败,可能是超时、可能是报文不符合要求,所以“失败即换渠道”的策略要谨慎,后面查单机制会兜底。

路由规则不是死的,我一般建议把“优先级”和“启用状态”做成后台可配置,而不是每次改代码重启。这样运营同学可以根据渠道故障情况临时切换,不用半夜叫醒开发。

2.3 回调层与状态机:代付单状态不能靠猜

代付单的状态流转比支付收款要长。支付收款是“用户付钱给你,回调确认即结束”,代付则是“你提交单子、渠道受理、渠道打款、回调通知结果”。整个过程不是一瞬间完成的,中间可能卡住几分钟甚至几小时。

所以代码里必须有一个清晰的订单状态机。常见的代付状态是这样的:

public enum WithdrawStatus { INIT, // 已创建,待提交 PROCESSING, // 已提交渠道,等回调 SUCCESS, // 代付成功 FAILED, // 代付失败 MANUAL // 人工介入处理 }

INIT 表示用户发起了提现申请但还没提交给渠道;PROCESSING 是提交给渠道后等待回执;SUCCESS 和 FAILED 是终态;MANUAL 是出现异常单时运营介入后的状态。回调到达后,只有在 PROCESSING 状态才允许被更新成 SUCCESS 或 FAILED,其他状态直接忽略。

这样设计有一个实际好处:防止乱覆盖。如果运营手动把一笔单子改成 MANUAL 并线下打款,此时渠道回调晚到一步,如果系统没有状态校验就会把人工处理结果覆盖掉,账就乱了。

2.4 支撑模块与那个 H5 前端分别负责什么

全开源项目里通常有两套前端:一套是 H5 用户端,一套是管理后台。很多人第一次看代码容易把这两套弄混。H5 用户端面向的是手机浏览器,用户在这里绑卡、发起提现申请、查看进度;管理后台面向的是运营和财务,负责审核、打款、渠道配置和对账。

H5 端通常是轻量页面,列表页加提现表单加几页状态展示,用 Vue 或 React 写的居多,打包后是纯静态文件。它和用户系统打通后,用户进来能看到可提现余额,提交提现申请后生成一张代付单,后续就是后端的事。管理后台则是重逻辑,渠道参数、路由优先级、费率都在这里配。理解这个分工后,你部署时就不会纠结 H5 前端该不该连数据库——它不该,它只需要调后端接口。

支撑模块里还有一块容易被忽视:定时任务。代付系统一定要有定时查单和自动对账任务。定时任务把处于 PROCESSING 状态的单子捞出来,循环调 query 查渠道结果;对账任务则把本地订单和渠道账单做比对。这两块在初版源码里有的有、有的只是留了接口,二次开发时最值得先补的就是它。

3. 从零搭建到上线:环境准备、数据库导入与前后端启动的完整步骤

这类项目我搭建过不少,第一步从来不是急着跑代码,而是确认环境。整套系统是 Java 技术栈,不是那种用 PHP 写的单文件源码,类型约束和并发处理上更稳当,但对应的部署门槛也高一点。下面按我实际的部署顺序走一遍,每一步的参数都给你标清楚。

3.1 环境选型:JDK、MySQL、Redis、Nginx 这套四件套怎么配

先准备一台 Linux 云服务器,配置不用太高,2 核 4G 起步就能跑动整套系统。JDK 建议 8 或者 17,具体看项目 pom 文件里指定的编译版本,别拿高版本 JDK 跑旧项目,容易出反射异常;MySQL 用 5.7 或 8.x 都可以,但字符集务必设置为 utf8mb4;Redis 装最新稳定版即可,存登录态和接口幂等键用;Nginx 做 H5 静态页服务和后端反向代理。

这里有个容易踩坑的点:JDK 版本和 Spring Boot 版本要匹配。如果项目用的是 Spring Boot 2.x,用 JDK 8 最省事;如果你非要上 JDK 17,需要确认 pom 里的依赖有没有兼容问题。我一般直接用项目管理工具自带的版本锁定,不手动升级大版本。

3.2 数据库初始化:先建库再改 application.yml

项目包里通常带一个 SQL 脚本,文件名一般是 init.sql 或者 install.sql。先创建独立数据库,再用命令行导入脚本:

mysql -uroot -p -e "CREATE DATABASE pay_merger DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -p pay_merger < init.sql

导入成功后别急着继续,先登录数据库看一眼核心表:代付订单表、商户表、渠道配置表、路由规则表。如果表都不存在,说明脚本没导对;如果表存在但没有任何字典数据,那你后面还要手工补渠道参数,这个后面会讲。

接下来改后端配置文件。常见路径是src/main/resources/application.yml,需要改三处连接信息:

spring: datasource: url: jdbc:mysql://127.0.0.1:3306/pay_merger?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: pay password: ChangeMe123 redis: host: 127.0.0.1 port: 6379 database: 0

注意 URL 里那串参数不要删:characterEncoding=utf8 保证中文不乱码,serverTimezone=Asia/Shanghai 保证时间不错八小时。数据库密码建议单独用环境变量注入,别硬编码进 yml,源码要提交到 Git 的时候,密钥和密码都要避免出现在配置文件里。这不是做样子,是防止仓库泄露后被人拖库。

3.3 后端打包启动:两条命令但顺序不能乱

后端如果是 Maven 项目,打包就一条命令:

mvn clean install -DskipTests

第一次执行会下载很多依赖,耗时取决于服务器带宽。打包完成后,根据模块名找到对应的 jar,比如有的项目叫 merchant-admin,有的叫 xxx-pay-service。以后台模块为例:

java -jar merchant-admin/target/merchant-admin.jar --spring.profiles.active=prod

启动顺序有讲究。必须先保证 MySQL 和 Redis 都启动了,再启动后端的这个模块;如果后端启动就在报连不上数据库,回头检查数据库时候会浪费时间。看启动日志里出现 “Started” 关键词才算起来成功,不要看到进程没报错就当没事。

启动成功后,验证一下后端管理台的登录接口。用 curl 在服务器本地访问一下健康检查路径,很多项目会暴露 /actuator/health 端口,返回 JSON 里 status 是 UP 就说明没问题。

3.4 H5 前端打包与 Nginx 反向代理

H5 前端是独立工程,目录里常见的是h5或web-h5。先安装依赖再打包:

npm install npm run build

打包产物会生成到 dist 目录。把 dist 目录里的静态文件传到服务器,假设放到/data/www/h5,然后配置 Nginx 把 H5 和后端接口串起来:

server { listen 443 ssl; server_name h5.pay.example.com; ssl_certificate /etc/nginx/ssl/server.pem; ssl_certificate_key /etc/nginx/ssl/server.key; root /data/www/h5; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

关键在 location /api/ 这一段,转发到后端 Java 服务的端口。这里有个新手翻车点:H5 页面能打开但接口全部 404,十有八九是 proxy_pass 后面的路径写错了。注意/api/结尾的斜杠和http://127.0.0.1:8080/api/结尾的斜杠要保持一致的路径语义,后端要求的上下文路径不同,这里的处理也不同,以项目里的后端约定为准。

HTTPS 证书建议直接用云服务商免费的,因为很多支付渠道要求回调地址必须是 HTTPS,不然签名校验通过后通知发不出来。别在这块省钱,申请证书加配置 Nginx 也就是半小时的事。

4. 你一定会碰到的关键参数:商户号、RSA 密钥、回调地址与路由规则

环境跑通只是第一步,真正的配置工作在支付渠道参数。这套系统能不能跑出第一笔真实代付单,取决于你在管理后台填的参数对不对。很多搭建帖跳过这部分,导致项目跑起来后一提交代付就报签名错误,我今天把这几个参数捋清楚。

4.1 四个必填参数与一对密钥的生成命令

每个支付渠道接入时,你至少要在渠道管理里填以下几项:

参数名含义取值示例
merchantId渠道分配给平台方的商户号一串数字编码
appId应用标识,常和商户号绑定数字或字符串混合
merchantPrivateKey平台私钥,用于上行请求签名PKCS8 格式
platformPublicKey渠道公钥,用于验证回调签名PKCS8 格式
notifyUrl渠道回调地址https://h5.pay.example.com/api/pay/notify

四个参数里最容易出错的是密钥。这类系统用 RSA 签名,生成密钥对用 openssl 一条命令:

openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem

然后把私钥内容粘贴到系统的商户私钥配置项。但这里有两个坑:一是有渠道要求私钥是 PKCS#1 格式,有渠道要求 PKCS#8,你粘贴进去如果格式不匹配就报错;二是粘贴时不要把-----BEGIN PRIVATE KEY-----换行符弄丢,常见做法是把整段带表头的字符串保持原样存入数据库。

判断格式其实一句话就能解决:复制粘贴前先看渠道接口文档。文档里如果示例是开头带BEGIN RSA PRIVATE KEY,那就是 PKCS#1;如果开头只写BEGIN PRIVATE KEY,就是 PKCS#8。Java 代码里很多类库更习惯 PKCS#8,遇到报错不要慌,用 openssl 转换一下格式再填。

4.2 金额、订单号与幂等键的硬约束

代付系统对金额和订单号的要求比普通业务严格得多。金额绝对不能在前端用 float 或 double 传给后端,也不能在 Java 里用 double 做计算。常见的可靠做法是在数据库里用 decimal 存,在接口层直接接收字符串金额,后端转成以“分”为单位的 long 再处理。

订单号必须全局唯一。提交代付时,以订单号作为幂等键提交给渠道,防止渠道重复受理。我见过不少翻车现场:同一笔代付单因为网络超时被重复提交,结果用户收到两笔钱,追回来极其困难。所以代码里提交前一定要有防重判断,Redis 里存一个 key,或者查本地订单表状态,两者必有其一。

4.3 路由优先级与费率参数的配置落点

管理后台的“渠道路由”页面,通常可以配置每个渠道的启用状态、优先级和限额。不要把优先级理解成排序数字那么简单,它其实是代付系统里的资金调度策略。比如 A 渠道单笔限额一千,你一笔代付金额是两千,那这笔单子无论优先级多高都该被跳过,换成额度合适的 B 渠道。

配置路由时建议遵循这样顺序:先用额度过滤,再用费率排序,最后用成功率的动态加权。很多源码实现了前两种,第三种可能是留了个空接口。如果你对成功率有强诉求,二次开发里第一个扩展点就可以选这里,把最近一小时的成功率实时统计并加权到路由排序里。

4.4 回调地址的配置:本地联调为什么收不到通知

回调地址是渠道主动请求你的服务器,所以本地开发环境直接写http://localhost:8080/notify大概率收不到。渠道服务器在公网,访问不到你本机的端口。我一般不会在本地全链路联调代付回调,而是把后端先部署到测试服务器,用测试域名配好 HTTPS,等回调验证通过了再把功能合入主分支。

如果你实在要在本地调试,也有一个折中做法:把渠道回调地址先配置成测试服务器的某个接口,该接口收到回调后原样转发到你的本地机器。这种方式能保留本地断点调试能力,但别把这个转发接口写到生产配置里,它会引入额外的故障点。

5. 避坑清单:回调丢失、金额精度、状态覆盖等六个高频问题

这一章是我最想写给读者看的,因为代付系统的坑不像普通 CRUD 那样一眼能看出来,大部分坑要等资金真正流动起来才暴露。下面的每一条都是真实项目里反复出现过的,按照“现象、原因、解决”写清楚,你只要对照着排查,能省出好几个通宵。

5.1 回调丢失:订单一直“处理中”怎么办

现象:用户发起了提现,订单状态停在 PROCESSING,过了一两个小时还是不动,用户追问客服,客服来问开发。原因:渠道回调因为网络波动、回调地址临时不可用、或者渠道侧压根没触发回调而丢失。解决:不要等回调,一定要有主动查单任务。写一个定时任务,每五分钟扫描处理中超过十分钟的订单,调渠道 query 接口查状态,查到终态就更新本地订单。这个任务在初期搭建时最容易被省略,但它恰恰是代付系统的保底机制,没有它,一半以上的客户投诉都从此来。

5.2 金额用 double 的教训:为什么必须以“分”为单位

现象:账目总是差几分钱,对账对不平,检查代码发现金额字段用的 Double。原因:double 在二进制里无法精确表示某些十进制小数,比如 0.1 加 0.2 不是正好 0.3。累计多了之后账目差异会变得神出鬼没。解决:所有接口传输金额用 String,数据库金额字段用 decimal(10,2),内部计算统一转成 long 型“分”。一旦代码里出现 float 或者 double 参与金额计算,直接改掉,不要心存侥幸。

5.3 RSA 密钥格式报错:PKCS#1 和 PKCS#8 的差别

现象:配置完渠道参数后提交代付,接口立刻返回签名错误或密钥格式错误。原因:很多渠道接口文档默认给 Java 示例,示例里的密钥是 PKCS#8;有的渠道只给 PKCS#1 格式示例,你不做转换直接粘贴就报错。解决:先确认渠道要求的密钥格式,然后用 openssl 转换格式,私钥转换命令是openssl pkcs8 -topk8 -inform PEM -in private.pem -outform PEM -nocrypt。转换后再填进配置,签名验证就能过。记住,系统里填的是密钥字符串本身,还是密钥路径,也要看清配置项设计,别填错位置。

5.4 返回成功但没到账:渠道挂账与查单补偿

现象:系统记录代付成功,但用户反馈银行卡没有收到钱。原因:渠道返回成功有时只是受理成功,资金清算有延迟;也有极少数情况是渠道侧支付出现问题,需要人工介入。解决:看到成功回调后不要直接停在那,建立日终对账机制。每天拉取渠道对账单,与本地订单逐笔比对,成功状态但渠道侧没有流水记录的订单,要标记异常并人工复核。代付系统做好对账,比你加多少个监控告警都有用。

5.5 手动修正以后又被回调覆盖:状态机为什么要锁终态

现象:一笔订单渠道一直没回调,运营等不及,在后台手动把状态改为失败并给用户退了款。结果第二天渠道回调到了,系统又把订单改成成功,用户和运营都蒙了。原因:处理回调的代码没有判断订单当前状态,无论什么状态都直接更新。解决:更新订单状态前,先使用条件更新语句带上状态参数,只在 PROCESSING 状态下才允许更新到终态,SQL 的 update 影响行数为 0 就表明当前状态不允许流转。这样手动终态就永远不会被晚到的回调覆盖。

5.6 数据库时区和连接串编码:两个让新手熬夜的配置

现象:订单创建时间早了八小时,用户姓名或备注出现乱码。原因:MySQL 连接串缺了 serverTimezone 参数,时区默认成了 UTC,存进去的时间比北京时间慢八小时;连接串缺了 characterEncoding=utf8,字符集解析乱套。解决:在 application.yml 的连接串里加上serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8,数据库创建时指定 utf8mb4。这两个配置每个项目都要检查一次,不要图省事复制旧项目配置就完事。

6. 二次开发怎么加第 15 个渠道:扩展写法、联调与验证技巧

当你把系统跑通、避坑也看完了,下一步大概率是往里面加自己的业务逻辑。这套源码所谓“可二次开发”,最典型的落地场景就是接入新支付渠道,下面是我实际的扩展流程。

6.1 三步接入一个新支付渠道

第一步,新建适配器类实现 PayChannelAdapter,按渠道文档实现 submit、query、verifyNotify 三个方法。第二步,在渠道枚举类里加上新渠道编码,保证全局不重复。第三步,在管理后台添加渠道参数和路由规则,启用后就能测试。

通道层接口基于统一抽象,新增渠道时最需要注意的是签名逻辑差异。有的渠道用 MD5 签名,有的用 RSA,有的用 HMAC,这些差异只写在 Adapter 内部,不要泄露到 Service 层:

@Component public class NewpayChannelAdapter implements PayChannelAdapter { @Override public String channelCode() { return "newpay"; } @Override public PayResult submit(WithdrawOrder order) { // 1. 组装渠道要求的报文 // 2. 用 newpay 渠道的私钥签名 // 3. 发起 HTTP 请求,记录渠道返回单号 // 4. 渠道受理成功返回 PROCESSING,明确失败返回 FAILED return null; } }

新增渠道后,一定要先在测试环境把 Submit、Query、Notify 三条链路都跑通,再切生产。没跑过真实资金流的渠道适配器,上线几乎必出幺蛾子。

6.2 本地联调怎么模拟回调验签

真实渠道的回调难以在本地触发,我一般会写一个模拟回调的脚本,在测试环境把渠道验签逻辑单独测透:

curl -X POST https://your.test.server/api/pay/notify/newpay \ -H "Content-Type: application/x-www-form-urlencoded" \ --data "orderNo=TEST202501010001&amount=100&status=SUCCESS&sign=xxx"

这里的 sign 要用渠道文档给的签名算法算出来,不能用明文代替。如果你用随便写的 sign 验证,代码走不到验签成功那一步,等于没测。测试重点有三项:验签失败时返回渠道需要的应答、验签成功后订单状态正确流转、重复回调不会改变终态。这三个点顺了,真实渠道的联调就只剩两边联调时间了。

6.3 上线前的对账脚本:把昨天的账自己先对一遍

新渠道上线后,头三天一定要人工盯对账。我通常写一个简单的 Python 脚本,从本地库和对账文件里拉数据做比对,跑完输出差异单:

import pymysql conn = pymysql.connect(host="127.0.0.1", user="pay", password="xxx", database="pay_merger", charset="utf8mb4") cur = conn.cursor() cur.execute("SELECT order_no, amount, channel_code FROM withdraw_order " "WHERE channel_code='newpay' AND date(create_time)=%s", ("2025-01-01",)) local = {row[0]: row[1] for row in cur.fetchall()} # 渠道对账单解析逻辑略,假设得到 billed_order_no -> billed_amount billed = {"NEW20250101001": 5000} for order_no, amount in local.items(): if order_no not in billed: print("缺失渠道流水:", order_no, amount) elif billed[order_no] != amount: print("金额不一致:", order_no, local[order_no], billed[order_no])

脚本逻辑很简单,但如果每天能跑一遍,资金安全隐患基本都能在萌芽期被发现。记住,代付系统出问题不可怕,可怕的是出了问题你不知道,隔了半个月才对账发现资金缺口,那时候谁都救不了你。

我带团队上线过好几套代付通道,最后悔的一次是只信回调、没做查单任务,结果半夜一单挂起没人管,第二天商户电话被打爆。此后凡是接代付项目,第一件事先把定时查单和对账补上,再谈功能上线。这套全开源代付系统,代码只是起点,真正值钱的是你对资金流的敬畏心。希望帮到你。

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