虚拟DOM挂载到真实DOM上之后,从路由中获取订单id和商品价格,带着订单id调用后端接口,后端接口(1)查询数据库获取订单信息,(2)计算剩余时间:当前时间-订单信息中的订单创建时间>=15min*50s=900s
,说明超时了,将剩余时间置为0,否则设置剩余时间=900-(当前时间-订单信息中的订单创建时间)
,将剩余时间保存在订单信息中并返回给前端,前端保存并从中取出商家信息、剩余时间,剩余时间为0,将overtime设为true,不为0,使用setInterval每隔1s计算剩余时间:(1)剩余时间-1,(2)计算倒计时(分钟、秒数)
mounted() {
let _this = this;
// 从路由中获取订单id和商品价格
this.order_id = this.$route.query.order_id;
this.price = this.$route.query.price;
orderInfo({order_id: this.order_id}).then((response) => {
this.order_info = response.data.data;// 获取订单信息
let remain_time = response.data.data.pay_remain_time;//支付剩余时间
this.restaurant_info = this.order_info.restaurant;//商家信息
if (remain_time === false) {// 剩余时间为0 到时间了 overtime设为true
this.overtime = true;
}
this.timer = setInterval(function () {
remain_time--;// 每一秒 剩余时间-1s
_this.calc_remain_time(remain_time);
}, 1000)
})
}
//获取订单信息
orderInfo(data){
let req = {url: `v1/order/${data.order_id}`}
return _get(req);
}
_get(req){
return axios.get(req.url, {params: req.data})
}
calc_remain_time(remain_time) {//倒计时
let minutes = (remain_time / 60 % 60)
this.minutes = minutes >= 10 ? minutes + '' : '0' + minutes;//计算剩余的分钟
let seconds = (remain_time % 60);
this.seconds = seconds >= 10 ? seconds + '' : '0' + seconds;//计算剩余的秒数
if (!this.minutes && !this.seconds) {
clearInterval(this.timer);
this.overtime = true;//支付超时
}
}
顶部导航条:复用head组件
<v-head title="支付订单" goBack="true" bgColor="#f4f4f4"></v-head>
美团外卖图片
<div class="img">
<img src="../../assets/pay_adv.png">
</div>
支付剩余时间
<h3>支付剩余时间</h3>
<!--没超时 展示剩余时间-->
<div class="remain-time" v-if="!overtime">
<span>{{minutes.slice(0, 1)}}</span><span>{{minutes.slice(1, 2)}}</span>
<span>:</span>
<span>{{seconds.slice(0, 1)}}</span>
<span>{{seconds.slice(1, 2)}}</span>
</div>
<!--超时了 展示“支付超时”-->
<span class="overtime" v-else>支付超时</span>
订单信息
展示餐厅图片 商品价格 店铺名 订单号
<div class="avatar">
<img :src="restaurant_info.pic_url">
</div>
<div class="info">
<span class="price">¥{{this.price}}</span>
<p>{{restaurant_info.name}} - {{order_id}}</p>
</div>
支付方式
<li @click="payType = '1'">
<!--支付宝图标-->
<span class="pay-icon"><i class="iconfont" style="color:#3d91e4;"></i></span>
<span class="pay-way-name">支付宝</span>
<!--如果你点了支付宝 此时payType=1 展示√这个iconfont-->
<span class="selected" v-if="payType === '1'"><i class="iconfont"></i></span>
<!--否则展示一个空白圆圈-->
<span class="select" v-else></span>
</li>
<li @click="payType = '2'">
<!--微信图标-->
<span class="pay-icon"><i class="iconfont" style="color:#2aaf90;"></i></span>
<span class="pay-way-name">微信支付</span>
<!--如果你点了微信 此时payType=2 展示√这个iconfont-->
<span class="selected" v-if="payType === '2'"><i class="iconfont"></i></span>
<!--否则展示一个空白圆圈-->
<span class="select" v-else></span>
</li>
确定支付
点击确定支付后 判断是否超时 超时了展示“支付超时” 没超时将payWayShow设为true 展示蒙版:“手机扫码支付”、“调起APP支付”
<div class="submit" @click="selectPayType()">确定支付</div>
<script>
selectPayType() {
if (this.overtime) {
this.alertText = '支付超时';
this.showTip = true;
return;
}
this.payWayShow = true;
}
</script>
蒙版
点击“确定支付”后 前端先判断订单是否正在提交 防止用户重复提交若没有 则继续判断订单是否超时 若超时 显示“支付超时” 否则前端调用自己的initPay方法 initPay会调用后端接口pay 执行initPay方法:
- 如果前端传来的order_id为空 后端返回给前端status为-1 错误信息:初始化支付失败参数有误
- 后端查询数据库 若order_id已经存在且在数据库中的状态为支付成功 则后端返回前端status为302 信息:该订单已完成支付!
- 若order_id已经存在且在数据库中的状态为未支付 则删除数据库中的order_id 所以前端拿到后端返回的信息首先判断状态码status 接下来 后端将订单数据 如订单编号、价格、支付方式等等信息使用md5加密
- 若是扫码支付 后端访问另一个接口 获取二维码 若该接口的返回的code为0000 获取成功 后端修改二维码为:扫码直接请求payNotice 将二维码返回给前端 前端将二维码传给scan组件并展示scan组件
- 若是调用APP支付 后端返回客户端同步跳转地址 前端会将这个地址放在表单中 然后调用app 提交表单
<transition name="fade">
<div class="pay-channel" v-show="payWayShow">
<div class="channel-select-container">
<div class="scan" @click="method = 'trpay.trade.create.scan'">
<!--如果这个div被点击 则method = 'trpay.trade.create.scan' 展示√这个iconfont-->
<i class="iconfont selected" v-if="method === 'trpay.trade.create.scan'"></i>
<!--否则展示空圆圈-->
<i class="select" v-else></i>
<span>手机扫码支付</span>
</div>
<div class="wap" @click="method='trpay.trade.create.wap'">
<!--如果这个div被点击 则method = 'trpay.trade.create.wap' 展示√这个iconfont-->
<i class="iconfont selected" v-if="method === 'trpay.trade.create.wap'"></i>
<!--否则展示空圆圈-->
<i class="select" v-else></i>
<span>调起app支付</span>
</div>
<!--preventRepeat初始值为false 点击提交后 会置true 防止重复提交 提交完会重置false-->
<div class="submit" :class="{disabled:preventRepeat}" @click="submit()">确定支付</div>
</div>
<!--×这个iconfont 点击后关闭蒙版-->
<div class="close" @click="close();">
<i class="iconfont icon-close"></i>
</div>
</div>
</transition>
<!--扫码支付-->
<scan :payType="payType" :orderData="orderData" @close="scanShow = false;" v-show="scanShow"></scan>
<!--调用app支付-->
<form action="http://pay.trsoft.xin/order/trpayGetWay" method="post" id="form" ref="form">
<input type="hidden" name="amount" v-model=form_data.amount>
<input type="hidden" name="outTradeNo" v-model="form_data.outTradeNo">
<input type="hidden" name="payType" v-model="payType">
<input type="hidden" name="tradeName" v-model="form_data.tradeName">
<input type="hidden" name="notifyUrl" v-model="form_data.notifyUrl">
<input type="hidden" name="synNotifyUrl" v-model="form_data.synNotifyUrl">
<input type="hidden" name="payuserid" v-model="form_data.payuserid">
<input type="hidden" name="appkey" v-model="form_data.appkey">
<input type="hidden" name="method" v-model="method">
<input type="hidden" name="sign" v-model="form_data.sign">
<input type="hidden" name="timestamp" v-model="form_data.timestamp">
<input type="hidden" name="version" v-model="form_data.version">
</form>
<script>
submit() {//提交支付
if (this.preventRepeat) return;
if (this.overtime) {
this.alertText = '支付超时';
this.showTip = true;
return;
}
this.preventRepeat = true;//防止多次点击
initPay({order_id: this.order_id, payType: this.payType, method: this.method}).then((response) => {
let res = response.data;// 支付前的准备工作所返回的数据
this.preventRepeat = false;
if (res.status === -1) {//支付接口出错
this.alertText = res.message;//提示
this.showTip = true;
return;
}
if (res.status === 302) {//该订单是否已经支付完成
let _this = this;
this.alertText = res.message;//提示
this.showTip = true;
setTimeout(() => {//订单支付成功 1s后跳转到订单页面
_this.$router.push('/order');
}, 1000)
return;
}
if (this.method === 'trpay.trade.create.scan') {//扫码支付方式
this.orderData = response.data.data;
this.scanShow = true;// 展示二维码
} else {//调起APP支付 提交form表单
this.form_data = response.data.data
this.$nextTick(() => {
this.$refs['form'].submit();
})
}
})
}
initPay = (data) => {
let req = {data,url: 'v1/pay'}
return _post(req);
}
_post(req){
return axios({method: 'post', url: `/${req.url}`, data: req.data})
}
</script>
scan扫描
用户扫码会去调用notifyUrl
虚拟DOM挂载到真实DOM上面之后监听orderData数据的变化 最初orderData为null 父组件给scan组件传入orderData 此时会执行下面的方法 若已有QRCode对象 重新生成一个二维码 否则创建QRCode对象并生成一个二维码 然后监听订单状态
watch: {
orderData(val) {
this.orderData = val;
if (this.qrcode) {
this.qrcode.makeCode(val.data.qrcode);// 已有QRCode对象 重新生成一个二维码
} else {
this.qrcode = new QRCode(this.$refs['qrcode'], {// 创建QRCode对象 生成一个二维码
text: val.data.qrcode,
width: 200,
height: 200,
});
}
this.listenStatus(val.outTradeNo);// orderData中的outTradeNo:商户自主生成的订单号
}
}
监听订单的状态:每隔3s执行listenStatus方法 listenStatus会调用后端接口listen_status 实时监听支付状态 listen_status会使用订单编号去数据库查询订单的状态 若状态为200代表支付成功 后端向前端返回状态码200及“支付完成” 清空定时器 并在1s后跳转到订单详情页 否则返回状态码-1及“未支付”
listenStatus(outTradeNo) {
clearInterval(this.timer);
let _this = this;
this.timer = setInterval(() => {
listenStatus({outTradeNo}).then((response) => {
if (response.data.status === 200) {
clearInterval(this.timer);
this.alertText = '支付成功,准备跳转';
this.showTip = true;
setTimeout(()=>{// 1s后跳转到订单详情页
_this.$router.push({path: '/order_detail', query: {id: _this.orderData.order_id}})
},1000);
}
})
}, 3000);
}
界面展示:
<div id="scan-container">
<header>
<!--展示支付宝或微信图标 根据payType判断-->
<i class="iconfont pay-icon" :style="{color:payTypeObj[payType]['color']}"
v-html="payTypeObj[payType]['icon']"></i>
<!--展示支付方式:支付宝/微信-->
<span class="pay-way-name">{{payTypeObj[payType]['name']}}</span>
</header>
<!--展示二维码-->
<div class="qrcode-container">
<div id="qrcode" ref="qrcode"></div>
</div>
<!--展示订单信息:产品名称 订单编号 订单金额 实付金额-->
<div class="info-container">
<ul>
<li><span>产品名称:{{orderData.tradeName}}</span></li>
<li><span>订单编号:{{orderData.outTradeNo}}</span></li>
<li><span>订单金额:{{orderData.amount / 100}}</span></li>
<li><span>实付金额:{{orderData.amount / 100}}</span></li>
</ul>
</div>
<!--×这个iconfont 点击后关闭蒙版-->
<div class="close" @click="close();">
<i class="iconfont icon-close"></i>
</div>
<alert-tip :text="alertText" :showTip.sync="showTip"></alert-tip>
</div>
<script>
close() {
clearInterval(this.timer)
this.$emit('close');
}
</script>
QrCode在vue中的使用
QRCode.js 是一个用于生成二维码的 JavaScript 库。主要是通过获取 DOM 的标签,再通过 HTML5 Canvas 绘制而成,不依赖任何库。
- 安装:
npm install qrcodejs2 --save
- 引入:
import QRCode from 'qrcodejs2'
- 需要一个生成二维码的div:
<div id="qrcode" ref="qrcode"></div>
- 生成二维码
let qrcode = new QRCode('qrcode', {
width: 132, //图像宽度
height: 132, //图像高度
text: code, // 二维码地址
colorDark: "#000", //前景色
colorLight: "#fff", //背景色
})
- 清除二维码
qrcode.clear();
- 生成另外一个二维码
qrcode.makeCode("http://www.w3cschool.cc");
QrCode的使用
- 在html中定义:
<div id="qrcode"></div>
- 在js中使用:
new QRCode(document.getElementById("qrcode"), "http://www.runoob.com"); // 设置要生成二维码的链接
- 其他可选参数:
var qrcode = new QRCode(document.getElementById("qrcode"), {
text: "http://www.runoob.com",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
qrcode.clear(); // 清除二维码
qrcode.makeCode("http://www.w3cschool.cc"); // 生成另外一个二维码