PAYATHON 2026

支付宝小程序唤起支付报错 isv.invalid-signature 如何解决?

支付老李

结论

isv.invalid-signature 表示支付宝网关无法使用当前应用配置的商户公钥验证请求签名。问题通常不在小程序前端的 my.tradePay,而在服务端调用 alipay.trade.create 等接口时,使用的 app_id、商户私钥、签名方式或证书配置不匹配。

App 支付、WAP 支付和 Web 支付正常,只能说明对应应用及密钥配置正确,不能说明小程序使用的应用配置也没有问题。排查时应先确认小程序支付请求实际使用的 app_id,再核对该应用在支付宝开放平台配置的接口加签方式。

常见原因

1. 小程序 app_id 与商户私钥不属于同一应用

同一个支付宝账号下可能有多个应用,App、网站和小程序支付也可能分别使用不同的 app_id。

常见的错误配置是:

  • 请求参数使用小程序的 app_id
  • 签名使用 App 支付应用的商户私钥
  • 小程序应用后台配置的是另一套商户公钥

在这种情况下,即使其他支付方式都正常,小程序相关接口仍会返回 isv.invalid-signature。

2. 混淆商户私钥和支付宝公钥

这几种密钥的用途不同:

  • 商户私钥:商户请求支付宝接口时用于生成签名
  • 商户公钥:配置在支付宝开放平台,供支付宝验证商户请求
  • 支付宝公钥:商户用于验证支付宝响应或异步通知

调用支付宝接口时,必须用商户私钥签名,不能使用支付宝公钥或商户公钥。

3. 普通公钥模式与证书模式混用

如果应用采用证书模式,服务端还要正确配置应用公钥证书、支付宝公钥证书和支付宝根证书,并携带对应的证书序列号。

常见问题有:

  • 开放平台配置为证书模式,代码仍按普通公钥模式初始化
  • 使用了其他应用的证书
  • 更新证书后,服务器仍在加载旧证书
  • 只替换商户私钥,没有同步更新应用公钥证书
  • 混用了沙箱证书和正式环境证书

同一套配置只能选择一种模式,不能混用。

4. sign_type 或字符集不一致

通常应使用:

sign_type=RSA2
charset=utf-8

如果请求声明使用 RSA2,实际却通过 SHA1withRSA 签名,或者签名和发送请求时采用了不同的字符集,都会导致验签失败。

5. 签名原文在发送前后发生变化

手工拼接签名时,容易出现这些问题:

  • 参数没有按要求排序
  • 把 sign 参数本身加入签名
  • 签名前对参数做了 URL 编码
  • biz_content 签名后又被重新序列化
  • JSON 中的空格、转义符或字段顺序发生变化
  • 金额、中文、换行符或特殊字符在传输时被修改
  • 网关收到的请求参数与生成签名时使用的参数不是同一份数据

不能先对一份 biz_content 生成签名,再重新构造另一份 JSON 发送。

6. 环境配置混用

以下配置必须来自同一个环境:

  • 支付宝网关地址
  • app_id
  • 商户私钥
  • 商户公钥或应用公钥证书
  • 支付宝公钥或支付宝公钥证书

沙箱应用的密钥不能用于正式网关,正式应用的密钥也不能用于沙箱网关。

推荐排查步骤

第一步:确定报错发生在哪个接口

先记录服务端调用支付宝时使用的接口名称。小程序支付的常见流程是:

  1. 服务端调用 alipay.trade.create
  2. 支付宝返回 trade_no
  3. 小程序调用 my.tradePay
  4. 将 trade_no 作为 tradeNO 传入

如果服务端调用 alipay.trade.create 时就返回了 isv.invalid-signature,应检查服务端的加签配置,不需要修改小程序前端代码。

第二步:核对实际发送的 app_id

不要只看配置文件。应在不输出私钥的前提下,记录程序运行时实际使用的 app_id,然后进入该应用的支付宝开放平台配置页面,核对接口加签方式。

三者必须互相对应:

请求中的 app_id
    ↕ 必须对应
开放平台应用中配置的商户公钥或应用公钥证书
    ↕ 必须对应
服务端用于签名的商户私钥

第三步:重新验证密钥对应关系

可以使用商户私钥生成一段测试签名,再用准备上传或已经配置的商户公钥在本地验签。

如果本地验签失败,说明公私钥不配对。此时应重新生成密钥,并把正确的商户公钥配置到对应应用中。

不要把商户私钥、完整签名原文或支付敏感信息写入公开日志。

第四步:检查 SDK 初始化

以 Java SDK 的普通公钥模式为例:

AlipayConfig config = new AlipayConfig();
config.setServerUrl("https://openapi.alipay.com/gateway.do");
config.setAppId("小程序对应的APP_ID");
config.setPrivateKey("与该应用商户公钥配对的商户私钥");
config.setFormat("json");
config.setCharset("UTF-8");
config.setSignType("RSA2");
config.setAlipayPublicKey("该应用对应的支付宝公钥");

AlipayClient alipayClient = new DefaultAlipayClient(config);

证书模式需要使用证书路径或证书内容初始化,具体写法取决于 SDK 版本:

AlipayConfig config = new AlipayConfig();
config.setServerUrl("https://openapi.alipay.com/gateway.do");
config.setAppId("小程序对应的APP_ID");
config.setPrivateKey("商户私钥");
config.setFormat("json");
config.setCharset("UTF-8");
config.setSignType("RSA2");

config.setAppCertPath("/path/appCertPublicKey.crt");
config.setAlipayCertPath("/path/alipayCertPublicKey_RSA2.crt");
config.setRootCertPath("/path/alipayRootCert.crt");

AlipayClient alipayClient = new DefaultAlipayClient(config);

普通公钥模式和证书模式只能选择一种,初始化参数不要混用。

第五步:使用官方 SDK 创建交易

尽量由 SDK 处理参数排序、编码和签名,避免手工拼接网关参数:

AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();

AlipayTradeCreateModel model = new AlipayTradeCreateModel();
model.setOutTradeNo("ORDER_202609080001");
model.setTotalAmount("0.01");
model.setSubject("订单支付");
model.setBuyerId("支付宝用户标识");

request.setBizModel(model);

AlipayTradeCreateResponse response = alipayClient.execute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        response.getCode() + " / "
        + response.getSubCode() + " / "
        + response.getSubMsg()
    );
}

String tradeNo = response.getTradeNo();

buyerId 的获取方式取决于小程序登录和用户授权流程,不能用小程序 app_id、商户 UID 或其他标识代替。

第六步:小程序端只传支付宝交易号

服务端成功创建交易后,小程序端可以这样调用:

my.tradePay({
  tradeNO: tradeNo,
  success: (result) => {
    console.log('tradePay result:', result.resultCode);
  },
  fail: (error) => {
    console.error('tradePay failed:', error);
  }
});

参数名是 tradeNO,其中 NO 必须大写。tradeNo 应是服务端调用 alipay.trade.create 后取得的支付宝交易号,不是商户订单号 out_trade_no。

如果必须手工签名

签名原文通常需要按照支付宝开放平台的规则处理公共请求参数:排除 sign,按参数名排序,再以 key=value 的形式连接。具体处理方式应以当前接口文档和所用加签模式为准。

RSA2 的核心操作类似:

Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(merchantPrivateKey);
signature.update(contentToSign.getBytes(StandardCharsets.UTF_8));

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

手工实现时必须满足:

签名使用的参数值 = 最终发送给支付宝的参数值

尤其不能在 biz_content 完成签名后再次序列化或修改。

注意事项

  • isv.invalid-signature 和“签名验证支付宝异步通知失败”属于两个方向的问题。前者通常表示支付宝无法验证商户请求。
  • 排查时可以记录 app_id、接口名、网关环境、sign_type、字符集,以及支付宝返回的 code、sub_code 和 sub_msg,但不要记录商户私钥。
  • 修改开放平台的公钥或证书后,需要确认配置已经生效,并重启可能缓存旧配置的应用实例。
  • 如果项目封装了统一支付网关,需要检查小程序渠道是否错误复用了 App、WAP 或其他商户配置。
  • 如果以上配置均一致,可以保存支付宝返回的请求流水号或 trace_id,再通过支付宝开放平台的诊断功能或技术支持继续定位。

备注:内容仅供参考。