4. JSAPI 支付:用户授权、调起支付接口、前端与后端协作流程

JSAPI 支付,说白了就是微信内置浏览器里的支付方式。你打开公众号文章,点个「购买」按钮,弹出来的那个支付界面——就是它。

我个人习惯把 JSAPI 支付叫做「公众号支付」。它跟 H5 支付最大的区别在于:JSAPI 必须在微信浏览器里才能用。你想想看,用户都打开微信了,体验自然是最好的。

4.1 用户授权:拿到 openid 是关键

做 JSAPI 支付,第一步不是调支付接口,而是拿 openid。为什么?因为微信支付接口要求你传一个 openid 参数,用来标识用户。

我记得刚入行那会儿,有个同事直接在前端写死了 openid 来测试。结果上线后,所有用户都扣了同一个人的钱……嗯,这个坑我至今记忆犹新。

获取 openid 的标准流程是这样的:

  1. 用户访问你的页面
  2. 页面跳转到微信授权页面
  3. 用户确认授权
  4. 微信回调你的服务器,带上 code
  5. 服务器用 code 换 openid

这里有个细节:授权分为静默授权和手动授权。静默授权(scope=snsapi_base)不会弹提示,但只能拿到 openid。手动授权(scope=snsapi_userinfo)会弹个确认框,能拿到用户昵称、头像这些信息。

我的建议:如果只是为了支付,用静默授权就够了。别没事弹个授权框,用户会烦的。

4.2 调起支付接口:后端组装,前端调起

拿到 openid 之后,就该调支付接口了。这里有个重要的原则:敏感操作放后端

为什么?因为调起支付需要用到商户密钥。密钥如果暴露在前端,那等于把钱包密码贴在大街上。

标准流程是这样的:

  1. 前端把商品信息传给后端
  2. 后端调用微信统一下单接口,拿到 prepay_id
  3. 后端用 prepay_id 生成签名,返回给前端
  4. 前端用这些参数调起微信支付

来看一段后端返回给前端的典型数据:

{
  "appId": "wx8888888888888888",
  "timeStamp": "1712345678",
  "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
  "package": "prepay_id=wx2017033010242291fcfe0db70013231072",
  "signType": "RSA",
  "paySign": "oR9d8PuhnIc...(签名值)"
}

前端拿到这些参数后,调用微信的 JSAPI:

function onBridgeReady() {
  WeixinJSBridge.invoke(
    'getBrandWCPayRequest',
    {
      "appId": "wx8888888888888888",
      "timeStamp": "1712345678",
      "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
      "package": "prepay_id=wx2017033010242291fcfe0db70013231072",
      "signType": "RSA",
      "paySign": "oR9d8PuhnIc..."
    },
    function(res) {
      if (res.err_msg == "get_brand_wcpay_request:ok") {
        // 支付成功
      }
    }
  );
}

if (typeof WeixinJSBridge == "undefined") {
  if (document.addEventListener) {
    document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
  }
} else {
  onBridgeReady();
}
注意:WeixinJSBridge 是微信内置的 JS 桥接对象。它不一定在页面加载时就存在,所以需要监听 WeixinJSBridgeReady 事件。我曾经见过有人直接调用,结果在部分手机上死活调不起支付——就是因为没等桥接准备好。

4.3 前端与后端协作流程:谁该做什么

很多团队在分工上容易乱。我见过前端自己调统一下单的,也见过后端直接返回 HTML 页面的。说白了,职责要清晰。

我习惯的分工是这样的:

环节 前端负责 后端负责
用户授权 跳转授权页、接收回调 用 code 换 openid
下单 传递商品信息 调统一下单接口、生成签名
调起支付 调用 WeixinJSBridge 返回签名参数
支付结果 显示支付结果 接收异步通知、更新订单

这里有个容易踩的坑:支付结果回调。前端 WeixinJSBridge 返回的 ok 并不完全可靠。为什么?因为用户可能支付成功后立刻关闭页面,或者网络延迟导致回调没触发。

正确的做法:前端只做 UI 展示。真正的支付结果,以后端收到的异步通知为准。我一般会在前端显示「支付成功」后,再轮询后端接口确认订单状态。

4.4 避坑指南:我踩过的那些坑

做 JSAPI 支付这些年,我踩过的坑能写满一页纸。挑几个典型的说说:

  • 签名不一致:我曾经排查了一下午,最后发现是参数排序问题。微信要求按字典序排序,我用了自然排序。嗯,差一个字母都不行。
  • openid 不对应:同一个用户在公众号和开放平台下的 openid 不一样。如果你同时做公众号和小程序,千万别混用。
  • 金额单位搞错:微信支付金额单位是「分」。有人传了「1.00」表示 1 元,结果微信当成 1 分处理。这个坑,我见过不止一次。
  • 异步通知重复:微信会多次发送异步通知,直到你返回 success。所以你的回调接口必须幂等——处理一次和一百次,结果一样。
一个小技巧:调试阶段,可以用微信开发者工具的「公众号网页」模式。它能模拟微信浏览器环境,还能看到 WeixinJSBridge 的调用日志。我每次开发支付功能,都会开着它。

4.5 总结一下

JSAPI 支付的核心就三件事:拿 openid、调统一下单、调起支付。前后端协作的关键是:敏感操作放后端,UI 交互放前端,支付结果以异步通知为准

你想想看,整个流程其实不复杂。但每个环节都有细节,稍不注意就会踩坑。我建议你第一次做的时候,先把流程图画出来,再一行一行写代码。别急着上线,先在测试环境跑通所有场景——包括支付成功、支付失败、用户取消支付。

嗯,今天就聊到这儿。下一章我们讲 H5 支付,那个坑更多,到时候再细说。