PAYATHON 2026

WAP网页支付证书模式下异步回调验签失败

支付小周

结论

POST 参数在验签前不需要额外加密,也不用手动排序或拼接。直接读取异步通知表单中的全部参数,按支付宝 SDK 的要求转换为参数 Map,然后调用证书模式对应的验签方法即可。

验签失败通常与参数是否经过特殊处理无关,常见原因有:

  • 调用了普通公钥模式的验签方法,没有使用证书模式方法。
  • 使用了应用公钥证书或商户私钥。回调验签需要使用支付宝公钥证书。
  • 参数经过二次 URL 解码、重新编码或其他修改。
  • 字符集、签名算法与实际配置不一致。
  • 解析参数时遗漏字段,或者自行排除了 sign、sign_type。
  • Web 框架提前读取或转换了请求体,改变了验签内容。

Java 验签示例

下面的示例适用于支付宝开放平台 Java SDK 的常见接口形式。SDK 版本不同,方法签名可能也会变化,请以项目当前依赖版本为准。

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

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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

    Map<String, String[]> requestParams = request.getParameterMap();
    for (Iterator<String> iterator = requestParams.keySet().iterator();
         iterator.hasNext();) {

        String name = iterator.next();
        String[] values = requestParams.get(name);

        StringBuilder value = new StringBuilder();
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                value.append(",");
            }
            value.append(values[i]);
        }

        params.put(name, value.toString());
    }

    String alipayPublicCertPath =
            "/path/to/alipayCertPublicKey_RSA2.crt";

    return AlipaySignature.rsaCertCheckV1(
            params,
            alipayPublicCertPath,
            "UTF-8",
            "RSA2"
    );
}

此处必须使用支付宝公钥证书,例如:

alipayCertPublicKey_RSA2.crt

不要传入应用公钥证书,也不要使用商户私钥文件验证回调签名。

不同版本的 SDK 可能会提供名称或参数略有差异的证书验签接口。如果当前版本中没有 rsaCertCheckV1,需要确认该 SDK 是否支持证书模式,并查看对应版本的 API 定义。

正确的处理流程

1. 原样获取表单参数

支付宝异步通知通常通过 application/x-www-form-urlencoded 格式 POST 到通知地址。应通过框架提供的表单参数接口读取数据,例如:

request.getParameterMap()

不要将请求体作为 JSON 解析,也不要自行拆分请求字符串。

2. 保留完整参数

传给 SDK 的 Map 应保留回调中的全部参数,包括:

sign
sign_type
trade_no
out_trade_no
trade_status
total_amount

不要自行移除 sign 或 sign_type。SDK 会在内部提取签名值,并按照规则生成待验签字符串。

3. 使用证书模式验签接口

使用证书模式时,应调用证书验签方法,并传入支付宝公钥证书路径:

AlipaySignature.rsaCertCheckV1(
    params,
    alipayPublicCertPath,
    charset,
    signType
);

普通公钥模式常用的 rsaCheckV1 接收支付宝公钥字符串,不能用应用公钥证书内容直接代替。

4. 保证字符集和算法一致

常见配置是:

charset = UTF-8
signType = RSA2

这两个值必须与支付请求及开放平台中的配置一致,不能只凭经验写死。如果实际请求使用了其他值,应按真实配置传入。

5. 验签成功后再校验业务字段

签名验证成功,只能说明通知内容通过了身份和完整性验证。之后还需要检查业务字段:

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

至少需要确认:

  • app_id 是当前应用的 AppID。
  • out_trade_no 在本地存在。
  • total_amount 与本地订单金额一致。
  • 收款方信息符合预期。
  • 订单没有被重复处理。
  • trade_status 属于业务允许处理的状态。

常见错误

对参数进行二次解码

下面的处理可能会破坏包含 +、%、空格或中文的字段:

URLDecoder.decode(value, "UTF-8");

如果 Servlet 容器或 Web 框架已经完成表单解码,就不要再次调用 URLDecoder.decode()。

自己排序和拼接参数

不要自行编写类似下面的处理:

params.entrySet()
      .stream()
      .sorted(...)
      .map(...)
      .collect(...);

如果没有完全按照支付平台的签名规范处理空值、编码和特殊字符,生成的待验签字符串很容易与 SDK 不一致。使用官方 SDK 时,直接传入参数 Map 即可。

使用错误的证书

证书模式会涉及多份文件,每份文件的用途不同:

  • 应用私钥:商户发起请求时用于签名。
  • 应用公钥证书:平台用于识别应用公钥。
  • 支付宝公钥证书:商户用于验证支付宝返回数据和异步通知的签名。
  • 支付宝根证书:用于证书链、序列号等相关处理,不能直接替代支付宝公钥证书验证回调签名。

异步通知验签需要使用支付宝公钥证书。

验签前修改参数

以下操作都可能导致验签失败:

  • 对字段调用 trim()。
  • 将空字符串转换为 null。
  • 修改金额格式,例如将 0.01 改成 0.010。
  • 修改换行符或中文字符。
  • 把参数转换成 JSON 后再验签。
  • 根据业务对象重新生成参数 Map。
  • 使用日志中经过转义或截断的数据验签。

验签时应使用从当前 HTTP 请求中直接解析得到的原始表单参数 Map。

回调响应

业务处理完成后,需要按照接口约定返回成功响应。支付宝异步通知通常要求返回以下纯文本:

success

示例:

if (!verifyNotify(request)) {
    response.getWriter().write("failure");
    return;
}

try {
    // 校验订单并执行幂等的业务处理
    response.getWriter().write("success");
} catch (Exception e) {
    response.getWriter().write("failure");
}

不要在 success 前后添加 HTML、JSON、空格或其他说明文字。只有验签成功并完成业务处理后才能返回成功,否则平台可能停止重试,导致订单状态没有正确更新。

备注:内容仅供参考。