PAYATHON 2026

支付宝付款 notify 如何调试:付款成功后未收到通知

支付小周

结论

付款成功却没有收到支付宝异步通知,通常不代表支付失败。更常见的原因是 notify_url 无法被支付宝访问、请求被网关拦截、通知地址配置有误,或服务端处理通知时发生异常但没有留下日志。

排查时,可以按照”支付宝是否发送 → 请求是否到达入口 → 签名是否通过 → 业务是否处理成功 → 是否正确返回 success”的顺序逐层确认。不要根据浏览器是否跳转到 return_url 来判断异步通知是否成功,这两个地址对应的是两套独立流程。

先确认通知地址是否正确

创建支付订单时,先检查实际提交给支付宝的 notify_url:

  • 必须是支付宝服务器可以访问的公网地址。
  • 不能使用 localhost、127.0.0.1、局域网 IP,或只能通过 VPN 访问的域名。
  • 地址应包含完整的协议、域名、端口和路径。
  • HTTPS 证书必须有效,证书链也要完整。
  • 接口不能依赖登录状态、Cookie、验证码或浏览器执行 JavaScript。
  • 检查鉴权中间件、CSRF 校验、WAF 和 IP 白名单,避免它们误拦截通知。
  • 测试环境和正式环境的应用、网关、密钥、通知地址不能混用。

如果创建订单时动态传入了 notify_url,应以当次请求提交的地址为准。只修改后台配置,不一定会影响已经生成的订单。

可以先从公网测试接口能否访问:

curl -i -X POST 'https://pay.example.com/alipay/notify' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'out_trade_no=DEBUG_ORDER&trade_status=TRADE_SUCCESS'

这个请求没有合法签名,业务程序应该拒绝,但服务器访问日志中必须能看到请求。如果完全没有访问记录,问题通常出在 DNS、端口、反向代理、路由或防火墙这一层。

在通知入口记录原始请求

调试期间,应在验签和业务处理之前记录必要信息。否则一旦验签失败,系统可能不会留下任何痕迹。

<?php

$notifyData = $_POST;

error_log(json_encode([
    'time'         => date('c'),
    'remote_addr'  => $_SERVER['REMOTE_ADDR'] ?? '',
    'content_type' => $_SERVER['CONTENT_TYPE'] ?? '',
    'method'       => $_SERVER['REQUEST_METHOD'] ?? '',
    'data'         => $notifyData,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

不要长期在日志中保存完整的敏感信息。生产环境至少要对用户标识、付款账号等字段脱敏,并限制日志访问权限。

同时检查以下记录:

  • CDN、负载均衡和反向代理的访问日志;
  • Nginx、Apache 等 Web 服务器日志;
  • 应用异常日志;
  • WAF、安全组和防火墙的拦截记录;
  • 请求是否发生重定向,例如 HTTP 跳转 HTTPS、域名跳转或尾斜杠跳转。

通知地址最好直接返回处理结果,不要依赖 301、302 等重定向。

正确读取通知参数

支付宝异步通知通常通过 POST 表单发送:

Content-Type: application/x-www-form-urlencoded

如果接口只解析 JSON,请求即使已经到达,应用也可能读不到参数。在 PHP 中通常从 $_POST 获取参数;Java、Python、Go 等环境也应该读取表单参数,不能只读取 JSON 请求体。

还要确认框架没有在日志中间件或过滤器中提前消费请求体。

验证签名配置

收到通知后必须验签,不能为了调试而跳过。常见的验签失败原因有:

  • 使用应用私钥验签,而不是支付宝公钥;
  • 混用了普通公钥模式和支付宝公钥证书模式;
  • 混用了测试环境与正式环境的密钥;
  • sign_type 与验签配置不一致;
  • 验签前修改了参数值、字符编码或空格;
  • 错误地将 sign、sign_type 放入待验签内容;
  • 使用 JSON 序列化后的内容验签,而不是通知中的原始参数集合;
  • 密钥更新后,服务仍在读取旧配置。

支付宝 SDK 不同版本的类名和验签方法可能不同,应以项目实际使用版本的文档为准。处理结构可以写成:

<?php

$params = $_POST;

// 此函数代表项目中对支付宝官方 SDK 的封装。
// 必须使用支付宝公钥或支付宝公钥证书验签。
$verified = verify_alipay_signature($params);

if (!$verified) {
    error_log('Alipay notify signature verification failed');
    http_response_code(400);
    echo 'failure';
    exit;
}

不要自行拼接验签字符串,优先使用与当前接入方式匹配的官方 SDK。

验签后校验业务数据

签名通过只能说明请求来自持有对应密钥的一方。业务程序还要确认这条通知确实属于自己的订单。

至少检查以下内容:

  • app_id 是否等于当前应用 ID;
  • out_trade_no 是否存在于本地系统;
  • 通知金额是否与本地订单金额一致;
  • 收款方信息是否符合预期;
  • 订单当前状态是否允许改为已支付;
  • trade_status 是否属于业务需要处理的状态。

比较金额时不要使用浮点数,可以先转换为最小货币单位:

<?php

function yuanToFen(string $amount): int
{
    if (!preg_match('/^\d+(\.\d{1,2})?$/', $amount)) {
        throw new InvalidArgumentException('Invalid amount');
    }

    [$yuan, $decimal] = array_pad(explode('.', $amount, 2), 2, '');
    $decimal = str_pad($decimal, 2, '0');

    return ((int) $yuan * 100) + (int) $decimal;
}

if (yuanToFen($_POST['total_amount']) !== $localOrderAmountFen) {
    http_response_code(400);
    echo 'failure';
    exit;
}

需要校验哪些收款方字段,要根据所用的支付宝产品和接口版本决定,不能假定所有通知都包含相同字段。

正确处理交易状态

付款类业务一般需要关注 TRADE_SUCCESS 和 TRADE_FINISHED。两者的业务含义并不完全相同,具体处理规则应根据接入产品确定。

<?php

$tradeStatus = $_POST['trade_status'] ?? '';

if (!in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
    // 对于已成功接收但不需要推进业务的合法通知,
    // 是否确认成功应依据具体产品的通知规则。
    echo 'success';
    exit;
}

收到通知不代表订单一定付款成功,也不能只根据 out_trade_no 更新订单。

业务处理必须支持幂等

同一笔交易可能收到多次通知,这是正常情况。系统不能因此重复发货、重复充值或重复增加积分。

可以通过数据库条件更新或唯一约束保证幂等:

UPDATE payment_order
SET
    status = 'PAID',
    alipay_trade_no = :trade_no,
    paid_at = :paid_at
WHERE
    out_trade_no = :out_trade_no
    AND status = 'PENDING';

再根据受影响的行数判断处理结果:

  • 影响一行,说明本次完成了状态变更;
  • 影响零行且订单已经支付,可以视为重复通知;
  • 影响零行且订单不存在或状态异常,需要记录告警并人工排查。

如果支付状态更新与发货、充值等操作分属不同系统,可以在本地事务中写入事件表,再通过消息队列或任务系统执行后续操作,避免支付已经入账,但后续业务动作丢失。

处理完成后返回 success

完成验签和必要的业务处理后,应在响应正文中返回支付宝要求的成功标识,通常是纯文本:

success

例如:

<?php

try {
    // 验签、订单校验和幂等更新
    processAlipayNotify($_POST);

    header('Content-Type: text/plain; charset=utf-8');
    echo 'success';
} catch (Throwable $e) {
    error_log($e->getMessage());

    http_response_code(500);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'failure';
}

不要返回 JSON、HTML 页面或额外的调试文本:

{"code":200,"message":"success"}

这段 JSON 不等同于纯文本 success。如果支付宝没有识别到成功响应,可能会继续发送通知。具体的重试次数和时间间隔,应以当前产品的官方文档为准。

响应也不应等待耗时任务执行完毕。完成验签和订单状态落库后,应尽快返回结果。短信、发货、积分计算等操作可以交给可靠的异步任务处理。

区分 notify_url 和 return_url

return_url 是用户完成支付后,浏览器跳转到的同步地址。用户关闭页面、网络中断或客户端行为都可能导致跳转没有发生。

notify_url 是支付宝服务器调用商户服务器的异步地址,不依赖用户浏览器。常见误区有:

  • 浏览器成功跳回页面,就认为异步通知已经收到;
  • 浏览器没有跳回页面,就认为支付失败;
  • 在 return_url 中直接发货;
  • 将只能由前端访问的页面配置为 notify_url。

最终支付状态应以服务端验签后的通知和主动查询结果为准。

没收到通知时主动查询订单

如果暂时无法确认通知链路是否正常,可以调用支付宝交易查询接口,根据 out_trade_no 或支付宝交易号查询订单状态。

主动查询适用于:

  • 支付页面轮询;
  • 通知超时后的补偿任务;
  • 定时扫描长期处于”支付中”状态的订单;
  • 对账和异常恢复。

查询结果也要校验订单号、金额和应用归属。主动查询属于补偿机制,不能代替异步通知入口。

推荐的排查顺序

  1. 从创建支付请求的日志中确认实际提交的 notify_url。
  2. 确认支付宝环境、应用 ID 和密钥是否正确。
  3. 从公网向通知地址发送 POST 请求,检查 DNS、HTTPS、端口和路由。
  4. 查看 CDN、WAF、负载均衡、Nginx 和应用访问日志。
  5. 确认接口能够解析表单参数,而不是只接收 JSON。
  6. 在验签前记录经过脱敏的通知参数和请求信息。
  7. 检查支付宝公钥、证书模式、sign_type 和字符编码。
  8. 校验订单号、金额、应用 ID、收款方和交易状态。
  9. 确认业务处理能够应对重复通知和并发通知。
  10. 确认响应正文是纯文本 success,并检查是否存在异常、重定向或超时。
  11. 对仍处于待支付状态的订单调用交易查询接口进行补偿。

如果服务器所有入口日志中都没有请求,应优先检查通知地址和公网网络链路。有请求但验签失败时,重点检查密钥和验签模式。验签成功但订单没有更新时,则要查看业务校验、数据库事务、幂等条件和异常日志。

备注:内容仅供参考。