PAYATHON 2026

Node.js 使用 alipay-sdk 4.14 报 invalid-signature

支付小周

结论

这类 invalid-signature 报错不一定说明密钥有误。从支付宝网关生成的待验签字符串来看,实际参与验签的参数只有:

app_id
charset
method
sign_type
timestamp
version

其中不包含 biz_content、return_url 等原本可能参与签名的参数。因此,应优先检查 SDK 生成签名时使用的参数,是否与最终发送到支付宝网关的参数一致。

常见原因有:

  • bizContent、returnUrl 等参数放错了位置,最终没有发出。
  • SDK 生成 URL 后,又对其进行了手动拼接、解析或重新编码。
  • sign 中的 + 被转换成空格,或者经过了重复的 URL 编码。
  • 沙盒应用的私钥与沙盒后台配置的应用公钥不属于同一密钥对。
  • 普通公钥模式和证书模式混用。
  • RSA2、字符集或私钥格式配置不一致。
  • 使用了错误的沙盒网关,或者混用了沙盒与正式环境的配置。

报错页面中的 & 通常只是 HTML 对 & 的转义显示,不代表实际签名字符串中包含 &。

首先检查最终请求

不要只检查传入 SDK 的 JavaScript 对象,还要查看浏览器或服务端最终请求支付宝时使用的完整 URL。

确认以下参数是否存在:

app_id
biz_content
charset
method
return_url
sign
sign_type
timestamp
version

如果最终 URL 中没有 biz_content,而 SDK 签名时包含了该参数,支付宝就无法计算出相同的签名结果。

还要检查:

sign=abc+def...

是否在中间处理过程中变成了:

sign=abc def...

Base64 签名中的 +、/、= 必须经过正确的 URL 编码。SDK 已经生成 URL 后,不要再次调用 decodeURIComponent() 或 encodeURIComponent(),也不要把 URL 拆成参数再重新拼装。

使用 SDK 直接生成跳转地址

alipay-sdk 4.x 通常可以通过 AlipayFormData 生成页面支付地址。具体导入方式可能会因 CommonJS、ES Module 和安装包的导出形式有所不同,但参数的组织方式应保持一致:

const AlipaySdk = require('alipay-sdk').default;
const AlipayFormData = require('alipay-sdk/lib/form').default;

const alipaySdk = new AlipaySdk({
  appId: '你的沙盒APPID',
  privateKey: process.env.ALIPAY_APP_PRIVATE_KEY,
  alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY,
  signType: 'RSA2',
  gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
});

async function createPayUrl() {
  const formData = new AlipayFormData();

  formData.setMethod('get');

  formData.addField(
    'returnUrl',
    'https://example.com/alipay/return'
  );

  formData.addField('bizContent', {
    outTradeNo: `TEST_${Date.now()}`,
    productCode: 'FAST_INSTANT_TRADE_PAY',
    totalAmount: '0.01',
    subject: '沙盒支付测试'
  });

  return alipaySdk.exec(
    'alipay.trade.page.pay',
    {},
    { formData }
  );
}

createPayUrl()
  .then((payUrl) => {
    console.log(payUrl);
  })
  .catch(console.error);

将 SDK 返回的 payUrl 直接用于 HTTP 重定向:

app.get('/pay', async (req, res, next) => {
  try {
    const payUrl = await createPayUrl();
    res.redirect(payUrl);
  } catch (error) {
    next(error);
  }
});

不要在 payUrl 后继续追加业务参数,也不要使用查询字符串库再次序列化。

核对密钥时要区分三种内容

支付宝接入中的几个名称很容易混淆:

  • 应用私钥:保存在服务端,用于对请求签名。
  • 应用公钥:上传到支付宝开放平台,由支付宝用来验证请求。
  • 支付宝公钥:配置在 SDK 中,用于验证支付宝返回的数据或通知。

出现 invalid-signature 时,需要重点核对以下关系:

服务端使用的应用私钥
        ↕ 必须属于同一密钥对
支付宝开放平台中配置的应用公钥

SDK 配置项 alipayPublicKey 一般填写支付宝公钥,而不是应用公钥。将应用公钥填入该字段,虽然不一定影响请求签名的生成,却会导致后续响应或异步通知验签失败。

可以使用 OpenSSL 从私钥导出应用公钥,再检查它是否与平台配置一致:

openssl pkey -in app_private_key.pem -pubout -out derived_public_key.pem

将导出的公钥正文与沙盒应用后台配置的应用公钥进行比较。比较时可以忽略 PEM 头尾和换行,但正文中的字符必须完全一致。

检查私钥格式

私钥通常以以下两种格式之一保存:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

或者:

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

前者通常是 PKCS#8,后者通常是 PKCS#1。应根据当前 SDK 版本支持的格式进行配置,不能只修改 PEM 的头尾来”转换”格式。

如果私钥保存在环境变量中,换行可能会变成字面量 \n。此时需要将其恢复为真实换行:

const privateKey = process.env.ALIPAY_APP_PRIVATE_KEY.replace(/\\n/g, '\n');

配置 SDK 时使用处理后的值:

const alipaySdk = new AlipaySdk({
  appId: process.env.ALIPAY_APP_ID,
  privateKey,
  alipayPublicKey: process.env.ALIPAY_PUBLIC_KEY.replace(/\\n/g, '\n'),
  signType: 'RSA2',
  gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do'
});

不要在日志中输出完整私钥或完整签名。

检查是否混用了证书模式

如果沙盒应用配置的是”应用公钥”,代码也应使用普通公钥模式。

如果应用配置的是证书模式,SDK 通常还需要配置应用公钥证书、支付宝公钥证书和支付宝根证书等内容。不能在平台中使用证书模式,却只向 SDK 传入普通公钥字段。

排查时,先确认开放平台应用使用的加签方式,再检查代码是否采用了相同模式。

检查时间和运行环境

示例报错中的时间是:

2026-01-07 09:43:42

如果这不是脱敏后的示例,而是服务端实际生成的时间,需要检查服务器的时区和系统时间。时间异常通常不会直接改变签名算法的计算结果,但可能触发请求有效期校验,也会干扰问题排查。

同时确认以下沙盒配置来自同一套环境:

沙盒 APPID
沙盒应用对应的应用私钥
沙盒后台配置的应用公钥
沙盒支付宝公钥
沙盒网关

不要将任何正式环境配置混入沙盒配置。

推荐的排查顺序

  1. 打印 SDK 最终生成的请求 URL,并对 sign 做脱敏处理。
  2. 确认最终 URL 中包含 biz_content 和 sign。
  3. 确认 URL 生成后没有再次经过拼接、解码或序列化。
  4. 检查 sign 中的 + 是否被转换成空格。
  5. 确认请求使用的是沙盒网关。
  6. 从当前私钥导出公钥,并与沙盒后台配置的应用公钥比较。
  7. 确认 signType 为 RSA2,且没有混用证书模式。
  8. 检查私钥的 PEM 格式、环境变量换行和文件编码。
  9. 校准服务器时间后重新生成请求,不要复用旧 URL。

结合当前错误信息,首先要确认的不是密钥是否需要再次更换,而是 SDK 签名时使用的参数集合,是否与支付宝网关最终收到的参数集合完全一致。

备注:内容仅供参考。