一、功能梳理

app内前h5涉及到支付的功能,ios非实物商品实付需要使用ios原生支付方式,实物商品则可以三方支付,主要的实现思路为后端返回跳转支付宝或微信的支付scheme链接,前端进行跳转支付,同时需要实时查询用户的支付状态。

整个过程中复杂的部分在于查询用户支付状态的体验方面,需要保证用户在支付成功、支付失败、跳转支付宝、微信回来或者没有跳转支付宝微信等未知的行为下的用户体验。

组件内容为底部升起的支付选择弹窗,可以选择支付宝或者微信。

二、实现步骤

  1. 用户进行下单操作,前端调用下单接口,成功则返回三方支付app跳转链接,前端进行跳转

  1. 跳转三方app同时进行查询订单接口轮训,实时获取订单状态,轮训时间定位30s,依据需求调整

  1. 用户如果支付时间超过了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('支付失败,请重新发起支付');
},

发表回复