PAYATHON 2026

支付宝回调返回 success 后为何仍持续收到通知?

支付小周

结论

支付宝收到商户回调接口返回的纯文本 success 后,通常会停止重试。如果通知持续到达,问题往往不在业务处理结果,而在于支付宝没有识别到有效应答。

常见原因有:

  • HTTP 状态码不是 200
  • 响应正文并非严格的 success
  • success 前后带有空格、换行、HTML、JSON 或其他内容
  • 接口响应超时,支付宝未能及时收到结果
  • 网关、反向代理或框架改写了响应
  • 同一订单收到了不同类型的通知,却被误认为重复回调

仅凭订单号 2022062822001401591428498179 无法确定具体原因,还需要查看回调日志、HTTP 访问日志和支付宝侧的通知记录。

为什么返回了 success 仍可能重试

支付宝判断通知是否成功,依据的是最终收到的 HTTP 响应,而不是代码是否执行过 return "success" 之类的语句。

例如,下面这些响应通常都不等同于纯文本 success:

success\n
{"message":"success"}
success<br>
处理成功:success

业务代码可能已经生成了 success,但之后又发生异常,导致接口最终返回 500。统一响应包装器也可能把字符串转换成 JSON:

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

这种包装用于普通业务接口通常没有问题,但支付回调接口一般需要直接输出指定文本。

解决步骤

1. 检查真实 HTTP 响应

不要只看应用日志中记录的返回值。还要核对支付宝访问回调地址时实际得到的结果,包括:

  • HTTP 状态码
  • 响应正文的原始字节
  • 响应耗时
  • 是否发生重定向
  • 请求是否经过 CDN、WAF、API 网关或反向代理

理想的响应应接近:

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

success

避免返回 301、302、404、499、500、502 或 504。

2. 验签后再处理业务

收到回调参数后,应先验签,再校验以下业务字段:

  • app_id
  • seller_id
  • out_trade_no
  • trade_no
  • total_amount
  • trade_status

金额、商户身份和订单归属必须与本地订单一致。如果验签失败或业务字段不匹配,不应更新订单状态。

3. 对重复通知做幂等处理

支付通知可能重复发送,所以多次收到通知并不代表发生了多次支付。

可以使用 trade_no 或本地 out_trade_no 建立唯一约束,并通过条件更新确保订单只处理一次:

UPDATE payment_order
SET status = 'PAID',
    alipay_trade_no = :trade_no,
    paid_at = CURRENT_TIMESTAMP
WHERE out_trade_no = :out_trade_no
  AND status = 'UNPAID';

如果受影响行数为 0,应继续查询订单状态:

  • 订单已支付:按重复通知处理,直接返回 success
  • 订单不存在或数据不一致:记录异常,暂不确认成功

退款、交易关闭等其他通知状态应按照各自的业务规则处理,不能一律当作支付成功。

4. 尽快返回 success

通知接口中不宜同步执行耗时操作,例如:

  • 发送短信或邮件
  • 调用多个下游系统
  • 生成报表
  • 执行复杂的库存计算
  • 等待人工审核

较稳妥的处理方式是完成验签和必要的事务更新,将后续工作投递到消息队列,然后立即返回 success。

Java 示例

以下示例只展示响应方式和基本处理结构。验签参数需要根据项目实际使用的支付宝 SDK 配置:

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

    boolean verified = verifyAlipaySignature(params);
    if (!verified) {
        return "failure";
    }

    if (!validateBusinessFields(params)) {
        return "failure";
    }

    try {
        processPaymentIdempotently(params);
        return "success";
    } catch (Exception e) {
        log.error("Failed to process Alipay notification", e);
        return "failure";
    }
}

如果项目启用了统一返回值包装,应将回调路径排除,否则返回的字符串可能被包装成 JSON。

PHP 示例

<?php

$params = $_POST;

if (!verifyAlipaySignature($params)) {
    http_response_code(200);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'failure';
    exit;
}

if (!validateBusinessFields($params)) {
    http_response_code(200);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'failure';
    exit;
}

try {
    processPaymentIdempotently($params);

    http_response_code(200);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'success';
    exit;
} catch (Throwable $e) {
    error_log($e->getMessage());

    http_response_code(200);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'failure';
    exit;
}

需要确认 PHP 文件在 <?php 之前没有输出 BOM、空格或其他内容,输出响应后也不要继续打印调试信息。

注意事项

  • 验签失败或订单校验失败时,不要为了停止重试而盲目返回 success。
  • 对于已经正确处理过的重复通知,应直接返回 success,不要重复扣库存、发货或记账。
  • 回调日志应记录通知时间、trade_no、out_trade_no、trade_status、验签结果、处理耗时和最终响应,但不要记录私钥等敏感信息。
  • 检查回调地址是否发生了 HTTP 到 HTTPS、域名或路径重定向。
  • 如果本地日志显示已经返回 success,还要检查 Nginx、负载均衡、网关和应用服务器的访问日志,确认最终响应没有被改写。
  • 如果只有订单 2022062822001401591428498179 持续出现问题,应对比每次通知的完整参数,确认是内容完全相同的重试,还是交易状态不同的多次通知。
  • 如果响应状态、正文和耗时都正常,但通知仍在持续,应携带订单号、商户订单号、通知时间和服务端访问日志,通过支付宝商家平台或官方技术支持进一步核查。

备注:内容仅供参考。