Skip to content

微信、支付宝及第三方 H5 支付,各平台分享总结

微信支付

微信内 H5 支付(公众号支付)

走统一引入 JSSDK 的新方式实现支付逻辑。

  • JSSDK 文档 地址
  • 网页授权(获取 access_tokenopenid) 文档 地址
  • 统一下单 文档 地址
  • 微信内 H5 调起支付 文档 地址

明确两种 access_token

  • 网页授权 access_token:基于 Oauth 2.0,需要 code 换取,用于维持登录状态。
  • 普通 access_token:后台可直接生成,换取 jsapi_ticket 进而加密生成 signature 作为 wx.config 参数。

整个流程

支付统一下单接口需要 openid,所以必须要授权获取。因为此处只需要 openid,所以 snsapi_basescope 走静默授权。(为了方便起见,此处每次进入都获取 code 走静默授权。)

  1. 进入页面判断链接上是否有 code,没有则请求接口得到 url 并重定向:

    js
    {
      url: 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1232123123&redirect_uri=带参数(包括锚点)的编码后的重定向的回调地址&response_type=code&scope=snsapi_base&connect_redirect=1&state=xxxx#wechat_redirect';
    }
    // connect_redirect=1 表示只只触发一次请求
  2. 微信内部多次重定向之后,最后带上 codestate 301 重定向回设置的回调地址:

    html
    301: https://www.flqin.com/test.html?id=135price=1322&code=ASDJIAJD13D823D&state=13212313#/
  3. 前端判断到链接有 code,将 code 及一些其他页面需要的参数一起传给后端,后端拿到 code 换取网页授权 access_tokenopenid,此处后端可将 access_token/refresh_token 存入 cookie 或者通过其他方式 jwt 维持登录状态,就无需重复获取 code

  4. openid 为统一下单 jsapi 接口必传参数,用于得到 prepay_id 参数值,即为 package 参数。最后接口统一返回 wx.confgwx.chooseWXPay 所需参数,前端调用 JSSDK 即可。

  5. wx.chooseWXPay 的成功、完成回调仅代表支付完成,

代码演示:

js
  async queryWxCode() {
    //...
    if (!code) {
      const { data } = await api.wxToPay({url:'当前页面完整链接'}).catch((e) => e); //获取重定向链接(微信授权地址)
      data && (window.location.href = data);
      return;
    }
    return api.wxPay({code,url:'当前页面包含参数的url'});  //获得wx.config、wx.choosepay全部参数
  },
  async registerWx() {
    const { data } = await this.queryWxCode().catch((e) => {});
    this.wxSdkInfo = data;
    wx.config({
      debug: false, // 开启调试模式
      appId: data.appId, // 必填,公众号的唯一标识
      timestamp: data.timeStamp, // 必填,生成签名的时间戳
      nonceStr: data.nonceStr, // 必填,生成签名的随机串
      signature: data.signature, // 必填,签名 通过普通access_token生成,无需授权
      jsApiList: ['chooseWXPay', 'hideMenuItems'] // 必填,需要使用的JS接口列表
    });
    wx.ready(() => {
      this.hideWxMenus();
      this.createWxpay();
    });
  },
  hideWxMenus() {
    // 禁用支付分享, 防止订单未生成而进入该页面导致的报错
    const menuList = ['menuItem:share:appMessage', 'menuItem:share:timeline', 'menuItem:copyUrl'];
    wx.hideMenuItems({
      menuList // 要隐藏的菜单项,只能隐藏“传播类”和“保护类”按钮,所有menu项见附录3
    });
  },
  createWxpay() {
    const data = this.wxSdkInfo;
    wx.chooseWXPay({
      timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
      nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
      package: `prepay_id=${data.prepayId}`, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) 该参数需要openid
      signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
      paySign: data.paySign, // 支付签名 参与签名的参数为:appId、timeStamp、nonceStr、package、signType
      success: this.queryPayment, //公共查询结果接口
      fail: this.showFail, //失败处理
      cancel: this.showRefail, //取消处理
      complete({ errMsg }) {
        const SUCCESS = /:ok/gi.test(errMsg);
        const CANCEL = /:cancel/gi.test(errMsg);
        if (SUCCESS) this.queryPayment();
        else if (CANCEL) this.showRefail();
        else this.showFail();
      }
    });
  },

支付宝支付

支付宝内 H5 支付

必须接入支付宝 JSAPI

注意点:

  • 不涉及读取用户优惠券之类的都走普通无单号支付,即无需授权。
  • 授权过程与微信大致一致。

代码演示:

js
    async registerAlipay() {
      const alipayInit = async () => {
        await this.queryAliOrder().catch((e) => e);
        this.createAlipay();
      };
      if (window.AlipayJSBridge) alipayInit();
      else document.addEventListener('AlipayJSBridgeReady', alipayInit, false);
    },
    async queryAliOrder() {
      // 无单号支付
      const {data: orderStr } = await api.aliPayH5({orderId:1}).catch((e) => e);//请求接口获取 orderStr
    },
    async queryAliCode() {
      // 有单号支付tradeNo(后台开通当面付),同微信授权流程
      const { href, search } = window.location;
      const query = search && search.replace(/[?\/]/g, '');
      const { auth_code: code } = qs.parse(query);
      if (!code) {
        const { data } = await api.aliToPay(href).catch((e) => e); //请求接口获取支付宝授权地址
        data && (window.location.href = data);
        return;
      }
      const {data: tradeNO }  = await api.aliPayH5({orderId:1,code}).catch((e) => e); //请求接口获取 tradeNO
    },
    async createAlipay() {
      AlipayJSBridge.call('tradePay', { 'orderStr/tradeNO' }, ({ resultCode }) => {
        const SUCCESS_CODES = ['9000', '8000', '6004'];
        const UNKNOW_CODES = ['7001', '6001', '6002'];
        if (~SUCCESS_CODES.indexOf(resultCode)) _this.queryPayment(); //支付查询
        else if (~UNKNOW_CODES.indexOf(resultCode)) _this.showRefail();
        else _this.showFail();
      });
    },

第三方浏览器中 支付宝支付

无需授权,直接将 url 和订单号传给后端,后端返回一个 form 表单添加到页面即可唤起支付宝 APP,支付完成后,根据后端配置支付宝会自动回跳到支付结果页并携带一堆参数。此时需要查询公共结果接口来确定支付是否成功。

注意点:

  • 通过链接上是否有参数 alipay.trade.wap.pay.return 来判断是否是支付回调回来的页面。

代码演示:

js
  registerH5() {
    const { origin, pathname, search } = window.location;
    const { method } = qs.parse(search.replace(/[?\/]/g, ''));
    if (method === 'alipay.trade.wap.pay.return') {
      this.queryPayment(); //执行公共查询结果接口
      return;
    }
    const { data: form } = await api.aliPayH5(orderId,url).catch((e) => e); //请求接口返回form表单
    if (!form) return;
    const div = document.createElement('div');
    div.innerHTML = form;
    document.body.appendChild(div);
    document.forms[0].submit();
  }

公共查询结果 queryPayment

需要轮询支付结果。

代码演示:

js
async queryPayment() {
  const TIMES_REACH = 0;
  const TIMES_MAX = 5;
  const SUCCESS_CODE = '1'; // 0待支付,需要继续查询 1支付成功  -1异常
  const WAITING_CODE = '0';
  if (this.qTimes === TIMES_REACH) { //qTimes为最大查询次数
    this.qTimes = TIMES_MAX;
    return;
  }
  const { data } = await api.payStatus(orderId).catch((e) => e); // 查询支付结果
  if (data == SUCCESS_CODE) { //成功状态
    this.showSuccess();
    this.qTimes = TIMES_MAX;
  } else if (data == WAITING_CODE) { //等待状态
    console.log('查询支付结果中...');
    clearTimeout(this.timer);
    this.timer = setTimeout(async () => {
      await this.queryPayment(); //递归轮询
      this.qTimes -= 1;
    }, 1000);
  } else {
    this.showFail();
  }
},

支付总结

  • 微信支付宝均需要配置合法域名,可精确到文件夹路径。
  • 开发可用微信开发工具,结合 alert 调试,直接把代码发到已配置好域名的测试环境测试最佳。
  • 注意参数的大小写及传递的内容,支付主要复杂度都集中在后端,前端需要配合后端传参。

分享

微信- > 微信(微信联系人,微信朋友圈,QQ 联系人,QQ 空间)

必须引入 JSSDK,否则分享出来无法设置分享描述及分享图片。

  • JSSDK 文档:地址
  • 获取普通 access_token 文档:地址

需要给后端传入带有参数的当前页面 url(无需编码),让后端返回 wx.config 所需参数即可:

js
const { data } = await api.getWxConfig({ url: '当前页面包含参数的url' }); //接口返回 appId,timeStamp,nonceStr,signature.
wx.config({
  debug: true,
  appId: data.appId, // 必填,公众号的唯一标识
  timestamp: data.timeStamp, // 必填,生成签名的时间戳
  nonceStr: data.nonceStr, // 必填,生成签名的随机串
  signature: data.signature, // 必填,签名
  jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone'], // 必填,需要使用的JS接口列表
});
wx.ready(() => {
  const shareList = ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone'];
  shareList.forEach((item) => {
    wx[item]({
      title: this.shareInfo.title, // 分享标题
      desc: this.shareInfo.description, // 分享描述
      link: window.location.href,
      imgUrl: this.shareInfo.image, // 分享图标
      success: function () {
        // 设置成功
      },
    });
  });
});

注意点:

  • 首先需要在微信后台设置接口安全域名:www.xxxx.com,无需到具体路径及参数。
  • 前端仅需传给后台当前页面带参数的 url 即可,甚至接口可以自己取请求页面带参数的 url,前端无需传。
  • 注意 timeStamp 的大小写。
  • 注意 jsApiList 选择即将废弃的分享接口,新分享接口反而不好用。
  • 分享无需授权,因:参数 nonceStr <- jsapi_ticket <- access_token <- appid,secret。此 access_token 为普通 access_tokenappidsecret 均在微信后台获取。区别于支付等网页授权 access_token

QQ(TIM) -> QQ(QQ 联系人,QQ 空间,微信联系人,微信朋友圈)

需要引入 api

html
<script src="//open.mobile.qq.com/sdk/qqapi.js"></script>

js 执行:

js
const share = {
  title: '分享标题,最大45字节',
  desc: '分享内容,最大60字节',
  image_url: '图片URL,最小需要200 * 200',
  share_url: '分享链接与页面URL同',
};
mqq.data.setShareInfo(share, callback);

mqq.ui.showShareMenu(); 可直接唤起 QQ 分享面板。

支付宝 -> 支付宝(朋友动态,联系人)

直接按如下设置 meta 即可:

html
<meta name="Alipay:title" content="分享标题" />
<meta name="Alipay:imgUrl" content="分享图片url" />
<meta name="Alipay:desc" content="分享描述" />
<meta name="Alipay:link" content="分享链接" />

微博,头条,知乎等其他平台浏览器 -> 微信,QQ

微博可注册轻应用使用 JS-SDK 完成分享设置。收益不大就没做了。 知乎实测 IOS 可以取写死的描述及动态标题,图片无法设置,安卓只能设置标题。

对于其他平台分享,统一兜底处理方式:

分享标题及描述设置 head 元素:

html
<head>
  <title>分享标题</title>
  <meta name="description" content="分享描述" />
</head>

分享图及描述设置(一般默认取页面第一张大于 300px 图及第一段描述):

html
<body>
  <div style="display: none;">
    <p>分享描述</p>
    <img src="图片地址url" />
  </div>
</body>

使用 ogp

js
<head>
    <meta property="og:type" content="website">
    <meta property="og:title" content="分享标题">
    <meta property="og:description" content="分享描述">
    <meta property="og:img" content="完整的分享图片链接">
    <meta property="og:url" content="完整的分享页面地址">
</head>