
简介这是面向代付/聚合支付场景的五合一代付系统前端源码包覆盖美团、携程、京东、拼多多、滴滴等多平台模板适合具备前端与支付业务基础的开发者二次开发或用于学习多商户代付页面实现。压缩包共118个文件整体约1.34MB核心代码以31个js、24个tsx及4个ts为主对应React/TypeScript组件与业务逻辑另含8个html、7个json、5个svg、css等界面资源以及安装搭建教程docx、环境配置env、SQL脚本和PEM证书等文件便于本地部署与接口联调这些配置与脚本覆盖环境初始化、数据导入和证书校验部署时尤其实用。当前已有645人学习下载。通过源码可快速了解五合一模板的页面结构、多端路由与主题切换方式配合教程文档可理清代付提单流程对于需要搭建同类型演示站或研究主流外卖平台代付交互的开发者是一份结构清晰、可直接对照查阅的实用参考。1. 代付系统源码不是黑匣子这个zip里到底在做什么生意拿到「美团代付系统源码 支付系统代收代付 五合一代付系统源码 滴滴代付多模版.zip」这个压缩包先用五分钟看一眼目录结构没必要急着解压去欣赏页面。标题里的核心词不是“美团”也不是“滴滴”而是“代付系统”——平台替商户把钱打给指定的个人或结算账户。美团代付、滴滴代付多模板通常只是两套演示皮肤加对应的业务参数真正值钱的是那一层能把代付单发到上游渠道、收回调、做对账的后端代码。适合谁做过支付但没碰过代付的 PHP/Java 后端、准备做聚合结算产品的小团队以及总把代收和代付混为一谈的测试同学。读完你能分清代收代付的差异也知道该在哪个文件里改配置而不是只在模板上换颜色。2. 代收代付与五合一先把业务模型拆开2.1 代收与代付在资金流上不是镜像代收和代付一进一出但技术上是两套完全不同的资金流模型。代收是“先拿到用户授权再扣款”风险在扣款前要素验证、短信验证码、签约协议、扣款失败后的自动重试。代付是“主动出款”风险在扣款后钱一旦出去回来就难了。很多源码翻车就是因为把代收的幂等策略原样套在代付上单号重复、金额校验不严结果一笔失败单被重试打给真实用户。另一个容易被忽略的差别是状态机。代收的状态一般是“待扣款 - 扣款中 - 已扣款”失败可以冲正代付的状态则是“待出款 - 处理中 - 代付成功 / 代付失败”。两个“处理中”的含义完全不同代收扣款失败可以自动冲正代付进入中间态后一旦真实资金已划出就不能简单撤销。所以代付系统的数据库表、异步回调、查单补偿逻辑都要单独设计不要和代收共用一张订单表。在这个标题的源码包里如果看到 controller 里把代收和代付接口放在同一个类里先别急着夸代码优雅要看两个接口是否真的走了独自的校验、幂等和回调处理链路。共用一张表但用biz_type区分常见但风险不小一旦渠道回调顺序乱掉代付单被代收逻辑处理资金方向就反了。2.2 五合一代付系统里“五”指什么“五合一”在不同源码包里的定义很乱。最干净的理解是五个代付渠道适配器并行存在统一接在一个抽象层后面。我见过最常见的组合是银行卡代付、支付宝余额代付、微信商户代付、企业内部钱包余额划拨外加一个模拟测试网关。为什么测试网关也算一个因为真实代付渠道联调成本高工作日窗口、金额上限、同名限制一堆Mock 网关能让开发者先跑通状态机和回调再把真实渠道参数换进去。序号常见代付渠道接口特点到账时效回调方式1银行卡代付需要收款卡号、姓名、部分场景校验身份证后四位两小时或 T1异步回调 日终对账文件2支付宝余额代付按账号或用户 ID 打款实时或几分钟异步回调3微信商户代付按 openid 或转账凭证打款实时或半小时异步回调4内部钱包余额划拨系统内部账户间转账即时无回调靠主动查询5Mock 测试网关本地 HTTP 服务即时可配置延迟和回调“五合一”还有另一种解释指五个业务模板美团模板、滴滴模板、打车模板、外卖模板、通用模板。这种包的重点不在渠道适配而在商户端 UI。判断标准很简单解压后看有没有channel/或payment/adapter目录。有五合一指的是渠道没有五合一多半只是五套页面皮肤代付逻辑仍然只有一家渠道在跑。这方面看走眼后面接第二家渠道时才会发现所有逻辑全是if ($channel ali)写死的。五合一对开发者的真正价值是检验抽象层是否合格。一个合格的五合一系统新增第六个渠道时只需要新增一个 adapter 类、改一下路由注册而不是在 Controller 里再加一个elseif。后面第 4 章会直接把这套 adapter 结构写出来。2.3 美团/滴滴这类多模板角色在哪一层美团和滴滴是业务场景模板不在支付核心链路里。美团模板常见的演示场景是“企业给员工统一代付外卖餐费”滴滴模板是“企业给司机代付加油费或报销款”。对代付系统来说模板到底影响什么影响三样东西后台界面风格、商户端默认渠道、代付单展示文案。实际代码里这些通常靠merchant_id - template_id映射完成模板本身只是视图目录和按钮文案。真正干活的代付下单、回调、对账接口所有模板共用同一套。也就是说你换页面皮肤不会影响资金流但如果你在源码包里看到template/meituan/controllers/这种目录里面真的写了独立的支付逻辑就要警惕了每个模板一套逻辑最终很难维护。拿到 zip 后建议先做一次轻量检查别急着解压覆盖unzip -l 美团代付系统源码*.zip | awk {print $4} | grep -E (template|channel|static) | head -80这一步能很快看清 template 和 channel 是不是双层结构。如果channel目录里是alipay、wechat、bank、wallet、mock这类子目录说明渠道适配层是真实存在的如果channel只有一堆空目录UI 模板再多也只是演示包。还有个常见细节这类 zip 经常附带微信小程序前端工程当作模板的一部分交付。如果你只改了小程序里的域名就去连后台回调地址、商户标识、渠道白名单大概率全是旧值联调时会花掉大量时间。3. 用 PHP 内建服务器跑通代付后台的最小命令3.1 解压前看三个地方第一看压缩包内部路径有没有恶意穿越。代付系统源码这种 zip 在灰色渠道里流传时经常被夹带后门路径穿越和文件覆盖是很常见的暗坑。unzip -l 美团代付系统源码*.zip | awk {print $4} | grep -E (\.\./|\.phtml|\.php\.|eval\() | head -30看到../开头或者.php.后缀的条目不一定就是后门但必须人工核对。路径穿越意味着解压时会写出到指定目录之外扩展名伪装是为了绕过服务器对.php的执行限制。第二检查 zip 是否有加密标志。unzip -l输出列表的最后一列如果是[encrypted]说明整个包是带密码的。带密码本身不代表有问题但很多支付源码包的密码写在注释或 readme 里解压前先把密码找出来别解到一半卡住。第三确认解压后是否有一个独立根目录。很多代付系统源码解压后把application、public、config全散在同级目录直接覆盖当前文件夹的config.php。常见做法是强行新建目录再解压mkdir -p /data/www/pay_system cd /data/www/pay_system unzip /tmp/美团代付系统源码*.zip ls -la这里ls -la看是不是只有根目录。散落文件的压缩包不管代码多好都建议先整理目录结构再启动否则后面改配置时不知道哪个文件被旧包覆盖了。3.2 运行环境选型这类源码最常见的是 PHP 系ThinkPHP 和 Laravel 各占一半也有少量 Java Spring Boot 版本。先确认包里有没有composer.json如果有它是 PHP 项目如果没有但看到pom.xml就是 Java。PHP 项目本地验证最省事的是内建服务器但要注意内建服务器是单进程的能验证接口逻辑验证不了并发和回调竞争。环境版本建议这样给组件建议版本说明PHP7.4 / 8.0 / 8.1不要直接用 8.3老源码常有 deprecated 报错MySQL5.7 / 8.0必须开 utf8mb4Redis6.x用于幂等锁2.x 也能跑但没必要Composer2.x安装依赖用为什么必须要有 Redis代付防重复锁和回调去重高度依赖 Redis 的原子操作。如果没有 Redis用 MySQL 唯一索引能挡住一部分重复但并发下两个请求同时在数据库里 “查状态为空” 后继续执行会真实重复调用渠道代付接口这不是概率问题是必然问题。3.3 配置数据库和处理金额字段先建库然后导入源码自带的 SQL 或迁移文件mysql -uroot -p -e CREATE DATABASE pay_system DEFAULT CHARSET utf8mb4; mysql -uroot -p pay_system install.sql如果源码包里没有现成的 SQL 文件或者表结构里有float类型的金额字段建议直接改成下面这个核心表这是代付系统最要紧的一张表CREATE TABLE pay_order ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL COMMENT 商户代付单号, platform_no VARCHAR(64) NOT NULL DEFAULT COMMENT 平台流水号, user_id INT UNSIGNED NOT NULL COMMENT 发起代付的商户/用户ID, amount_fen BIGINT UNSIGNED NOT NULL COMMENT 代付金额单位分, fee_fen BIGINT NOT NULL DEFAULT 0 COMMENT 预计算手续费单位分, status TINYINT NOT NULL DEFAULT 0 COMMENT 0初始化 1处理中 2成功 3失败 4待复核, channel_id TINYINT NOT NULL DEFAULT 0 COMMENT 渠道ID, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status (user_id,status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT代付订单表;金额用BIGINT表示分禁止用float或double。order_no是商户传过来的单号必须有唯一键这是挡重单的第一层。platform_no是渠道返回的平台流水号在渠道受理后才生成所以不能设唯一键——多个渠道可能各自维护一套流水号体系设了唯一键反而把自己卡死。接下来改环境配置常见.env长这样APP_ENVdev APP_DEBUGtrue DB_CONNmysql DB_HOST127.0.0.1 DB_PORT3306 DB_DATABASEpay_system DB_USERNAMEroot DB_PASSWORDtest REDIS_HOST127.0.0.1 REDIS_PORT6379 REDIS_PREFIXpay_system: CHANNEL_ENABLE1,2,3,4,5 PAY_CALLBACK_URLhttp://192.168.31.10:8000/api/callback这里最坑的是PAY_CALLBACK_URL。本地联调时回调地址要填局域网 IP别写127.0.0.1。渠道回调是从另一个进程发起请求指向127.0.0.1会打到它自己不是打到你正在调试的电脑。很多新手在这个字段上能卡掉半天。3.4 启动命令与健康检查依赖安装和启动以 Laravel 风格为例cd /data/www/pay_system composer install --no-dev php artisan migrate --seed php -S 0.0.0.0:8000 -t public public/index.php如果包里是 ThinkPHP 结构入口命令换成php think run -H 0.0.0.0 -p 8000启动后用 curl 打健康检查curl -s http://127.0.0.1:8000/api/ping正常会返回 JSON比如{code:0,data:pong}。如果返回 HTML 错误页先看storage/logs下的日志通常是数据库连不上或.env没读对。这里有一点要提醒内建服务器只能证明“能启动”不能证明“并发正确”。要验证回调幂等至少要用 Mock 渠道压 20 个并发请求后面第 6 章会讲具体清单。4. 代付主流程下单、回调、对账三个关键节点4.1 商户下单与签名校验代付接口通常叫pay/issue请求参数至少有merchant_id、order_no、amount_fen、settle_account、sign。这里用settle_account而不是bank_no是因为五合一渠道里结算账户可能是银行卡号也可能是支付宝账号或微信 openid。统一放在一个字段里由渠道适配层决定怎么用而不是在 Controller 里写死。签名算法常见的是 SHA256 with RSA先把参数名按 ASCII 升序排序拼接成键值对再用商户私钥签名服务端用商户公钥验签。// app/Http/Controllers/Api/PayController.php public function issue(Request $request) { // 验签前只取白名单参数避免 sign 和 extra 混入原文 $params $request-only([merchant_id, order_no, amount_fen, settle_account]); $sign (string) $request-input(sign, ); // 按参数名排序后拼接 $payload collect($params) -filter(fn($v) $v ! ) -keys() -sort() -map(fn($k) $k . . $params[$k]) -implode(); if (!openssl_verify($payload, base64_decode($sign), $merchant-public_key, OPENSSL_ALGO_SHA256)) { return [code 40001, msg 签名校验失败]; } // Redis 锁做单号幂等防并发重复下单 $lockName pay_lock:{$params[merchant_id]}:{$params[order_no]}; if (!Redis::set($lockName, 1, EX, 5, NX)) { return [code 40002, msg 重复下单请求]; } $order PayOrder::create([ order_no $params[order_no], user_id $params[merchant_id], amount_fen intval($params[amount_fen]), status 0, channel_id $this-routeChannel($params[merchant_id], intval($params[amount_fen])), ]); // 异步处理立刻向商户返回受理结果 dispatch(new ProcessPayout($order-id)); return [code 0, data [order_no $order-order_no, status pending]]; }逻辑说明白名单参数过滤很重要sign和额外的extra字段不能进验签原文否则不同调用方拼出来参数顺序不一样永远验不过。Redis::set的NX是原子操作同一商户同一单号 5 秒内只有第一个请求能拿到锁。最后把耗时渠道调用丢进队列接口可以立刻响应商户端不会因为渠道慢而大量超时。如果源码包里没有队列环境退而求其次的做法是同步调用渠道但 HTTP 超时时间至少要设 8 秒超过 8 秒要标记为“处理中”而不是“失败”否则渠道其实已经受理你这里返回失败商户重试就是重复代付。4.2 渠道适配层与状态转移五合一代付系统的核心不是页面是渠道适配层。一个干净的适配层由接口和路由组成// app/Services/Channel/ChannelAdapter.php interface ChannelAdapter { public function send(PayOrder $order): array; public function query(PayOrder $order): array; public function parseCallback(array $payload): array; } // app/Services/Channel/ChannelRouter.php class ChannelRouter { public function __construct(private array $adapters) {} public function get(PayOrder $order): ChannelAdapter { return $this-adapters[$order-channel_id] ?? throw new ChannelNotFoundException(); } }send负责发起代付query负责主动查单parseCallback负责把渠道回调格式统一成标准化结果。ChannelRouter根据订单上的channel_id选适配器而不是在 Controller 里写if ($channel 1)。状态转移是整个代付系统的命根子建议按下面这张表实现原状态事件新状态约束条件0 初始化渠道受理成功1 处理中渠道返回平台流水号1 处理中回调成功2 成功验签通过订单未超时1 处理中回调失败3 失败失败原因明确1 处理中超过 N 分钟未回调4 待复核不能自动置失败0 初始化回调成功2 成功需要允许见下方说明注意最后一行的约束条件回调可能在渠道受理的同时到达本系统此时订单还没从 0 改成 1。如果回调逻辑只允许“处理中”一档更新这条回调会被丢弃订单永远卡死在初始化状态。4.3 回调处理与主动查单兜底回调接口最核心的一段是“原子更新”先看代码public function callback(Request $request) { $raw $request-getContent(); $payload json_decode($raw, true); $adapter $router-getByChannelId($payload[channel_id]); $result $adapter-parseCallback($payload); if (!$result[ok]) { return [code 0, msg skip]; } // 关键只有 0 和 1 状态允许更新到终态影响行数为 0 则该回调被忽略 $updated PayOrder::where(order_no, $result[order_no]) -whereIn(status, [0, 1]) -update([ status $result[status] success ? 2 : 3, platform_no $result[platform_no], ]); if ($updated 0) { return [code 0, msg ignored]; } PayCallbackLog::create([ order_no $result[order_no], raw $raw, status $result[status], ]); return [code 0, msg ok]; }这里的whereIn(status, [0, 1])不是习惯问题是必须。回调如果早于队列里的下单事务到达订单状态可能还是 0只允许 1 会漏掉回调。回调处理后顺手写PayCallbackLog后续对账时如果发现订单状态和渠道账单不一致这就是第一手证据。主动查单用来兜住“渠道根本没回调”的情况。常见做法是 crontab 每分钟扫处理中订单超过 10 分钟还没回调就去渠道查一次*/3 * * * * cd /data/www/pay_system php bin/cli.php pay:confirm --minutes10 storage/logs/confirm.log 21查单逻辑里不要直接把超时订单置为失败。渠道可能已经打出款了只是没回调此时置失败会让商户发起退款然后渠道那边又真实打款两边一交叉就是资金事故。超时订单应该进入“4 待复核”等人工或对账文件来定夺。5. 代付系统最容易翻车的几个坑现象、原因、解决5.1 重复代付回调重试导致同一个单子打款两遍现象渠道因网络抖动重复推送同一笔成功回调后台每次收到回调都执行了一次“更新状态 通知商户”的操作结果商户侧可见重复放款。原因回调处理没有先做幂等或者用“先查状态再更新”的非原子方式。两个并发回调同时查到状态是 1同时更新都成功就重复了。解决用“原子更新”代替“查再改”。核心是UPDATE pay_order SET status2 WHERE order_no? AND status IN (0,1)影响行数为 0 的那次回调直接丢弃。Redis 锁可以做第一层拦截数据库的状态约束才是最终防线。5.2 回调地址配置错误联调时回调迟迟不到现象本地启动项目后商户下单成功但渠道回调一直没有收到日志里什么也没有。原因回调地址写成了127.0.0.1或者用了内网地址但没有同步配置到渠道后台白名单。渠道回调是从渠道服务器访问你的服务127.0.0.1指向它自己永远到不了你的电脑白名单里没有你的出口 IP回调会被渠道直接拒绝。解决回调地址统一配置成线上可访问的 HTTPS 域名本地联调时填写局域网 IP并确保防火墙放行对应端口。测试环境至少用三层校验回调 URI 固定、签名必须通过、请求来源 IP 可配白名单。5.3 金额精度不对对账差几分钱现象代付单金额是 0.1 元数据库存储的是 0.099999回调成功后再算手续费平台数据比渠道账单少一分钱。原因金额字段用float或double存储。浮点数在二进制下无法精确表示十进制小数累计到一定订单量后对账必然出差异。解决数据库统一用BIGINT存“分”程序里接收金额时统一转分四舍五入要显式写$amountFen (int) round((float) $amount * 100);这条代码看起来简单但很容易写错(int) $amount * 100会把 0.1 先转成 0 再乘 100结果变成 0这是常见的玄学 BUG。5.4 手续费没有预计算月底资金对不上现象代付成功很多笔月底服务平台应收手续费与上游扣费差异大主账户余额对不上。原因手续费在渠道回调后才手工登记没有在下单时按费率锁定。有的渠道按单笔限额收费有的按最高上限如果后台只算百分比超限部分就漏掉了。解决下单或路由时根据channel_id的费率规则计算fee_fen并写入订单如果渠道实际扣费和预计算差异大于阈值订单进入“4 待复核”不能自动成功。手续费表要和订单表分离方便后续按渠道和结算周期汇总。5.5 压缩包解压后页面路径被覆盖现象解压后源码可以运行但几小时后页面样式错乱或者多出一些莫名跳转。原因zip 内部存在同名文件或路径穿越解压时某个config.php被后门版本覆盖。解决解压前先看文件列表解压到独立目录不要覆盖已有项目。对二手源码包启动后重点检查三个可疑位置公共目录下的index.php是否包含eval、base64_decode等关键字控制器基类是否额外引入不明逻辑composer.json里的依赖是否混入非公开包。不要迷信“号称原版”的源码运行一个能进钱出钱的后门风险大于项目本身。6. 从源码到能上线先跑一次模拟网关演练代付系统上线前最值得做的一件事是搭一个模拟网关把状态机、幂等和超时补偿都验证一遍。不要直接拿真实渠道去试渠道联调环境有工作日限制而且一笔测试款打给陌生人可能收不回来。先把模拟网关开启在配置里留出可控参数// config/mock.php return [ enabled env(MOCK_CHANNEL_ENABLED, true), delay 500, // 模拟渠道处理耗时单位毫秒 error_rate 5, // 模拟失败比例百分比 ];然后按下面这组用例跑一遍用例输入预期结果正常代付金额 100.10回调一次订单状态 0 - 1 - 2重复回调同一回调重放 10 次订单只成功一次更新影响行数为 1超时查单模拟网关延迟 30 秒订单进入 4 待复核不自动失败手续费拆分费率 2.5%预计算 fee_fen2.5 元对应分到账金额和费用分离签名失败篡改 sign 后提交返回 40001订单不创建跑完这五条才算这套代付系统在逻辑上立住了。注意超时用例要把 mock 的delay临时调到大于主动查询的阈值触发pay:confirm的补偿路径不然这一分支永远覆盖不到。进阶用法是给美团/滴滴这类多模板加“渠道路由”把模板和渠道绑定关系从代码挪到配置// config/channel_rule.php return [ default [channel_id 1, template common], rules [ [min 1, max 2000, channel_id 2, template meituan], [min 2001, max 50000, channel_id 3, template didi], ], ];这个文件的意思很直接金额在 0.01 到 20 元之间美团商户走渠道 2滴滴商户走渠道 3超过 500 元统一走默认银行卡渠道。这样改模板不会影响后端接新渠道时也不用手工改十几个 Controller。最后说句私人的经验。我第一套代付系统上线前跳过模拟网关直接在正式渠道拿 1 块钱真实代付做验证结果渠道受理成功但回调没到前端显示失败我发起退款最后两笔钱来回折腾了四个小时才追回。从那以后我给自己定了条规矩任何代付系统上线前必须跑一遍模拟网关的五个用例并且把测试记录截图放进发布单。这个习惯帮我挡掉过不止一次资金事故也希望帮到你。本文还有配套的精品资源点击获取