PAYATHON 2026

支付宝扫码支付成功但无法接收回调是什么原因?

支付小周

结论

扫码支付成功,只能说明下单和支付流程正常,不能证明异步通知也正常。遇到这类问题,通常不是 app_id 或应用私钥配置错误,而是以下几种情况:

  • 下单时没有传入 notify_url,或者传入的地址有误。
  • 支付宝服务器无法通过公网访问回调地址。
  • 接口只接收 JSON,而支付宝异步通知通常以 application/x-www-form-urlencoded 格式提交。
  • 验签使用了错误的公钥、字符集或签名算法。
  • 业务处理发生异常,接口没有向支付宝返回纯文本 success。
  • 网关、反向代理、WAF、防火墙或 HTTPS 证书拦截了请求。
  • 混用了沙箱和正式环境的应用、密钥、网关或回调配置。

排查时,先检查下单请求中的 notify_url,再确认回调地址能否从公网访问,并查看服务端访问日志。

1. 确认下单请求携带了 notify_url

创建支付订单时,通常需要明确设置异步通知地址。只在本地配置文件中定义回调地址,并不表示这个地址已经提交给支付宝。

以 Java SDK 的当面付预创建请求为例:

AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl("https://pay.example.com/alipay/notify");
request.setBizContent("""
{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "0.01",
  "subject": "测试订单"
}
""");

AlipayTradePrecreateResponse response = alipayClient.execute(request);

设置方式取决于使用的 SDK 和支付接口,但最终请求中必须包含有效的异步通知地址。不要混淆同步跳转地址 return_url 和异步通知地址 notify_url:

  • return_url:用于支付完成后的页面跳转,不保证一定触发。
  • notify_url:支付宝服务器用它向商户服务器主动发送异步通知。

2. 检查回调地址能否从公网访问

支付宝服务器无法访问以下地址:

http://localhost/alipay/notify
http://127.0.0.1/alipay/notify
http://192.168.1.10/alipay/notify

回调地址应使用公网可以访问的域名,例如:

https://pay.example.com/alipay/notify

需要检查:

  1. 域名是否解析正确。
  2. 服务端口是否向公网开放。
  3. HTTPS 证书是否有效、完整且未过期。
  4. 路由是否允许 POST 请求。
  5. 接口前是否有登录认证、验证码或 IP 白名单限制。
  6. CDN、WAF 或 API 网关是否拦截了支付宝请求。
  7. 回调地址是否出现非预期的 301、302 跳转。
  8. 接口能否在合理时间内返回响应。

可以从公网环境测试接口是否连通:

curl -i -X POST https://pay.example.com/alipay/notify \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "test=1"

这个测试只能确认网络和路由是否畅通,无法模拟真实且能够通过验签的支付宝通知。

3. 按表单参数接收通知

支付宝异步通知通常通过 HTTP POST 提交表单参数。如果接口只使用 @RequestBody 接收 JSON,可能无法正确获取验签所需的参数。

Spring Boot 示例:

import com.alipay.api.internal.util.AlipaySignature;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
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 AlipayNotifyController {

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

    @PostMapping(
        value = "/alipay/notify",
        produces = "text/plain;charset=UTF-8"
    )
    public String notify(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 appId = params.get("app_id");
            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");

            // 1. 校验 app_id 是否属于当前商户应用
            // 2. 根据 out_trade_no 查询本地订单
            // 3. 校验订单金额、商户身份等关键字段
            // 4. 幂等更新订单状态
            // 5. 业务处理成功后返回 success

            return "success";
        } catch (Exception e) {
            // 记录异常,暂不确认通知,让支付宝按机制重试
            return "failure";
        }
    }
}

示例中的类名和方法只适用于部分 Java SDK。实际项目应以当前使用的支付宝 SDK 为准,不要自行拼接待验签字符串。

4. 确认验签公钥没有用错

验签应使用支付宝提供的公钥,或者证书模式对应的支付宝公钥。以下内容都不能代替它:

  • 商户应用公钥。
  • 商户应用私钥。
  • 另一套应用的支付宝公钥。
  • 沙箱应用的公钥。
  • 已经重新生成,但尚未同步配置的密钥。

密钥模式也必须保持一致:

  • 普通公钥模式使用支付宝公钥验签。
  • 证书模式应按照证书模式初始化客户端和验签逻辑。
  • RSA2、字符集等配置必须与实际通知一致。

如果近期更换过密钥,还要同时核对开放平台配置、服务端配置和当前运行的实例,防止部分实例仍在使用旧密钥。

5. 收到通知后必须返回纯文本 success

验签和业务处理都成功后,响应体应直接返回:

success

不要返回 JSON:

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

也不要返回 HTML、统一响应包装或带有额外说明的字符串:

success!

如果支付宝没有收到约定的成功响应,通常会把本次通知视为处理失败,并按相应机制再次发送。因此,日志中多次出现同一条通知不一定表示异常,回调处理本身必须具备幂等性。

6. 除了验证签名,还要校验业务字段

验签通过,只能说明通知内容没有被篡改,不能代替业务校验。至少要检查:

  • app_id 是否与当前应用一致。
  • out_trade_no 是否存在,且属于当前商户。
  • total_amount 是否与本地订单金额一致。
  • 交易状态是否属于业务允许处理的状态。
  • 订单是否已经处理,防止重复入账。
  • 通知中的商户身份信息是否符合当前收款主体。

可以使用本地订单号作为幂等键,并通过数据库条件更新避免重复处理:

UPDATE payment_order
SET pay_status = 'PAID',
    alipay_trade_no = ?
WHERE out_trade_no = ?
  AND pay_status = 'UNPAID';

只有受影响行数为 1 时,才执行首次支付成功后的业务操作。已经支付的订单再次收到相同通知时,只要校验无误,可以直接返回 success。

7. 检查服务端日志和中间链路

建议分别记录以下内容:

  • 回调到达时间。
  • 请求路径和 HTTP 方法。
  • 请求参数名称。
  • 验签结果。
  • out_trade_no、trade_no 和交易状态。
  • 业务处理结果。
  • 最终响应体和 HTTP 状态码。

不要在日志中记录应用私钥、完整证书内容或其他敏感凭据。

如果应用日志中完全没有请求,应继续检查 DNS、负载均衡、网关、WAF 和防火墙。如果请求已经到达但验签失败,应检查参数接收方式和支付宝公钥。如果验签成功后仍然反复收到通知,则要重点检查业务异常,以及响应体是否确实为纯文本 success。

8. 核对沙箱与正式环境

测试时,以下配置必须来自同一环境:

  • 支付宝网关。
  • app_id。
  • 应用私钥。
  • 支付宝公钥或相关证书。
  • 支付账号。
  • 回调地址及其服务端配置。

使用正式环境密钥验证沙箱订单,或者让正式订单继续向测试回调域名发送通知,都会导致通知失败或验签失败。

9. 增加主动查询作为补偿机制

不能只依靠异步通知确认支付结果。对于长时间停留在“待支付”状态的订单,可以调用支付宝交易查询接口核实状态,再通过定时任务完成对账和状态补偿。

可以按以下方式处理:

  1. 以异步通知作为主要的支付确认入口。
  2. 用支付结果查询处理异常情况。
  3. 通过每日账单或对账数据做最终校验。
  4. 所有状态更新都保持幂等。

排查时,可以先选定一个确认已经支付的 out_trade_no,依次检查下单请求是否包含正确的 notify_url、网关是否收到请求、应用是否正确解析表单、验签是否成功,以及接口最终是否返回纯文本 success。沿着这条链路逐项检查,通常就能找到问题所在。

备注:内容仅供参考。