支付宝付款 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 或支付宝交易号查询订单状态。
主动查询适用于:
- 支付页面轮询;
- 通知超时后的补偿任务;
- 定时扫描长期处于”支付中”状态的订单;
- 对账和异常恢复。
查询结果也要校验订单号、金额和应用归属。主动查询属于补偿机制,不能代替异步通知入口。
推荐的排查顺序
- 从创建支付请求的日志中确认实际提交的
notify_url。 - 确认支付宝环境、应用 ID 和密钥是否正确。
- 从公网向通知地址发送
POST请求,检查 DNS、HTTPS、端口和路由。 - 查看 CDN、WAF、负载均衡、Nginx 和应用访问日志。
- 确认接口能够解析表单参数,而不是只接收 JSON。
- 在验签前记录经过脱敏的通知参数和请求信息。
- 检查支付宝公钥、证书模式、
sign_type和字符编码。 - 校验订单号、金额、应用 ID、收款方和交易状态。
- 确认业务处理能够应对重复通知和并发通知。
- 确认响应正文是纯文本
success,并检查是否存在异常、重定向或超时。 - 对仍处于待支付状态的订单调用交易查询接口进行补偿。
如果服务器所有入口日志中都没有请求,应优先检查通知地址和公网网络链路。有请求但验签失败时,重点检查密钥和验签模式。验签成功但订单没有更新时,则要查看业务校验、数据库事务、幂等条件和异常日志。
备注:内容仅供参考。
