应优先使用ThinkPHP的$request->param()并配合bind('json', true)或getRawInput(),而非$_POST或直接file_get_contents('php://input');验签需严格按平台规则拼接签名原文并统一UTF-8编码;防重放须用trade_no唯一索引+原子插入,而非仅查out_trade_no;事务需显式指定数据库连接,避免连接复用问题。

支付回调数据结构不统一,$_POST 和 file_get_contents('php://input') 怎么选
ThinkPHP 默认只解析 application/x-www-form-urlencoded 和 multipart/form-data 类型的请求体,但微信、支付宝、银联等回调常发 application/json 或原始字符串(无 Content-Type 或设为 text/plain),此时 $_POST 为空。
必须主动读取原始输入流,但不能硬写 file_get_contents('php://input') —— ThinkPHP 的 Request 对象已封装好兼容逻辑:
- 用
$this->request->param()前先确认是否已自动解析:对 JSON 回调,需在中间件或控制器开头调用$this->request->bind('json', true)启用 JSON 自动解析 - 若回调连
Content-Type都没设(如某些旧版银行通道),直接用$this->request->getRawInput()获取原始字节,再按需json_decode()或urldecode() - 切忌在同一个请求里混用
$_POST和php://input—— PHP 会因输入流只能读一次导致后者返回空字符串
验签失败?检查签名原文拼接顺序和编码一致性
不同支付平台签名规则差异极大:微信用字段按字典序拼接 + key,支付宝用字段按约定顺序拼接 + private_key,银联用 UTF-8 编码后拼接 + cert。ThinkPHP 不提供通用验签,必须手写适配。
常见错误是把「签名原文」当成明文字符串直接拼,忽略了:
立即学习“PHP免费学习笔记(深入)”;
- 微信回调中
sign字段本身不参与签名,但其他所有字段(包括sign_type)都要参与,且值需trim()去首尾空格 - 支付宝回调的
sign是对待签名字符串的 base64 编码结果,而该字符串是 URL 参数键值对按字母升序排列后,用&拼接,再用=连接键值 —— 注意不是 JSON 键名顺序 - 所有字段值必须统一用
UTF-8编码,若数据库或配置文件用了GBK,hash_hmac('sha256', $data, $key)结果必然错
回调重复通知怎么防重放?别只依赖 out_trade_no
支付平台可能因网络问题多次推送同一笔订单的回调,仅查 out_trade_no 是否存在不能完全防重:并发场景下两个请求同时查到“不存在”,都会写库。
必须加原子性控制:
- 用数据库唯一索引约束
trade_no(支付平台返回的流水号),插入前不查,直接INSERT IGNORE或ON DUPLICATE KEY UPDATE,失败即说明已处理过 - Redis 分布式锁慎用:锁 key 必须包含
trade_no,且过期时间要大于业务处理最大耗时,否则锁提前释放会导致重复执行 - 不要把防重逻辑放在验签之后 —— 先验签再防重,否则恶意构造的重复无效通知也会打满你的 Redis 或 DB
异步回调里调用 Db::transaction() 报错?事务和连接生命周期对不上
ThinkPHP 的 Db::transaction() 默认使用当前请求的 PDO 连接,但支付回调常被 Nginx/Apache 当作独立请求处理,PHP-FPM 进程复用可能导致连接状态异常,尤其在长事务或连接池配置下。
稳妥做法是显式管理连接:
- 用
Db::connect('mysql')->transaction(function ($db) { ... })强制指定连接实例,避免复用上一个请求残留的连接 - 若回调需更新多张表,且其中一张表用的是读写分离从库(如日志表),必须手动切回主库:
Db::master()->table('log_table')->insert(...) - 别在事务里做 HTTP 请求(比如回调成功后再调用内部通知服务)—— 超时或失败会导致事务卡住,连接无法释放
最麻烦的其实是回调链路里的字符集隐式转换:MySQL 连接默认 utf8mb4,但某些老支付 SDK 返回的参数含 emoji,进库前没 mb_convert_encoding($str, 'UTF-8', 'GBK') 就会截断,后续对账时发现金额对不上,得翻三天日志。


















