PAYATHON 2026

支付成功后未收到 notify_url 回调,如何排查?

支付阿杰

明确结论

Postman 能访问 notify_url,只说明这个接口可以手动请求,不能证明支付平台能够正常回调。

先确认以下事项:

  1. 下单时是否提交了 notify_url,支付平台是否接受了这个参数。
  2. 回调地址是否为公网可访问的完整 URL,而不是内网地址、临时域名或受限地址。
  3. 服务器、防火墙或反向代理是否拦截了支付平台的请求。
  4. 回调接口是否支持平台使用的请求方法和数据格式。
  5. 接口收到通知后,是否按平台协议返回了正确的成功响应。
  6. 该订单在支付平台是否确实处于”支付成功”状态。

使用 GET 方法获取支付链接,通常不影响异步通知。支付跳转与 notify_url 回调是两个独立过程。需要重点检查下单时传递的回调参数和服务端的接收逻辑。

第一步:确认下单请求中实际提交了 notify_url

不要只看代码中是否定义了变量,还要记录最终发送给支付接口的完整参数。记录时应隐藏密钥、签名和用户隐私数据。

重点核对:

out_trade_no=363563A20241007220649919797
notify_url=https://pay.example.com/payment/notify

常见问题有:

  • 只定义了参数变量,却没有将其加入最终请求。
  • 参数名拼写错误,例如写成 notifyUrl。
  • 生成签名后又修改了 notify_url。
  • notify_url 参与签名时,使用的编码方式不符合平台要求。
  • 使用 SDK 时,没有将该字段设置到实际发送的请求对象中。
  • 获取支付链接的接口不支持异步通知参数。
  • 平台后台配置覆盖了接口传入的地址。

参数的具体名称、是否参与签名以及配置优先级,都要以所用支付平台的接口文档为准。

第二步:检查回调地址的公网可达性

支付平台的服务器需要主动访问 notify_url。以下地址通常不能用作正式的回调地址:

http://localhost/payment/notify
http://127.0.0.1/payment/notify
http://192.168.1.10/payment/notify

应使用完整的公网 HTTPS 地址:

https://pay.example.com/payment/notify

同时检查:

  • 域名能否在公网正常解析。
  • HTTPS 证书是否有效,证书链是否完整。
  • TLS 配置是否受支付平台支持。
  • 端口是否开放。
  • URL 是否要求登录,或依赖 Cookie、Token、验证码。
  • 是否配置了 IP 白名单、WAF、CDN 或限流规则。
  • 回调地址是否出现 301、302、307 等重定向。
  • 路由是否区分末尾斜杠,例如 /notify 和 /notify/。
  • 服务器是否拒绝未知 User-Agent 或非浏览器请求。

使用 Postman 测试时,请求可能带有登录状态或自定义请求头,也可能来自白名单 IP。因此,Postman 请求成功不代表支付平台也能访问。

可以在一台外部服务器上发起不带认证信息的测试:

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

这些测试数据只能用于检查网络和接口行为,不能作为真实支付通知处理,因为它没有通过平台的签名验证。

第三步:在接口入口记录原始请求

许多看似”没有收到回调”的问题,其实是请求已经到达,但业务代码在验签、解析参数或更新数据库之前报错了。

应尽早在回调入口记录:

  • 到达时间
  • 请求方法
  • Content-Type
  • 请求头
  • 原始请求体
  • 查询参数
  • HTTP 响应状态
  • 处理结果和异常信息

不要在日志中记录商户私钥、API 密钥或完整的敏感信息。

下面是一个通用的 PHP 排查示例:

<?php

$rawBody = file_get_contents('php://input');

$log = [
    'time' => date('c'),
    'method' => $_SERVER['REQUEST_METHOD'] ?? '',
    'content_type' => $_SERVER['CONTENT_TYPE'] ?? '',
    'query' => $_GET,
    'form' => $_POST,
    'raw_body' => $rawBody,
];

error_log(
    json_encode($log, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);

// 后续必须按照支付平台文档解析参数并验证签名。
// 验签成功且业务处理完成后,再返回平台要求的成功响应。

如果平台发送的是 JSON,不能只读取 $_POST:

<?php

$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);

if (!is_array($data)) {
    http_response_code(400);
    exit('invalid request');
}

// 按平台文档验证签名。
// 验签通过后再处理订单。

如果入口日志中完全没有记录,问题通常出在支付平台发送请求之前,或者 DNS、网络、防火墙、网关、路由等环节。如果入口已有日志,但订单状态没有更新,则要继续排查参数解析、验签和业务处理。

第四步:确认请求方法和数据格式

异步通知通常由支付平台服务器发起,常见形式如下:

POST /payment/notify
Content-Type: application/x-www-form-urlencoded

也可能是:

POST /payment/notify
Content-Type: application/json

不要根据浏览器的跳转方式判断回调方式。即使支付链接通过 GET 打开,异步通知仍可能使用 POST。

回调接口需要按照实际协议读取数据:

// application/x-www-form-urlencoded
$data = $_POST;
// application/json
$rawBody = file_get_contents('php://input');
$data = json_decode($rawBody, true);

还要确认框架是否因 CSRF 校验而拒绝外部 POST 请求。支付通知接口通常需要排除 CSRF 校验,但支付签名验证仍然不能省略。

第五步:检查网关和应用日志

沿着请求链路逐层检查:

支付平台
  → DNS/CDN/WAF
  → Nginx 或 Apache
  → 应用框架
  → 验签逻辑
  → 数据库更新
  → 成功响应

重点查看回调时间附近的以下记录:

  • CDN 或 WAF 拦截日志
  • Nginx、Apache 访问日志
  • Web 服务器错误日志
  • 应用异常日志
  • PHP-FPM、Java、Node.js 等运行时日志
  • 数据库错误或事务回滚记录

常见 HTTP 状态码及其原因:

状态码常见原因
301、302回调地址发生跳转
400参数解析失败或请求格式不匹配
401、403登录验证、IP 白名单、WAF 或 CSRF 拦截
404路由配置错误
405接口不允许平台使用的请求方法
413请求体大小受到限制
500验签、数据库或业务代码异常
502、504上游服务异常或处理超时

例如,可以在 Nginx 访问日志中按订单号检索,前提是日志确实记录了请求参数或请求体:

grep '363563A20241007220649919797' /var/log/nginx/access.log

如果访问日志不记录 POST 请求体,可根据回调时间、请求路径、来源 IP 和状态码排查。

第六步:按平台要求返回成功响应

很多支付平台会根据响应内容判断通知是否处理成功。只返回 HTTP 200 不一定够,有些平台还要求返回指定文本或 JSON。

示意代码如下:

<?php

try {
    // 1. 读取原始通知
    // 2. 验证签名
    // 3. 核对订单信息
    // 4. 幂等更新订单状态

    http_response_code(200);

    // 此处必须替换成支付平台文档规定的内容。
    echo 'success';
} catch (Throwable $e) {
    error_log($e->getMessage());
    http_response_code(500);
    echo 'fail';
}

success 和 fail 只是常见示例,并非所有平台都采用这一标准。响应内容、大小写和格式必须严格遵循对应平台的规定。

如果平台没有收到正确响应,通常会按照一定策略重试。重试次数和时间间隔也要以平台文档为准。

第七步:核对订单状态和通知记录

使用 out_trade_no 查询订单:

363563A20241007220649919797

需要核对:

  • 商户订单号是否与支付时使用的订单号完全一致。
  • 平台订单是否已经支付成功,而不是仍在处理中。
  • 是否存在重复订单号。
  • 下单请求是否成功创建了平台订单。
  • 支付平台后台是否有异步通知记录。
  • 通知记录中的 URL、发送时间、响应状态和响应内容分别是什么。
  • 平台是否支持手动重发通知。

如果支付平台提供订单查询接口,应主动查询订单状态,不能只依赖异步通知来确认支付结果。

回调处理的安全示例

处理真实回调时,不能只根据 trade_status 更新订单。至少要验证:

  • 通知签名是否有效。
  • out_trade_no 是否存在并属于当前商户。
  • 平台商户号是否与本地配置一致。
  • 支付金额和币种是否与本地订单一致。
  • 当前订单状态是否允许变更。
  • 重复通知是否会造成重复发货或重复记账。

通用伪代码如下:

<?php

$rawBody = file_get_contents('php://input');
$data = parseNotifyData($rawBody, $_POST);

// verifySignature 的实现必须遵循对应支付平台的签名规则。
if (!verifySignature($data)) {
    http_response_code(400);
    exit('invalid signature');
}

$order = findOrderByOutTradeNo($data['out_trade_no'] ?? '');

if ($order === null) {
    http_response_code(404);
    exit('order not found');
}

if (!amountMatches($order, $data) || !merchantMatches($data)) {
    http_response_code(400);
    exit('order mismatch');
}

// 使用事务或唯一约束实现幂等处理。
if (!$order->isPaid()) {
    markOrderAsPaid($order, $data);
}

http_response_code(200);

// 替换为平台文档要求的成功响应。
echo 'success';

推荐的排查顺序

按以下顺序检查,通常可以较快找到问题所在:

  1. 在支付平台后台确认订单是否支付成功。
  2. 查看该订单的异步通知记录。
  3. 核对下单请求实际发送的 notify_url。
  4. 检查 Nginx、WAF 和应用日志,确认请求是否到达。
  5. 检查请求方法、Content-Type、路由和 CSRF 设置。
  6. 排查验签、金额核对、数据库事务及异常日志。
  7. 确认接口返回了平台规定的成功响应。
  8. 修复问题后,通过平台重发通知,或主动查询订单状态并进行安全补偿。

现有信息不足以判断故障具体发生在哪一层。补充支付平台名称、下单接口、实际提交的 notify_url、平台通知记录和服务器响应状态后,可以进一步缩小排查范围。

备注:内容仅供参考。