598 lines
19 KiB
Vue
598 lines
19 KiB
Vue
<template>
|
||
<view class="cart">
|
||
<!-- 自定义导航栏 -->
|
||
<uniNavBar class="interval" @clickLeft="goBack" title="确定支付" left-text="关闭" fixed="true" ></uniNavBar>
|
||
<view class="pay-main">
|
||
<!-- 加label就算点击文字或者图片都能选中 -->
|
||
<radio-group @change="radioChange">
|
||
<label for="" @tap="goPayment">
|
||
<view class="pay-item">
|
||
<image class="pay-img" src="../../static/image/zfb.png" mode=""></image>
|
||
<view>
|
||
<view>支付宝支付</view>
|
||
<view class="pay-txt">推荐支付宝用户使用</view>
|
||
</view>
|
||
<label class="radio">
|
||
<radio v-if="vkPay.status == 0" value="alipay"
|
||
:checked="form1.provider == 'alipay'" color="#FF3333" /><text></text>
|
||
</label>
|
||
</view>
|
||
</label>
|
||
<label for="" @tap="goPayment">
|
||
<view class="pay-item" >
|
||
<image class="pay-img" src="../../static/image/wxf.png" mode=""></image>
|
||
<view>
|
||
<view>微信支付</view>
|
||
<view class="pay-txt">推荐微信用户使用</view>
|
||
</view>
|
||
<label class="radio">
|
||
<radio v-if="vkPay.status == 0" value="wxpay"
|
||
:checked="form1.provider == 'wxpay'" color="#FF3333" /><text></text>
|
||
</label>
|
||
</view>
|
||
</label>
|
||
</radio-group>
|
||
</view>
|
||
<!-- <view>
|
||
你选择的商品为:{{details.name}}</text>
|
||
</view> -->
|
||
|
||
|
||
<!-- vue2版本的支付组件开始 -->
|
||
<!-- #ifndef VUE3 -->
|
||
<vk-uni-pay ref="vkPay" :status.sync="vkPay.status" :code-url.sync="vkPay.codeUrl"
|
||
:qrcode-image.sync="vkPay.qrcodeImage" :query-payment-action="vkPay.queryPaymentAction"
|
||
:page-show="vkPay.pageShow" :polling="vkPay.polling" :return-url="vkPay.returnUrl"
|
||
:await-notify="vkPay.awaitNotify" :pay-order-info="vkPay.payOrderInfo"></vk-uni-pay>
|
||
<!-- #endif -->
|
||
<!-- vue2版本的支付组件结束 -->
|
||
|
||
<!-- vue3版本的支付组件开始 -->
|
||
<!-- #ifdef VUE3 -->
|
||
<vk-uni-pay ref="vkPay" v-model:status="vkPay.status" v-model:codeUrl="vkPay.codeUrl"
|
||
v-model:qrcodeImage="vkPay.qrcodeImage" :query-payment-action="vkPay.queryPaymentAction"
|
||
:page-show="vkPay.pageShow" :polling="vkPay.polling" :return-url="vkPay.returnUrl"
|
||
:await-notify="vkPay.awaitNotify" :pay-order-info="vkPay.payOrderInfo"></vk-uni-pay>
|
||
<!-- #endif -->
|
||
<!-- vue3版本的支付组件结束 -->
|
||
|
||
|
||
<!-- 弹窗开始 -->
|
||
|
||
<!-- 二维码支付弹窗开始 -->
|
||
<view class="pay-qrcode-popup" v-if="vkPay.status < 2 && vkPay.codeUrl">
|
||
<view class="pay-qrcode-popup-mask" @click="cancelPay"></view>
|
||
<view class="pay-qrcode-popup-content">
|
||
<image v-if="vkPay.qrcodeImage" :src="vkPay.qrcodeImage" class="pay-qrcode-popup-image"></image>
|
||
<vk-uni-qrcode v-else :text="vkPay.codeUrl" :size="450"></vk-uni-qrcode>
|
||
<view class="pay-qrcode-popup-info">
|
||
<view>
|
||
<text class="pay-qrcode-popup-info-fee">{{ (form1.total_fee / 100).toFixed(2) }}</text>
|
||
<text>元</text>
|
||
</view>
|
||
<view v-if="form1.provider == 'wxpay'">请用微信扫码支付</view>
|
||
<view v-else-if="form1.provider == 'alipay'">请用支付宝扫码支付</view>
|
||
</view>
|
||
<button v-if="vkPay.status == 1" type="primary" @click="queryPayment">我已完成支付</button>
|
||
</view>
|
||
</view>
|
||
<!-- 二维码支付弹窗结束 -->
|
||
|
||
<!-- 外部浏览器H5支付弹窗确认开始 -->
|
||
<view class="pay-confirm-popup" v-if="vkPay.confirmShow">
|
||
<view class="pay-confirm-popup-content">
|
||
<view class="pay-confirm-popup-title">请确认支付是否已完成</view>
|
||
<view><button type="primary" @click="queryPayment">已完成支付</button></view>
|
||
<view class="pay-confirm-popup-refresh"><button type="default" @click="afreshPayment">支付遇到问题,重新支付</button></view>
|
||
<view class="pay-confirm-popup-cancel" @click="vkPay.confirmShow = false">暂不支付</view>
|
||
</view>
|
||
</view>
|
||
<!-- 外部浏览器H5支付弹窗确认结束 -->
|
||
|
||
<!-- 弹窗结束 -->
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import uniNavBar from '../../components/uni/uni-nav-bar/uni-nav-bar.vue'
|
||
import process from '../../utils/process.js'
|
||
const db = uniCloud.database();
|
||
export default {
|
||
data() {
|
||
return {
|
||
details: {
|
||
name: null,
|
||
id: null,
|
||
imgUrl: null,
|
||
price: 0
|
||
},
|
||
orderId: String,
|
||
vkPay: {
|
||
/**
|
||
* 查询支付状态的云函数配置
|
||
* 如果是非路由框架,则action为字符串,值为云函数名称
|
||
* 如果是路由框架,则按下方配置填写
|
||
* 注意:queryPaymentAction内参数用默认即可,无需更改。(除非你已经知道参数的意义)
|
||
*/
|
||
queryPaymentAction: {
|
||
name: "vk-pay", // 云函数名称
|
||
action: "pay/queryPayment", // 路由模式下云函数地址
|
||
actionKey: "action", // 路由模式下云函数地址的识别key
|
||
dataKey: "data" // 路由模式下云函数请求参数的识别key
|
||
},
|
||
// PC支付的付款二维码地址,渲染二维码需要自己写,可以参考示例中的二维码组件 vk-uni-qrcode
|
||
codeUrl: "",
|
||
qrcodeImage: "",
|
||
// 当前支付状态 0:等待发起支付 1:支付中 2:已支付
|
||
status: 0,
|
||
// 当前页面是否显示
|
||
pageShow: true,
|
||
// 启用轮询检测订单支付状态(仅h5支付有效)
|
||
polling: true,
|
||
// 微信手机外部浏览器H5支付时有效,必须是http或https开头的绝对路径,且在微信H5支付配置的域名白名单中。你还需要将此H5域名加入uniCloud白名单
|
||
// 如果此值为空,则默认使用返回当前页面,返回的页面url参数会带上confirmShow=true&out_trade_no=实际订单号
|
||
returnUrl: "",
|
||
// 确认已支付的弹窗开关(微信手机外部浏览器H5支付时有效)
|
||
confirmShow: false,
|
||
// 支付成功后,是否需要等待异步回调全部执行完成后才通知前端
|
||
awaitNotify: true,
|
||
// 支付成功后,是否需要返回支付订单数据
|
||
payOrderInfo: true,
|
||
},
|
||
// 表单请求数据
|
||
form1: {
|
||
provider: "",
|
||
total_fee: null,
|
||
out_trade_no: "",
|
||
subject: "测试订单标题",
|
||
body: "订单详情",
|
||
type: "recharge",
|
||
openid: ""
|
||
}
|
||
}
|
||
},
|
||
components: {
|
||
uniNavBar,
|
||
},
|
||
onLoad(options) { //option为object类型,会序列化上个页面传递的参数
|
||
//判断options.dataList是否为空 不然会因为json.parse解析undefined报错
|
||
if (options.dataList) {
|
||
this.details = JSON.parse(options.dataList);
|
||
console.log(options.dataList); //查看传入的id值 打印出上个页面传递的参数。
|
||
}
|
||
// 此处是因为微信H5外部浏览器支付后微信会重新打开指定页面,我们需要做一个弹窗让用户自己点下是否支付成功,同时云函数会去请求微信服务器检查是否真的付款成功。
|
||
this.vkPay.confirmShow = options.confirmShow || false;
|
||
if (options.out_trade_no) this.form1.out_trade_no = options.out_trade_no;
|
||
if (options.code && options.state) {
|
||
// 获取微信公众号的openid
|
||
setTimeout(() => {
|
||
this.getOpenid({
|
||
code: options.code
|
||
});
|
||
}, 300);
|
||
}
|
||
},
|
||
// 监听 - 页面每次【显示时】执行(如:前进和返回) (页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面)
|
||
onShow() {
|
||
this.vkPay.pageShow = true;
|
||
// process.pageState[0] = process.pageState[0] + 1;
|
||
},
|
||
// 监听 - 页面每次【隐藏时】执行(如:返回)
|
||
onHide() {
|
||
this.vkPay.pageShow = false;
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
//返回上一页(选择商品样式)返回到选择棉花糖样式的界面
|
||
uni.reLaunch({
|
||
url:'/pages/productlist/productlist'
|
||
})
|
||
},
|
||
//发起支付
|
||
goPayment(obj = {}) {
|
||
//生成订单号
|
||
let {
|
||
form1
|
||
} = this;
|
||
|
||
function setTimeDateFmt(s) {
|
||
return s < 10 ? '0' + s : s;
|
||
}
|
||
function orderNumber() {
|
||
const now = new Date(); //当前时间
|
||
let fullYear = now.getFullYear(); //年
|
||
let month = setTimeDateFmt(now.getMonth() + 1); //月
|
||
let day = setTimeDateFmt(now.getDate()); //日
|
||
let hour = setTimeDateFmt(now.getHours()); //时
|
||
let minutes = setTimeDateFmt(now.getMinutes()); //分
|
||
let seconds = setTimeDateFmt(now.getSeconds()); //秒
|
||
//年月日时分秒后添加 随机数
|
||
let orderCode = fullYear + month + day + hour + minutes + seconds + (Math.round(Math.random() * 1000000));
|
||
return orderCode;
|
||
}
|
||
this.orderId = orderNumber();
|
||
console.log(this.orderId)
|
||
//把商品价格、名称、id 、图片 传给数据库 云数据库库添加数据
|
||
db.collection("CommodityOrder").add({
|
||
CommodityName: this.details.name,
|
||
CommodityPrice: this.details.price,
|
||
CommodityImg: this.details.imgUrl,
|
||
CommodityID: this.details.id,
|
||
Order_ID: this.orderId,
|
||
OrderStatus: "未支付", //1未支付 当支付完徐娅改成2 表示支付完成
|
||
}).then((res) => { // res 为数据库查询结果
|
||
// console.log(res)
|
||
})
|
||
//然后从云端下载订单号 ,怎么获取最新的订单号
|
||
db.collection('CommodityOrder').where(
|
||
//查询最新的订单号
|
||
{
|
||
Order_ID: this.orderId
|
||
}).get().then((res) => {
|
||
const DownloadData = res.result.data[0]
|
||
//查询到的订单号、金额后弹出扫码界面
|
||
console.log(DownloadData.Order_ID)
|
||
|
||
//当支付完后再出现提示支付完成,或者支付失败 都跳转到选中商品中
|
||
// 发起支付
|
||
// 这里的订单号\金额等数据应该是从数据库里获取的,这里为演示需要,故直接本地生成.
|
||
form1.out_trade_no = DownloadData.Order_ID; //订单号
|
||
form1.subject = DownloadData.CommodityName; //商品名称
|
||
form1.total_fee = DownloadData.CommodityPrice; //金额
|
||
// #ifdef MP-WEIXIN
|
||
form1.provider = "wxpay";
|
||
// #endif
|
||
// #ifdef MP-ALIPAY
|
||
form1.provider = "alipay";
|
||
// #endif
|
||
this.$refs.vkPay.createPayment({
|
||
// 如果是非路由框架,则外层action不再是json,而为字符串,值为云函数名称,如 action: "你的云函数名称"
|
||
// 如果是路由框架,则按下方配置填写
|
||
// 如果云函数为 vk-pay,则无需改动 action
|
||
action: {
|
||
name: "vk-pay", // 云函数名称
|
||
action: "pay/createPayment", // 路由模式下云函数地址
|
||
actionKey: "action", // 路由模式下云函数地址的识别key(注意VK路由框架下,此值为$url)
|
||
dataKey: "data" // 路由模式下云函数请求参数的识别key
|
||
},
|
||
// 请求数据(data内的参数会传给云函数,云函数中通过 data.xxx 的方式获取)
|
||
data: {
|
||
provider: form1.provider,
|
||
total_fee: form1.total_fee,
|
||
out_trade_no: form1.out_trade_no,
|
||
subject: form1.subject,
|
||
body: form1.body,
|
||
type: form1.type,
|
||
openid: form1.openid // 如果是微信公众号支付,则openid不能为空
|
||
},
|
||
// 成功回调
|
||
success: res => {
|
||
// 此处一般是写支付成功的提示或跳转到支付成功的页面。
|
||
this.toast("支付成功", "success");
|
||
console.log("paySuccess", res);
|
||
//支付成功后需要把数据库的商品状态改成2
|
||
db.collection('CommodityOrder').where({
|
||
CommodityID: this.details.id
|
||
}).update({
|
||
OrderStatus: "已支付", //2为该订单支付成功
|
||
}).then((res) => {
|
||
console.log(res)
|
||
})
|
||
// 成功后跳转到做糖的交互页面
|
||
uni.navigateTo({
|
||
url: '/pages/cottonCandy/SugarMaking'
|
||
})
|
||
},
|
||
// 失败回调
|
||
fail: res => {
|
||
if (res.failType === "create") {
|
||
// 创建支付失败时提示
|
||
this.alert(res.msg);
|
||
} else if (res.failType === "request") {
|
||
// 请求支付失败时提示
|
||
this.toast("请求支付失败");
|
||
} else if (res.failType === "result") {
|
||
// 支付结果失败时提示
|
||
this.toast("支付失败");
|
||
}
|
||
},
|
||
// 取消回调
|
||
cancel: res => {
|
||
this.toast("用户取消支付");
|
||
}
|
||
});
|
||
});
|
||
},
|
||
// 支付状态查询
|
||
queryPayment() {
|
||
// 支付状态查询你可以直接查你的订单表,看订单是否已支付(因为最终判定用户是否支付成功应该以你的订单表为准)
|
||
// 如果vkPay.queryPayment接口返回支付成功,但你的订单表查询到未支付,代表你的异步回调函数写的有问题。
|
||
this.$refs.vkPay.queryPayment({
|
||
title: "查询中...",
|
||
data: {
|
||
out_trade_no: this.form1.out_trade_no
|
||
},
|
||
needAlert: true,
|
||
success: data => {
|
||
this.vkPay.status = 2; // 标记为已支付
|
||
this.vkPay.confirmShow = false; // 关闭确认弹窗(微信H5支付有弹窗确认)
|
||
this.toast(data.msg);
|
||
},
|
||
fail: (res = {}) => {
|
||
if (res.msg === "订单已退款") {
|
||
this.vkPay.confirmShow = false; // 关闭确认弹窗(微信H5支付有弹窗确认)
|
||
}
|
||
}
|
||
});
|
||
},
|
||
// 退款,此为演示,实际业务开发不应该写在前端,而是写在云函数中。
|
||
refund() {
|
||
this.callFunction({
|
||
title: "退款中...",
|
||
name: "vk-pay",
|
||
data: {
|
||
action: "pay/refund",
|
||
data: {
|
||
out_trade_no: this.form1.out_trade_no,
|
||
//refund_fee: 1, // 退款金额,100=1元(单位分)不填则全额退款
|
||
}
|
||
},
|
||
success: data => {
|
||
this.toast(data.msg);
|
||
}
|
||
});
|
||
},
|
||
// 获取微信支付v3版本的微信平台证书
|
||
getWxpayPublicCert() {
|
||
this.callFunction({
|
||
title: "请求中...",
|
||
name: "vk-pay",
|
||
data: {
|
||
action: "pay/getWxpayPublicCert",
|
||
data: {
|
||
|
||
}
|
||
},
|
||
success: data => {
|
||
this.alert("请按F12 看浏览器控制台的输出内容");
|
||
let wxpayPublicCertSn = data.wxpayPublicCertSn;
|
||
let wxpayPublicCertContent = data.wxpayPublicCertContent;
|
||
console.log('wxpayPublicCertSn: ', wxpayPublicCertSn)
|
||
console.log(`wxpayPublicCertContent: 请将它压缩成一行(复制下面的证书,用""引号框住证书内容,按一下回车,自动把换行转为\\n`)
|
||
console.log(wxpayPublicCertContent)
|
||
}
|
||
});
|
||
},
|
||
cancelPay() {
|
||
this.vkPay.status = 0;
|
||
this.vkPay.codeUrl = "";
|
||
},
|
||
// 云函数调用简易封装,你也可以用你原本框架的云函数请求封装方法来调用云函数。
|
||
callFunction(obj = {}) {
|
||
let {
|
||
needAlert = true
|
||
} = obj;
|
||
if (obj.title) uni.showLoading({
|
||
title: obj.title
|
||
});
|
||
uniCloud.callFunction({
|
||
...obj,
|
||
success: (result = {}) => {
|
||
if (obj.title) uni.hideLoading();
|
||
let res = result.result;
|
||
if (res.code === 0) {
|
||
if (typeof obj.success == "function") obj.success(res);
|
||
} else {
|
||
if (needAlert && res.msg) this.alert(res.msg);
|
||
if (typeof obj.fail == "function") obj.fail(res);
|
||
}
|
||
},
|
||
fail: (res = {}) => {
|
||
if (obj.title) uni.hideLoading();
|
||
if (needAlert && res.msg) this.alert(res.msg);
|
||
if (typeof obj.fail == "function") obj.fail(res);
|
||
}
|
||
});
|
||
},
|
||
// 选择支付方式
|
||
radioChange(e) {
|
||
this.form1.provider = e.detail.value;
|
||
},
|
||
// 获取公众号code
|
||
getWeiXinJsCode(scope = "snsapi_base") {
|
||
let appid = "wx2ebf03d174875bed"; // 填写公众号的appid
|
||
let redirect_uri = window.location.href.split("?")[0];
|
||
let url =
|
||
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=STATE#wechat_redirect`;
|
||
window.location.href = url;
|
||
},
|
||
// 获取公众号openid
|
||
getOpenid(data) {
|
||
let {
|
||
code
|
||
} = data;
|
||
this.callFunction({
|
||
title: "请求中...",
|
||
name: "vk-pay",
|
||
data: {
|
||
action: "pay/code2SessionWeixinH5",
|
||
data
|
||
},
|
||
success: res => {
|
||
if (res.openid) {
|
||
this.form1.openid = res.openid;
|
||
this.toast("已获取到openid,可以开始支付", );
|
||
}
|
||
}
|
||
});
|
||
},
|
||
// uni.showToast的简易封装
|
||
toast(title, icon = "none", mask = false) {
|
||
uni.showToast({
|
||
title,
|
||
icon,
|
||
mask,
|
||
duration: 1500
|
||
});
|
||
},
|
||
// uni.showModal的简易封装
|
||
alert(content, title = "提示") {
|
||
uni.showModal({
|
||
title,
|
||
content,
|
||
showCancel: false
|
||
});
|
||
}
|
||
},
|
||
// 计算属性
|
||
computed: {
|
||
// h5运行环境
|
||
h5Env() {
|
||
// #ifdef H5
|
||
let ua = window.navigator.userAgent.toLowerCase();
|
||
if (ua.match(/MicroMessenger/i) == 'micromessenger' && (ua.match(/miniprogram/i) == 'miniprogram')) {
|
||
// 微信小程序
|
||
return "mp-weixin";
|
||
}
|
||
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
|
||
// 微信公众号
|
||
return "h5-weixin";
|
||
}
|
||
if (ua.match(/alipay/i) == 'alipay' && ua.match(/miniprogram/i) == 'miniprogram') {
|
||
// 支付宝小程序
|
||
return "mp-alipay";
|
||
}
|
||
if (ua.match(/alipay/i) == 'alipay') {
|
||
// 支付宝内
|
||
return "h5-alipay";
|
||
}
|
||
// 外部 H5
|
||
return "h5";
|
||
// #endif
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.interval{
|
||
margin-top: 20rpx;
|
||
}
|
||
.pay-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border-bottom: 2rpx solid #CCCCCC;
|
||
}
|
||
|
||
.pay-txt {
|
||
color: #CCCCCC;
|
||
}
|
||
|
||
.pay-img {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
}
|
||
|
||
.page-content {
|
||
padding: 15px;
|
||
|
||
.input {
|
||
height: 46px;
|
||
border: solid 1px #dddddd;
|
||
border-radius: 5px;
|
||
margin-bottom: 15px;
|
||
padding: 0px 15px;
|
||
}
|
||
|
||
.button {
|
||
margin-bottom: 15px;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
.pay-confirm-popup {
|
||
position: fixed;
|
||
z-index: 2;
|
||
width: 100vw;
|
||
top: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
|
||
.pay-confirm-popup-content {
|
||
width: 550rpx;
|
||
margin: 50% auto 0 auto;
|
||
background-color: #ffffff;
|
||
border-radius: 10rpx;
|
||
padding: 40rpx;
|
||
|
||
.pay-confirm-popup-title {
|
||
text-align: center;
|
||
padding: 20rpx 0;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.pay-confirm-popup-refresh {
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.pay-confirm-popup-cancel {
|
||
margin-top: 20rpx;
|
||
text-align: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 二维码支付弹窗开始 */
|
||
.pay-qrcode-popup {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
display: grid; /* 启用 Grid 布局 */
|
||
place-items: center; /* 单行属性实现居中 */
|
||
z-index: 1000;
|
||
|
||
|
||
.pay-qrcode-popup-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
background-color: rgba(0, 0, 0, 0.6);
|
||
}
|
||
|
||
.pay-qrcode-popup-content {
|
||
position: relative;
|
||
width: 500rpx;
|
||
// margin: 40% auto 0 auto;
|
||
background-color: #ffffff;
|
||
border-radius: 10rpx;
|
||
padding: 40rpx;
|
||
box-sizing: content-box;
|
||
text-align: center;
|
||
|
||
.pay-qrcode-popup-info {
|
||
text-align: center;
|
||
padding: 20rpx;
|
||
|
||
.pay-qrcode-popup-info-fee {
|
||
color: red;
|
||
font-size: 60rpx;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
.pay-qrcode-popup-image {
|
||
width: 450rpx;
|
||
height: 450rpx;
|
||
}
|
||
}
|
||
}
|
||
</style>
|