PAYATHON 2026

付款成功后收不到支付结果且无 IPN 日志

支付小周

结论

当前参数不是 IPN(异步通知),而是支付宝支付完成后的同步跳转结果。判断依据是:

method=alipay.trade.page.pay.return

这类请求是在用户浏览器访问 return_url 时产生的,不能代替服务端异步通知,也无法通过这些参数判断支付宝异步请求的来源 IP。

如果服务器完全没有 IPN 日志,应先确认下单时是否正确传入了 notify_url,再检查该地址能否通过公网访问,以及网关、防火墙或反向代理是否在请求到达应用前将其拦截。请求来源 IP 只能通过接入层日志确认,现有参数不包含这项信息。

同步返回与异步通知的区别

支付宝网页支付通常有两个回调地址:

  • return_url:支付完成后跳转到商户页面,请求由用户浏览器发起。
  • notify_url:支付宝服务器主动向商户服务端发送支付结果,也就是通常所说的 IPN。

当前数据中包含 alipay.trade.page.pay.return,表明它来自同步跳转流程。用户关闭页面、网络中断或浏览器拦截都可能导致同步跳转无法到达,因此不能将它作为订单支付成功的唯一依据。

订单状态应以以下结果为准:

  1. notify_url 收到异步通知,并且验签通过;
  2. 商户服务端调用 alipay.trade.query 主动查询订单状态。

排查步骤

1. 检查创建支付请求时的 notify_url

确认创建支付请求时已经正确设置 notify_url,而不是只配置了 return_url。

示例:

$request->setNotifyUrl('https://pay.example.com/alipay/notify');
$request->setReturnUrl('https://www.example.com/payment/return');

具体设置方式取决于项目使用的支付宝 SDK。还需要检查最终提交给支付宝的请求是否确实包含 notify_url,以及这项配置是否被其他环境的配置覆盖。

2. 验证回调地址能否从公网访问

notify_url 应满足以下条件:

  • 使用可通过公网访问的域名;
  • DNS 解析正确;
  • HTTPS 证书有效,证书链完整;
  • 不要求用户登录;
  • 不跳转到其他地址;
  • 不经过验证码或人机验证;
  • 能接收支付宝实际使用的请求方法和表单参数;
  • 能在较短时间内处理完请求,并返回纯文本 success。

可以通过外部网络测试该地址能否访问。普通测试请求只能检查网络连通性,无法模拟合法的支付宝签名。

3. 从接入层逐级检查日志

如果应用中没有相关日志,可以按照请求经过的顺序逐级检查:

  1. CDN 或云防护日志;
  2. WAF、安全组和防火墙日志;
  3. 负载均衡访问日志;
  4. Nginx、Apache 或网关日志;
  5. 应用访问日志和异常日志。

例如,可以在 Nginx 日志中记录来源地址、代理链和请求状态:

log_format payment_callback
    '$time_iso8601 remote_addr=$remote_addr '
    'xff="$http_x_forwarded_for" '
    'request="$request" status=$status '
    'request_time=$request_time';

location = /alipay/notify {
    access_log /var/log/nginx/alipay_notify.log payment_callback;
    proxy_pass http://payment_backend;
}

如果 Nginx 日志也没有记录,通常表示请求没有到达这台服务器。此时应继续检查域名解析、CDN、WAF、防火墙和 notify_url 配置。

4. 检查路由和请求处理方式

常见问题包括:

  • 回调路由只允许 GET,无法接收异步通知使用的请求;
  • Web 框架的 CSRF 校验拒绝了回调;
  • HTTP 被强制跳转到 HTTPS;
  • URL 末尾的斜杠触发了 301 或 302 跳转;
  • WAF 将签名中的 /、+ 等字符判定为异常;
  • 应用按 JSON 请求体读取数据,但实际收到的是表单参数;
  • 回调处理发生异常,却没有单独记录异常日志。

支付回调接口通常需要绕过交互式登录和 CSRF 校验,但支付宝签名验证仍然必不可少。

5. 主动查询交易状态

对于已经付款但未收到异步通知的订单,可以使用:

out_trade_no=790781425401073664

或:

trade_no=2021122222001416101432415596

调用 alipay.trade.query 查询交易状态。只有查询结果验签成功,且应用编号、商户身份、订单号、金额等信息均与本地订单一致时,才能更新订单状态。

回调处理示例

以下示例展示了基本的处理结构。SDK 类名和初始化参数需要根据项目实际使用的支付宝 SDK 调整:

<?php

// 异步通知通常以表单参数发送
$params = $_POST;

error_log(json_encode([
    'event' => 'alipay_notify_received',
    'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? null,
    'params' => $params,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

// 使用支付宝 SDK 验证签名
$verified = $aopClient->rsaCheckV1(
    $params,
    $alipayPublicKey,
    $params['sign_type'] ?? 'RSA2'
);

if (!$verified) {
    http_response_code(400);
    echo 'failure';
    exit;
}

// 还应核对 app_id、seller_id、订单号、金额和本地订单信息
$outTradeNo = $params['out_trade_no'] ?? '';
$tradeNo = $params['trade_no'] ?? '';
$tradeStatus = $params['trade_status'] ?? '';
$totalAmount = $params['total_amount'] ?? '';

// 伪代码:必须使用事务或唯一约束保证幂等
$order = findOrderByOutTradeNo($outTradeNo);

if (
    !$order ||
    $params['app_id'] !== EXPECTED_APP_ID ||
    $params['seller_id'] !== EXPECTED_SELLER_ID ||
    !amountEquals($order->amount, $totalAmount)
) {
    http_response_code(400);
    echo 'failure';
    exit;
}

if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
    markOrderPaidIdempotently($order, $tradeNo);
}

echo 'success';

比较金额时应使用十进制定点数或最小货币单位,不要直接使用浮点数:

function amountEquals(string $expected, string $actual): bool
{
    return bccomp($expected, $actual, 2) === 0;
}

如何确认请求来源 IP

来源 IP 应从 CDN、负载均衡、Web 服务器或应用日志中获取,例如:

$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';

如果服务部署在可信的反向代理之后,真实地址可能记录在 X-Forwarded-For 等请求头中。这些请求头可以被客户端伪造,因此只有在确认请求经过受信任代理,且代理会覆盖相应请求头时,才能采用其中的地址。

当前提供的回调参数不包含网络层来源地址,无法据此判断请求由哪个 IP 发出。另外:

  • 同步 return_url 请求通常由用户浏览器发起,来源 IP 可能是用户出口、代理或 CDN 地址;
  • 异步 notify_url 请求由支付平台服务端发起,但日志中最先出现的也可能是 CDN、WAF 或负载均衡地址;
  • 支付结果是否可信,应通过 RSA2 验签和业务字段校验来判断,不能只依赖 IP 白名单。

注意事项

不要根据同步跳转页面直接将订单标记为已支付。同步返回页可以显示”正在确认支付结果”,再由服务端查询订单状态。

支付宝可能重复发送异步通知,因此回调处理必须具备幂等性。同一个 out_trade_no 或 trade_no 被多次通知时,不能重复发货、充值或记账。

日志可以记录订单号、交易号、请求时间、来源地址、HTTP 状态和验签结果,但不应长期以明文保存完整签名、密钥或其他敏感信息。当前已经公开展示的完整 sign,建议在后续排查材料中进行脱敏。

备注:内容仅供参考。