PAYATHON 2026

支付宝支付后未收到异步回调,如何确认推送状态?

支付小周

结论

仅凭 out_trade_no=6898896632557862913 和回调地址,无法确认支付宝是否已经推送通知,也无法判断推送过程中是否报错。需要同时核对以下信息:

  1. 支付宝侧的交易状态和异步通知记录。
  2. 接收服务器、网关及应用日志中是否出现支付宝发出的 POST 请求。
  3. 应用是否正确完成验签和业务校验,并返回了正确内容。

交易支付成功不代表异步通知已经被业务系统成功处理。支付宝可能已经发出通知,但接口超时、响应内容不是纯文本 success、发生重定向、被 WAF 拦截或验签失败。在这些情况下,支付宝通常会将通知视为失败。

首先确认交易状态

使用支付宝交易查询接口 alipay.trade.query,将商户订单号作为字符串传入:

{
  "out_trade_no": "6898896632557862913"
}

重点检查返回的 trade_status:

  • TRADE_SUCCESS:交易支付成功。
  • TRADE_FINISHED:交易已结束。
  • WAIT_BUYER_PAY:尚未支付。
  • TRADE_CLOSED:交易已关闭。

交易查询只能确认支付结果,不能证明异步通知已经送达。业务系统也不应只依赖回调。订单长时间未更新时,可以主动调用查询接口进行补偿。

out_trade_no 应始终以字符串形式保存和传输。该值超过 JavaScript 的安全整数范围,转换为 Number 可能导致精度丢失。

检查服务器是否收到请求

接收地址为:

https://www.eeeknow.com/payCallback/alipayCallback

先根据支付发生时间检查负载均衡、CDN、WAF、Nginx 和应用日志。可以按路径筛选 Nginx 日志:

grep '/payCallback/alipayCallback' /var/log/nginx/access.log

如果日志记录了请求参数,也可以搜索订单号:

grep '6898896632557862913' /var/log/nginx/access.log

至少需要检查以下内容:

  • 请求时间和来源 IP。
  • HTTP 方法是否为 POST。
  • HTTP 状态码。
  • 请求处理耗时。
  • 是否出现 301、302、403、404、499 或 5xx。
  • 请求是否进入应用。
  • 验签和订单处理是否成功。
  • 最终是否返回纯文本 success。

如果网关有请求记录而应用没有,通常需要排查路由、反向代理、WAF 或访问控制。如果应用收到了请求但订单没有更新,则应重点检查参数解析、验签和业务校验。

正确接收支付宝通知

支付宝异步通知通常以表单参数提交,不能按 JSON 请求体处理。下面是 Java Spring Boot 示例:

import com.alipay.api.internal.util.AlipaySignature;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class AlipayCallbackController {

    @Value("${alipay.public-key}")
    private String alipayPublicKey;

    @PostMapping(
        value = "/payCallback/alipayCallback",
        produces = MediaType.TEXT_PLAIN_VALUE
    )
    public String callback(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();

        request.getParameterMap().forEach((name, values) -> {
            if (values != null && values.length > 0) {
                params.put(name, String.join(",", values));
            }
        });

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

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

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

            // 1. 根据 outTradeNo 查询本地订单。
            // 2. 校验 appId、sellerId、totalAmount 与本地订单一致。
            // 3. 仅处理预期的 tradeStatus。
            // 4. 使用事务或唯一约束保证重复通知不会重复入账。
            // 5. 保存 tradeNo,更新订单状态并提交事务。

            if ("TRADE_SUCCESS".equals(tradeStatus)
                    || "TRADE_FINISHED".equals(tradeStatus)) {
                return "success";
            }

            return "failure";
        } catch (Exception e) {
            // 记录异常,但不要在日志中泄露私钥或完整敏感参数。
            return "failure";
        }
    }
}

配置项 alipay.public-key 必须填写支付宝公钥,不能使用应用公钥或应用私钥。具体公钥要与当前应用、签名模式及密钥配置一致。

回调处理的要求

准确返回处理结果

处理成功后直接返回:

success

不要返回 JSON、HTML 或其他提示文字,例如:

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

即使 HTTP 状态码为 200,上述 JSON 也可能不会被识别为成功响应。还要检查统一响应包装器,避免它将字符串包装成 JSON。

不要重定向

支付宝配置的地址应直接处理通知,不能依赖以下跳转:

HTTP 301
HTTP 302
HTTP 307
HTTP 308

HTTP 跳转 HTTPS、域名规范化以及自动补充路径末尾的 /,都可能影响通知处理。应直接配置最终可访问的 HTTPS 地址。

完成业务事务后再返回成功

只有验签通过、订单校验完成且数据库事务成功提交后,才能返回 success。如果先返回成功,再异步更新订单,后续处理一旦失败,可能无法通过通知重试恢复。

同一笔交易可能收到多次通知,因此接口必须支持幂等处理。可以为 out_trade_no 或 trade_no 设置唯一约束。订单已经处理成功时,直接返回 success。

验签后继续校验业务参数

验签通过后,还需要核对:

  • out_trade_no 是否存在。
  • app_id 是否属于当前应用。
  • seller_id 是否为预期收款账号。
  • total_amount 是否与订单金额一致。
  • trade_status 是否属于业务允许处理的状态。
  • 订单是否已经处理,避免重复入账。

金额建议使用 BigDecimal 比较,不要使用 double:

BigDecimal notifiedAmount = new BigDecimal(params.get("total_amount"));
BigDecimal orderAmount = order.getTotalAmount();

if (notifiedAmount.compareTo(orderAmount) != 0) {
    return "failure";
}

排查接口可用性

可以从公网测试接口的网络连通性:

curl -i -X POST \
  'https://www.eeeknow.com/payCallback/alipayCallback' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'out_trade_no=6898896632557862913'

这个请求没有支付宝的有效签名,因此业务接口返回 failure 或提示验签失败是正常现象。该测试用于确认:

  • 域名能否解析。
  • TLS 证书是否有效,证书链是否完整。
  • 接口是否超时或发生重定向。
  • 请求能否进入网关和应用。
  • 日志能否记录该请求。

不要为了让测试请求通过而关闭验签。

常见原因

如果日志中完全没有请求记录,优先检查:

  • 支付时传入的 notify_url 是否确实是当前地址。
  • 该地址是否允许公网直接访问。
  • DNS、HTTPS 证书和证书链是否正常。
  • 防火墙、CDN、WAF 或 IP 白名单是否拦截了请求。
  • 支付宝侧是否有通知发送或失败记录。

如果有请求记录但订单没有更新,优先检查:

  • 是否将表单请求错误地按 JSON 解析。
  • 是否使用了错误的支付宝公钥。
  • charset 或 sign_type 是否与实际配置一致。
  • 是否在取参后修改了内容,又使用修改后的参数验签。
  • 订单号是否被转换为数值并丢失精度。
  • 金额、应用 ID 或卖家账号是否校验失败。
  • 数据库更新是否异常,事务是否回滚。
  • 统一异常处理是否返回了 HTML 错误页。
  • 实际响应是否为 JSON,而不是纯文本 success。
  • 接口处理时间是否过长,导致支付宝侧超时。

最终确认方式

由有权限的技术人员根据支付时间和订单号,分别导出以下证据:

  1. alipay.trade.query 返回的交易状态。
  2. 支付宝侧的异步通知记录及响应结果。如果当前后台没有相关入口,需要向支付宝技术支持提供应用信息、订单号和支付时间,请其协助查询。
  3. 网关访问日志中的请求时间、状态码和耗时。
  4. 应用日志中的验签结果、业务校验结果和响应内容。
  5. 数据库中该订单的处理状态及更新时间。

将这些记录按时间和订单号对应起来,才能准确判断支付宝没有发出通知、通知没有到达服务器,还是服务器收到通知后处理或响应失败。

备注:内容仅供参考。