简介:这是一套面向美团平台代付业务的全开源源码方案,适合中小型商家、个人开发者及运营人员快速搭建代付系统。源码支持多套界面模板自由切换,集成多种支付通道,并附带完整搭建教程与测试环境配置说明(PHP7.2+MySQL5.6),即便开发经验有限也能按指南完成部署。压缩包共约2000个文件,大小44.24MB,以1272个js脚本、196个html页面、150个css样式及164个json配置为主,另含sql建表文件、md说明文档与少量sh、xml等辅助文件,前后端资源与依赖结构清晰。目前已有376人学习下载。读者可获得可直接二次开发的全套源代码、多模板与多支付通道的对接思路、数据库脚本及环境搭建参考,便于快速构建自动化代付流程,节省从零开发的时间成本。
1. 美团代付源码到底在解决什么问题:从一笔代付订单说起
你在外卖群里看到有人发“帮我付一下这单,红包马上转你”,点开链接跳到一个 H5 页面,选微信或支付宝,输密码,钱付了,订单状态同步到下单人那边——这套流程背后跑的就是代付系统。美团代付源码这个标题,说的不是美团官方开放平台的东西,而是一套第三方实现的代付中间层:用户 A 下单生成代付链接,用户 B 打开链接完成支付,系统回调通知 A 订单已付。它解决的核心问题是“支付动作和下单动作分离”,适合做本地生活服务、社群团购、跑腿接单这类场景。多模板指的是前端页面可以换皮,全开源意味着你能改支付通道、改回调逻辑、改模板样式,搭建教程则是把这套东西跑起来的最小路径。这篇文章按“先搞懂资金流和信息流怎么走,再动手把环境跑通,最后把支付通道和模板改到能用”的顺序写,中间会给出可复现的命令和配置,也会说清楚哪些地方容易翻车。
2. 代付系统的资金流与信息流:先把账对清楚再写代码
2.1 一笔代付订单从生成到完结的完整链路
代付系统的本质是一个状态机。下单人创建订单时,系统生成一个唯一订单号,状态置为“待支付”,同时生成一个代付令牌(token),拼成链接。这个链接指向代付页,页面上展示订单金额、商品描述、倒计时。付款人打开链接后,前端调后端接口校验令牌有效性,后端返回订单详情和可用支付通道。付款人选择通道发起支付,后端向第三方支付网关(微信 Native、支付宝当面付、或者聚合支付)发起预下单请求,拿到支付二维码或跳转 URL。付款人完成支付后,支付网关异步回调后端,后端验签、改订单状态为“已支付”,然后触发通知逻辑——可能是 WebSocket 推给下单人,也可能是短信或公众号模板消息。
这里的关键是:代付链接的令牌必须有时效性和一次性。我见过有人把订单号直接当令牌用,结果链接被转发出去后,任何人拿到都能看到订单详情,甚至重复支付。正确做法是令牌单独生成,存 Redis 并设 TTL,支付成功后立即删除。另一个容易忽略的点是金额校验——回调回来的时候必须比对回调金额和订单金额,防止有人改价。
2.2 支付通道的选型:官方直连还是聚合支付
源码标题里写“多种支付通道”,实际落地时你面临一个选择:接微信支付宝官方接口,还是接第四方聚合支付。官方直连的优点是费率低、资金直接到商户号,缺点是开通门槛高——微信 Native 支付需要企业资质,支付宝当面付需要营业执照,个人开发者基本走不通。聚合支付的优点是开通快,个人也能用,缺点是费率高(通常 0.6% 到 1.2%),而且资金要经过聚合平台中转,存在跑路风险。
我的建议是:如果只是内部测试或者小规模跑,先用聚合支付的测试环境把流程跑通,源码里通常已经封装好了统一的支付接口层,你只需要改配置。等量起来了再换官方直连。源码里一般会有一个pay目录或者payment模块,里面按通道分文件,比如wechat.php、alipay.php、aggregate.php。你要做的是确认每个通道的notify_url和return_url配置正确,这两个地址必须是公网可访问的,本地开发时用内网穿透工具临时映射。
2.3 数据库表结构里必须有的几个字段
拿到源码后先别急着跑,打开数据库设计文档或者直接看 SQL 文件。代付系统的核心表通常有三张:订单表、支付记录表、通道配置表。订单表里必须有的字段包括:订单号(唯一索引)、代付令牌(唯一索引)、订单金额(decimal 类型,别用 float)、实付金额、订单状态(枚举:待支付/已支付/已过期/已退款)、创建时间、过期时间、支付时间、付款人标识(openid 或用户 ID)。支付记录表用来存每次支付尝试的流水,包括通道名称、通道订单号、回调原始数据(建议存 JSON 或 text,方便排查)、回调时间。通道配置表存商户号、密钥、费率、开关状态。
注意:订单金额字段用 decimal(10,2),不要用 float。我踩过这个坑,0.1 + 0.2 在 float 下不等于 0.3,对账的时候差几分钱,查了半天。
2.4 回调验签的逻辑怎么写才不出错
回调验签是代付系统里最容易出问题的地方。以微信支付 V3 为例,回调数据是加密的,你需要用商户私钥解密,然后用微信平台证书验签。源码里如果用的是 V2 接口,验签逻辑是拼接待签名字符串,MD5 后比对 sign 字段。不管哪个版本,核心原则是:先验签,再处理业务逻辑,最后返回成功响应。顺序反了,攻击者可以伪造回调把你订单改成已支付。
// 微信支付 V2 回调验签示例(源码中常见写法) public function notify() { $data = file_get_contents('php://input'); $xml = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA); $arr = json_decode(json_encode($xml), true); // 第一步:验签 $sign = $arr['sign']; unset($arr['sign']); ksort($arr); $str = urldecode(http_build_query($arr)) . '&key=' . $this->apiKey; if (strtoupper(md5($str)) !== strtoupper($sign)) { // 验签失败,记录日志,返回失败 Log::error('notify sign error', $arr); return 'FAIL'; } // 第二步:校验金额和订单状态 $order = Order::where('order_no', $arr['out_trade_no'])->first(); if (!$order || $order->status != 0) { return 'FAIL'; } if (bccomp($order->amount, $arr['total_fee'] / 100, 2) !== 0) { Log::error('notify amount mismatch', $arr); return 'FAIL'; } // 第三步:更新订单状态 $order->status = 1; $order->pay_time = time(); $order->save(); // 第四步:返回成功 return '<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>'; }这段代码的逻辑说明:先解析 XML 拿到回调数据,然后按微信规则拼接待签名字符串做 MD5 比对。验签通过后查订单,比对金额(微信返回的是分,要除以 100),最后更新状态。参数方面,apiKey是微信商户平台的 API 密钥,out_trade_no是你系统的订单号,total_fee是回调金额。失败时返回 FAIL,微信会重试,所以你的接口必须做幂等——同一个订单号重复回调时,如果状态已经是已支付,直接返回 SUCCESS,不要再改数据。
3. 把源码跑起来:环境准备、安装步骤与多模板切换
3.1 服务器环境选型与宝塔面板的安装
源码通常是 PHP 写的,环境要求一般是 PHP 7.4 到 8.1,MySQL 5.7 或 8.0,Nginx 或 Apache。如果你用的是云主机,系统选 CentOS 7.9 或者 Ubuntu 20.04 都行,宝塔面板可以省掉很多配环境的功夫。安装宝塔的命令官方有提供,装完之后在面板里一键安装 Nginx、MySQL、PHP 和 Redis。PHP 扩展需要装 fileinfo、redis、curl、openssl、bcmath,缺一个都可能报错。
# 宝塔面板安装(CentOS) yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh # 安装完成后,在面板软件商店安装以下环境: # Nginx 1.20+ # MySQL 5.7 # PHP 7.4(安装扩展:fileinfo, redis, curl, openssl, bcmath) # Redis 6.0+装完环境后,新建一个站点,把源码上传到站点根目录解压。注意运行目录要指向public目录,这是 ThinkPHP 或 Laravel 框架的惯例。伪静态规则选 ThinkPHP 或 Laravel,宝塔面板里有现成的选项。
3.2 数据库导入与配置文件修改
源码包里一般有一个.sql文件,在宝塔的 phpMyAdmin 里新建一个数据库,导入这个 SQL 文件。然后找到源码的配置文件,通常在config/database.php或者.env文件里,把数据库地址、用户名、密码、库名填进去。Redis 配置也在同一个文件里,默认是127.0.0.1:6379,如果没有设密码就留空。
// config/database.php 中需要修改的部分 return [ 'type' => 'mysql', 'hostname' => '127.0.0.1', 'database' => 'daifu', // 你新建的数据库名 'username' => 'daifu_user', // 数据库用户名 'password' => 'your_password', // 数据库密码 'hostport' => '3306', 'charset' => 'utf8mb4', 'prefix' => 'df_', // 表前缀,看 SQL 文件里的定义 ];参数说明:prefix是表前缀,导入 SQL 时如果表名是df_order,这里就填df_。hostname一般不用改,除非数据库不在本机。改完配置后访问站点域名,如果看到安装页面或者登录页,说明数据库连上了。
3.3 多模板切换的机制与自定义模板的步骤
多模板的实现方式通常有两种:一种是后台设置里选模板主题,系统根据主题名去view目录下找对应的文件夹;另一种是每个代付链接生成时绑定一个模板 ID,付款人打开时根据模板 ID 渲染不同页面。源码里一般会在config/template.php或者后台设置表里存当前模板名。
要自定义模板,先找到模板目录,通常在application/index/view或者resources/views下面,每个模板一个文件夹,里面是 HTML 文件。你可以复制一份默认模板,改文件夹名,然后修改 HTML 和 CSS。模板里会用到模板变量,比如{$order.amount}、{$order.subject},这些变量由控制器 assign 过来。改完之后在后台切换到新模板,清一下缓存就能看到效果。
提示:改模板时不要动表单的 name 属性和提交地址,否则支付请求会失败。只改样式和文案,结构保持原样最安全。
3.4 支付通道配置的实操:以聚合支付为例
聚合支付的配置一般在后台的“支付通道”菜单里,填商户号、密钥、网关地址。有些源码把通道配置写在config/pay.php里,需要手动改文件。以常见的聚合支付为例,你需要填的字段包括:mch_id(商户号)、key(密钥)、gateway(网关地址,比如https://api.example.com/pay)、notify_url(异步回调地址,填https://你的域名/pay/notify)、return_url(同步跳转地址)。
// config/pay.php 聚合支付配置示例 return [ 'aggregate' => [ 'mch_id' => '100001', // 聚合平台分配的商户号 'key' => 'your_secret_key', // 聚合平台分配的密钥 'gateway' => 'https://api.example.com/pay/create', 'notify_url' => 'https://yourdomain.com/pay/notify/aggregate', 'return_url' => 'https://yourdomain.com/pay/return', ], ];配置完成后,在后台新建一个测试订单,生成代付链接,用手机打开链接走一遍支付流程。如果支付成功但订单状态没变,先看回调日志——源码一般会把回调数据写到runtime/log或者数据库的支付记录表里。常见问题是notify_url填错、服务器防火墙拦截了回调请求、或者验签密钥填错。
4. 避坑与排查:代付源码搭建中最容易翻车的五个地方
4.1 回调地址 404 或 502:先查伪静态和运行目录
现象:支付成功后订单状态一直是待支付,查看日志发现回调请求返回 404。原因通常是伪静态规则没配,或者站点运行目录没指向public。ThinkPHP 的 URL 是index.php/pay/notify这种形式,如果 Nginx 没配 rewrite,直接访问会 404。解决方法是检查宝塔站点设置里的伪静态规则,选 ThinkPHP 或 Laravel,然后确认运行目录是public而不是根目录。
4.2 订单金额比对失败:float 和 decimal 的精度问题
现象:回调验签通过,但金额比对不通过,日志里显示0.1 != 0.10。原因是数据库字段用了 float,PHP 浮点数运算有精度损失。解决方法是在数据库里把金额字段改成decimal(10,2),PHP 里用bccomp函数做比较,不要用==。如果源码里已经用了 float,写个脚本把历史数据转成 decimal,然后改表结构。
4.3 代付链接被重复使用:令牌没有设过期和一次性
现象:同一个代付链接被多人打开,都能看到订单详情,甚至有人重复支付。原因是令牌生成后没有存 Redis 或没有设 TTL,支付成功后也没有删除。解决方法是生成令牌时setex到 Redis,过期时间设 15 到 30 分钟,支付回调里先del令牌再处理业务。如果源码没做这个逻辑,在createOrder和notify两个方法里各加一段代码。
4.4 模板切换后样式错乱:静态资源路径用了绝对路径
现象:后台切换到新模板后,页面能打开但 CSS 和 JS 加载失败,控制台报 404。原因是模板里的静态资源路径写死了,比如/static/default/css/style.css,切换模板后路径没跟着变。解决方法是把路径改成相对路径或者用模板变量,比如{$Think.const.__STATIC__}/css/style.css。如果源码里写死了,批量替换一下。
4.5 支付通道回调验签失败:密钥填错或编码问题
现象:回调日志里显示验签失败,但密钥反复确认没填错。原因可能是编码问题——微信 V2 回调的 XML 里如果有中文,http_build_query会做 urlencode,而微信签名规则要求原始值不编码。解决方法是先urldecode再拼接待签名字符串,或者直接用微信官方提供的 SDK。另一个可能是服务器时间不对,微信要求时间戳误差在 5 分钟内,装个 NTP 同步一下时间。
5. 进阶:把代付系统接进现有业务的技术路径
5.1 用 API 对接现有订单系统而不是独立部署
如果你已经有自己的订单系统,不需要把代付源码整套跑起来,只需要把它的核心接口抽出来。源码里通常有api模块,提供创建订单、查询状态、关闭订单三个接口。你可以在自己的系统里调这些接口,把代付链接拼到你的订单详情页上。对接时注意两点:一是订单号要传你自己的,不要用源码生成的;二是回调地址填你系统的接口,收到回调后更新你自己的订单状态。
// 在你的系统中调用代付源码的创建订单接口 $params = [ 'out_trade_no' => $yourOrderNo, // 你自己的订单号 'amount' => $orderAmount, // 金额,单位元 'subject' => '订单代付', // 商品描述 'notify_url' => 'https://yourdomain.com/notify/daifu', // 你的回调地址 'template' => 'default', // 模板名 ]; $sign = md5(http_build_query($params) . '&key=' . $apiKey); $params['sign'] = $sign; $result = curl_post('https://daifu-domain.com/api/order/create', $params); // 返回结果里包含 pay_url,把它展示给用户即可参数说明:out_trade_no是你系统的订单号,代付系统会用它做幂等;notify_url填你系统的回调接口,代付系统支付成功后会通知你;sign是签名,防止参数被篡改。返回的pay_url就是代付链接,你可以生成二维码或者直接跳转。
5.2 用定时任务清理过期订单和补偿回调
代付订单有有效期,过期后要自动关闭,否则数据库里会堆积大量待支付订单。源码里一般有closeOrder方法,但没有自动触发。你可以在宝塔面板里加一个计划任务,每分钟执行一次php think close:order或者访问一个 URL 来触发清理。另外,如果回调丢失,订单状态会卡在待支付,需要写一个补偿脚本,定时查支付网关的订单状态,主动更新本地订单。
# 宝塔计划任务:每分钟清理过期订单 * * * * * cd /www/wwwroot/daifu && php think close:order >> /tmp/close_order.log 2>&1 # 每 5 分钟补偿查询一次未支付订单 */5 * * * * cd /www/wwwroot/daifu && php think query:order >> /tmp/query_order.log 2>&1这两个命令依赖源码里的命令行工具,如果源码没有提供,你需要自己写一个脚本,查status=0且expire_time < time()的订单,批量更新为已过期。补偿查询则是调支付网关的查单接口,如果网关返回已支付,就手动触发回调逻辑。
5.3 日志和监控:出问题时先看哪里
代付系统出问题时,第一手信息在日志里。源码的日志一般分几类:应用日志(runtime/log)、支付回调日志(数据库支付记录表)、Nginx 访问日志(/www/wwwlogs/)。我的习惯是先在数据库里查支付记录表,看回调有没有进来、验签有没有通过、金额对不对。如果回调没进来,查 Nginx 日志看有没有请求到notify接口。如果请求到了但没处理,查应用日志看报错信息。
监控方面,至少加两个告警:一是待支付订单超过 30 分钟未支付的占比,如果突然升高说明支付通道有问题;二是回调失败率,超过 5% 就要查验签和网络。宝塔面板有简单的监控,但不够细,建议接一个 Uptime 类的服务监控notify_url的可达性。
5.4 安全加固:别让代付链接变成提款机
代付系统有几个安全点必须做:第一,代付令牌必须随机且足够长,用bin2hex(random_bytes(16))生成,不要用订单号或时间戳;第二,回调接口必须验签,且验签逻辑要放在业务处理之前;第三,金额校验必须做,防止 1 元订单回调 100 元;第四,后台登录必须加验证码和登录失败锁定,我见过后台密码被爆破后攻击者把支付通道改成自己账户的案例;第五,数据库和 Redis 不要暴露公网端口,宝塔面板里把 3306 和 6379 的防火墙规则删掉。
注意:源码是开源的,意味着漏洞也是公开的。拿到源码后先搜一遍
eval、exec、system这些函数,看看有没有后门。我习惯用grep -rn "eval(" .扫一遍,有可疑的代码直接删掉。
5.5 从单机到多机:什么时候需要拆分服务
单机跑代付系统,PHP + MySQL + Redis 在一台机器上,撑个几百单每天没问题。但如果你的业务量到了每天几千单,回调延迟会变高,这时候要考虑拆分。第一步是把 Redis 独立出去,因为订单令牌和队列都依赖 Redis,它挂了整个系统就挂了。第二步是把 MySQL 做主从,回调写入走主库,查询走从库。第三步是把支付回调处理做成异步队列,收到回调后先丢 Redis 队列,返回 SUCCESS,然后由消费者进程慢慢处理。这样即使业务逻辑再复杂,也不会阻塞回调响应。
我自己的习惯是,不管量大量小,回调接口里只做三件事:验签、写队列、返回成功。剩下的逻辑全部异步处理。这样回调响应时间稳定在 50ms 以内,支付网关不会因为超时重试。队列消费者可以用php think queue:work或者 Supervisor 守护进程来跑,失败了自动重试三次,三次还失败就告警。
希望帮到你。
本文还有配套的精品资源,点击获取