PAYATHON 2026

PC网页支付宝支付成功但未收到回调

支付阿杰

结论

支付成功但系统没有收到回调,通常不代表交易失败。更常见的情况是支付宝异步通知没有送达 notify_url,或者通知已经到达,却被网关、验签逻辑或业务代码拦截。

交易号 2021082622001426621423547493 只能用来定位交易,无法单凭交易号确定原因。应先调用 alipay.trade.query 确认交易状态,再查看支付宝通知记录、服务器访问日志和回调处理日志。确认支付成功后,即使异步通知丢失,也可以通过主动查询补单。

常见原因

notify_url 无法被支付宝访问

notify_url 必须是支付宝服务器能够直接访问的公网地址。以下情况可能导致通知失败:

  • 使用了 localhost、内网 IP 或只能从公司网络访问的域名。
  • 域名解析错误,或者 HTTPS 证书无效。
  • 防火墙、安全组、WAF、CDN 或反向代理拦截了请求。
  • 回调地址触发登录跳转、HTTP 重定向或鉴权拦截。
  • 创建支付请求时没有正确传递 notify_url。

先查看 Web 服务器访问日志,确认是否收到支付宝发来的 POST 请求。不要只检查应用业务日志,因为请求可能根本没有进入应用。

混淆同步跳转和异步通知

return_url 是用户完成支付后,浏览器跳转到的地址。如果用户关闭页面或网络中断,跳转可能不会发生。

notify_url 才是支付宝服务器发送异步支付结果的地址。业务系统应以异步通知或主动查询结果为准,不能根据浏览器跳转直接认定支付成功。

回调到达后验签失败

支付宝异步通知通常以 application/x-www-form-urlencoded 方式提交。验签失败常见于以下情况:

  • 使用 JSON 读取请求体,造成通知参数丢失。
  • 验签时遗漏参数,或者修改了参数值。
  • 配置了错误的支付宝公钥。
  • 混淆了“支付宝公钥”和“应用公钥”。
  • charset、sign_type 与实际配置不一致。
  • 验签前对参数做了二次 URL 解码或字符集转换。
  • 混用了沙箱环境和正式环境的密钥或网关配置。

验签时应使用支付宝发送的完整原始参数集合,并交给支付宝 SDK 处理,不要自行拼接待签名字符串。

业务处理异常,但日志看起来像“没有回调”

通知可能已经进入应用,却在后续处理环节失败,例如:

  • 数据库连接或事务异常。
  • 根据 out_trade_no 找不到本地订单。
  • 订单金额或商户身份校验不通过。
  • 重复通知引发唯一键冲突。
  • 回调接口响应超时。
  • 处理成功后没有返回纯文本 success。

如果支付宝没有收到符合要求的成功响应,通常会按照通知机制重试。因此,还应检查同一订单是否收到过多次通知。

排查步骤

1. 主动查询交易状态

使用 alipay.trade.query 查询交易号:

{
  "trade_no": "2021082622001426621423547493"
}

也可以使用商户订单号 out_trade_no 查询。根据接口响应中的 trade_status 判断交易状态。常见的支付成功状态包括:

  • TRADE_SUCCESS
  • TRADE_FINISHED

如果查询结果不是成功状态,不应执行支付成功补单。具体响应字段以当前接入的支付宝开放平台文档和 SDK 定义为准。

2. 核对支付请求中的回调地址

检查创建支付请求时实际提交的 notify_url,不要只看配置文件。多环境部署时,正式环境仍在使用测试地址是常见问题。

Java SDK 的调用形式通常类似:

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/alipay/return");
request.setNotifyUrl("https://www.example.com/alipay/notify");
request.setBizContent(bizContent);

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

确保 notify_url:

  • 可以从公网访问;
  • 接受 POST 请求;
  • 不要求用户登录;
  • 不依赖浏览器 Cookie;
  • 不经过验证码或人机校验;
  • 不会被 CSRF 校验直接拒绝。

3. 按链路逐层检查日志

按照请求经过的链路依次检查:

  1. DNS、CDN 或负载均衡访问日志;
  2. Nginx、Apache 等 Web 服务器访问日志;
  3. API 网关、WAF 和安全防护日志;
  4. 应用入口日志;
  5. 验签日志;
  6. 订单更新和数据库事务日志;
  7. 接口最终返回的内容及耗时。

如果 Web 服务器完全没有请求记录,应重点检查公网可达性和安全策略。如果服务器有请求记录,但应用没有记录,则应检查代理转发、路由、请求方法和应用异常。

4. 正确处理并验证通知

下面是 Java Servlet 风格的示例。具体类名和配置方式取决于所使用的支付宝 SDK 版本:

@PostMapping("/alipay/notify")
@ResponseBody
public String notify(HttpServletRequest request) {
    Map<String, String> params = new HashMap<>();

    Map<String, String[]> requestParams = request.getParameterMap();
    for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
        params.put(entry.getKey(), String.join(",", entry.getValue()));
    }

    try {
        boolean verified = AlipaySignature.rsaCheckV1(
            params,
            alipayPublicKey,
            "UTF-8",
            "RSA2"
        );

        if (!verified) {
            return "failure";
        }

        String tradeStatus = params.get("trade_status");
        String tradeNo = params.get("trade_no");
        String outTradeNo = params.get("out_trade_no");
        String totalAmount = params.get("total_amount");
        String appId = params.get("app_id");
        String sellerId = params.get("seller_id");

        // 校验 app_id、seller_id、订单号和订单金额。
        // 只有全部匹配时才能更新订单。
        validateNotification(
            appId,
            sellerId,
            outTradeNo,
            totalAmount
        );

        if ("TRADE_SUCCESS".equals(tradeStatus)
                || "TRADE_FINISHED".equals(tradeStatus)) {
            markOrderPaidIdempotently(outTradeNo, tradeNo);
        }

        return "success";
    } catch (Exception e) {
        // 记录异常,但不要在日志中输出私钥等敏感配置。
        return "failure";
    }
}

验签通过后,不能立即更新订单。还应至少校验以下内容:

  • app_id 是否属于当前应用;
  • seller_id 是否为预期收款账号;
  • out_trade_no 是否对应本地订单;
  • total_amount 是否与本地订单金额一致;
  • trade_status 是否表示支付成功;
  • 该支付宝交易号是否已经绑定到其他订单。

处理成功后应返回纯文本:

success

不要返回 JSON、HTML 页面或包含额外说明的字符串。

5. 做好幂等和补单

异步通知可能重复发送,因此订单更新必须具备幂等性。例如,只允许未支付订单第一次变更为已支付:

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

如果受影响行数为 0,应继续判断订单是否已经处理成功,不能直接认定回调失败。

对于已经确认支付成功,但异步通知始终无法正常处理的订单,可以通过人工或自动方式补单:

  1. 调用 alipay.trade.query;
  2. 校验商户订单号、交易金额和收款方信息;
  3. 在数据库事务中幂等更新订单;
  4. 记录补单来源、交易号和查询响应摘要;
  5. 修复 notify_url 或回调处理问题,防止后续订单再次漏单。

注意事项

  • 不要仅凭用户截图、前端提示或 return_url 参数更新订单。
  • 不要通过关闭验签临时解决问题。
  • 不要将应用私钥或支付宝公钥全文写入日志。
  • 回调接口应尽快完成必要处理并返回结果。耗时任务可以在订单状态写入数据库后异步执行。
  • 比较金额时建议使用十进制定点类型,例如 Java 的 BigDecimal,不要使用 double。
  • 生产环境和沙箱环境必须分别配置网关、应用标识及密钥。
  • 交易号属于支付业务数据。对外排查时应做好脱敏,并通过商户后台或开放平台工单提交给有权限的支持人员。

备注:内容仅供参考。