微信支付回调必须用file_get_contents('php://input')读取原始XML,经simplexml_load_string转数组后按字典序拼接非空非sign字段+密钥MD5验签,成功后立即返回纯文本success。
微信
支付回调必须用原生
读取原始 XML 数据
微信服务器发起的异步通知是 POST 请求,但**不带
**,也不走 PHP 的
。它发的是纯 XML(
),且不附带任何 URL
编码
参数。直接读
或
一定是空的。
正确做法是:
用
获取原始请求体
不能用
(PHP 7.0+ 已废弃,且默认关闭)
必须在脚本开头就读,否则后续调用
会返回空
验签前先用
解析并转成数组
微信返回的 XML 结构固定,但字段名大小写敏感、存在嵌套(如
在根节点),直接用 DOM 或正则解析容易出错。推荐用
+
转为关联数组。
注意两点:
立即学习
“
PHP免费学习笔记(深入)
”;
PHP 8.5.5
PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。
下载
XML 中的
会被自动剥离,内容直接作为字符串值
如果 XML 格式非法(比如微信重试时传了乱码),
会返回
,必须判空并返回
验签必须按微信文档拼接字符串:字段名升序 + 不含
+ 签名密钥末尾加
微信签名规则不是简单地把所有字段连起来,而是有严格顺序和过滤条件:
只取
中值非空、且 key 不是
的字段
key 按字典序(ASCII 升序)排序,不是中文拼音,也不是 PHP 数组键顺序
每个
用
连接,末尾**不加 &**
最后拼上
(你的商户平台「API 密钥」,32 位)
常见翻车点:
漏掉
或
等可选字段导致签名不一致
把
当整数参与拼接(实际要原样字符串,如
)
用
但没转大写(微信要求大写 HEX)
处理成功后必须立即返回
纯文本,且 HTTP
状态码
为 200
微信服务器收到非
(比如
、空响应、HTML 页面、JSON 字符串)或超时(>5 秒)、状态码非 200,就会持续重试(最多 10 次,间隔不断拉长)。你不能在返回前查数据库、发邮件、调第三方 API——这些必须异步做。
安全建议:
先验签,再检查
和
用
查询本地订单是否存在且未支付,避免重复处理
更新订单状态后,再返回
;哪怕 DB 写失败,也应记录日志并返回
触发重试
微信回调最常卡在「以为能读
」和「签名字符串拼错顺序或漏字段」,这两处多打几个
对比微信文档里的示例签名,基本就能定位。
php://inputContent-Type: application/x-www-form-urlencoded$_POSTContent-Type: text/xml$_POST$_REQUESTfile_get_contents('php://input')$HTTP_RAW_POST_DATAphp://input$xml = file_get_contents('php://input');
if (empty($xml)) {
// 微信可能没发数据,或被中间件/代理截断
exit('fail');
}simplexml_load_string()simplexml_load_string()json_decode(json_encode(...), true)simplexml_load_string()falsefail$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
if ($data === false) {
exit('fail');
}
$notify = json_decode(json_encode($data), true);sign$notifysignkey=value&&key=YOUR_MCH_KEYtrade_typebank_typetotal_fee"100"md5()$params = [];
foreach ($notify as $k => $v) {
if ($k !== 'sign' && $v !== '' && !is_array($v)) {
$params[$k] = $v;
}
}
ksort($params);
$string = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
$string .= '&key=your_32bit_mch_key';
$localSign = strtoupper(md5($string));
if ($localSign !== $notify['sign']) {
exit('fail');
}successsuccessFAIL$notify['result_code'] === 'SUCCESS'$notify['return_code'] === 'SUCCESS'$notify['out_trade_no']successfailif ($notify['result_code'] === 'SUCCESS' && $notify['return_code'] === 'SUCCESS') {
// 查单、更新、发消息等业务逻辑(建议丢进队列)
updateOrderStatus($notify['out_trade_no'], $notify['transaction_id']);
}
// 必须无任何输出、无换行、纯文本
echo 'success';$_POSTvar_dump()