Alipay Payment Gateway 的 notify URL 未收到响应
结论
notify_url 是由支付宝服务器发起的异步 HTTP 回调,不依赖用户浏览器跳转。要正常接收通知,需要同时满足以下条件:
- 创建支付请求时传入了完整的
notify_url。 - 该 URL 可以从公网直接访问,不能使用
localhost、局域网 IP,也不能指向需要登录的页面。 - 接口允许支付宝发送
POST请求,并且不会被 CSRF、防火墙、反向代理或访问控制拦截。 - 回调程序完成验签和业务处理后,直接返回纯文本
success。
支付成功只能说明交易已经完成,不能证明异步通知接口可以正常访问。
首先检查支付请求
以常见的 PHP RSA2 SDK 为例,notify_url 通常设置在支付请求对象上,不应放在业务参数 biz_content 中:
<?php
$request = new AlipayTradePagePayRequest();
$request->setReturnUrl('https://www.example.com/payment/return');
$request->setNotifyUrl('https://www.example.com/payment/notify');
$request->setBizContent(json_encode([
'out_trade_no' => 'ORDER_202608310001',
'product_code' => 'FAST_INSTANT_TRADE_PAY',
'total_amount' => '99.00',
'subject' => '订单支付',
], JSON_UNESCAPED_UNICODE));
$response = $aopClient->pageExecute($request);
echo $response;
不同产品和 SDK 版本使用的类名可能不同,具体应以当前 SDK 的支付接口为准。不过有一点不变:请求中必须包含一个能从公网访问的绝对 URL,例如:
https://www.example.com/payment/notify
以下写法不能使用:
http://localhost/payment/notify
http://127.0.0.1/payment/notify
http://192.168.1.10/payment/notify
/payment/notify
return_url 和 notify_url 的用途并不相同:
return_url:用户完成支付后,浏览器跳转到的页面。notify_url:支付宝服务器主动调用的后台接口。
所以,即使用户关闭浏览器,也不会影响 notify_url。
编写异步通知接口
下面是一段简化的 PHP 示例。AlipaySignature::rsaCheckV1() 适用于部分常见的 PHP SDK。如果当前 SDK 提供了其他验签方法,需要换成对应的实现。
<?php
require_once __DIR__ . '/alipay-sdk/AopSdk.php';
// 不要输出调试信息、HTML 或 JSON。
$params = $_POST;
if (empty($params)) {
http_response_code(400);
echo 'failure';
exit;
}
$alipayPublicKey = '支付宝公钥内容';
$verified = AlipaySignature::rsaCheckV1(
$params,
$alipayPublicKey,
'RSA2'
);
if (!$verified) {
http_response_code(400);
echo 'failure';
exit;
}
$outTradeNo = $params['out_trade_no'] ?? '';
$tradeNo = $params['trade_no'] ?? '';
$tradeStatus = $params['trade_status'] ?? '';
$totalAmount = $params['total_amount'] ?? '';
$appId = $params['app_id'] ?? '';
if ($outTradeNo === '') {
http_response_code(400);
echo 'failure';
exit;
}
// 必须从数据库读取订单,并校验:
// 1. out_trade_no 对应的订单真实存在
// 2. total_amount 与订单金额一致
// 3. app_id、seller_id 等信息与商户配置一致
$order = findOrderByNumber($outTradeNo);
if (
!$order ||
bccomp((string) $order['amount'], (string) $totalAmount, 2) !== 0 ||
$appId !== '你的支付宝应用 APPID'
) {
http_response_code(400);
echo 'failure';
exit;
}
if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
// 使用事务或条件更新保证幂等,避免重复通知造成重复入账。
markOrderPaidIfUnpaid($outTradeNo, $tradeNo);
}
// 处理成功后必须返回纯文本 success。
echo 'success';
其中,findOrderByNumber() 和 markOrderPaidIfUnpaid() 是业务层函数,需要根据实际使用的数据库实现。不要用回调参数直接拼接 SQL,应使用参数化查询。
排查回调没有到达的原因
1. URL 无法从公网访问
可以在另一台公网机器上测试:
curl -i -X POST https://www.example.com/payment/notify \
-d "test=1"
需要逐项检查:
- DNS 是否解析到了正确的服务器。
- HTTPS 证书是否有效,证书链是否完整。
- 80 或 443 端口是否开放。
- CDN、WAF、安全组或防火墙是否拦截了请求。
- URL 是否发生了不必要的 301、302 跳转。
- 路由是否只接受
GET,没有开放POST。 - 接口是否要求登录、Cookie、验证码或固定的来源页面。
本地开发时,应通过受控的公网测试地址进行联调,不要把临时隧道地址直接用于生产环境。
2. 被 CSRF 或鉴权中间件拦截
支付宝不会携带网站用户的登录状态或 CSRF Token。通常需要将回调路由排除在浏览器登录验证和 CSRF 校验之外,但支付宝签名验证仍然必须保留。
例如,可以在框架中只为明确的通知路由配置 CSRF 例外:
/payment/notify
不要关闭整个网站的 CSRF 防护。
3. 程序读取参数的方式不正确
支付宝异步通知通常以表单形式通过 POST 发送。在 PHP 中一般这样读取:
$params = $_POST;
如果框架只读取 JSON 请求体,程序可能拿到空参数。需要确认接口支持:
application/x-www-form-urlencoded
验签时必须使用收到的完整通知参数。在验签之前,不要随意修改、重新编码或格式化字段值。
4. 验签配置错误
常见问题包括:
- 把应用私钥误当成支付宝公钥。
- 使用了其他环境的公钥,例如混用了沙箱和生产环境。
- 签名方式配置为
RSA,实际请求却使用RSA2,或者反过来。 - 公钥内容复制不完整。
- 验签前修改了参数值或字符编码。
只有验签成功后,才能更新订单。旧版接口可能还需要校验 notify_id,是否需要这一步取决于具体产品和 SDK,不能拿旧版示例代替当前接口文档。
5. 没有正确返回 success
业务处理成功后,响应体应保持简单:
success
不要返回:
{"status":"success"}
也不要输出 HTML、调试日志、PHP Warning、BOM 或其他字符。如果支付宝没有识别到成功响应,通常会按照相应接口的机制继续尝试通知,所以数据库更新必须具备幂等性。
建议的处理顺序
回调接口可以按下面的顺序处理:
- 接收完整的通知参数。
- 验证支付宝签名。
- 根据
out_trade_no查询本地订单。 - 校验订单金额、
app_id、收款方身份等重要字段。 - 检查
trade_status。 - 使用事务或条件更新,将订单标记为已支付。
- 记录
trade_no和必要的审计信息。 - 返回纯文本
success。
不能只根据 trade_status 更新数据库,也不能在验签前信任通知中的订单号和金额。
日志与补偿机制
可以记录以下信息,但不要把私钥等敏感内容写入日志:
error_log(json_encode([
'time' => date('c'),
'out_trade_no' => $_POST['out_trade_no'] ?? null,
'trade_no' => $_POST['trade_no'] ?? null,
'trade_status' => $_POST['trade_status'] ?? null,
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null,
], JSON_UNESCAPED_UNICODE));
如果日志中完全没有收到请求,应先检查 notify_url 配置和网络可达性。如果日志中有请求,但处理结果失败,则需要检查路由、中间件、验签配置和程序异常。
异步通知也不应是唯一的支付状态确认方式。对于长时间停留在“待支付”状态的订单,可以调用对应的支付宝交易查询接口主动核实支付状态,用于补偿通知丢失或业务处理失败的情况。
备注:内容仅供参考。
