代码 微信支付 JSAPI 前端调起排查:验证签名失败、URL 未注册、permission value is offline verifying

2026-10-17 09:01:29

微信支付 JSAPI 前端调起排查:验证签名失败、URL 未注册、permission value is offline verifying

这篇只讲一件事:prepay_id 已经拿到之后,前端把收银台调起来这一步出的问题。服务端下单、请求签名、回调验签不在这里展开。

适用范围:公众号 JSAPI(WeixinJSBridge)为主,小程序 wx.requestPayment 参数结构类似但报错文案不同。文中的命令行验证是本地做的字符串比对,真机全链路没有全部复现,涉及白名单配置的部分以官方文档为准。

官方文档

先分层:调起支付到底分几步

  1. 后端调 JSAPI 下单接口,拿到 prepay_id。该值有效期 2 小时,超期要重新下单。
  2. 前端用微信浏览器内置对象 WeixinJSBridge 的 getBrandWCPayRequest 调起收银台。

第二步没有后端接口交互,但对参数要重新做一次签名(paySign),不能复用下单返回的 sign。绝大多数「验证签名失败」都出在这里:拿下单的签名直接塞给前端。

APIv3 调起支付的 6 个参数

全部是 string:

  • appId:填下单时传入的 appid,必须与当前实际调起支付的公众号 appid 一致,否则调不起来。注意这个参数名里是大写 I。
  • timeStamp:Unix 时间戳,秒级(10 位)。不少语言默认给毫秒(13 位),必须转成秒。
  • nonceStr:随机字符串,不长于 32 位。
  • package:格式 prepay_id=***,取下单接口返回的 prepay_id。
  • signType:固定 RSA。这个参数不参与签名,但必须传。
  • paySign:用 appId、timeStamp、nonceStr、package 算出来的签名值。

签名串构造(4 行)

每一行一个参数,行尾以 \n(换行符,ASCII 0x0A)结束,最后一行也要。

appId\n
时间戳\n
随机字符串\n
prepay_id=xxx\n

待签名串示例:

wx8888888888888888
1414561699
5K8264ILTKCH16CQ2502SI8ZNMTM67VS
prepay_id=wx201410272009395522657a690389285100

用商户 API 证书私钥对待签名串做 SHA256 with RSA 签名,再 Base64 编码得到 paySign。命令行验证:

echo -n -e "wx8888888888888888\n1414561699\n5K8264ILTKCH16CQ2502SI8ZNMTM67VS\nprepay_id=xxx\n" | openssl dgst -sha256 -sign apiclient_key.pem | openssl base64 -A

签名用的是商户 API 证书私钥(apiclient_key.pem),不是 APIv3 密钥,也不是平台证书。这三样东西搞混,签名串再怎么拼都对不上。

前端调用:

WeixinJSBridge.invoke('getBrandWCPayRequest', {
  "appId": "wx2421b1c4370ec43b",
  "timeStamp": "1395712654",
  "nonceStr": "e61463f8efa94090b1f366cccfbbb444",
  "package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
  "signType": "RSA",
  "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ..."
}, function(res){ if(res.err_msg=="get_brand_wcpay_request:ok"){ /* 仍需后端查单 */ } });

get_brand_wcpay_request:ok 只在用户完成支付时返回,但官方明确说它「并不保证绝对可靠」,必须以服务端查单结果为准。get_brand_wcpay_request:cancel 和 :fail 可以统一当「用户取消或出错」处理,不用细分。

常见报错与归因

支付验证签名失败

官方给的排查方向:

  1. 签名类型与下单时不一致(下单 HMAC-SHA256,调起却用了 MD5)。
  2. 生成签名原串的参数和实际发出去的参数不一致,比如时间戳、随机串两处取的不是同一个值。
  3. 调起时大小写写错(appId 的大写 I 写成小写 i)。
  4. API 版本混用:下单走 V2,调起参考了 V3。
  5. 商户号不一致:下单用 A 商户号,调起签名用 B。
  6. key 用错:V2 用 APIv2 密钥,V3 用商户 API 证书私钥。
  7. 没有重新生成签名,直接复用了统一下单返回的 sign。

本地签名校验工具通过、真机仍报失败,常见原因就是「下单和调起不是同一套参数 / 商户号 / appid」。

chooseWXPay:fail, the permission value is offline verifying

  1. 在模拟器里发起,没在真机。
  2. 支付授权目录(V2)与真实发起支付的 URL 不匹配。例如页面是 http://www.xxx.com/order/pay/id-115,授权目录应配 http://www.xxx.com/order/pay/。
  3. 授权目录规则:区分大小写、需 http/https 开头、以 / 结尾、不能配 IP、最多 5 个。
  4. 网页授权域名 / JS 安全域名未配置或配错。

当前页面的URL未注册:http://xxx/pay.do(V2)

先看下单接口用的商户号有没有在商户平台配对应支付目录;服务商或子商户任一侧配了能匹配上的目录即可调起。

调用支付JSAPI缺少参数: appId / total_fee

  • 检查调起参数里 appId 有没有传。用 wx.chooseWXPay 时,appId 依赖 wx.config 注入是否成功。
  • 检查 prepay_id 是否已失效(2 小时)。
  • 检查请求的 appid 与下单接口的 appid 是否一致。

V2 和 V3 别混着参考

  • V2:签名 MD5 / HMAC-SHA256,需要支付授权目录、网页授权域名、JS 接口安全域名。
  • V3:signType 固定 RSA,签名用商户 API 证书私钥做 SHA256withRSA,调起参数的重点是那四行签名串和 appid 一致性。

V3 下支付目录、域名白名单的具体要求,不同接入方式(服务商 / 直连)不一样,以官方文档为准。

推荐文章

程序员茄子在线接单