回调验签失败主因是原始XML读取错误、响应格式不合规及缺乏幂等控制;须用file_get_contents('php://input')获取原始XML,严格输出标准XML响应,并以out_trade_no做幂等校验。

回调验签失败:notify_url 收到数据但 verifyNotify() 总返回 false
ThinkPHP 官方 SDK(v3.2/v5.0+)对微信回调验签封装较浅,常见问题不是签名错,而是原始 XML 未正确还原。微信回调是纯 XML POST,$_POST 为空,必须用 file_get_contents('php://input') 读取原始体——但很多人直接用 $request->post() 或 input('post.'),拿到的是空数组或被自动转义/解码过的乱码。
- 务必在控制器入口第一行用
file_get_contents('php://input')获取原始 XML 字符串,不要依赖框架自动解析 - 验签前先用
simplexml_load_string()解析并转成关联数组,再剔除sign字段,按字典序拼接键值对(注意:微信要求字段值为空时不参与拼接) - 签名密钥必须是商户平台「APIv3 密钥」(v2 接口用的是「API 密钥」),且不能带前后空格;v3 接口还需用证书私钥验签,不能只比对
sign - ThinkPHP 5.1+ 若启用了全局 JSON 输入解析中间件,会干扰 XML 回调,需在该路由上禁用:
['middleware' => ['cors', 'throttle', 'not_json']]
v2 和 v3 回调处理逻辑完全不同,混用会导致验签永远失败
v2 接口走 MD5 签名,回调 XML 里带 <sign></sign> 字段;v3 接口用 AES-GCM 加密 + RSA 签名,回调是加密 JSON,必须先用平台证书私钥解密,再验签响应头里的 Wechatpay-Signature ——两者代码完全不兼容。
- v2 场景:用
WxPayApi::notify($notify, $callback)(TP3.2)或自写验签逻辑,核心是拼接k1=v1&k2=v2&key=KEY后 MD5 - v3 场景:必须调用微信官方 PHP SDK(
wechatpay-php),用WechatPay\GuzzleMiddleware\WechatPayMiddleware构造验证器,不能手写 - 别试图把 v3 的加密 JSON 当普通 JSON 用
json_decode()解析——它被 AES 加密过,直接 decode 会报错或返回空 - TP6 中若用
think-wechat扩展,默认只支持 v2;v3 需手动集成官方 SDK 并绕过扩展的自动路由
验签通过后,为什么 return success 不生效,微信还一直重发?
微信收到非 200 响应、响应体含不可见字符(BOM、空格、换行)、或 Content-Type 不为 text/xml,都会判定回调失败并持续重试(最长 24 小时)。
- 响应必须严格输出:
<xml><return_code>SUCCESS</return_code><return_msg>OK</return_msg></xml>,不能多一个空格、不能有 echo/print_r、不能触发任何日志输出 - TP5/6 中默认开启调试模式会输出 debug 信息,上线前必须关掉
app_debug = false,否则响应体开头就有 HTML 或 JSON - 别用
exit('success')或die('success'),微信只认标准 XML 结构;也不要用return json(['return_code'=>'SUCCESS']),类型和结构全错 - 建议在响应前加
ob_end_clean(); header('Content-Type: text/xml; charset=utf-8');,再 echo XML 字符串
异步处理订单状态时,数据库更新失败导致重复支付或漏单
微信回调只保证「至少一次」送达,不保证「恰好一次」。如果业务逻辑中没做幂等控制,同一笔订单可能被多次更新,比如重复扣库存、重复发货。
立即学习“PHP免费学习笔记(深入)”;
- 必须用微信返回的
out_trade_no作为数据库唯一索引或加锁依据,更新前先查是否已处理:WHERE out_trade_no = ? AND status = 'wait_pay' - 避免在回调中执行耗时操作(如调第三方 API、生成 PDF),应立即写入任务队列(如 think-queue),由后台 Worker 处理
- 记录完整原始回调日志(XML 全文 + 时间戳 + IP),方便排查重试原因;日志路径建议设为独立文件,避免和框架日志混在一起被轮转清空
- 微信回调 IP 是固定几段(如
182.254.0.0/16),可在 Nginx 层加白名单限制,防止伪造请求打穿验签逻辑











