ThinkPHP 3.2码支付源码迁移:兼容PHP8与回调验签实战

发布时间:2026/9/11 18:30:33
ThinkPHP 3.2码支付源码迁移:兼容PHP8与回调验签实战 简介码支付mpay源码是一套基于ThinkPHP8框架开发的个人免签约收款系统面向需要接入微信、支付宝个人码收款的开发者或电商站长解决订单支付回调通知、多通道轮询配置等实际问题。程序采用易支付接口标准兼容绝大多数商城系统支持长按识别扫码内置微信、支付宝、收钱吧三套收款插件其中微信和支付宝插件支持赞赏码、个人码、经营码、商家码等收钱吧插件可免挂机仅需设置定时任务即可监听回调部署运维门槛较低。资源共包含1376个文件以PHP业务逻辑代码、PNG图标与静态资源、JS与CSS交互样式为主附带JSON配置文件、HTML页面模板、Markdown说明文档及各插件的SQL安装语句整体压缩包仅5.7MB目录层次清晰方便本地运行、按需修改和二次开发。目前已有326人学习或下载适合具备PHP基础、希望快速搭建免签收款服务或想研究ThinkPHP8加Layui后台开发流程的中高级开发者。1. 码支付 mpay 到底是什么以及为什么它长在 thinkphp 上做个人站、发卡网、资源店这类小体量业务的人大概率见过“码支付”“mpay”这类名字。它们本质上是个人聚合支付网关解决的是企业支付接口申请门槛高、个人无法直接接入支付宝或微信官方收单的问题。系统在中间做一层桥梁商户在前台发起订单系统生成一个收款二维码或跳转链接用户扫码完成真实支付后支付通道把结果异步通知回网关网关再回调给商户业务系统。整个链路里mpay 负责的是订单生成、通道分发、回调接收、验签、掉单补单这几件事。选择 thinkphp 来承载这套逻辑是历史原因也是合理选择。码支付这类项目的高峰期在 2015-2018 年那会儿 thinkphp 3.2 是国内 PHP 生态里占有率最高的框架上手快、文档全、虚拟主机都能跑。而 mpay 这类“源码”产品面向的用户不是大厂研发是一群要快速部署、能改能卖的中小型开发者thinkphp 的 MVC 结构和 Active Record 式的 M() 模型正好匹配这个场景。哪怕是今天你拿到手的 mpay 源码大概率还是 thinkphp 3.2 的目录结构——Application/作为应用目录Home和Admin两个模块分开前后台ThinkPHP/框架目录整体躺在网站根目录。这套代码能解决的问题很具体对接码支付平台、生成订单、接收异步通知、同步跳转、查询订单状态、处理掉单。但老框架在 PHP 7.4 / 8.x 环境下会踩不少坑二次开发时也有安全问题需要补。下面我按一个实际拿到源码的开发者视角把部署、兼容、改造、验证这条线完整走一遍。2. thinkphp 3.2 的 mpay 源码迁移到新版 PHP 环境的兼容性改造拿到一份 thinkphp 3.2 写的 mpay 源码第一件事不是看业务代码而是确认运行环境。TP3.2 官方支持到 PHP 5.3 以上但本质上是为 PHP 5.x 设计的。今天常见的服务器环境已经是 PHP 7.4 甚至 PHP 8.0/8.1直接跑会报一堆致命错误你看到的可能只是白屏或者Fatal error: Uncaught Error: Call to undefined function。2.1 PHP 8 对 TP3.2 做了哪些“断头”操作先列几个最常见的兼容性杀手。each()函数在 PHP 8.0 被彻底移除。TP3.2 的很多方法内部用了each()来遍历配置项或参数数组一旦走到就直接 fatal。create_function()在 PHP 8.0 被移除。TP3.2 的模板引擎在解析某些动态标签时会用到它字符串形式的匿名函数在老版本里是唯一方案新版本必须换掉。curl_init()等 curl 扩展函数如果服务器没开扩展会直接报未定义。PHP 7.4 之后 curl 是一个默认启用但可被禁用的扩展很多虚拟主机出于安全考虑会关闭它而 mpay 的支付请求核心恰恰依赖 curl。mcrypt_encrypt()在 PHP 7.2 就被移除了如果 mpay 源码里有老式的加密逻辑比如某些版本对接旧接口用了 mcrypt这部分必须重写。好在大部分码支付源码用的是 MD5 签名影响面不大。提示先php -v确认版本再php -m | grep curl确认扩展别一上来就改代码。2.2 用兼容层还是直接改源码我的建议是业务代码可以直接改框架核心文件尽量不做大迁移。TP3.2 的框架核心已经稳定了十年与其去修框架内部的老函数调用不如在入口处写一个兼容层把已移除的函数重新映射回来。常用的做法是新建一个compat.php文件在index.php入口最先加载?php // compat.php - PHP 8 兼容函数层 if (!function_exists(each)) { function each($array) { $key key($array); $result ($key null) ? false : [$key, current($array), key $key, value current($array)]; next($array); return $result; } } if (!function_exists(create_function)) { function create_function($args, $body) { static $id 0; $fnName compat_lambda_ . ($id); $code function . $fnName . ( . $args . ) { . $body . }; eval($code); return $fnName; } }这段代码解决的是“函数不存在”引发的 fatal error。逻辑很简单each用key()、current()、next()三个基础数组函数模拟原来的行为把当前键值对取出后指针下移一位create_function用eval动态声明一个函数并返回函数名行为和旧版一致。但这里有个更稳妥的选择现在很多人在维护的 TP3.2 on PHP 7.4 / 8.0 兼容分支直接替换ThinkPHP/目录里的核心文件配合trait机制把Think\Db等类做了适配。如果你拿到的 mpay 源码里框架目录很完整我建议直接搜索有没有Compatible相关目录没有的话再手工加兼容层。2.3 数据库驱动坑mysqli 还是 pdo_mysqlTP3.2 默认数据库驱动是mysql这是基于mysql_*系列函数的实现PHP 7.0 就已移除。你现在能跑起来的运行环境要么已经用的是mysqli要么是pdo_mysql。在 mpay 的Application/Common/Conf/config.php里找到数据库配置?php return array( DB_TYPE mysqli, // 低版本可能是 mysql DB_HOST 127.0.0.1, DB_NAME mpay_db, DB_USER root, DB_PWD your_password, DB_PORT 3306, DB_PREFIX pay_, );如果DB_TYPE是mysql直接改成mysqli这是最快的兼容方案。mysqli跟 TP3.2 的Think\Db\Driver\Mysqli驱动搭配工作正常支持预处理、事务性能比老驱动更好。如果你的环境只装了pdo_mysql那需要确认ThinkPHP/Library/Think/Db/Driver/Pdo.class.php存在并把DB_TYPE改成pdo、DB_DSN配置成mysql:host127.0.0.1;dbnamempay_db。2.4 打开调试定位报错TP3.2 默认关闭调试模式白屏的时候你什么都看不到。改造第一步是打开调试// index.php 入口文件 define(APP_DEBUG, true);同时配置Application/Common/Conf/config.phpSHOW_ERROR_MSG true, ERROR_MESSAGE 页面错误请稍后再试,打开后具体哪个文件、哪一行调用被移除的函数就一目了然了。实际遇到最多的几个位置是ThinkPHP/Library/Think/Db/Driver.class.php里的_execute方法、ThinkPHP/Common/functions.php里的get_magic_quotes_gpc这个函数 PHP 7.4 废弃、PHP 8.0 移除、以及 mpay 的核心支付类Application/Home/Common/Pay.class.php里可能出现的eregi老正则。逐个击破即可。3. 码支付核心逻辑异步回调验签与订单状态流转兼容层做完代码能跑接下来就要看得懂业务。mpay 这类系统里最重要的代码不在页面显示而在回调处理。用户扫码支付后码支付平台向你的服务器发送一个异步通知你的程序要判断这笔通知是真是假、金额对不对、订单是不是已经处理过。这一步做错要么被人刷单要么漏单。3.1 回调验签的标准姿势码支付平台通常会在异步通知里带一个sign参数生成规则一般是把核心参数按 key 排序拼接加上商户密钥做 MD5。商户收到通知后做同样计算比对两个 sign 是否一致一致才说明通知确实来自码支付平台。下面是一段典型的 mpay 回调处理代码放在Application/Home/Controller/NotifyController.class.php?php namespace Home\Controller; use Think\Controller; class NotifyController extends Controller { public function index() { // 1. 接收回调参数 $params I(post.); $sign $params[sign]; unset($params[sign]); // 2. 按 ASCII 码排序并拼接 ksort($params); $str ; foreach ($params as $k $v) { if ($v ! $v ! null) { $str . $k . . $v . ; } } $str rtrim($str, ); // 3. 拼接商户密钥并 MD5 $merchantKey C(MPAY_KEY); // 从配置读取 $calcSign strtoupper(md5($str . $merchantKey)); // 4. 验签 if ($calcSign ! $sign) { echo sign error; exit; } // 5. 根据订单号查本地订单 $orderModel M(order); $order $orderModel-where(array(out_trade_no $params[out_trade_no]))-find(); if (!$order) { echo order not found; exit; } // 6. 金额比对用浮点数格式化避免 0.10.2 类问题 if (abs(floatval($params[money]) - floatval($order[money])) 0.01) { echo money mismatch; exit; } // 7. 订单状态幂等处理 if ($order[status] 1) { echo success; exit; } // 8. 更新订单状态 $orderModel-where(array(id $order[id]))-save(array( status 1, trade_no $params[trade_no], pay_time time(), )); // 9. 触发商户业务回调 $this-triggerMerchantNotify($order); echo success; } }这段代码的每一步都有讲究第 2 步ksort很重要签名算法要求参数按 key 的 ASCII 升序排列漏掉排序直接导致验签失败。第 3 步拼接密钥的时机不在拼接之前而在最后顺序错了生成签名永远不一致。第 6 步金额比对用abs取差值而不是直接因为浮点运算可能带来极小误差。第 7 步幂等处理防止重复通知导致订单状态被二次处理、用户被加两次余额。码支付平台的异步通知机制是“收到 success 即停止通知”没返回 success 会每 2 分钟重发一次最长持续 24 小时。3.2 TP3.2 里的 I() 方法细节I(post.)是 TP3.2 的输入过滤方法源码这一行经常是I(post.)一次性取整个$_POST。这里有个安全隐患如果码支付平台的签名算法里某些字段没参与签名而你又用整个数组去重放攻击者就可以在$_POST里多加参数。所幸绝大多数码支付平台都是“参与签名的参数固定、其余忽略”旧源码沿用这一套问题不大。如果你在二次开发时新增了字段记得新增的字段不要参与验签、不要影响原有逻辑最好单独用I(post.new_field)取值而不是改签名拼接逻辑。3.3 订单状态机用户看到的和系统处理的分离mpay 的订单表通常是这样的字段设计字段名类型说明idint自增主键out_trade_novarchar(32)商户本地订单号trade_novarchar(64)码支付平台订单号moneydecimal(10,2)订单金额statustinyint0未支付1已支付2已关闭3退款create_timeint下单时间戳pay_timeint支付时间戳notify_urlvarchar(255)商户异步回调地址return_urlvarchar(255)商户同步跳转地址状态机里最容易出问题的是“已支付”后不能变回“未支付”。有些垃圾源码在收到回调时会先查询订单发现查询失败就自动把订单删除或者状态置 0导致用户付了钱单子没了。正确做法是状态只能单调递增0 到 1 可以1 到 3 可以1 到 0 永远不允许。改造时可以加一层更新条件$result M(order)-where(array(id $order[id], status 0))-save($updateData); if (!$result) { echo success; // 说明已经处理过了直接回复平台不要重复发 exit; }这样即使平台重复发送通知第二次更新会因为status ! 0命中不了条件返回受影响行数为 0程序直接回复 success 结束。这是防止重复到账的最实用手法。4. mpay 的目录结构与二次开发常见的 3 处扩展点聊完了业务核心回到源码本身。thinkphp 3.2 的 mpay 源码通常会把你领到一条固定的目录链路上搞清楚这条链路、知道去哪里改对接、去哪里加新功能后面接码支付平台或者其他支付通道就顺手了。4.1 Application 目录里找功能mpay/ ├── index.php ├── Application/ │ ├── Common/ │ │ ├── Common/ │ │ │ └── function.php // 公共函数 │ │ └── Conf/ │ │ └── config.php // 全局配置数据库、密钥、站点信息 │ ├── Home/ │ │ ├── Controller/ │ │ │ ├── IndexController.class.php // 收银台首页 │ │ │ ├── PayController.class.php // 发起支付 │ │ │ ├── NotifyController.class.php // 异步回调 │ │ │ └── QueryController.class.php // 订单查询 │ │ ├── Model/ │ │ │ └── OrderModel.class.php │ │ └── View/ │ │ └── default/ │ └── Admin/ │ ├── Controller/ // 后台管理商户管理、订单列表、平台设置 │ └── View/ ├── ThinkPHP/ // 框架目录 ├── Public/ └── mpay.sql // 数据库初始化脚本这是 mpay 类项目最常见的布局。Home 模块面向商户和用户Admin 模块面向站长。拿到代码后先导入mpay.sql再改config.php的数据库连接。登录后台默认密码一般在mpay.sql里初始化的admin表不要用默认密码上线。4.2 扩展点一接入一个新的支付通道支付通道代码一般收敛在Application/Home/Common/或Application/Common/Common/里的一个通道类比如AliPay.class.php、WechatPay.class.php。新增通道的固定套路?php // Application/Home/Common/Qpay.class.php // 新通道的支付类 class Qpay { protected $config; public function __construct($config) { $this-config $config; } /** * 生成支付请求参数 * param array $order 订单信息 * return array 返回给模板的支付参数 */ public function buildRequest($order) { $params array( pid $this-config[pid], out_trade_no $order[out_trade_no], money $order[money], notify_url $order[notify_url], return_url $order[return_url], name $order[subject], ); $params[sign] md5($this-buildSign($params, $this-config[key])); $params[sign_type] MD5; return $params; } public function buildSign($params, $key) { ksort($params); $str ; foreach ($params as $k $v) { if ($v ! $v ! null) { $str . $k . . $v . ; } } return trim($str, ) . $key; } }接入的时候只需要在PayController里根据通道参数new对应的类调用buildRequest得到参数数组再redirect到支付平台下单。关键是在buildSign里确认码支付平台要求的拼接顺序有的平台要求把sign_type也参与签名有的排除这个必须以你要对接的平台文档为准。4.3 扩展点二订单查询与掉单补单码支付这类异步通知机制偶尔会丢通知或者延迟用户付了钱却显示未支付。好的 mpay 源码会在收银台页面加一段轮询 JS去请求QueryController的接口?php namespace Home\Controller; use Think\Controller; class QueryController extends Controller { public function status() { $outTradeNo I(get.out_trade_no, , trim); $order M(order)-where(array(out_trade_no $outTradeNo))-find(); if ($order $order[status] 1) { $this-ajaxReturn(array( status 1, url U(Index/done, array(out_trade_no $outTradeNo)), )); } else { $this-ajaxReturn(array(status 0)); } } }前端 JS 每 2 秒请求一次拿到 status1 跳转结果页。这个小功能在用户体验上的提升比任何优化都明显。如果源码里没有按上面这样补就行。4.4 扩展点三通知重试队列更进阶的做法是把回调转发做成队列收到码支付通知后先把订单状态落库然后把“通知商户”这个动作丢进队列让队列消费者去 POST 给商户的notify_url。这样即使商户接口暂时不可用也不会阻塞码支付平台的回调线程。TP3.2 没有原声队列常见做法是建一张notify_queue表Cron 每分钟扫一次把状态为 0 且重试次数小于 10 的记录取出来发送。5. 上线前把源码里的后门和配置异常全部扫一遍最后聊一个老源码必定存在的问题后门和安全漏洞。thinkphp 3.2 时代的源码很多是商业源码被破解流传出来的里面可能藏着作者留的后门。不建议直接拿看起来干净的 mpay 就上线。5.1 排查文件写入和可疑 eval第一种排查方式是全局搜索危险函数grep -rn eval( Application/ --include*.php grep -rn assert( Application/ --include*.php grep -rn system( Application/ --include*.php grep -rn exec( Application/ --include*.php grep -rn file_put_contents Application/ --include*.php重点看这些函数出现的上下文。如果eval拼接了$_POST、$_GET或$_REQUEST里的值那基本可以断定是后门或预留的木马。正常业务代码里eval几乎不出现TP3.2 的模板编译有独立的编译类不走eval。还有一个隐蔽的排查点view目录里看起来像模板、实际上是一段 PHP 代码的.html文件也要全局grep -rn ?php去看。5.2 检查 TP3.2 公开漏洞thinkphp 3.2 存在一些历史上著名的 SQL 注入和更新注入问题常见的是I(get.id)直接拼 SQL。还好大多数 mpay 的查询模型用的where(array(...))数组方式不会拼接字符串。如果没有用数组而是字符串比如where(id . I(get.id))那必须改成数组方式或者用intval强制类型转换。5.3 一份可执行的上线前验证清单用一个简易 checklist 收尾这是我对付所有 thinkphp 老源码的固定步骤检查项命令或方法通过标准PHP 版本与扩展php -v php -m无 fatalcurl、pdo_mysql 已启用魔术引号模拟php -r var_dump(function_exists(get_magic_quotes_gpc));输出 false 且页面无调用错误数据库连通访问首页或php think无 DB 错误页面正常渲染调试模式关闭index.php里APP_DEBUG改为 false打开错误不泄露路径回调验签自测用码支付平台工具手动提交一条 mock 通知sign 正确时返回 success重复通知去重连续提交两条相同交易号的通知第二条返回 success 且订单金额不变后门扫描grep -rn eval(\\$\_\? Application/无输出或逐条确认为合理用法做完这些一份 thinkphp 老码支付源码才算是从“能打开”变成了“能上线”。如果你拿到的 mpay 源码在验签、金额比对、幂等处理上有缺漏按上面第 3 章的改法处理这套模式适用于所有码支付类系统比换一个新框架重写成本低得多。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询