微信公众号支付在ThinkPHP中失败,主因是授权目录未精确匹配带尾斜杠的URL路径、spbill_create_ip未获取真实客户端IP、JSAPI二次签名参数顺序及key未按微信2026年规则严格对齐。

微信公众号支付在 ThinkPHP 里跑不起来,八成不是代码写错了,而是授权目录、spbill_create_ip、签名顺序这三处没对齐微信 2026 年当前规则。官方文档没明说的坑,全卡在这儿。
授权目录必须精确匹配 URL PATH,且带尾部斜杠
微信校验的是你发起支付时浏览器地址栏的完整路径前缀,不是域名,也不是控制器名。比如用户正在访问 https://example.com/Home/Order/pay/id/123,那授权目录就得填 https://example.com/Home/Order/(注意末尾斜杠)。
- TP6 默认开启 URL 重写,实际请求可能是
/index.php/Home/Order/pay,此时授权目录应为https://example.com/index.php/Home/Order/ - 用
phpinfo()或var_dump($_SERVER['REQUEST_URI'])确认真实路径结构,别靠猜 - 测试阶段务必用外网可访问地址,本地
127.0.0.1或内网 IP 直接被微信拦截,连授权目录校验都进不去
spbill_create_ip 必须是真实客户端出口 IP,不能是空或 127.0.0.1
微信会校验这个字段是否与发起请求的设备公网 IP 一致。Nginx/Apache 默认不透传真实 IP,$_SERVER['REMOTE_ADDR'] 拿到的往往是反向代理地址。
- 确保 Nginx 配置了
proxy_set_header X-Real-IP $remote_addr;和proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - PHP 中取值优先用
$_SERVER['HTTP_X_REAL_IP'], fallback 到$_SERVER['HTTP_X_FORWARDED_FOR'](取第一个非私有 IP),最后才用$_SERVER['REMOTE_ADDR'] - 本地调试时,可在配置中临时硬编码为你的公网 IP(查 ip.cn),但上线前必须切回自动获取逻辑
JSAPI 支付二次签名必须用原始 key,且参数顺序固定
前端调 wx.chooseWxPay 所需的 paysign,不是用新 key 签,而是拿统一下单时生成签名的那个 key,再按严格顺序拼参签名。
公众号运营:文章发布至草稿、样式封面、评论与用户管理、数据统计等。用户要求将 Markdown 发送到公众号草稿、查看阅读量统计或类似后台操作时,使用本技能。
立即学习“PHP免费学习笔记(深入)”;
- 参与签名的字段只有 5 个:
appId、nonceStr、package、signType、timeStamp,缺一不可,大小写敏感(比如nonceStr不是noncestr) -
package值必须是字符串"prepay_id=wx123456789",不是 XML、不是 JSON,也不能加任何前缀后缀 -
timeStamp必须是当前秒级时间戳(time()),缓存或复用旧值会导致微信返回 “时间戳无效” - 签名算法仍是 MD5,不是 HMAC-SHA256;拼接格式为
key1=value1&key2=value2&key=xxx,末尾加商户 key 后 MD5
notify_url 验签失败,大概率毁在数据源和响应格式上
微信回调不走常规 POST 解析流程,直接发原始 XML 流,很多 TP 中间件会提前消费掉它。
- 绝对不要用
$this->request->post()或input('post.')读取回调数据,必须用file_get_contents('php://input') - XML 转数组后,先
unset($data['sign']),再按 key 字典序升序排列,拼成k1=v1&k2=v2字符串,末尾加&key=xxx,再 MD5 - 验签通过后,必须原样输出
<xml><return_code>SUCCESS</return_code><return_msg>OK</return_msg></xml>,HTTP 状态码 200,不能多空格、换行、BOM 头 - TP6 若启用了全局 JSON 中间件,需在 notify 路由上显式禁用:
->middleware(AllowCrossDomain::class)->except(['json'])类似处理
最常被跳过的细节:微信回调不带 cookie,TP 默认 session_start() 会尝试创建新 session,导致文件锁阻塞或日志污染;notify 方法开头加 if (session_status() === PHP_SESSION_ACTIVE) { session_write_close(); } 更稳妥。


















