对接H5支付(微信和支付宝)

1,903 阅读4分钟

项目需求

在之前从来没有接触过接入支付相关的内容,最近公司有一个项目,需求是在一个H5页面中,接入支付宝支付,接入微信支付。要求这个页面不仅可以在浏览器可以进行支付相关的操作,而且要能够在App中也能够支付(是嵌入H5页面的App),也能在微信中支付。

需求分析

最开始没有考虑直接写代码,因为支付环境的多样,以及消费者发起支付的多种操作可能性,所以,对项目需求做了简单的流程分析。

支付流程图

代码实现

从下单页面到支付页面

当用户选择好了订单商品,就会前往对象的支付页面,在支付之前,需要有前置准备。 首先,根据微信内置浏览器和普通浏览器的不同,需要在跳转页面之前获取浏览器类型,以便对后续的业务进行处理。

  • 获取浏览器标识
const ua = window.navigator.userAgent.toLowerCase();

根据流程图,如果用户访问页面使用的浏览器是微信内置浏览器,那么就会获取用户的相关信息,为后续微信支付做准备

  • 微信内置浏览器获取用户openId
// created阶段
const ua = window.navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i) == 'micromessenger'){
  this.getCode(); 
}
  • 用户同意授权,获取openId
getCode() {
  const openId = this.GetUrlParam('code') // 截取路径中的code,如果没有就去微信授权,如果已经获取到了就直接传code给后台获取openId
  const local = window.location.href
  const appid = 'wxxxxxxx' // 服务号id
  if (openId == null || openId === '') {
    window.location.href =
      'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + appid + '&redirect_uri=' + encodeURIComponent(local) + '&response_type=code&scope=snsapi_base&state=#wechat_redirect'
  } else {
    this.openId = openId
  }
}
  • 没有openId的情况下,获取用户的授权openId
GetUrlParam(code) {
  var reg = new RegExp('(^|&)' + code + '=([^&]*)(&|$)')
  let url = window.location.href.split("#")[0]
  let search = url.split('?')[1]
  if (search) {
    var r = search.substr(0).match(reg)
    if (r !== null) return unescape(r[2])
    return null
  } else {
    return null
  }
}

进入支付页面,选择支付方式,提交支付

/**
 * buttonDisabled是button绑定disabled指令的状态,避免重复提交
 * radioOptions是支付单独选项的value,1为支付宝支付,2为微信支付
 * openId是微信内置浏览器所获取的用户openId
 */
payMoney() {
  this.buttonDisabled = true
  if(this.radioOptions === '') {
    this.buttonDisabled = false
    return this.$toast('请选择支付方式')
  } else if(this.radioOptions === '1' && this.openId !== '' && this.openId !== null) {
    this.buttonDisabled = false
    return this.$toast('请选择微信')
  } else if(this.radioOptions === '1' && this.openId === '') {
    return this.getOrderNumber(this.radioOptions)
  } else if(this.radioOptions === '2') {
    return this.getOrderNumber(this.radioOptions)
  }
}

提交支付进入获取订单号流程

当用户选择了相应的支付方式,并且点击了提交支付按钮,可以主观认为用户是有了支付意愿的,所以可以在此处根据选择的商品信息获取对应的订单号,之所以不选择在用户进入支付页面就获取,因为有一定的支付不确定性。

  • 获取订单号
getOrderNumber(val) {
  /**
   *  此处应对商品数据前置校验处理,确保数据的合法性
   */
  // 创建订单的接口
  createOrderNumber({
    // 创建订单的传参
  }).then(res => {
    const orderInfo= sessionStorage.getItem('orderInfo') // 获取session中存储的订单信息
    if(res.code === 0) { // 此处表示有新的订单号产生
      sessionStorage.removeItem('orderInfo') // 清除session中旧的订单信息,无论是否有
      sessionStorage.setItem('orderInfo', res.orderNum) // 存入新的订单号到session中
    } else if (res.code === 1 && orderInfo !== null) { // 没有新的订单号产生,并且订单信息不为空
      sessionStorage.getItem('orderInfo') // 从session中获取订单号
    } else if (res.code === 1 && orderInfo === null){ // 没有新的订单号产生,并且订单信息为空
      this.buttonDisabled = false
      return this.$toast(res.msg) // 提示返回信息,如商品售空,座位被占等信息
    }
    // 因为在之前的代码中,可能有对session中订单号清除等操作,所以需要获取最新的订单号
    const orderNumber = sessionStorage.getItem('orderInfo')
    if (orderNumber && val === '1') { // 有订单号信息,并且传参为支付宝支付的参数
      return this.getApplyAliPay(orderNumber) // 支付宝支付的方法
    } else if(orderNumber && val === '2' && this.openId !== '' ) { // 有订单号信息,并且传参为微信支付的参数,但是用户openId不为空
      // 微信内置浏览器支付
      const status = 1
      return this.getWxPay(orderNum, status) // 此处传参一个是订单号,一个是微信支付的支付参数数据,会传参给服务端,以返回不同的值
    } else if(orderNumber && val === '2' && this.openId === '') { // 有订单号信息,并且传参为微信支付的参数,但是用户openId为空
      // 微信h5支付
      const status = 2
      return this.getWxPay(orderNumber, status)
    } else { 
      this.buttonDisabled = false
      return this.$toast('此处自定义提示信息')
    }
  }).catch(err => {
    console.log(err)
  })
}

支付宝支付调用的方法

getApplyAliPay(orderNumber) {
  getApplyAliPay({
    // 传递的其它参数
    orderNumber: orderNumber,
    paysuccessUrl: 'xxx' // 支付成功之后重定向的地址
  }).then(res => {
    const div = document.createElement('div')
    div.id = 'alipay'
    div.innerHTML = res
    document.body.appendChild(div)
    document.querySelector('#alipay').children[0].submit() // 执行后会唤起支付宝
  }).catch(err => {
    console.log(err)
  })
}
  • 具体div.innerHTML的值由后台返回的值确定,格式为下
<form name="punchout_form" method="post" action="xxx">
<input type="hidden" name="biz_content" value="xxx">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>

微信支付调用的方法

getApplyWxPay(orderNumber, status) {
  getApplyWxPay({
    // 根据后端要求传递的参数,自定义
    code: this.openId,
    orderNumber: orderNumber,
    status: status
  }).then(res => {
    if(res.code === 0 && res.appId) { // 微信内调用支付的处理流程,可看微信官方文档
      /**
       * res.code 为成功获取数据的状态值
       * res.appId 为获取的公众号唯一标识,位置是举例,由返回值确定
       * 微信内支付的值由后端返回值具体确定
       */
      wx.config({
        appId: 'xxx', // 必填,公众号的唯一标识
        timestamp: 'xxx', // 必填,生成签名的时间戳
        nonceStr: 'xxx', // 必填,生成签名的随机串
        signature: 'xxx', // 必填,签名,见附录1
        jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,这里只写支付的
      })
      wx.chooseWXPay({
        timestamp: 'xxx', // 支付签名时间戳
        nonceStr: 'xxx', // 支付签名随机串,不长于32 位
        package: 'xxx', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
        signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
        paySign: 'xxx', // 支付签名
        success: function(res) {
          // 成功支付回调
        },
        cancel: function(res) {
          // 取消支付回调
        },
        fail: function(res) {
          // 支付失败回调
        }
      })
    } else if(res.code === 0 && res.url) { // 后端返回的H5调起微信支付的链接,具体设定根据后端确定
      const url = res.url + '&redirect_url=' + '支付成功后的重定向地址'
      window.location.href = url
    }
  }).catch(err => {
    console.log(err);
  })
}

完成回顾

这个项目业务逻辑倒不复杂,支付对接了两天,主要时间都花在微信对接上。尤其是微信支付的对接,需要本地做内网映射,而且还要在微信开发者工具,结合个人微信开发。不然就会出现什么请在微信浏览器打开,商家未获得参数等乱七八糟的问题,但是内网映射也是大坑,打开页面都需要数十秒,甚至一分多钟,苦不堪言。

在对接支付的过程中查询了不少资料,有的甚至直接把文档搬成一篇教程,还有的就是互相直接抄来抄去。

当然也遇到了很多实用的教程,少走了很多弯路,总结一下支付接入的流程,为下次避免踩坑打下基础。