项目需求
在之前从来没有接触过接入支付相关的内容,最近公司有一个项目,需求是在一个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);
})
}
完成回顾
这个项目业务逻辑倒不复杂,支付对接了两天,主要时间都花在微信对接上。尤其是微信支付的对接,需要本地做内网映射,而且还要在微信开发者工具,结合个人微信开发。不然就会出现什么请在微信浏览器打开,商家未获得参数等乱七八糟的问题,但是内网映射也是大坑,打开页面都需要数十秒,甚至一分多钟,苦不堪言。
在对接支付的过程中查询了不少资料,有的甚至直接把文档搬成一篇教程,还有的就是互相直接抄来抄去。
当然也遇到了很多实用的教程,少走了很多弯路,总结一下支付接入的流程,为下次避免踩坑打下基础。