PAYATHON 2026

支付宝当面付用户付款成功后收不到支付回调怎么办?

支付阿杰

结论

用户已经付款成功,但商户没有收到支付宝当面付的异步通知,并不意味着支付失败。应先调用 alipay.trade.query 查询订单状态并补单,再检查回调地址能否从公网访问,以及 HTTPS 证书、网关、防火墙、接口响应、验签配置和应用参数是否正常。

如果此前运行正常,到了某个时间点突然集中失败,应优先排查服务器、DNS、证书、WAF/CDN、网络线路和近期发布变更。支付宝侧也可能出现通知延迟,但没有官方公告或通知日志佐证时,不能直接认定是支付宝故障。

常见原因

回调地址无法从公网访问

支付宝服务器必须能直接访问下单时传入的 notify_url。以下变化都可能导致回调中断:

  • 域名解析异常,或者近期修改过 DNS。
  • HTTPS 证书过期、证书链不完整或 TLS 握手失败。
  • 服务器端口没有开放。
  • 防火墙、安全组、WAF 或 CDN 拦截了支付宝请求。
  • 回调地址发生重定向、要求登录或新增了鉴权。
  • 应用发布后,路由、反向代理或接口路径发生变化。
  • 服务器负载过高,导致连接或处理超时。

可以先从外网检查接口:

curl -i -X POST "https://pay.example.com/alipay/notify"

再检查 HTTPS 证书和握手情况:

openssl s_client -connect pay.example.com:443 \
  -servername pay.example.com \
  -showcerts

服务器本机访问正常,不代表支付宝也能访问。应当使用不同的公网网络进行测试。

下单时没有正确传递 notify_url

异步通知地址通常需要在调用接口时传入,不能只依赖 SDK 的全局配置。应检查实际发送给支付宝网关的请求,确认:

  • 请求中存在 notify_url,且地址正确。
  • 使用的是 HTTPS 公网地址,不是 localhost、内网 IP 或测试环境域名。
  • 当前应用、网关环境和回调地址使用同一套配置。
  • 最近发布的代码没有覆盖或漏传该参数。

同步返回地址 return_url 不能代替异步通知地址 notify_url。当面付也不能依赖用户浏览器跳转来确认支付结果。

接口收到请求,但处理失败

业务系统没有更新订单,不一定是支付宝没有发送通知。排查时需要同时查看 Web 服务器访问日志、应用日志、网关日志和 WAF 日志。

常见情况包括:

  • 请求已经到达,但程序抛出了异常。
  • 请求体被框架、网关或过滤器提前读取。
  • 参数解析方式不正确。
  • 数据库连接失败或事务回滚。
  • 接口返回了 JSON、HTML、空字符串或错误状态码。
  • 业务处理时间太长,连接在响应前被关闭。

支付宝异步通知处理成功后,应返回纯文本:

success

不要返回:

{"success":true}

也不要返回包含模板、空格或调试信息的 HTML 页面。如果没有正确响应,支付宝通常会按照通知机制重试。具体策略应以当前官方文档和实际通知记录为准。

验签失败

如果请求已经到达,但日志显示验签失败,应检查:

  • 配置的是支付宝公钥,而不是商户应用公钥。
  • app_id 与实际收款应用一致。
  • sign_type、charset 与应用配置一致。
  • 没有混用普通公钥模式和证书模式。
  • 证书或密钥轮换后,服务器配置已经同步更新。
  • 验签前没有修改、转码或遗漏通知参数。

下面是普通公钥模式的 Java 验签示例。证书模式需要使用对应 SDK 的证书验签方式,不能直接使用普通公钥配置。

import com.alipay.api.internal.util.AlipaySignature;

import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public boolean verifyNotify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();

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

    return AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );
}

验签失败时不能返回 success,也不能根据未经验证的通知更新订单。

推荐排查步骤

1. 先确认支付宝侧订单状态

使用商户订单号 out_trade_no 或支付宝交易号 trade_no 调用 alipay.trade.query。如果查询结果是 TRADE_SUCCESS,说明付款已经成功,应立即进入补单流程,不要继续等待通知。

Java SDK 示例:

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
request.setBizModel(model);

AlipayTradeQueryResponse response = alipayClient.execute(request);

if (response.isSuccess()
        && "TRADE_SUCCESS".equals(response.getTradeStatus())) {
    // 校验商户订单号、金额、收款方等信息后,以幂等方式更新订单
}

不同版本 SDK 的类结构可能略有差异,应以项目实际使用的支付宝 SDK 为准。

2. 检查是否收到过 HTTP 请求

按照付款时间、out_trade_no、trade_no 和回调路径搜索日志:

rg "out_trade_no_value|trade_no_value|/alipay/notify" /path/to/logs

如果 Nginx 有访问记录,但应用没有日志,应检查反向代理、路由和应用异常。如果所有入口日志都没有记录,则应重点检查公网访问、DNS、证书、WAF、安全组和 notify_url。

3. 对照最近的环境变更

检查故障发生前是否有以下变更:

  • 应用发布或配置中心变更。
  • 域名、证书或 DNS 调整。
  • WAF、CDN 或负载均衡规则修改。
  • Java、PHP、Web 框架或支付宝 SDK 升级。
  • 密钥或支付宝证书轮换。
  • 数据库、缓存或消息队列故障。
  • 服务器扩容后,部分节点的配置不一致。

「晚上突然出现」只能说明故障发生的时间,不能据此判断问题出在哪一方。

4. 查看支付宝侧通知记录

如果商家平台或开放平台提供该笔交易的异步通知记录,可以核对通知时间、目标地址、响应内容和重试情况。页面入口和展示字段可能随平台版本调整,应以当前后台界面为准。

如果支付宝显示已经发送通知,但响应失败,问题通常出在商户入口或接口处理环节。如果没有通知记录,并且多个订单同时出现异常,再结合官方公告或技术支持反馈,判断是否存在平台侧延迟。

正确处理回调

回调接口应按照「先验签、再核对、幂等更新、最后响应」的顺序处理:

@PostMapping(
    value = "/alipay/notify",
    produces = "text/plain;charset=UTF-8"
)
public String handleNotify(HttpServletRequest request) {
    try {
        Map<String, String> params = extractParams(request);

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

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

        if (!expectedAppId.equals(params.get("app_id"))) {
            return "failure";
        }

        if (!expectedSellerId.equals(params.get("seller_id"))) {
            return "failure";
        }

        String tradeStatus = params.get("trade_status");
        if (!"TRADE_SUCCESS".equals(tradeStatus)
                && !"TRADE_FINISHED".equals(tradeStatus)) {
            return "success";
        }

        String outTradeNo = params.get("out_trade_no");
        String totalAmount = params.get("total_amount");

        // 从数据库读取订单,并严格核对订单号、金额、币种和收款方。
        // updateOrderPaidIfUnpaid 必须具备幂等性。
        updateOrderPaidIfUnpaid(outTradeNo, totalAmount);

        return "success";
    } catch (Exception e) {
        // 记录异常及必要的交易标识,避免记录密钥等敏感信息。
        return "failure";
    }
}

生产代码还需要核对通知中的 app_id、seller_id、out_trade_no、total_amount 等字段。金额应使用 BigDecimal 或最小货币单位进行比较,不能直接使用 double。

补单与容错

支付系统不能只依赖异步通知,还应增加主动查询机制:

  • 用户付款后,在短时间内轮询 alipay.trade.query。
  • 定时查询长时间处于「待支付」状态的订单。
  • 用户返回订单页时,触发一次受限的状态查询。
  • 记录订单是通过回调还是主动查询完成处理。
  • 使用唯一约束或状态条件更新,避免重复通知引起重复发货或重复记账。

订单状态可以通过条件更新:

UPDATE orders
SET pay_status = 'PAID',
    paid_at = CURRENT_TIMESTAMP
WHERE order_no = ?
  AND pay_status = 'UNPAID';

只有受影响行数为 1 时,才执行首次支付成功后的业务操作。发货、积分和消息通知等后续处理也必须具备幂等性。

注意事项

不能因为用户提供了付款截图,就直接把订单改成已支付。订单状态必须以支付宝验签通过的通知,或 alipay.trade.query 返回的可信结果为准。

回调接口中不要执行耗时任务。完成验签、订单核对和可靠落库后,应尽快返回 success。发货或通知等后续任务可以通过事务消息、消息队列或任务表异步处理。

如果只有一笔订单缺少通知,先主动查询并补单。如果同一时段有大量订单同时异常,应立即检查基础设施和近期变更,同时保存订单号、支付宝交易号、付款时间、回调日志和查询结果,供后续定位问题。

备注:内容仅供参考。