PAYATHON 2026

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_status
  • trade_no
  • out_trade_no
  • total_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'] ?? '';

建议依次校验:

  1. app_id 是否属于当前应用。
  2. out_trade_no 对应的订单是否存在于本地系统。
  3. 通知中的订单金额是否与本地金额完全一致。
  4. 收款方是否符合预期。
  5. trade_status 是否属于业务认可的成功状态。
  6. 本地订单是否已经处理。

比较金额时应使用十进制定点方式,避免浮点误差。例如,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;

订单处理必须具备幂等性。同一条通知可能发送多次,程序不能因此重复发货、重复充值或重复生成业务记录。可以通过订单状态判断、数据库唯一索引和事务锁来控制。

推荐的排查顺序

  1. 使用 alipay.trade.query 查询订单的真实状态。
  2. 在支付宝开放平台的相关工具或通知记录中,检查支付宝是否发起过异步通知,并查看响应状态。
  3. 从公网使用 curl 发送 POST 请求,测试回调地址。
  4. 检查回调地址是否发生 HTTP 到 HTTPS、末尾斜杠或登录页面重定向。
  5. 查看 CDN、WAF、负载均衡和 Nginx/Apache 的访问日志。
  6. 在回调入口最前面记录原始请求。
  7. 检查 PATH_INFO 和框架的 POST 路由。
  8. 确认程序读取的是表单参数,而不是只解析 JSON。
  9. 使用正确的支付宝公钥或证书验签。
  10. 业务处理成功后原样返回 success。

对于当前案例,首先应检查 http 回调地址能否从公网访问,以及 /bqvVrJaHgp.php/mobile/... 这种 PATH_INFO 路由是否发生了重定向、拒绝 POST 或解析失败。只确认控制器“支持 POST”,还不能证明支付宝可以完成异步通知。

备注:内容仅供参考。