简介:这是一套面向支付系统开发者与二次开发者的全开源H5十四合一代付源码,基于PHP7.4与MySQL5.7环境运行,程序简洁流畅,可在微信内正常使用且不易红域名,适合需要快速搭建代付平台或进行深度定制的技术团队。资源包共112个文件,以25个php核心逻辑文件、6个html页面、5个js脚本、2个css样式及1个sql数据库文件为主体,另含31个png与25个jpg模板素材、10个txt说明文档,压缩包约9.46MB,结构完整便于部署与查阅。目前已有227人学习下载。源码内置美团、京东、拼多多、滴滴、携程、抖音、淘宝、得物、飞猪、猫眼等14套模板,支持代理分成、用户提现、推广下级、商品自主上架与后台审核,支付接口可对接总后台或由用户自行配置,涵盖微信官方、易支付、个人码等。本次更新修复了分享卡片显示、远程资源本地化加载、后台加载慢等问题,并新增订单多条件筛选、后台手动添加用户、分享卡片图文自定义等功能,具备较高的二次开发价值。
1. 十四合一代付系统到底合了什么:从聚合支付到二次开发的真实边界
很多人第一次听到「十四合一代付系统」这个词,脑子里浮现的是十四个支付通道堆在一起。实际做过聚合支付的人知道,这里的「十四合一」通常指的是十四类上游通道或结算方式的适配层——银行卡快捷、云闪付、数字人民币受理、各类钱包、聚合码、代收代付分离通道等等,具体到某一份源码里合了哪十四家,得看它的通道适配目录,标题里不会写死。代付(也叫下发、出款)和代收是两套逻辑:代收是用户把钱付进来,代付是平台把钱打给商户或用户,风控、对账、回调补偿的复杂度完全不在一个量级。
这份源码之所以被反复搜,核心诉求就三个:一是想拿一套能跑起来的 H5 收银台,二是想接自己的上游通道做二次开发,三是想省掉从零写订单、对账、回调这套脏活。它适合有一定 PHP 或 Java 后端基础、手里已经有上游通道资源、需要快速搭一套代付中台的小团队或个人开发者。不适合完全不懂支付业务、指望改个配置就能上线收单的人——支付这行的坑,从来不在代码里,在通道规则和资金流里。
2. 拆开源码看结构:H5 收银台、订单中台、通道适配层怎么分工
拿到一套「全开源可二次开发」的代付源码,第一件事不是急着composer install,而是把目录结构和数据流摸清楚。绝大多数这类系统的骨架是三层:面向用户的 H5 收银台、中间的订单与账务中台、底层的通道适配层。三层之间靠订单号和回调串起来,任何一层设计得含糊,后面二次开发都会翻车。
2.1 先认清三个核心目录和它们的数据流
典型目录长这样(不同源码命名有差异,但职责基本一致):
project/ ├── app/ │ ├── controller/ │ │ ├── Cashier.php # H5 收银台入口,渲染支付页、发起下单 │ │ ├── Notify.php # 上游异步回调统一入口 │ │ └── Merchant.php # 商户后台接口 │ ├── service/ │ │ ├── OrderService.php # 订单创建、状态机流转 │ │ ├── PayService.php # 代收/代付业务编排 │ │ └── SettleService.php# 对账与结算 │ └── channel/ │ ├── ChannelA.php # 每个上游通道一个适配类 │ ├── ChannelB.php │ └── ChannelFactory.php # 通道选择与路由 ├── config/ │ └── channel.php # 通道密钥、费率、限额配置 └── public/ └── index.php # 入口数据流是这样的:用户在 H5 收银台点支付 →Cashier调OrderService生成订单(状态pending)→PayService通过ChannelFactory选一个可用通道 → 通道适配类拼装上游请求参数并发出 → 上游返回支付链接或二维码 → 用户完成支付 → 上游异步回调打到Notify→ 校验签名后更新订单状态为paid→SettleService参与后续对账。
提示:先把这条链路在纸上画一遍,再去看代码。直接读代码很容易陷进某个通道的签名细节里,忘了整体。
2.2 通道适配层是二次开发的主战场
二次开发 90% 的需求集中在通道适配层:加一个新通道、改一个通道的签名算法、调整通道的限额和费率。所以适配层设计得好不好,直接决定你后面改起来是十分钟还是三天。
一个干净的通道适配类应该只做四件事:拼装下单参数、解析上游返回、校验回调签名、查询订单状态。业务逻辑(订单状态机、账务)不应该出现在通道类里。判断一份源码值不值得二次开发,就看它的通道类有没有偷偷写业务逻辑——如果ChannelA.php里出现了直接改数据库订单状态的代码,这份源码的耦合度就偏高,后面加通道会越来越痛。
// 通道适配类的标准骨架(以 PHP 为例) class ChannelA implements ChannelInterface { // 1. 拼装下单参数 public function buildPayParams(array $order): array { return [ 'merchant_no' => $this->config['mch_id'], 'order_no' => $order['order_no'], 'amount' => $order['amount'], // 单位:分,务必和上游对齐 'notify_url' => $this->config['notify_url'], 'sign' => $this->sign($order), ]; } // 2. 校验回调签名 public function verifyNotify(array $data): bool { $sign = $data['sign'] ?? ''; unset($data['sign']); return $sign === $this->sign($data); } // 3. 签名算法,每个通道不同,是二次开发最常改的地方 private function sign(array $data): string { ksort($data); $str = urldecode(http_build_query($data)) . '&key=' . $this->config['secret']; return strtoupper(md5($str)); } }这段代码里三个参数最要命:amount的单位(分还是元)、notify_url是否可被上游公网访问、sign的拼接顺序。金额单位搞错是新手最常见的翻车点,上游收 1 分你传 1 元,或者反过来,对账时能对到你怀疑人生。签名拼接顺序则完全取决于上游文档,ksort只是其中一种,有的通道要求按固定字段顺序拼,改的时候必须逐字对照文档。
2.3 订单状态机:别让回调把状态改乱
订单状态机是这类系统里最容易被忽视、又最容易出事故的地方。一个订单从创建到终态,正常路径是pending → paying → paid → settled,异常路径有pending → closed(超时关单)、paid → refunded(退款)。问题出在回调上:上游可能重复回调、可能延迟回调、可能在订单已关闭后才回调。
处理原则只有一条:状态只能单向流转,且每次流转前先查当前状态。回调进来先判断订单是不是已经是paid,是就直接返回成功(幂等),不要再改任何东西。很多源码为了「简单」,回调里直接update order set status='paid',一旦上游重推,账就乱了。
public function handleNotify(string $orderNo, array $data): string { $order = $this->orderService->getByNo($orderNo); if (!$order) { return 'fail'; // 订单不存在,让上游重试或人工介入 } // 幂等:已支付直接返回成功,不重复处理 if ($order['status'] === 'paid') { return 'success'; } // 已关闭的订单收到支付回调,属于异常,需要人工核对 if ($order['status'] === 'closed') { $this->logger->error("closed order paid: {$orderNo}"); return 'success'; // 返回成功避免上游无限重推,但必须告警 } if (!$this->channel->verifyNotify($data)) { return 'fail'; } $this->orderService->markPaid($orderNo, $data['trade_no']); return 'success'; }这里的逻辑说明:先查订单、再判幂等、再验签、最后改状态。顺序不能反,验签放在幂等判断之后能省掉重复回调时的无谓计算。参数上,markPaid内部应该用数据库行锁或乐观锁(where status='pending')来保证并发安全,否则两个回调同时进来还是会重复处理。
3. 本地把 H5 收银台跑起来:环境、配置、下单全流程
摸清结构之后,下一步是在本地把整套流程跑通。这一步的目标不是上线,是让你亲眼看到「下单 → 支付 → 回调 → 状态变更」这条链路走通,后面二次开发才有参照。
3.1 环境准备与依赖安装
这类 PHP 源码通常要求 PHP 7.4 或 8.x、MySQL 5.7+、Redis(做订单锁和队列)。用宝塔或手动装都行,关键是版本对齐——PHP 8 跑 PHP 7 写的源码,很多老扩展会直接报错。
# 以 Ubuntu 为例,装基础环境 sudo apt install php8.1 php8.1-fpm php8.1-mysql php8.1-redis php8.1-curl php8.1-mbstring -y sudo apt install mysql-server redis-server nginx -y # 拉代码、装依赖 cd /www/wwwroot git clone <你的源码地址> pay-system cd pay-system composer install --no-dev # 生产环境不装 dev 依赖 # 导入数据库 mysql -uroot -p -e "CREATE DATABASE pay DEFAULT CHARSET utf8mb4;" mysql -uroot -p pay < install.sqlcomposer install --no-dev这个参数值得说一句:开发依赖(phpunit、faker 之类)在生产环境没必要装,装了既占空间又可能引入不必要的自动加载。数据库字符集一定用utf8mb4,订单备注、商户名里出现 emoji 时utf8会直接截断报错。
3.2 配置文件里必须改对的五个参数
配置集中在config/目录,通常有database.php、channel.php、app.php。下面这张表是我每次部署都会逐项核对的:
| 配置项 | 位置 | 说明 | 常见错误 |
|---|---|---|---|
| 数据库连接 | database.php | host/port/user/pass/dbname | 用了 127.0.0.1 但 MySQL 只监听 socket |
| 回调域名 | app.php | notify_url 的域名前缀 | 填了 localhost,上游根本访问不到 |
| 通道密钥 | channel.php | 每个通道的 mch_id 和 secret | 测试密钥和正式密钥混用 |
| 订单超时 | app.php | 未支付订单自动关闭时间 | 设太短,用户还没付就关单 |
| 日志级别 | app.php | debug/info/error | 生产开 debug,日志爆炸还泄露敏感信息 |
回调域名是本地调试最大的坎。上游服务器要能访问到你的notify_url,localhost和127.0.0.1都不行。常见做法是用内网穿透工具把本地端口映射到一个公网可访问的临时域名,或者干脆部署到一台有公网 IP 的测试服务器上。这一步没有捷径,回调不通,整条链路就是断的。
3.3 用一条测试订单验证全链路
配置改完,用系统自带的测试下单接口或后台手动建单,走一遍完整流程。
# 模拟商户发起一笔代收下单(具体接口路径看源码路由) curl -X POST http://your-domain/api/pay/create \ -H "Content-Type: application/json" \ -d '{ "merchant_no": "M10001", "order_no": "TEST20250101001", "amount": 100, "channel": "channel_a", "notify_url": "http://your-domain/api/notify/channel_a" }'参数说明:amount这里是 100,单位取决于源码约定,多数是分(即 1 元),下单前务必确认。order_no必须全局唯一,重复下单会被订单表的唯一索引拦下。channel指定走哪个通道,测试阶段建议先用源码自带的沙箱通道或 mock 通道,别一上来就接真实通道。
下单成功后,你会拿到一个支付链接或二维码。用手机扫码或打开链接,如果通道是沙箱的,通常会有一个模拟支付成功的按钮。点完之后去看Notify的日志,确认回调进来了、签名验过了、订单状态变成paid了。这三步任何一步断了,都要停下来查,别急着往下走。
注意:本地调试时把日志级别开到 debug,重点看回调请求的原始报文。签名验不过十有八九是参数拼接顺序或编码问题,原始报文一比就清楚了。
4. 二次开发避坑:通道对接、回调、对账里最容易翻车的地方
这一章是血泪经验集中区。代付系统的坑,代码层面的其实好查,业务和通道层面的才要命。下面五条是我和身边同行踩过、且反复有人踩的。
4.1 现象:回调一直验签失败,日志里签名怎么算都不对
原因通常有三个:一是参数里混入了上游没参与签名的字段(比如上游回调多带了一个sign_type,你把它也算进去了);二是编码问题,http_build_query默认会把空格转成+,而上游要求%20;三是上游文档写的拼接顺序和你代码里的ksort不一致。
解决:把回调的原始报文完整打出来,手动按文档拼一遍签名,和代码算出来的对比。编码问题用http_build_query($data, '', '&', PHP_QUERY_RFC3986)强制 RFC3986 编码。顺序问题就老老实实按文档写死字段顺序,别图省事用ksort。
4.2 现象:订单明明支付成功了,状态还是 pending
原因:回调没进来,或者进来了但处理失败。常见的是notify_url配错、服务器防火墙拦了上游 IP、或者回调处理里抛了异常但被吞掉了。
解决:先确认上游后台能看到回调记录(多数通道后台有回调日志)。如果上游显示已发送但你这边没收到,查防火墙和 Nginx 访问日志。如果收到了但没处理成功,看应用日志里的异常。兜底方案是加一个主动查单的定时任务,对pending超过一定时间的订单主动调上游查询接口,别完全依赖回调。
4.3 现象:对账时金额对不上,差几分钱
原因:金额单位不统一。上游有的用分,有的用元,有的用「厘」。你的系统内部如果没统一,通道适配层又没做转换,就会差出数量级。
解决:系统内部统一用「分」作为金额单位,所有通道适配类在入口和出口做转换,转换逻辑写在一个公共函数里,别每个通道各写一遍。对账时以分为单位比对,差 1 分都要查。
4.4 现象:并发下单时同一笔订单被处理两次
原因:回调没有做幂等,或者幂等判断和状态更新之间有竞态。两个回调几乎同时进来,都查到状态是pending,都往下走。
解决:状态更新用带条件的 SQL,update orders set status='paid' where order_no=? and status='pending',根据影响行数判断是否真的更新成功。影响行数为 0 说明已经被别的请求处理过了,直接返回成功。
4.5 现象:代付发出去了,但上游返回失败,钱却扣了
原因:代付是资金流出,最怕的就是「请求超时但上游实际成功」。你的系统以为失败,重试一次,结果付了两笔。
解决:代付接口必须支持幂等,用商户订单号作为幂等键传给上游。请求超时时不要立即判定失败,先调上游的查询接口确认状态。系统内部对代付订单加唯一约束,同一笔业务只允许发起一次代付。这块没有后悔药,设计阶段就要想清楚。
5. 从能跑到能扛:代付系统的对账补偿与灰度上线技巧
把系统跑通只是起点,真正决定这套源码值不值得投入的,是它能不能扛住真实资金流。这里讲两个进阶点:对账补偿机制和灰度上线。
对账补偿的核心思路是「以 upstream 为准,本地主动对齐」。每天定时拉取上游的对账文件或调用对账接口,和本地订单逐笔比对,分出三类:本地有上游无(可能下单没成功)、本地无上游有(回调丢了)、金额不一致(单位或费率问题)。前两类自动补单或关单,第三类必须人工介入。
# 对账脚本核心逻辑(伪代码,语言按你系统来) def reconcile(date): upstream = fetch_upstream_bills(date) # 拉上游账单 local = query_local_orders(date) # 查本地订单 up_map = {b['order_no']: b for b in upstream} lo_map = {o['order_no']: o for o in local} for no, o in lo_map.items(): b = up_map.get(no) if not b: # 本地有上游无:下单可能没成功,标记待查 mark_suspect(no, 'no_upstream') elif b['amount'] != o['amount']: # 金额不一致:单位或费率问题,人工核对 mark_diff(no, b['amount'], o['amount']) for no in up_map.keys() - lo_map.keys(): # 上游有本地无:回调丢了,补单 create_order_from_bill(up_map[no])这段逻辑的关键在最后一步「补单」:上游有记录但本地没有,说明用户确实付了钱但回调丢了,必须补上,否则用户投诉你查无此单。补单时要标记来源,方便后续审计。
灰度上线方面,我的习惯是先切 1% 的流量到新系统,观察 24 小时的对账差异率和回调成功率,没问题再逐步放大。代付这种资金流出业务,宁可慢一点,也别一次性全量切。上线前把回滚方案写清楚:数据库怎么回退、通道配置怎么切回旧系统,写在纸上,别只存在脑子里。
最后说个我自己的习惯:每接一个新通道,我都会先用最小金额(1 分钱)跑通全链路,包括下单、支付、回调、对账、退款,五步全过才算这个通道接完。省这一步,后面出问题排查的成本是它的几十倍。希望帮到你。
本文还有配套的精品资源,点击获取