支付调起失败别先查签名:微信 JSAPI / 小程序 / 支付宝手机网站支付的五层排查
调起支付失败先别查签名:签名只解释一类错误,下面按环境层→配置层→参数层→场景层→前端回调语义拆报错;微信 paySign 的签名串构造见站内另一篇《微信 paySign 两种构造与 8 条排查顺序》。
资料:
- 微信支付商户文档中心 v3:常见问题_JSAPI支付
- 微信支付商户文档中心 v3:常见问题_小程序支付
- 小程序
wx.requestPayment参数 - 支付宝:手机网站支付快速接入
高频报错与所属层对照:
| 报错/现象 | 层 | 先查什么 |
|---|---|---|
| 该商户暂不支持通过外部拉起微信完成支付 / 为保障支付安全,暂不支持从外部进入微信网页并完成支付 | 环境 | 是否在微信内置浏览器;外部浏览器改 H5(trade_type=MWEB) |
chooseWXPay:fail, the permission value is offline verifying | 环境 | 换真机;支付授权目录是否为实际发起页 URL |
system:access_denied | 环境 | 是否小程序 web-view;调起页 URL 是否过长 |
WeixinJSBridge.invoke is not a function | 环境 | 小游戏需用小游戏虚拟支付 |
| 当前页面的URL未注册:http://xxx/pay.do | 配置 | 下单商户号的支付授权目录 |
| 调用支付JSAPI缺少参数: appId | 配置 | wx.config 是否成功 |
| 调用支付JSAPI缺少参数:total_fee | 参数 | package 是否为 "prepay_id=xxx"、timeStamp 是否为字符串等 |
| 商户传入的APPID参数不正确 | 参数 | 下单/调起 appid 是否一致、prepay_id 是否过期 |
| 下单账号与支付账号不一致,请核实后再支付 | 参数 | openid 是否实时获取、是否跨用户复用 |
| 支付场景非法 | 场景 | 下单接口与调起方式是否成对 |
| 当前商户该支付方式存在异常,暂时无法支付 | 场景 | 是否拿别的下单接口的 prepay_id 去小程序调起 |
第一层:环境层(浏览器/容器不对,改代码没用)
微信 JSAPI 只能在微信内置浏览器里调起。外部浏览器会报:
- 「该商户暂不支持通过外部拉起微信完成支付」
- 「为保障支付安全,暂不支持从外部进入微信网页并完成支付」
外部浏览器要用 H5 支付(trade_type=MWEB,订单默认有效期 5 分钟),JSAPI 的 prepay_id 在外部浏览器无解。
判断是否在微信内:
const isWeChat = typeof navigator !== 'undefined' && navigator.userAgent.includes('MicroMessenger')
其它环境类报错:
chooseWXPay:fail, the permission value is offline verifying:多在模拟器发起,或支付授权目录填的成分不是实际发起页面的 URL。用真机测。system:access_denied:小程序内嵌 web-view 不支持发 JSAPI 支付;也可能是调起页 URL 过长导致 webview 鉴权异常。WeixinJSBridge.invoke is not a function:小游戏不支持 JSAPI 支付,要用小游戏虚拟支付。- 小程序 webview 内嵌网页不支持 JSAPI 支付,官方明确不支持。
第二层:配置层(商户平台/公众号后台的开关)
「当前页面的URL未注册:http://xxx/pay.do」= 下单用的商户号没配置匹配的支付目录。规则:
- 最多 5 个授权目录;需要覆盖多级目录时直接配父级
- 严格区分大小写,必须带 http/https 头、以
/结尾 - 不能配 IP(如
https://8.8.8.8/test) - 授权目录指「付款页面所在目录」,不是后端接口地址
- 设置后一般 5 分钟内生效
- 服务商模式下,服务商或子商户任一配置了匹配目录即可调起(这也解释了「子商户没配却能成功」)
公众号侧还有几条:
- 错误码 10003「redirect_url 域名与后台配置不一致」:获取 openid 的接口报的,检查下单 appid 与取 openid 的 appid 是否同一个、授权域名是否一致(公众平台→设置→公众号设置→功能设置→网页授权域名)
- 错误码 10005「此公众号并没有这些scope的权限」:订阅号/未认证公众号调认证服务号的能力,或 AppID 填错、认证过期;用
snsapi_userinfo授权可绕开一部分 wx.chooseWXPay的「调用支付JSAPI缺少参数: appId」:该方法 appId 依赖wx.config注入,先查 config 是否成功
「商家暂时没有此类交易权限」:商户号没有 jsapi 支付权限(商户平台→产品中心),或下单没指定支付用户身份。
第三层:参数层(形态错,肉眼像对的)
「调用支付JSAPI缺少参数:total_fee」是最容易误判的一条——total_fee 是 v2 的参数名,v3 报这个错通常意味着调起参数形态不对。逐项查:
package必须是字符串"prepay_id=xxx",不是裸prepay_id,也不能为空prepay_id是否失效(默认 2 小时)- 下单金额是否为空、订单号是否重复或为空(会导致
prepay_id为空) - 下单 appid 与调起 appid 是否一致
- 金额单位是分,不能有小数点
timeStamp必须是字符串。社区里有案例:后端已返回 string,前端又toString()一次,结果报 total_fee 缺失——排查时先打印实际传给requestPayment的对象- 「商户传入的APPID参数不正确」:统一下单的 appid 与调起时的 appid 不一致,或
prepay_id已过期;也可能是参数带空格、大小写问题
「下单账号与支付账号不一致,请核实后再支付」:下单传的 openid 与调起支付的人不是同一个,openid 必须实时获取,不要缓存跨用户复用。
第四层:场景层(下单接口与调起方式必须成对)
「支付场景非法」= 下单与调起场景不匹配。对应关系:
| 调起方式 | 下单接口/参数 |
|---|---|
| 公众号 JSAPI | JSAPI 下单,trade_type=JSAPI |
| 小程序 | JSAPI/小程序下单,且 prepay_id 只能用于小程序调起 |
| APP | trade_type=APP |
| 扫码 Native | trade_type=NATIVE |
| H5 | trade_type=MWEB |
用别的下单接口拿到的 prepay_id 去小程序调起,会报「当前商户该支付方式存在异常,暂时无法支付」。
第五层:前端回调语义——不等于订单状态
各端「用户取消」的返回值不一样,别写成统一判断:
- 小程序:
requestPayment:fail cancel - JSAPI:
get_brand_wcpay_request:cancel - App:
errCode=-2
密码输错不是终态,用户还能继续输,前端无法判断。前端 success/fail 都不可靠(网络中断、用户切后台),订单状态只能以后端查单 + 异步通知为准。
支付宝侧:唤端是另一个问题
alipay.trade.wap.pay(手机网站支付 2.0)默认就会唤起支付宝客户端。老接口 alipay.wap.create.direct.pay.by.user 需要显式传 app_pay=Y。
常见「装了支付宝却唤不起来」:
- 在商家 App 内嵌 webview 里走纯手机网站支付:主要是 alipays 协议不在该 webview 白名单,官方建议 App 内改用 App 支付,或用「手机网站支付转 Native 支付」;需自行让 webview 处理 alipays 协议
- 浏览器不支持或用户禁止唤起:换浏览器或改设置
- 沙箱环境要装沙箱版客户端(仅安卓),用生产客户端扫码会失败
电脑网站支付(pageExecute)没有同步报错:先用 GET 方式拿到 URL 手工打开验证,GET 通而 POST 表单失败就是表单构造问题;再检查 biz_content 是否合法 JSON(末尾多逗号是高频问题)、out_trade_no/total_amount/subject 是否齐全。
唤端失败率本身是产品层面的取舍:H5 唤起受网络和浏览器版本影响,成功率低于原生 SDK 唤端;如果业务强依赖 App 内支付,别把手机网站支付当作 App 支付的替代。