一、功能梳理
app内前h5涉及到支付的功能,ios非实物商品实付需要使用ios原生支付方式,实物商品则可以三方支付,主要的实现思路为后端返回跳转支付宝或微信的支付scheme链接,前端进行跳转支付,同时需要实时查询用户的支付状态。
整个过程中复杂的部分在于查询用户支付状态的体验方面,需要保证用户在支付成功、支付失败、跳转支付宝、微信回来或者没有跳转支付宝微信等未知的行为下的用户体验。
组件内容为底部升起的支付选择弹窗,可以选择支付宝或者微信。
二、实现步骤
用户进行下单操作,前端调用下单接口,成功则返回三方支付app跳转链接,前端进行跳转
跳转三方app同时进行查询订单接口轮训,实时获取订单状态,轮训时间定位30s,依据需求调整
用户如果支付时间超过了30s未返回我们app的情况下,需要针对此种情况进行处理,捕捉用户返回我们app的情况
三、组件可支配参数&事件设置
设置支持参数2个、事件4个,分别为
参数:
successStatus: { default: '1' }, // 支付成功的状态码,默认1 errorStatus: { default: '2' } // 支付失败的状态码,默认2
复制
事件:
@payMoney="payMoney" // 下单事件 @payStatus="payStatus" //查询支付状态事件 @succcessFunction="succcessFunction" // 成功支付操作 @errorFunction="errorFunction" // 失败实付操作
复制
四、从支付app返回,事件捕获方法
为了防止用户在支付时间超过我们设置轮训时长情况,需要监听用户从支付app返回当前页面,来进行查询支付状态操作。方法为监听页面显示or隐藏事件
reloadState () { // 添加监听器 if (typeof document.hidden !== 'undefined') { this.hidden = 'hidden'; this.visibilityChange = 'visibilitychange'; } else if (typeof document.mozHidden !== 'undefined') { this.hidden = 'mozHidden'; this.visibilityChange = 'mozvisibilitychange'; } else if (typeof document.msHidden !== 'undefined') { this.hidden = 'msHidden'; this.visibilityChange = 'msvisibilitychange'; } else if (typeof document.webkitHidden !== 'undefined') { this.hidden = 'webkitHidden'; this.visibilityChange = 'webkitvisibilitychange'; } document.addEventListener(this.visibilityChange, this.forceUpdate); }, forceUpdate () { if (document.visibilityChange === this.hidden) { } else { this.searchTimer(); } },
复制
五、组件源码
<template> <div class="pay-info"> <div class="pay-content"> <span class="pay-left"><img class="pay-icon" src="../assets/zfb.png" alt=""> 支付宝支付</span> <img class="radio-icon" src="../assets/radio2.svg" alt=""> </div> <div class="pay-content"> <span class="pay-left"><img class="pay-icon" src="../assets/wx.png" alt=""> 微信支付</span> <img class="radio-icon" src="../assets/radio2.svg" alt=""> </div> <div class="pay-button" @click="payFun"><span>支付</span></div> <div class="loading-dom" v-if="loadingPay"> 支付中 <van-loading color="#ffffff" style="margin-left: 5px"/> </div> </div> </template> <script type="text/javascript"> export default { data () { return { isApp: !!browers.appUA, timer: null, loadingPay: false, orderNo: '', // 订单号 payStatus: '' // 订单支付状态 }; }, props: { successStatus: { default: '1' }, // 支付成功的状态码,默认1 errorStatus: { default: '2' } // 支付失败的状态码,默认2 }, mounted () { this.$once('hook:beforeDestroy', () => { clearInterval(this.timer); this.timer = null; }); }, methods: { reloadState () { // 添加监听器 if (typeof document.hidden !== 'undefined') { this.hidden = 'hidden'; this.visibilityChange = 'visibilitychange'; } else if (typeof document.mozHidden !== 'undefined') { this.hidden = 'mozHidden'; this.visibilityChange = 'mozvisibilitychange'; } else if (typeof document.msHidden !== 'undefined') { this.hidden = 'msHidden'; this.visibilityChange = 'msvisibilitychange'; } else if (typeof document.webkitHidden !== 'undefined') { this.hidden = 'webkitHidden'; this.visibilityChange = 'webkitvisibilitychange'; } document.addEventListener(this.visibilityChange, this.forceUpdate); }, forceUpdate () { if (document.visibilityChange === this.hidden) { } else { this.searchTimer(); } }, payFun () { this.$emit('payMoney', (res) => { this.reloadState(); // 下单接口的回调,执行轮训结果 this.orderNo = res.order_no; this.loadingPay = true; this.searchPay();// 监听从其他app返回,为了解决从支付宝回来不轮训的问题 setTimeout(() => { location.href = res.pay_address; }, 200); }); }, searchPay () { this.$emit('payStatus', (res) => { if (this.payStatus === '') { this.searchTimer(); } // 下单接口的回调,执行轮训结果 this.payStatus = res; }); }, searchTimer() { if (this.timer) { return; } if (this.orderNo) { let times = 0; this.searchPay(); if (this.payStatus === '') { this.timer = setInterval(res => { times++; this.searchPay(); if (this.payStatus === this.$props.successStatus) { clearInterval(this.timer); this.timer = null; this.loadingPay = false; document.removeEventListener(this.visibilityChange, this.forceUpdate); // 支付成功事件 this.$emit('succcessFunction', (res) => { }); } if (this.payStatus === this.$props.errorStatus) { clearInterval(this.timer); this.timer = null; this.loadingPay = false; document.removeEventListener(this.visibilityChange, this.forceUpdate); // 支付失败事件 this.$emit('errorFunction', (res) => { }); } if (times > 30) { this.$toast('未查询到支付状态,请重新支付'); clearInterval(this.timer); this.timer = null; this.loadingPay = false; document.removeEventListener(this.visibilityChange, this.forceUpdate); } }, 1000); } } } } }; </script> <style lang="less" scoped> .pay-info { .pay-content { background: #fff; display: flex; align-items: center; justify-content: space-between; padding: 8px 0; font-size: 28px; color: #222; .pay-left { display: flex; align-items: center; } .pay-icon { width: 60px; height: 60px; margin-right: 10px } .radio-icon { width: 34px; height: 34px; } } .pay-button { width: 100%; text-align: center; margin-top: 30px; span { display: inline-block; background: #4e88f6; color: #fff; width: 100%; font-size: 32px; height: 88px; line-height: 88px; border-radius: 50px; display: inline-block; } } .loading-dom { width: 100%; height: 100vh; background: rgba(0,0,0,.5); color: #fff; text-align: center; font-size: 34px; font-weight: 500; position: fixed; display: flex; align-items: center; justify-content: center; top: 0; left: 0 } } </style>
复制
六、父组件调用
<Pay @payMoney="payMoney" @payStatus="payStatus" @succcessFunction="succcessFunction" @errorFunction="errorFunction" :success-status='1' :error-status='2' ></Pay>
复制
// 下单事件 payMoney (callback) { this.$axios.post(`url`, params).then((res) => { let data = res.data; if (Number(data.code) === 0) { // 执行支付操作,跳转url,结果回调给收银台组件 this.currentOrder = data.data.order_no; callback(data.data); } else { this.$toast(data.message); } }).catch((e) => { }); }, // 轮训状态接口 payStatus (callback) { this.$axios.get(`url`).then((res) => { let data = res.data; if (Number(data.code) === 0) { // 执行支付结果查询,结果回调给收银台组件 callback(data.data.pay_result); if (data.data.list && data.data.list.length) { this.resultData = data.data.list; } } else { this.$toast(data.message); } }).catch((e) => { }); }, succcessFunction () { // 支付成功父组件操作事件 this.$toast('支付成功'); // 先弹出支付成功提示,延时1秒出结果弹窗 setTimeout(() => { // 支付成功后刷新一下接口 }, 1000); }, errorFunction () { // 支付失败父组件操作事件 this.$toast('支付失败,请重新发起支付'); },
复制