Alipay notify_url 未被调用,如何排查?
结论
return_url 能打开,不等于 notify_url 也能正常接收请求。return_url 由用户的浏览器访问,notify_url 则由支付宝服务器在交易状态变化后发起异步 HTTP 请求。
当前链接至少需要核查三个问题:
- 使用的是 testing credential,请求却发往
https://mapi.alipay.net/gateway.do。需要确认测试凭据是否应当配合沙箱网关使用。 notify_url使用裸 IP 和 HTTP,可能受到支付宝接口规则、安全策略、云网络配置或域名校验限制。建议改用公网域名和 HTTPS。notify_url和return_url没有经过 URL 编码。包含?task=...的回调地址应作为完整的参数值编码,再按照支付宝的规则重新计算签名。
如果 Apache access log 中完全没有请求记录,说明问题通常还没有进入业务代码。此时应先检查交易状态、网关环境、回调地址格式和公网入口,不必急着排查 PHP 处理逻辑。
先确认异步通知是否应该发生
打开支付跳转链接本身不会触发 notify_url。一般要等到支付成功、交易关闭或其他受支持的交易状态发生变化后,支付宝才会发送异步通知。确切的触发条件取决于所使用的接口。
先在对应环境的商户后台确认:
- 订单是否真正创建成功;
- 用户是否完成付款;
- 交易状态是否已经变化;
- 是否存在异步通知记录或通知失败记录;
out_trade_no是否唯一;- 当前
partner、密钥和网关是否属于同一套环境。
如果只是打开了支付页,订单并未完成支付,没有收到异步请求可能是正常的。
检查测试凭据与网关是否匹配
示例请求使用的是:
https://mapi.alipay.net/gateway.do
这是生产网关的形式。如果 testing credential 指的是沙箱账号或测试密钥,就不能默认它能与生产网关配合使用。应查看该账号对应产品的文档,确认以下配置都来自同一环境:
gateway
partner
seller_id / seller_email
MD5 key 或其他签名密钥
notify_url
return_url
不要混用生产网关、沙箱商户号和测试密钥。网关不匹配时,支付请求可能直接报错,也可能无法形成一笔可以发送异步通知的有效交易。
不同地区、不同时期的 Alipay 接口使用的网关并不完全相同。沙箱地址应以当前签约产品后台或官方接口文档为准,不要只根据示例地址推测。
改用公网域名和 HTTPS
示例中的回调地址是:
http://52.xx.xx.xx/payment/alipay/?task=alipay.notifyurl
这个 IP 能从个人电脑访问,只能说明浏览器到服务器的网络路径可用,无法证明支付宝服务器也能正常访问。建议换成稳定的公网域名:
https://pay.example.com/payment/alipay/notify
还需要确认:
- DNS 可以从公网解析;
- 证书由公网可信 CA 签发;
- 证书链完整且没有过期;
- 服务监听标准的
443或80端口; - AWS Security Group 和 Network ACL 允许入站访问;
- Apache 或前置负载均衡器能够接受该
Host; - 入口不依赖 VPN、Cookie、登录状态或 HTTP Basic Authentication;
- 没有根据国家、地区、User-Agent 或 IP 白名单拦截请求;
- AWS WAF、CDN、反向代理和安全插件没有阻止 POST 请求。
除非当前官方文档明确提供了可维护的地址范围,否则不建议把支付宝出口 IP 固定写入白名单。
正确编码回调参数
构造支付请求时,需要对每个参数值进行 URL 编码。包含 ?、=、& 的 URL 尤其不能直接拼入查询字符串。
例如,原始回调地址是:
http://52.xx.xx.xx/payment/alipay/?task=alipay.notifyurl
编码后应类似:
http%3A%2F%2F52.xx.xx.xx%2Fpayment%2Falipay%2F%3Ftask%3Dalipay.notifyurl
在 PHP 中,可以用参数数组生成查询字符串:
<?php
$params = [
'_input_charset' => 'utf-8',
'currency' => 'USD',
'notify_url' => 'https://pay.example.com/payment/alipay/notify',
'out_trade_no' => '559a50fe904bb',
'partner' => 'YOUR_PARTNER_ID',
'return_url' => 'https://pay.example.com/payment/alipay/return',
'service' => 'create_forex_trade',
'sign_type' => 'MD5',
'subject' => 'Package',
'total_fee' => '0.01',
];
// sign 必须按照该接口规定的排序、字符集和签名算法生成。
// 不要直接复用参数变化前的签名。
$params['sign'] = createAlipaySignature($params, $secret);
$gateway = 'https://mapi.alipay.net/gateway.do';
$redirectUrl = $gateway . '?' . http_build_query(
$params,
'',
'&',
PHP_QUERY_RFC3986
);
header('Location: ' . $redirectUrl);
exit;
createAlipaySignature() 在这里仅为占位函数。哪些参数参与签名、是否排除空值、如何排序,以及 URL 编码发生在签名前还是签名后,都必须严格按照当前接口文档处理。
只要参数发生变化,包括 notify_url、域名、协议或路径有改动,就必须重新生成 sign。示例中的原签名不能继续使用。
用独立入口接收通知
异步通知和同步跳转最好不要共用一个脚本,再依靠查询参数区分:
/payment/alipay/?task=alipay.notifyurl
/payment/alipay/?task=alipay.returnurl
可以改用两个独立路径:
/payment/alipay/notify
/payment/alipay/return
这样能减少路由重写、查询参数解析、缓存和重定向引发的问题。
通知入口必须允许支付宝要求的 HTTP 方法,通常需要处理 POST,不能只实现浏览器常用的 GET:
<?php
// 支付宝异步通知通常通过 POST 提交,具体字段以对应接口为准。
$payload = $_POST;
error_log('Alipay notify received: ' . json_encode(
$payload,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
));
// 1. 验证签名。
// 2. 校验通知来源或通知 ID。
// 3. 校验 partner、out_trade_no、金额、币种和交易状态。
// 4. 以幂等方式更新订单。
$verified = verifyAlipayNotification($payload);
if (!$verified) {
http_response_code(400);
echo 'fail';
exit;
}
http_response_code(200);
header('Content-Type: text/plain; charset=utf-8');
echo 'success';
只有签名、商户号、订单号、金额、币种和交易状态都校验成功后,才能更新订单并返回成功响应。
避免回调被重定向
先检查最终回调地址是否返回 301、302、403、404 或 500。常见情况有:
- HTTP 自动跳转到 HTTPS;
- 裸 IP 自动跳转到域名;
/payment/alipay自动补/;- Apache RewriteRule 把 POST 重定向到其他地址;
- Web 应用把未登录请求跳转到登录页;
- CSRF 中间件拒绝外部 POST;
- WAF 将回调参数识别为异常请求。
可以从外部网络模拟一次 POST:
curl -i -X POST \
'https://pay.example.com/payment/alipay/notify' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test-001&trade_status=TEST'
这个命令只能检查公网入口、路由和应用能否接收 POST。它不能模拟真实的支付宝签名,也不能证明支付宝一定会发送通知。
理想情况下,测试入口应直接返回:
HTTP/1.1 200 OK
Content-Type: text/plain
success
不要返回 HTML 页面、JSON、额外空格或调试信息。支付宝要求的确认字符串仍应以所用接口的文档为准。
分层查看日志
如果 Apache access log 没有记录,可以沿请求路径逐层排查:
- 支付宝后台是否记录了通知尝试;
- DNS 是否正确解析;
- AWS Security Group 和 Network ACL 是否放行;
- Load Balancer、CloudFront 或 WAF 是否收到或拦截请求;
- Apache 是否监听正确的端口和 VirtualHost;
- Apache access/error log 是否写入了其他日志文件;
- 应用路由、CSRF 和异常日志是否有记录。
使用虚拟主机时,裸 IP 请求可能会进入默认 VirtualHost,相关日志也可能写入另一个文件。改用正式域名后,还要检查对应 VirtualHost 的 ServerName、证书和日志配置。
处理成功响应与重复通知
支付宝异步通知通常要求在规定时间内收到明确的成功响应,否则可能再次发送。因此,通知处理必须具备幂等性:
UPDATE orders
SET payment_status = 'paid'
WHERE out_trade_no = ?
AND payment_status <> 'paid';
处理时还应注意:
- 先验证通知,再更新订单;
- 不要把
return_url参数作为支付成功的依据; - 同一通知重复到达时,不要重复发货或记账;
- 记录通知时间、订单号、交易号、验证结果和响应状态;
- 返回成功前不要执行耗时任务;
- 原始请求日志需要脱敏,不要记录密钥或完整的敏感信息。
建议按以下顺序修正:先确认交易确实已经完成,再核对测试凭据与网关是否属于同一环境;随后换成公网 HTTPS 域名,正确编码 notify_url 并重新签名;最后使用独立、无须登录的 POST 回调入口进行外网测试。
备注:内容仅供参考。
