天猫精灵二维码支付 my.call('qrcodePay') 创建订单失败
结论
问题发生在“创建支付订单”阶段,而不是二维码展示阶段。返回信息为:
createOrderFailure
resMsg=商户密钥不能为空
这说明收银台已经成功唤起,但支付服务端创建订单时没有取得有效的商户密钥,所以无法生成二维码。code=-2 表示订单创建失败,二维码没有显示只是后续结果。
建议先检查服务端的商户配置,以及 content 中用于识别商户身份的字段。使用 RSA2 私钥为 content 签名,不等于已经完成商户密钥配置。
content 是否必须是淘宝订单
不能简单地说“必须是淘宝订单”。
qrcodePay 的 content 应当包含该支付产品支持的订单内容,并按接口要求生成和签名。它通常不能只是任意一个淘宝订单号,也不能把普通淘宝订单数据自行拼装、签名后直接传入。
具体支持哪些订单类型,取决于当前应用接入的支付产品、商户权限和接口协议。需要逐项确认:
- 应用是否已经开通二维码支付能力;
- 商户是否已与应用正确绑定;
- 订单是否通过对应的服务端下单接口创建;
content的字段、格式和签名方式是否符合接口文档;- 淘宝订单是否属于当前接口明确支持的交易类型。
如果使用淘宝侧订单,还需要确认它能否通过天猫精灵对应的支付链路付款。不能只根据“这是淘宝订单”来判断是否可用。
为什么 RSA2 签名成功仍会提示商户密钥为空
RSA2 私钥与错误信息中的“商户密钥”不一定指同一项配置。
RSA2 私钥用于证明请求由合法商户签发。服务端还需要根据请求中的商户号、应用 ID 或其他身份字段,找到对应的商户配置。如果身份字段缺失或填写错误,或者后台没有配置相应密钥,即使 RSA2 签名成功,服务端仍可能返回“商户密钥不能为空”。
常见原因有:
- 商户平台尚未配置 RSA2 公钥或商户密钥。
- 应用与商户号尚未完成绑定。
content中的商户号、应用 ID 等字段为空,或与后台配置不一致。- 使用了其他环境的密钥,例如测试环境订单使用了生产环境密钥。
- 使用公钥签名,或者平台上的公钥与本地私钥不匹配。
- 签名算法填写为 RSA,但接口实际要求 RSA2。
- 完成签名后又修改了订单字段、字段顺序、编码或空格。
content被重复 URL 编码或重复 JSON 序列化,导致验签原文发生变化。- 私钥只保存在客户端,服务端创建订单时并未从商户配置中加载到密钥。
IDEA 版本通常不会影响服务端读取商户密钥,因此 IDEA 1.13.1505 不是优先排查项。
建议的排查顺序
1. 检查商户后台配置
先确认支付平台后台已经完成以下配置:
- 商户号已启用;
- 应用具有二维码支付权限;
- 应用 ID 已与商户号绑定;
- RSA2 公钥已上传并生效;
- 接口要求的商户密钥、应用密钥或渠道密钥已填写;
- 当前请求环境与密钥所属环境一致。
如果后台有单独的“商户密钥”配置项,就必须按平台要求填写。RSA2 私钥不能自动替代这个配置项。
2. 检查 content 中的商户身份
打印签名前的原始订单参数,重点核对用于定位商户的字段。实际字段名应以当前接口文档为准,可能是 merchantId、sellerId、appId 或其他名称。不要自行猜测字段名并直接提交。
检查这些字段是否存在以下问题:
- 字段缺失或值为空字符串;
- 使用了测试商户号;
- 值的前后带有空格;
- 与上传公钥时使用的商户账号不一致;
- 签名后又被修改。
3. 核对 RSA2 密钥对
可以使用 OpenSSL 检查私钥能否正常解析,并导出对应的公钥:
openssl pkey -in merchant_private_key.pem -check
openssl pkey -in merchant_private_key.pem -pubout -out derived_public_key.pem
将导出的公钥与商户平台中配置的公钥进行比较。两者必须来自同一密钥对。
如果平台要求使用 PKCS#8 私钥,而现有私钥是其他格式,可以先转换:
openssl pkcs8 -topk8 -inform PEM \
-in merchant_private_key.pem \
-outform PEM \
-nocrypt \
-out merchant_private_key_pkcs8.pem
不要将私钥上传到前端、日志系统或代码仓库。
4. 核对待签名字符串
RSA2 一般对应 SHA256withRSA,但参数排序、空值处理、字符集以及签名字段是否参与计算,都必须以支付接口文档为准。
调试时应同时记录:
签名前的业务参数
最终待签名字符串
签名算法
字符集
Base64 签名结果
提交给 qrcodePay 的最终 content
商户号或应用 ID
请求环境
不要记录完整私钥或商户密钥。
如果接口要求对排序后的参数字符串签名,就必须对最终字符串进行签名。不能先签名 JSON,再将 JSON 转换成另一种格式后提交。
RSA2 签名示例
下面的代码只演示 Java 中标准的 RSA2 签名过程。待签名字符串仍需按照实际接口的参数排序和编码规则生成。
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class Rsa2Signer {
public static String sign(String unsignedContent, String privateKeyPem)
throws Exception {
String privateKeyBase64 = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
PrivateKey privateKey = KeyFactory
.getInstance("RSA")
.generatePrivate(keySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(unsignedContent.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
}
调用时,要保证提交的内容与签名原文完全一致:
String unsignedContent = buildContentAccordingToApiSpec();
String sign = Rsa2Signer.sign(unsignedContent, privateKeyPem);
// 按接口规定将 sign 放入指定字段。
// 此后不要再修改参与签名的参数。
不要在小程序端或设备端保存 RSA2 私钥。应由业务服务端创建订单并完成签名,再将接口要求的 content 返回给前端:
my.call('qrcodePay', {
content: contentFromYourServer
}, function (result) {
console.log(result);
});
这里的字段结构只用于说明调用方式。实际参数名称和回调格式应以当前版本的接口文档为准。
如何区分配置问题和签名问题
如果只是 RSA2 验签失败,服务端通常会返回签名错误、验签失败或非法签名等信息。当前返回的是“商户密钥不能为空”,因此更可能是服务端没有找到对应的商户配置,或者相关配置项为空。
建议按以下顺序检查:
商户号/应用 ID → 商户绑定关系 → 商户密钥配置 → RSA2 公钥 → content → 签名
确认商户后台配置无误后,再逐字比较“最终待签名字符串”和“最终提交的 content”。不少问题并非出在 RSA2 算法本身,而是签名完成后发生了字段重排、编码变化或二次转义。
如果以上检查均正常,但仍然返回相同错误,需要携带请求时间、应用 ID、脱敏后的商户号、订单号和完整错误码,联系接口提供方查询服务端日志。不要在工单中提交 RSA2 私钥或完整商户密钥。
备注:内容仅供参考。
