PAYATHON 2026

APP 支付调用 alipay.trade.app.pay 提示 invalid-signature

支付老李

结论

invalid-signature(结果码 40002)表示支付宝网关无法使用当前 app_id 对应的应用公钥验证请求签名。

即使本地验证结果显示私钥与某个公钥匹配,也只能证明这对密钥本身有效,不能说明支付宝开放平台实际配置的公钥、应用环境和签名方式都正确。

建议先检查以下内容:

  1. 签名使用的是商户应用私钥,不是支付宝公钥或支付宝私钥。
  2. 开放平台配置的应用公钥与当前私钥配套。
  3. app_id、网关地址和密钥配置属于同一环境。
  4. sign_type 与实际签名算法一致,通常应为 RSA2。
  5. 签名原文与最终发送的内容完全一致。
  6. 先完成签名,再进行 URL 编码,不能对编码后的字符串签名。
  7. 没有混用公钥模式和证书模式。

优先使用官方 SDK 生成订单字符串

APP 支付不建议手工拼接和签名参数。服务端可以通过支付宝 SDK 调用 sdkExecute,取得完整订单字符串后原样返回给 APP。

Java 示例:

AlipayClient alipayClient = new DefaultAlipayClient(
    "https://openapi.alipay.com/gateway.do",
    appId,
    merchantPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount("0.01");
model.setSubject("订单支付");
model.setProductCode("QUICK_MSECURITY_PAY");

request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

// 将该字符串原样返回给 APP,不要再次排序、签名或整体 URL 编码
String orderString = response.getBody();

这里需要注意:

  • merchantPrivateKey 是应用私钥。
  • alipayPublicKey 是支付宝公钥,主要用于验证支付宝返回的内容,不用于生成商户请求签名。
  • response.getBody() 应原样交给支付宝 APP SDK。
  • 使用沙箱环境时,需要改为对应的沙箱网关,并使用沙箱应用的 app_id 和密钥。

检查开放平台中的应用公钥

本地公私钥匹配,只能说明这一对密钥有效。还要确认支付宝开放平台当前应用中保存的应用公钥,确实是由这份私钥配套生成的公钥。

常见问题有:

  • 把支付宝公钥填进了应用公钥配置。
  • 修改或重新生成私钥后,没有同步更新应用公钥。
  • 公钥已经更新,服务却仍在使用旧私钥。
  • 公钥配置在另一个应用中,请求使用的却是不同的 app_id。
  • 在生产环境中使用了沙箱应用密钥,或在沙箱环境中使用了生产密钥。
  • 多台服务器加载了不同的私钥配置。
  • 密钥轮换期间,部分实例仍在使用旧密钥。

核对时要比较密钥内容本身,不能只看文件名。不要把私钥写入日志,也不要将其提交到代码仓库。

检查 RSA2 与字符集

如果请求中声明:

sign_type=RSA2

实际签名算法必须是:

SHA256withRSA

如果使用 RSA,对应的通常是 SHA1withRSA。请求声明 RSA2,实际却使用 SHA1withRSA,验签一定会失败。

签名时的字节编码也必须与请求中的 charset 一致。例如:

charset=utf-8

签名原文就应使用 UTF-8 转换为字节:

Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();

String sign = Base64.getEncoder().encodeToString(signed);

实际项目中,仍建议交给支付宝 SDK 处理私钥解析、参数排序、签名和编码。

检查签名原文

手工签名时,签名原文通常由顶层公共请求参数组成。排除 sign 和值为空的参数后,按照参数名的 ASCII 顺序排序,再以 key=value 的形式用 & 连接。

例如:

app_id=202100xxxxxxxxxx
biz_content={"out_trade_no":"ORDER_001","product_code":"QUICK_MSECURITY_PAY","subject":"订单支付","total_amount":"0.01"}
charset=utf-8
method=alipay.trade.app.pay
notify_url=https://example.com/alipay/notify
sign_type=RSA2
timestamp=2026-09-09 12:00:00
version=1.0

排序并拼接后,形式如下:

app_id=202100xxxxxxxxxx&biz_content={"out_trade_no":"ORDER_001","product_code":"QUICK_MSECURITY_PAY","subject":"订单支付","total_amount":"0.01"}&charset=utf-8&method=alipay.trade.app.pay&notify_url=https://example.com/alipay/notify&sign_type=RSA2&timestamp=2026-09-09 12:00:00&version=1.0

需要检查这些细节:

  • 不要把 sign 放进签名原文。
  • 不要对已经完成 URL 编码的参数值签名。
  • 签名后不要重新序列化或修改 biz_content。
  • JSON 中的空格、转义字符、字段顺序或小数格式发生变化,都会改变签名原文。
  • notify_url 是否包含查询参数或末尾斜杠,也会影响签名。
  • 签名值应先进行 Base64 编码,再按照 APP 支付订单字符串的要求进行 URL 编码。
  • 不要对完整订单字符串重复进行 URL 编码。

例如,参与签名的内容是:

subject=测试订单

发送时可以编码为:

subject=%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95

但不能先对 测试订单 进行 URL 编码,再用编码后的结果参与签名。

检查私钥格式

不同语言和加密库接受的私钥格式可能不同,常见格式包括 PKCS#1 和 PKCS#8。Java SDK 通常要求传入去掉头尾标记和换行后的私钥正文,具体应以当前 SDK 构造参数的要求为准。

配置系统也可能破坏私钥内容,例如:

  • 换行被保存成字面量 \n。
  • 私钥前后出现多余的空格或引号。
  • 环境变量长度被截断。
  • Base64 正文被错误转义。
  • 配置文件包含 BOM。
  • 公钥内容被误填到私钥配置项中。

如果 SDK 能成功加载私钥,但支付宝网关仍然验签失败,应优先核对开放平台中的应用公钥和实际发送的签名原文。

公钥模式与证书模式不要混用

使用普通公钥模式时,需要配置:

  • 应用私钥;
  • 开放平台中的应用公钥;
  • 支付宝公钥。

使用证书模式时,则要采用相应的证书初始化方式,并正确配置:

  • 应用公钥证书;
  • 支付宝公钥证书;
  • 支付宝根证书;
  • 对应的证书序列号。

不能在开放平台按证书模式配置应用,却使用普通公钥模式的客户端初始化方式。不同语言和版本的 SDK,初始化类与参数名称可能不同,应以当前使用的官方 SDK 接口为准。

推荐排查顺序

  1. 确认请求中的 app_id。
  2. 确认请求发送到生产网关还是沙箱网关。
  3. 查看运行实例实际加载的私钥。可以记录公钥指纹,但不能记录私钥。
  4. 从该私钥推导出公钥,并与开放平台中当前应用的公钥逐字节比较。
  5. 确认请求声明 sign_type=RSA2,实际使用的算法是 SHA256withRSA。
  6. 确认 charset=utf-8 与签名时使用的字节编码一致。
  7. 记录脱敏后的签名原文和最终订单字符串,检查参数是否在签名后又被修改。
  8. 检查 APP、代理层或网关是否进行了二次 URL 编码。
  9. 暂时改用官方 SDK 的 sdkExecute 生成订单字符串,以排除手工签名问题。
  10. 如果使用集群,逐台检查密钥配置,确认没有部分实例仍在使用其他密钥。

出现这个错误时,最常见的原因不是公私钥在数学上不匹配,而是支付宝开放平台配置的应用公钥并非当前私钥对应的公钥,或者签名完成后,编码、JSON 序列化或参数修改导致实际发送的字符串与签名原文不一致。

备注:内容仅供参考。