H5支付:微信和支付宝

6,633 阅读6分钟

微信和支付宝的支付应用场景

  1. APP(Android & IOS)
  2. 浏览器
    1. PC端
    2. Wap端
      1. 应用浏览器:Chrome、Firefox、Opera……
      2. 微信内置浏览器

在这里,我们姑且分为:PC端、移动端、微信端,方便分类说明

前端实现原理

  1. APP方面,需处理手机系统跳转方式兼容问题。

  2. 浏览器方面,在PC端和wap端非微信内置浏览器上进行支付宝和微信支付时,前端都只需根据后端接口返回来的路径(get方式URL跳转)或form标签字符串(post方式submit提交)进行跳转即可;

  3. 而在微信内置浏览器上,则需前端进行特殊处理,因为有PC端微信内置浏览器和移动端微信内置浏览器。

众所周知,不同平台都会对“潜在危险网址”链接进行屏蔽,不允许跳转,比如在微信点击支付宝支付链接,是无法跳转的,反之亦然,所以在前端页面我们需要去做一个中间提示页。

最近,针对“互联网围墙”现象,工信部提出的“拆墙行动”,很明显这场发生在互联网世界中的一场美丽“雪崩”,在座的各位都会是其中或大或小的片片雪花。

支付宝支付

支付宝开放平台文档

  • 浏览器平台

    1. PC端: alipay.trade.page.pay

    2. Wap端:alipay.trade.wap.pay - 实现效果:唤起支付宝 App - 参考文档:支付API > 手机网站支付接口2.0 - 示例URL https://openapi.alipay.com/gateway.do?method=alipay.trade.wap.pay&app_id=2021002104604……

    3. 微信端:

      微信内置浏览器中调用支付宝支付时,微信无法直接唤醒支付宝,需要一个引导页作为中间页,提示用户在浏览器中打开,再进行唤醒支付宝应用进行支付

      注意,PC端微信内置浏览器alipay.trade.page.pay和移动端微信内置浏览器alipay.trade.wap.pay,支付宝接口的Method是不同的,确保换取支付宝支付路径时提交的参数正确

      微信公众平台无法使用支付宝收付款的解决方案官方文档

      • 微信中使用支付宝支付方案的实现步骤:
        1. 新建引导页APGuide.vue
        2. 添加引导页路由/apGuide,并将其设置到不重定向白名单whiteList中,避免登录跳转影响
        3. 选择支付宝支付的确认页跳转引导页时,先将支付宝跳转路径进行encodeURIComponent双重编码或escape加密,拼接在URL后面作为参数,再用location.href方式跳转引导页
        4. 引导页判断是否在微信内置浏览器上,还是已通过微信菜单跳转到浏览器
          • 在微信上,则显示引导页内容
          • 在PC或Wap浏览器上,则处理URL参数,取出并将支付宝跳转路径decodeURIComponent双重解密或unescape解密,location.href方式跳转支付宝
        5. vue示例核心代码:
        // RechargeInfo.vue 支付确认页
        window.location.href = `${location.origin}/apGuide?url=${encodeURIComponent(encodeURIComponent((res.data.pay_url)))}`
        
        // APGuide.vue 引导页
        // template
        <template>
                <div class="ap-guide-dialog">
                        <div class="ap-guide-dialog-content">
                                <div class="J-weixin-tip weixin-tip" ref="weixinTip">
                                        <div class="weixin-tip-content">
                                                请在菜单中选择在浏览器中打开,<br/>
                                                以完成支付
                                        </div>
                                </div>
                                <div class="J-weixin-tip-img weixin-tip-img" ref="weixinTipImg"></div>
                        </div>
                </div>
        </template>
        
        // script
        mounted(){
                 if (location.hash.indexOf('error') != -1) {
                        alert('参数错误,请检查');
                 } else {
                        var ua = navigator.userAgent.toLowerCase();
                        var tip = this.$refs.weixinTip;
                        var tipImg = this.$refs.weixinTipImg;
                        if (ua.indexOf('micromessenger') != -1) {
                                tip.style.display = 'block';
                                tipImg.style.display = 'block';
                                if (ua.indexOf('iphone') != -1 || ua.indexOf('ipad') != -1 || ua.indexOf('ipod') != -1) {
                                        tipImg.className = 'J-weixin-tip-img weixin-tip-img iphone'
                                } else {
                                        tipImg.className = 'J-weixin-tip-img weixin-tip-img android'
                                }
                         } else {
                                let APurl = decodeURIComponent(decodeURIComponent((location.search.split('?')[1].substr(location.search.split('?')[1].indexOf('=')+1))));
                                window.location.href = APurl;
                         }
                }
        },
        

        decodeURIComponent(decodeURIComponent())两次编码原因: 两次编码传到后台之后,服务器收到参数第一次进行解码之后变为一次utf-8编码的字符串,所以再次调用解码一次就变为中文。
        所以,中文参数携带中文需要两次编码,一次编码不能得到正确结果

  • APP:alipay.trade.app.pay

    • 实现方式:APP内嵌H5页面,使用技术与浏览器端一直,主要考虑APP正常跳转返回问题
    • 实现效果:唤起支付宝 App - 参考文档:支付API > app支付接口2.0 - 示例URL https://openapi.alipay.com/gateway.do?method=alipay.trade.app.pay&app_id=202100210460……
  • 知识延伸:escapeencodeURIencodeURIComponent的区别:

    1. 作用:
      1. escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如"春节"的返回结果是%u6625%u8282escape()不对+编码。

        主要用于汉字编码,现在已不提倡使用。

      2. encodeURI()是JS中真正用来对URL编码的函数。 编码整个url地址,但对特殊含义的符号; / ? : @ & = + $ , #,也不进行编码。

        对应的解码函数是:decodeURI()

      3. encodeURIComponent() 能编码; / ? : @ & = + $ , #这些特殊字符。对应的解码函数是decodeURIComponent()

        如果要传递带&符号的网址,建议用encodeURIComponent()

    2. 应用场景:
      1. 如果只是编码字符串,与URL无关,那么用escape
      2. 如果你需要编码整个URL,然后需要使用这个URL,那么用encodeURI
      3. 当你需要编码URL中的参数的时候,那么encodeURIComponent是最好方法。

微信支付

微信支付开发者文档

  • 浏览器平台
    1. PC端:

      • 实现效果:由后端返回二维码或前端实现路径转二维码,从而显示二维码图片扫码支付 - 参考文档:Native下单API
      • 示例URL
      weixin://wxpay/bizpayurl?pr=OaEfZ……
      
    2. Wap端:

      • 实现效果:用window.location.href直接跳转后端返回路径,从而唤起微信 App进行支付
      • 参考文档:H5微信支付
      • 示例URL
      https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx081520……&package=31101……
      
    3. 微信端:

      created() {
        //  微信端支付
        if (isWx() && !this.params('is_success_auth')) {
          this.$confirm('请先允许微信授权?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
          })
            .then(() => {
              // 跳转微信授权
              this.wxLogin();
            })
            .catch(() => {});
          return;
        }
      },
      methods: {
        //  v1.6 微信授权 由后端去获取用户openId
        wxLogin: function () {
          // 授权后返回的地址
          let callbackUrl = location.href;
          // 授权地址和所带参数
          let url =
            getreqinfo.getreqinfo().apiurl +
            '/user/wechat/oauth' +
            '?callback=' +
            escape(callbackUrl) +
            '&token=' +
            window.tools.cache.get('token') +
            '&service=user';
          window.location.href = url;
        },
      
        // 确认充值
        confirmPay() {
          let _this = this;
          let ajaxData = {
            pay_type: Number(this.payType),
            money: this.rechragePrice,
            method: 'api.pay.userRecharge',
            api_trade_type: '', // api交易类型
          };
      
          //  api_trade_type	string	否	api交易类型  h5支付宝或微信移动端浏览器  jsapi微信浏览器, 默认web端
          if (isWx()) {
            // 微信端
            if (this.payType == 2) {
              // alipay
              // 移动端微信内置浏览器 & PC端微信内置浏览器
              ajaxData.api_trade_type = isWapWx() ? 'h5' : '';
            } else if (this.payType == 3) {
              // wx
              // 移动端微信内置浏览器 & PC端微信内置浏览器
              ajaxData.api_trade_type = isWapWx() ? 'jsapi' : '';
            } else {
              ajaxData.api_trade_type = '';
            }
          } else {
            // 手机端 & pc端
            ajaxData.api_trade_type = isAndroid() || isIOS() ? 'h5' : '';
          }
          this.$myAjax({
            ajaxData: params,
            type: 'POST',
            successFun: res => {
              this.payRelated.out_trade_no = res.data.out_trade_no;
              if (isWx() && this.params('is_success_auth')) {
                WeixinJSBridge.invoke(
                  'getBrandWCPayRequest',
                  {
                    appId: res.data.js_params.appId, //公众号名称,由商户传入
                    timeStamp: res.data.js_params.timeStamp, //时间戳,自1970年以来的秒数
                    nonceStr: res.data.js_params.nonceStr, //随机串
                    package: res.data.js_params.package,
                    signType: res.data.js_params.signType, //微信签名方式:
                    paySign: res.data.js_params.paySign, //微信签名
                  },
                  function (res) {
                    if (res.err_msg == 'get_brand_wcpay_request:ok') {
                      // H.$toast('支付成功');
                      // 轮询支付结果
                      // ……
                    } else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
                      window.tools.alert.message('取消支付');
                    } else {
                      window.tools.alert.error('支付失败');
                    }
                  }
                );
                return;
              }
      
              // 其他支付类型处理
              // ……
            },
          });
        },
      },
      

      注意支付过程中,轮询订单支付状态,避免出错

      1. 微信注意问题:
        1. 如果是调用原生支付,而非如乐刷聚合等第三方支付平台的话,记得在微信公众平台配置js接口安全域名,并下载相应MP_verify_***文件,放到项目根目录下。
        2. 由于微信升级,现在调用微信支付确认后,会自动关闭网站回到微信主界面,想要停留在当前网站,需要开通微信的“点金计划”才行。
        3. 除非是有跟微信合作从而开放接口的合作开发商外,普通开发者无法通过weixin://调起微信后直接跳转网站页面,微信合作伙伴才有weixin://dl/business/?ticket=***这样的功能。

js页面跳转方式

  1. form表单提交(post)
    • 如果后台接口返回来的是form标签字符串,则用该方式提交跳转
    • 示例demo
    // 后台接口返回来的是form标签字符串
    let _str = `
        <form id='alipaysubmit' name='alipaysubmit' action='https://openapi.alipay.com/gateway.do' method='POST'>
                <input type='hidden' name='app_id' value='201805106……'/>
                <input type='hidden' name='method' value='alipay.trade.page.pay'/>
                <input type='hidden' name='format' value='JSON'/>
                // ……
                <input type='submit' value='ok' style='display:none;''>
        </form>
        <script>document.forms['alipaysubmit'].submit();</script>
    `;
    
     _str = _str.replace(/form/, 'form target="_blank"');
    div.innerHTML = _str;
    document.body.appendChild(div);
    document.forms.alipaysubmit.submit();
    
  2. window.location.href
  3. window.open
  4. a标签触发click事件

    注意:用该方式在安卓和PC端跳转支付都可以,但iOS却无法执行,不建议使用该方式

  5. ……