
简介一套集成美团、京东、拼多多、滴滴、携程五大场景的五合一代付系统源码面向具备 Node.js 与 React.js 开发基础的开发者、站长或技术团队用于快速搭建多平台代付业务、下单发货流程及运营后台。压缩包共 89 个文件以 JS、TSX、JSON、TS 为核心类型其中 JS 与 TSX 负责前后端逻辑与界面组件JSON 与 TS 用于配置和类型约束另有 SQL 数据库脚本、PEM 证书及环境变量文件辅助部署整包仅 595KB属于轻量级纯源码资源。目前已有 911 人浏览/学习。资源全开源无加密内置商城系统、后台管理系统与自动化脚本一比一还原从下单到发货、收货的完整业务链路五个前端模板分别对应美团、京东、拼多多、滴滴、携程均配置专属标题名且目录划分前端、后端、管理端与公共配置区域既便于独立部署也可作为全栈项目架构、API 设计与前端组件拆分的参考。1. 美团代付五合一代付系统一套源码接四个平台省掉的不只是联调时间做代付服务的团队最头疼的不是支付本身而是每个平台都要单独走一遍对接流程。美团外卖、京东、拼多多、携程这几个平台的订单结构、回调机制、验签方式各不相同每接一个新平台就要重新读文档、调接口、处理各种边界情况。美团代付五合一代付系统的价值就是把这一层封装好——一份 PHP 源码一个统一接口层五个平台共用一套订单处理逻辑。你不用再为每个平台写一套独立的对接代码改一个平台的接入参数就能切换渠道。这套方案适合正在做代付服务、聚合支付、电商订单代付的中小团队也适合想快速验证代付业务可行性的个人开发者。下面我把这套系统的业务模型、部署步骤、接口对接和踩坑记录整个过一遍。2. 先看懂代付的业务模型订单、支付单、回调三条链怎么串2.1 代付和普通支付差在哪从“用户自己付”到“他人替付”普通电商支付的链路很简单用户下单用户本人支付平台回调确认订单完成。代付的链路多了一个角色——付款人和下单人不是同一个人。举个例子A 在美团外卖选好餐下完单生成一个待支付订单但 A 自己不想付款把订单扔给 BB 付完钱订单才算成立。这套系统里要处理的就是这条链路的自动化。五个平台的业务差异很大但代付的抽象模型是一致的平台侧产生待支付订单代付系统把订单信息落库生成代付单然后向外输出一个可访问的收款链接或二维码付款人完成支付后平台异步回调通知支付结果代付系统更新订单状态、记录流水、同步对账数据。所以在碰任何代码之前先把这条抽象模型立住来源平台 - 订单信息 - 代付单 - 付款动作 - 渠道回调 - 订单终态。后面的所有表结构、接口设计和代码实现都是围绕这六个节点展开的。这条链没理清楚后面接任何渠道都会反复返工。2.2 五合一的接入层设计一个接口层接四个平台的关键所谓五合一本质上是把五个平台的差异封装在接入层之下。对外暴露统一的代付接口对内按平台分别适配。每个平台在接入层里只做三件事拉取待支付订单、生成代付支付链接、解析回调通知。我把接入层的接口设计成固定的方法签名比如getPendingOrders()、createPaymentLink()、parseCallback()每个平台各自实现这三个方法。这样做的好处是业务逻辑层只依赖接口不依赖具体平台实现。新增一个平台时只需要多写一个适配器类业务层和前端界面完全不动。三个核心方法的职责划分也很重要。getPendingOrders()负责从平台拉取当前待支付的订单美团外卖返回的是外卖订单号加应付金额京东返回的是商品订单加运费拼多多和携程的字段命名又各有差异。createPaymentLink()负责生成代付链接有的平台支持直接唤起支付有的平台只能跳转到一个 H5 页面。parseCallback()负责把平台的异步回调消息解析成统一的状态码——支付成功、支付失败、订单关闭。interface PaymentPlatformAdapter { // 拉取待支付订单列表返回统一结构的数组 public function getPendingOrders(array $params): array; // 根据订单号生成代付支付链接 public function createPaymentLink(string $orderNo, float $amount): string; // 解析平台回调数据返回统一支付状态 public function parseCallback(array $callbackData): array; }逻辑说明接口三个方法对应代付链路的三个关键节点拉单、生成支付入口、处理回调。定义成接口而不是具体类是为了让美团外卖、京东、拼多多、携程各自的适配器类都能被业务层用完全相同的方式调用。getPendingOrders()返回的数组建议固定字段order_no、platform_order_no、amount、platform。createPaymentLink()返回的是一个可访问的 URL前端拿到这个 URL 后可以直接跳转或生成二维码。parseCallback()返回的状态码建议统一为paid、failed、closed三个值便于业务层做状态流转判断不用关心平台原始返回里的各种英文状态。2.3 数据表设计订单表、代付单表、回调日志表的最小字段接入层设计好了接下来就要落地数据存储。三个表是必不可少的平台订单表、代付单表、回调日志表。平台订单表负责保存从各平台拉下来的原始订单信息里面的platform_order_no是关键后续所有跟平台的对账和查询都以它为准。代付单表记录这次代付行为本身——谁给谁付、付了多少钱、当前状态。回调日志表是排错时最重要的一个表平台每一次回调都必须留下记录不能丢。CREATE TABLE platform_order ( id BIGINT AUTO_INCREMENT PRIMARY KEY, platform VARCHAR(20) NOT NULL COMMENT 平台标识: meituan/jd/pdd/ctrip, platform_order_no VARCHAR(64) NOT NULL COMMENT 平台侧订单号, order_amount DECIMAL(10,2) NOT NULL COMMENT 订单应付金额, order_title VARCHAR(255) DEFAULT COMMENT 订单标题, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已关闭, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_platform_order (platform, platform_order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE payment_bill ( id BIGINT AUTO_INCREMENT PRIMARY KEY, bill_no VARCHAR(32) NOT NULL COMMENT 代付单号业务侧唯一, platform_order_id BIGINT NOT NULL COMMENT 关联platform_order.id, payer_uid VARCHAR(32) DEFAULT COMMENT 付款人标识, pay_amount DECIMAL(10,2) NOT NULL COMMENT 实际支付金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, paid_at DATETIME NULL COMMENT 支付完成时间, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_bill_no (bill_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE callback_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, platform VARCHAR(20) NOT NULL, platform_order_no VARCHAR(64) NOT NULL, callback_raw TEXT NOT NULL COMMENT 平台回调原始报文, parse_result VARCHAR(20) DEFAULT COMMENT 解析结果: paid/failed/closed, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_platform_order (platform, platform_order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明三张表在业务里的协作关系是订单表先有数据代付单表基于订单表生成一条代付记录回调来的时候先写日志表再更新代付单表和订单表的状态。callback_raw字段我建议直接存平台返回的原始 JSON 字符串不要只存解析后的字段——后续如果发现解析逻辑有 bug还能拿原始报文重新解析不用重新等平台回调。UNIQUE KEY在这里不是可有可无的约束平台的回调在极端情况下可能重复推送唯一键可以挡住插入重复日志但状态更新靠业务逻辑做幂等判断。这三个表在最简单的情况下够用代付单表加个channel字段可以支持后续接入更多付款渠道。3. 部署与初始化半小时内把源码跑起来3.1 环境准备PHP 8.x 加上 Nginx 就够了这套代付系统是 PHP 源码运行环境不需要太复杂。常见做法是 Nginx 加 PHP-FPMPHP 版本建议 8.0 以上因为源码里可能用了构造器属性提升和 match 表达式这些在 7.4 上跑不起来。数据库用 MySQL 5.7 或 8.0 都行按上面三张表初始化即可。对于本地先跑通验证的开发者可以用 PHP 内置服务器快速起一个环境调试不用先配 Nginx。项目根目录指向源码目录数据库配置改好就能跑。等验证完业务逻辑再上 Nginx避免第一次部署就把时间耗在配置环境上。3.2 配置导入与初始化数据库连接与平台参数拿到源码之后第一件事不是看代码而是把配置文件和数据库准备好。源码包里通常带一个.env.example或config.php复制一份改成自己的配置。数据库连接、各平台的密钥都在这里。平台参数里有个关键的字段是平台授权凭证相当于代付系统跟平台通信的门票这些凭证在平台的开放平台后台申请每个平台的申请流程不同但拿到之后填到对应字段就行。# 复制环境配置模板 cp .env.example .env # 编辑数据库配置 vim .env # 导入数据库结构 mysql -u root -p database.sql参数说明.env文件里主要改三块内容。数据库连接部分填主机地址、端口、库名、用户名、密码平台参数部分按MEITUAN_APP_ID、MEITUAN_APP_SECRET这样的键名逐项填写系统基础参数部分主要设置一个业务密钥这个密钥用于生成代付单号和验证回调签名建议换成足够长的随机字符串。.env文件一定不要提交到 Git 仓库里面全是密钥信息。database.sql是源码里自带的初始化脚本执行之后会创建上面三张表外加一个平台配置表用来在后台界面管理各平台开关和参数。3.3 跑通第一个本地代付订单环境配好后建议先用命令行脚本把整条链路走一遍而不是直接上页面。源码里一般自带了测试脚本比如tests/test_meituan_order.php或者可以通过一个简单的 PHP 脚本模拟下单到回调的完整过程。?php require __DIR__ . /bootstrap.php; use App\PaymentGateway; // 初始化代付网关 $gateway new PaymentGateway(meituan); // 调试模式开启记录每一步请求和响应 $gateway-enableDebug(true); // 模拟拉取一笔待支付订单 $orders $gateway-fetchPendingOrders(MTO-2025-0001); print_r($orders); // 为第一笔订单生成代付链接 $link $gateway-createPayLink($orders[0][order_no], $orders[0][amount]); echo $link . PHP_EOL;逻辑说明这个脚本把最核心的两个动作跑通了拉单和生成代付链接。enableDebug(true)很关键开启之后网关会把请求参数和响应数据写到日志文件里第一次联调全靠它定位问题。fetchPendingOrders传的是平台的测试订单号如果本地没有真实订单可以先用模拟数据脚本构造一条。createPayLink返回的代付链接可以先在浏览器里打开看看如果平台的测试环境有限制就把链接输出存下来方便后续手动模拟回调。跑通这一步就说明接入层的参数配置没有问题。4. 对接美团外卖、京东、拼多多、携程订单预下单、代付链接生成、回调验签4.1 各平台的参数差异与配置对照四个平台的对接参数差异是第一次实施时最容易被绊倒的地方。美团外卖的订单号和金额字段相对标准京东会多返回运费和优惠明细拼多多强调商品列表信息携程的订单状态码体系完全独立不能按常规支付状态去猜。平台拉单接口代付方式回调方式关键字段美团外卖订单查询接口收银台 URL异步通知orderId, actualPrice京东订单详情接口H5 收银台异步通知orderId, orderPrice拼多多订单列表接口链接跳转推送通知order_sn, amount携程订单查询接口联动收银台回调通知orderNo, totalAmount这四个平台的字段命名完全没有统一规范所以适配器里做的第一件事就是字段映射。美团外卖的actualPrice换算成分传给收银台京东的orderPrice要先把运费加进去拼多多的amount是字符串类型需要转 float携程的totalAmount包含税费。这个映射逻辑一定要在适配器里做不要散落在业务代码里。把映射集中管理后续平台字段如果调整只改适配器一个文件。4.2 代付链接生成与状态锁定代付链接的核心不是跳转而是保证一笔代付单在同一时间只有一个付款入口。如果用户点了两次生成按钮同一笔订单生成了两条代付链接后面回调回来的时候状态更新就会混乱。所以在生成代付链接之前必须先检查代付单的状态只有待支付状态才能生成。public function createPayLink(string $platformOrderNo, float $amount): string { // 查询订单是否存在且待支付 $platformOrder PlatformOrder::where(platform_order_no, $platformOrderNo)-first(); if (!$platformOrder || $platformOrder-status ! 0) { throw new \Exception(订单不存在或不在待支付状态); } // 检查是否有未完成的代付单防止重复生成 $existingBill PaymentBill::where(platform_order_id, $platformOrder-id) -whereIn(status, [0, 1]) -first(); if ($existingBill) { return $existingBill-bill_no; // 已有代付单直接返回原单号 } // 创建新代付单 $billNo $this-generateBillNo(); PaymentBill::create([ bill_no $billNo, platform_order_id $platformOrder-id, pay_amount $amount, status 0 ]); // 调用适配器生成支付链接 $adapter $this-getAdapter($platformOrder-platform); return $adapter-createPaymentLink($platformOrder-platform_order_no, $amount); }逻辑说明这里是代付系统的核心业务逻辑重点在防重处理。whereIn(status, [0, 1])这个查询很关键——待支付和已支付的代付单都不能再生成新链接只有已取消的才允许重新生成。这避免了两条链接同时有效导致的对账困难。generateBillNo()建议生成规则是日期加随机串加业务标识比如B20250612A8K3M2规则要保证一天内不重复。同时这里先查已有的代付单发现存在就直接返回原单号而不是抛异常保证了前端重复点击时的幂等性。回调验签是接入层最重要的安全环节也是最容易写漏的部分。美团外卖的签名逻辑是拼接 secret、orderId、amount 之后做 MD5京东的签名体包含整个订单详情 JSON拼多多的签名方式带时间戳防止重放携程的验签规则比较复杂用的是公钥验签。这些逻辑都必须封装在适配器内部业务层不做验签统一通过一个verifyCallbackSignature()方法。// 美团外卖回调验签示例 public function verifyCallbackSignature(array $callbackData): bool { $sign $callbackData[sign] ?? ; unset($callbackData[sign]); // 按字段名排序后拼接 ksort($callbackData); $str ; foreach ($callbackData as $key $value) { $str . $key . . $value . ; } $str . key . $this-config[app_secret]; return strtoupper(md5($str)) $sign; }参数说明美团外卖的验签是把回调参数按字段名升序排序拼接成keyvaluekeyvaluekeysecret形式的字符串再做 MD5 并转大写。这个逻辑不复杂但注意sign字段本身要剔除拼的时候也不能漏掉任何非空参数。实际操作中出现验签失败多半是参数类型不一致——平台回传的数字可能是字符串你的代码库里存的是 float拼接出来的字符串就不一样。建议在验签前统一(string)强转每个字段值。携程的回调验签参考它的接入文档来实现一般会给你一个公钥字符串验签算法是 SHA256withRSA。把公钥配置在适配器里和美团外卖的实现方式完全隔离——再次强调验签逻辑一定要放在适配器里面因为四种平台放在同一份代码里业务层不关心差异但适配器要实现各自的验签算法哪个平台对接就验哪个平台的签名。5. 代付系统避坑指南四个平台的接口差异在哪里5.1 坑一平台订单金额与代付金额不一致现象美团外卖订单金额是 32.5 元但生成的代付链接跳转后收银台显示要付 33 元。原因订单金额没有包含配送费或包装费。平台的订单详情接口返回的是一个总金额字段但那个字段已经自动减免了部分优惠实际待支付金额要看actualPrice而不是totalPrice。美团外卖的totalPrice是商品原价actualPrice才是账单应付金额。解决适配器里统一改成读actualPrice字段同时打印日志记录两个字段的差异方便排查金额不一致的订单。5.2 坑二回调重复导致订单状态错乱现象同一笔订单支付成功后回调日志表里出现两条记录代付单状态被更新两次。原因京东平台在支付成功后会推送回调如果回调方没有正确返回响应会间隔 30 秒再送一次。生产环境里网络抖动也会导致回调重发。解决在回调处理逻辑里加一个状态判断只有当前状态是待支付的代付单才允许更新为已支付已支付的回调直接忽略。public function handleCallback(string $platform, array $callbackData): void { // 先验签失败直接拒绝 $adapter $this-getAdapter($platform); if (!$adapter-verifyCallbackSignature($callbackData)) { throw new \Exception(签名验证失败); } // 解析出统一状态和平台订单号 $parsed $adapter-parseCallback($callbackData); // 幂等更新只有待支付才更新为已支付 $bill PaymentBill::where(bill_no, $parsed[bill_no])-first(); if (!$bill) { throw new \Exception(代付单不存在); } $updated PaymentBill::where(id, $bill-id) -where(status, 0) -update([status 1, paid_at date(Y-m-d H:i:s)]); if ($updated 0 $bill-status 0) { throw new \Exception(更新失败请检查并发情况); } }逻辑说明这段代码的核心在where(status, 0)条件——通过 SQL 的原子更新来防止并发覆写。即使同一笔回调同时进来两个请求也只有一个能命中status0的条件另一个更新 0 行自动失效。handleCallback处理完之后要返回一个字符串给平台比如success告诉平台不要重发。这里注意幂等不是把状态改回去而是确保状态变更只发生一次。日志表保留全部回调记录但状态更新是严格的状态机流转待支付只能到已支付已支付不会回头。5.3 坑三携程订单号带后缀导致查询失败现象用携程返回的订单号去查询订单详情返回结果为空。原因携程的订单号有人在末尾带了一个短横线下划线形式的渠道后缀直接拿去查询会导致匹配不上。而且这个后缀不是固定的不同渠道来源后缀还不一样。解决在适配器的订单号解析逻辑里用正则把末尾的-\w部分去掉只保留纯数字的主订单号。这个规则写清楚之后加个注释因为携程的文档里并没有明确说明这个后缀格式是三批联调才摸出来的规律。5.4 坑四拼多多回调返回的内容编码是 GBK现象拼多多的回调通知内容用json_decode解析后是乱码。原因拼多多的部分回调接口返回的不是 UTF-8 编码源码里默认按 UTF-8 读取导致乱码。这是平台侧的历史遗留行为不是你的代码问题但你不适配就解析不了。解决回调入口检测一下编码非 UTF-8 的先转成 UTF-8 再解析。// 拼多多回调内容编码处理 $rawBody file_get_contents(php://input); if (!mb_check_encoding($rawBody, UTF-8)) { $rawBody mb_convert_encoding($rawBody, UTF-8, GBK); } $callbackData json_decode($rawBody, true);参数说明这一段代码直接加在拼多多适配器的回调入口处。先用mb_check_encoding检查编码如果不合法再用mb_convert_encoding转码。这里有一个容易忽略的小坑有些回调推送的明文是 GBK但因为字节数碰巧满足 UTF-8 的规则导致mb_check_encoding返回 true 实际上还是乱码。遇到这种情况可以加一个校验策略检测转换前后订单号是否有变化或者直接根据平台请求头里的Content-Type字符集来做判断——拼多多如果没标字符集就不要按 UTF-8 解析。5.5 坑五同一订单在四个平台之间的时间戳基准不同现象美团外卖的订单时间返回的是 13 位毫秒时间戳京东返回的是 10 位秒级时间戳拼多多和携程的又不同。原因各平台的技术栈不同对时间戳的约定没有统一。解决在适配器里把所有时间字段统一转成Y-m-d H:i:s格式存库后端只存格式化时间不存原始时间戳。private function formatTime($timestamp): string { // 13位毫秒值先转秒 if (strlen($timestamp) 13) { $timestamp (int)($timestamp / 1000); } return date(Y-m-d H:i:s, (int)$timestamp); }参数说明这个formatTime函数放在适配器的基类里所有平台的时间字段都调用它处理。注意有些平台返回的时间是带时区后缀的字符串比如2025-06-12T10:30:0008:00date()函数直接处理会出问题要用strtotime()先转成时间戳。整体思想就是适配器层把平台差异消化透业务层面对的全是统一格式的数据。这样后续对账、筛选、报表都不用关心平台类型。6. 上线前的最后一道工序压测回调频率跑通自动对账系统能跑通订单流程不等于可以上线。我一般会花半天时间做两件事压一下回调处理的吞吐量再把自动对账逻辑验证一遍。回调频率是最容易在生产环境压垮代付系统的地方。正常单量下回调是低频的但遇到平台群发活动订单量会瞬间放大十倍以上。在压测阶段用脚本模拟平台推送回调频率从每秒 10 条逐步提高到每秒 200 条观察 MySQL 的 CPU 占用率和响应时间。回调处理的耗时瓶颈通常不在 PHP 逻辑本身而在PaymentBill::where(...)-first()这条查询上所以建议给平台的回调日志表加一个platform_order_no的索引并对代付单表做WHERE status 0条件查询的索引优化。实测中把索引建好之后每秒 200 条回调基本不消耗什么资源。自动对账我是这样做的每天凌晨跑一次脚本把当天的平台订单拉回来和本地的代付单表做比对。比对口径是平台侧已支付的订单号本地也必须是已支付状态。如果平台侧有支付记录但本地是待支付说明回调丢了或验签失败——从回调日志表里找到这笔订单的原始报文重新走一遍解析逻辑。如果本地是已支付但平台侧没有记录说明支付链接可能被伪造或测试环境的数据没清干净。对账的目的不是把账做平而是发现链路里的黑洞。回调丢失这个问题是客观存在的消息推送天然不可靠只能靠对账补位。这个对账脚本我会设置成定时任务脚本结束后把结果发到内部群里有差异就立即处理。整个系统跑稳之后我的习惯是再做一次代码级别的回看重点检查三件事验签逻辑是否能和平台侧保持同步更新、代付单状态机的流转是否有未知分支、日志记录是否覆盖了所有外部接口调用。代付系统本质上是信任系统每一笔代付背后都是真实订单和真实资金宁可多写日志多验签也不能为了省事跳过校验。我的经验是代付系统上线初期订单量一定要控制节奏先开放内部小流量测试一两周确认没有异常后再放开给全部用户。代码写得再周到也一定存在平台侧规则更新、字段变化这类外因保持灰度上线的意识才能睡得着觉。希望这些记录能帮你在部署这套系统的时候少走几个弯路。本文还有配套的精品资源点击获取