H5 接入支付宝支付成功后 notify_url 未收到异步回调
结论
支付成功后,notify_url 完全收不到请求,问题通常不在 H5 前端流程,而是支付宝服务器无法正常访问回调地址,或者请求被反向代理、路由、防火墙等环节拦截。
从现有参数来看,应先检查这个地址:
http://yizhi.zhxy.ruizhiedu.com/bqvVrJaHgp.php/mobile/notifyx/paytype/alipay
主要检查以下内容:
- 公网能否直接访问
- HTTP 请求是否被重定向到 HTTPS
POST请求是否被 WAF、防火墙或 Web 服务器拦截php后面的 PATH_INFO 路由能否被当前服务器正确解析- 回调处理成功后是否原样输出
success - 是否把“没有收到回调”与“收到回调但验签失败或业务处理失败”混在了一起
return_url 是用户浏览器的跳转地址。浏览器能够正常返回,不代表 notify_url 也可以正常使用。异步通知由支付宝服务器主动发起,用户是否关闭支付页面不会影响通知。
先确认交易是否进入可通知状态
alipay.trade.wap.pay 用于创建支付页面。交易只有进入支付宝规定的可通知状态后,支付宝才会向 notify_url 发送异步通知。
不要仅凭前端显示“支付完成”判断订单状态。应使用该订单的 out_trade_no 调用:
alipay.trade.query
查看以下字段:
trade_statustrade_noout_trade_nototal_amount
常见交易状态包括:
WAIT_BUYER_PAY
TRADE_SUCCESS
TRADE_FINISHED
TRADE_CLOSED
如果订单仍是 WAIT_BUYER_PAY,就不会收到支付成功通知。如果查询结果已经是 TRADE_SUCCESS,再继续检查回调链路。
检查 notify_url 能否从公网访问
“接口支持 POST”只说明接口允许这种请求方式,不代表支付宝服务器能够访问它。测试时应使用服务器之外的网络,不要只在本机或内网测试:
curl -v -X POST \
'http://yizhi.zhxy.ruizhiedu.com/bqvVrJaHgp.php/mobile/notifyx/paytype/alipay' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test&trade_status=TRADE_SUCCESS'
测试时需要确认:
- 域名可以在公网正常解析
- 使用公网可访问的标准 HTTP 或 HTTPS 端口
- IP 白名单没有误拦截请求
- 接口不要求登录、Cookie、Token 或验证码
- 接口没有返回
301、302、403、404、405或500 - 接口响应时间不过长
- 域名、证书和网络链路均正常工作
支付回调最好使用稳定的 HTTPS 地址,例如:
https://example.com/payment/alipay/notify
是否强制使用 HTTPS,应以当前支付宝开放平台规则为准,但生产环境不宜长期使用明文 HTTP。
特别检查当前 URL 的 PATH_INFO 路由
当前回调地址使用了以下结构:
/bqvVrJaHgp.php/mobile/notifyx/paytype/alipay
这种地址依赖 Web 服务器和 PHP 环境对 PATH_INFO 的支持。Nginx、Apache 和 PHP-FPM 的配置不同,可能导致以下问题:
- GET 请求可以访问,但 POST 被转发到其他控制器
- Nginx 找不到实际脚本并返回
404 fastcgi_split_path_info配置错误- 应用框架重写地址后返回
301或302 - PATH_INFO 被服务器安全策略拒绝
- 路由只允许 GET,收到 POST 时返回
405
可以暂时创建一个路由更简单的回调入口进行验证,例如:
https://example.com/alipay-notify.php
如果简单入口能够收到通知,而原地址收不到,基本可以把问题定位在 Web 服务器重写或框架路由上。
在回调入口记录请求,不要只记录业务成功日志
支付宝可能已经发送了通知,只是程序在验签、解析参数或判断业务条件之前就退出了。日志应写在回调入口最前面,而不是等业务处理成功后才记录。
PHP 示例:
<?php
$logData = [
'time' => date('c'),
'method' => $_SERVER['REQUEST_METHOD'] ?? '',
'content_type' => $_SERVER['CONTENT_TYPE'] ?? '',
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? '',
'post' => $_POST,
'raw_body' => file_get_contents('php://input'),
];
file_put_contents(
__DIR__ . '/alipay-notify.log',
json_encode($logData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL,
FILE_APPEND | LOCK_EX
);
生产环境中的日志应存放在公网无法下载的位置,也不要长期记录完整的敏感参数。
还应逐层检查以下日志:
- CDN 或云防护访问日志
- 负载均衡访问日志
- Nginx/Apache access log
- Nginx/Apache error log
- PHP-FPM 日志
- 应用框架日志
- 数据库事务及异常日志
可以根据日志所在的位置判断故障环节:
- 所有访问日志中都没有请求:检查 DNS、防火墙、端口、域名可达性以及支付宝侧的通知记录。
- Web 服务器有记录,但应用没有:检查路由、反向代理和 PHP-FPM。
- 应用记录到了原始请求,但订单没有更新:检查参数读取、验签和业务条件。
- 支付宝反复发送通知:接口通常没有正确返回
success。
正确读取支付宝的 POST 参数
支付宝异步通知通常使用以下内容类型:
application/x-www-form-urlencoded
PHP 一般通过 $_POST 获取参数,不要只把请求体当作 JSON 解析。
$params = $_POST;
if (empty($params)) {
http_response_code(400);
echo 'failure';
exit;
}
如果框架只使用下面这种 JSON 解析方式,得到的参数可能为空:
$data = json_decode(file_get_contents('php://input'), true);
这种方式不适合直接解析支付宝发送的表单通知。
使用支付宝通知的原始参数验签
异步通知必须验签。验签失败时不能更新订单,也不要为了“先跑通”而长期关闭验签。
示意代码:
<?php
$params = $_POST;
$signature = $params['sign'] ?? '';
$signType = $params['sign_type'] ?? 'RSA2';
unset($params['sign'], $params['sign_type']);
ksort($params);
$signContent = urldecode(http_build_query($params));
$verified = openssl_verify(
$signContent,
base64_decode($signature),
$alipayPublicKey,
OPENSSL_ALGO_SHA256
);
if ($verified !== 1) {
http_response_code(400);
echo 'failure';
exit;
}
这段代码仅用于说明验签结构。实际项目应优先使用支付宝官方 SDK 提供的通知验签方法。字符编码、参数拼接或密钥格式处理不正确,都可能造成验签失败。
常见的验签错误有:
- 使用应用公钥,而不是支付宝公钥
- 支付宝公钥与当前应用或密钥模式不匹配
- 使用 RSA2 时却选择了 SHA1 算法
- 验签前修改了参数值
- 将
sign本身放入待验签字符串 - 参数被重复 URL 编码或解码
- 使用公钥证书模式,却按普通公钥模式处理
- 混用了沙箱环境和正式环境的密钥
验签通过后还要核对业务参数
验签通过后,不能直接把本地订单改成已支付。至少需要读取并核对以下参数:
$appId = $params['app_id'] ?? '';
$outTradeNo = $params['out_trade_no'] ?? '';
$tradeNo = $params['trade_no'] ?? '';
$tradeStatus = $params['trade_status'] ?? '';
$totalAmount = $params['total_amount'] ?? '';
$sellerId = $params['seller_id'] ?? '';
建议依次校验:
app_id是否属于当前应用。out_trade_no对应的订单是否存在于本地系统。- 通知中的订单金额是否与本地金额完全一致。
- 收款方是否符合预期。
trade_status是否属于业务认可的成功状态。- 本地订单是否已经处理。
比较金额时应使用十进制定点方式,避免浮点误差。例如,PHP 可以使用 BCMath:
if (bccomp($totalAmount, $localOrderAmount, 2) !== 0) {
echo 'failure';
exit;
}
必须原样返回 success
完成验签和业务处理后,响应正文只能输出:
success
例如:
http_response_code(200);
echo 'success';
exit;
不要输出 JSON:
{"code": 200, "message": "success"}
也不要返回 HTML、调试信息或包含其他内容的文本,例如:
success processed
如果响应内容不符合支付宝的成功确认规则,支付宝可能按照通知策略继续重试。具体的重试次数和时间间隔,应以当前支付宝开放平台文档和通知记录为准。
一个较完整的处理结构
下面的代码只展示推荐流程。verifyAlipayNotify() 和订单查询部分需要根据项目使用的支付宝 SDK 实现:
<?php
$params = $_POST;
writeNotifyLog([
'time' => date('c'),
'params' => $params,
]);
if (!$params) {
http_response_code(400);
echo 'failure';
exit;
}
if (!verifyAlipayNotify($params)) {
writeNotifyLog(['error' => 'invalid_signature']);
http_response_code(400);
echo 'failure';
exit;
}
$outTradeNo = $params['out_trade_no'] ?? '';
$tradeStatus = $params['trade_status'] ?? '';
$totalAmount = $params['total_amount'] ?? '';
$appId = $params['app_id'] ?? '';
$order = findOrderByOutTradeNo($outTradeNo);
if (!$order) {
writeNotifyLog(['error' => 'order_not_found']);
echo 'failure';
exit;
}
if ($appId !== ALIPAY_APP_ID) {
writeNotifyLog(['error' => 'app_id_mismatch']);
echo 'failure';
exit;
}
if (bccomp($totalAmount, $order['amount'], 2) !== 0) {
writeNotifyLog(['error' => 'amount_mismatch']);
echo 'failure';
exit;
}
if (in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
processPaidOrderIdempotently(
$order,
$params['trade_no'] ?? '',
$params
);
}
echo 'success';
exit;
订单处理必须具备幂等性。同一条通知可能发送多次,程序不能因此重复发货、重复充值或重复生成业务记录。可以通过订单状态判断、数据库唯一索引和事务锁来控制。
推荐的排查顺序
- 使用
alipay.trade.query查询订单的真实状态。 - 在支付宝开放平台的相关工具或通知记录中,检查支付宝是否发起过异步通知,并查看响应状态。
- 从公网使用
curl发送 POST 请求,测试回调地址。 - 检查回调地址是否发生 HTTP 到 HTTPS、末尾斜杠或登录页面重定向。
- 查看 CDN、WAF、负载均衡和 Nginx/Apache 的访问日志。
- 在回调入口最前面记录原始请求。
- 检查 PATH_INFO 和框架的 POST 路由。
- 确认程序读取的是表单参数,而不是只解析 JSON。
- 使用正确的支付宝公钥或证书验签。
- 业务处理成功后原样返回
success。
对于当前案例,首先应检查 http 回调地址能否从公网访问,以及 /bqvVrJaHgp.php/mobile/... 这种 PATH_INFO 路由是否发生了重定向、拒绝 POST 或解析失败。只确认控制器“支持 POST”,还不能证明支付宝可以完成异步通知。
备注:内容仅供参考。
