PAYATHON 2026

支付宝统一收单交易查询接口返回值如何验签?

支付小周

结论

支付宝统一收单交易查询接口 alipay.trade.query 的同步返回值,需要使用支付宝公钥验签。

验签对象不是整个响应 JSON,也不是简单拼接 trade_no、out_trade_no 等字段,而是原始响应中 alipay_trade_query_response 节点对应的待验签内容。验签还需要用到响应中的 sign、接口配置的字符集和签名算法。只有验签通过,才能将返回的交易状态和金额用于后续业务处理。

典型响应结构如下:

{
  "alipay_trade_query_response": {
    "code": "10000",
    "msg": "Success",
    "trade_no": "2026xxxxxxxx",
    "out_trade_no": "ORDER_10001",
    "trade_status": "TRADE_SUCCESS"
  },
  "sign": "支付宝返回的签名值"
}

验签需要哪些参数

验签至少需要以下技术参数:

参数用途
sign支付宝返回的签名值
待验签内容原始响应中的 alipay_trade_query_response 节点内容
支付宝公钥用于验证响应确实由支付宝签发
sign_type通常为 RSA2,必须与实际接入配置一致
charset通常为 UTF-8,必须与请求和响应处理方式一致

还要区分以下几种密钥:

  • 应用私钥:商户向支付宝发送请求时,用于生成签名。
  • 应用公钥:支付宝用它验证商户请求。
  • 支付宝公钥:商户用它验证支付宝的同步响应或异步通知。

因此,不能使用应用公钥或应用私钥验证支付宝返回值。

如果采用支付宝公钥证书模式,应按照当前 SDK 的证书初始化方式配置支付宝公钥证书,不能直接沿用普通公钥模式的配置。

推荐做法:使用官方 SDK

使用支付宝官方 SDK 时,应在初始化客户端时正确配置支付宝公钥、字符集和签名算法,再通过 SDK 调用接口。不同语言、不同版本的 SDK 对同步响应的自动验签方式可能不同。有些版本在解析响应时完成验签,有些版本仍需调用单独的验签方法,因此应查看当前版本的说明。

Java 客户端的基本配置示例如下:

String serverUrl = "https://openapi.alipay.com/gateway.do";
String appId = "your_app_id";
String appPrivateKey = "your_app_private_key";
String format = "json";
String charset = "UTF-8";
String alipayPublicKey = "your_alipay_public_key";
String signType = "RSA2";

AlipayClient alipayClient = new DefaultAlipayClient(
        serverUrl,
        appId,
        appPrivateKey,
        format,
        charset,
        alipayPublicKey,
        signType
);

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
        "{\"out_trade_no\":\"ORDER_10001\"}"
);

AlipayTradeQueryResponse response = alipayClient.execute(request);

接口调用完成后,不能只根据 response.getCode() 或 response.isSuccess() 判断数据是否可信。还应确认当前 SDK 是否已经完成响应验签,并妥善处理验签失败时抛出的异常或返回的错误状态。

手动验签流程

如果没有使用 SDK 的自动验签功能,可以按以下流程处理。

1. 保留原始响应字符串

读取 HTTP 响应体后,先保留未经修改的原始字符串:

String responseBody = readOriginalResponseBody();

提取待验签内容之前,不要格式化响应,也不要调整字段顺序、转换字符转义或重新序列化。

2. 提取签名和待验签内容

从顶层响应中获取:

  • sign 字段的值;
  • alipay_trade_query_response 节点对应的原始内容。

重点在于保留“原始内容”。下面的处理可能导致验签失败:

Object data = parseJson(responseBody);
String content = serializeJson(data);

JSON 经过解析和重新序列化后,字段顺序、空白字符、Unicode 转义或斜杠转义都可能发生变化,导致字节内容与支付宝签名时使用的内容不一致。

应优先使用支付宝 SDK 提供的响应解析和签名内容提取功能。具体方法名可能因 SDK 的语言和版本而异,不能只凭方法名推断参数格式。

3. 使用支付宝公钥验证签名

从密码学角度看,验签过程相当于:

Verify(
    alipay_public_key,
    response_content_bytes,
    Base64Decode(sign),
    signature_algorithm
)

sign_type 为 RSA2 时,通常对应 SHA256withRSA。历史系统如果配置为 RSA,通常对应 SHA1withRSA。实际算法必须以当前应用的接入配置为准,不能在代码中自行切换。

Java 伪代码如下:

String responseContent = extractOriginalResponseNode(
        responseBody,
        "alipay_trade_query_response"
);

String sign = extractTopLevelSign(responseBody);

boolean verified = verifyAlipaySignature(
        responseContent,
        sign,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
);

if (!verified) {
    throw new SecurityException("支付宝交易查询响应验签失败");
}

extractOriginalResponseNode 和 verifyAlipaySignature 代表当前 SDK 或基础组件提供的功能。具体方法签名应以项目实际引入的 SDK 版本为准。

不依赖 SDK 的 RSA2 验签示例

如果已经可靠地取得原始待验签字符串,可以使用 JCA 验证 RSA2 签名:

import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class AlipaySignatureVerifier {

    public static boolean verifyRsa2(
            String content,
            String sign,
            String alipayPublicKey,
            String charset
    ) throws Exception {

        String normalizedPublicKey = alipayPublicKey
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", "");

        byte[] publicKeyBytes = Base64.getDecoder()
                .decode(normalizedPublicKey);

        PublicKey publicKey = KeyFactory.getInstance("RSA")
                .generatePublic(new X509EncodedKeySpec(publicKeyBytes));

        Signature verifier = Signature.getInstance("SHA256withRSA");
        verifier.initVerify(publicKey);
        verifier.update(content.getBytes(Charset.forName(charset)));

        byte[] signatureBytes = Base64.getDecoder().decode(sign);
        return verifier.verify(signatureBytes);
    }
}

调用示例:

boolean verified = AlipaySignatureVerifier.verifyRsa2(
        originalResponseContent,
        responseSign,
        alipayPublicKey,
        "UTF-8"
);

if (!verified) {
    throw new SecurityException("Invalid Alipay response signature");
}

这段代码只负责密码学验证,不能从完整 JSON 中准确提取原始待验签内容。生产环境仍建议使用官方 SDK,以免因 JSON 边界提取或转义处理不一致造成误判。

验签通过后的业务检查

验签成功只能说明响应在传输和处理过程中没有被篡改,并且签名可以通过对应的支付宝公钥验证。这并不代表交易一定成功。

验签通过后,还要继续检查:

if (!"10000".equals(response.getCode())) {
    // 接口调用未成功,根据 code、subCode、subMsg 处理
}

if (!expectedOutTradeNo.equals(response.getOutTradeNo())) {
    throw new SecurityException("商户订单号不匹配");
}

if (!expectedAmount.equals(response.getTotalAmount())) {
    throw new SecurityException("订单金额不匹配");
}

String tradeStatus = response.getTradeStatus();
switch (tradeStatus) {
    case "TRADE_SUCCESS":
    case "TRADE_FINISHED":
        // 根据业务规则更新订单
        break;
    case "WAIT_BUYER_PAY":
        // 等待付款
        break;
    case "TRADE_CLOSED":
        // 交易关闭
        break;
    default:
        // 未识别或暂不处理的状态
        break;
}

比较金额时,建议使用 BigDecimal.compareTo(),不要使用浮点数:

BigDecimal expected = new BigDecimal("99.00");
BigDecimal actual = new BigDecimal(response.getTotalAmount());

if (expected.compareTo(actual) != 0) {
    throw new SecurityException("交易金额不匹配");
}

常见验签失败原因

使用了错误的公钥

验签必须使用当前支付宝应用对应的支付宝公钥。沙箱环境与生产环境、普通公钥模式与证书模式的配置不能混用。

对整个响应体进行验签

顶层的 sign 字段不属于待验签业务内容。如果把包含 sign 在内的整个 JSON 用于验签,通常会失败。

JSON 被重新序列化

即使重新序列化后的 JSON 在语义上完全相同,只要字符表示发生变化,签名校验就可能失败。

字符集不一致

如果支付宝使用 UTF-8 签名,而本地使用系统默认字符集转换字节,验签结果就会不一致。代码中应显式指定字符集。

签名算法不一致

接口配置为 RSA2,本地却使用 SHA1withRSA,或者接口配置为 RSA,本地却使用 SHA256withRSA,都会导致验签失败。

公钥格式不正确

普通支付宝公钥一般按 X.509 格式解析。复制公钥时缺行、混入多余字符或全角空格,或者把证书内容当成普通公钥使用,都可能造成解析或验签失败。

处理原则

生产代码应按以下顺序处理:

读取原始响应
    → 提取原始待验签内容和 sign
    → 使用支付宝公钥验签
    → 验签失败立即拒绝处理
    → 检查接口返回 code
    → 核对订单号、金额等业务字段
    → 根据 trade_status 更新订单

验签失败后,不要继续使用响应中的交易状态,也不要为了“兼容”而跳过验签。如果官方 SDK 的方法名、自动验签行为或证书配置方式与示例不同,应以项目实际使用的 SDK 版本为准。但无论使用哪个版本,都不能混淆待验签内容、公钥用途和业务校验顺序。

备注:内容仅供参考。