Files
lucky_shop/components/payment/payment.vue

832 lines
34 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view>
<!-- 选择支付方式弹窗 -->
<uni-popup ref="choosePaymentPopup" type="center" v-if="payInfo" :mask-click="false">
<view class="choose-payment-popup popup" @touchmove.prevent.stop>
<view class="popup-header">
<text class="tit">支付方式-测试</text>
<text class="iconfont icon-close" @click="close()"></text>
</view>
<scroll-view scroll-y="true" class="popup-body">
<view class="pay-money">
<text class="money">支付金额{{ payMoney | moneyFormat }}</text>
</view>
<view class="payment-item" v-if="balanceDeduct > 0 && balanceUsable && balanceConfig == 1">
<view class="iconfont icon-yue"></view>
<view class="info-wrap">
<text class="name">余额抵扣</text>
<view class="money">可用¥{{ balanceDeduct | moneyFormat }}</view>
</view>
<ns-switch class="balance-switch" @change="useBalance" :checked="isBalance == 1"></ns-switch>
</view>
<block v-if="payMoney > 0">
<block v-if="payTypeList.length">
<block v-for="(item, index) in payTypeList">
<view v-if="offlineShow || item.type != 'offlinepay'" class="payment-item" :key="index"
@click="payIndex = index">
<view class="iconfont" :class="item.icon"></view>
<text class="name">{{ item.name }}</text>
<text class="iconfont"
:class="payIndex == index ? 'icon-yuan_checked color-base-text' : 'icon-checkboxblank'"></text>
</view>
</block>
</block>
<block v-else>
<view class="empty">平台尚未配置支付方式</view>
</block>
</block>
</scroll-view>
<view class="popup-footer">
<view class="confirm-btn color-base-bg" @click="confirm()">确认支付</view>
</view>
</view>
</uni-popup>
</view>
</template>
<!-- 新版支付组件 订单表为order表 的订单支付时使用该组件 -->
<script>
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import nsSwitch from '@/components/ns-switch/ns-switch.vue';
// #ifdef H5
import { Weixin } from 'common/js/wx-jssdk.js';
// #endif
// ========== 引入三端支付工具类(核心:适配官方规范) ==========
import { getWechatPay } from '@/utils/wechat-pay.js';
import { getAlipayPay } from '@/utils/alipay-pay.js';
import { getHuaweiPay } from '@/utils/huawei-pay.js';
export default {
name: 'payment',
components: {
uniPopup,
nsSwitch
},
props: {
// 是否可用余额支付
balanceUsable: {
type: Boolean,
default: true
},
},
data() {
return {
payIndex: 0,
payTypeList: [
// 所有端都显示微信支付
{
name: '微信支付',
icon: 'icon-weixin1',
type: 'wechatpay'
},
// 所有端都显示支付宝支付
{
name: '支付宝支付',
icon: 'icon-zhifubaozhifu-',
type: 'alipay'
},
// 所有端都显示华为支付
{
name: '华为支付',
icon: 'icon-zhekou',
type: 'huaweipay'
},
{
name: '线下支付',
icon: 'icondiy icon-yuezhifu',
type: 'offlinepay'
},
],
// #ifdef H5
timer: null,
// #endif
payInfo: null,
balanceConfig: 0,
// 预售页面判断
sale: true,
isBalance: 0,
balance: 0,
//重置是否已完成没有完成不能调用api/pay/pay
resetPayComplete: true,
repeatFlag: false,
// ========== 支付工具类实例 ==========
wechatPay: null,
alipayPay: null,
huaweiPay: null
};
},
created(e) {
this.getPayType();
if (this.balanceUsable) this.getBalanceConfig();
// ========== 初始化三端支付工具类(填写官方申请的参数) ==========
this.initPayUtils();
},
computed: {
balanceDeduct() {
let money = 0;
if (this.payInfo && this.balance) {
money = this.balance > this.payInfo.pay_money ? this.payInfo.pay_money : this.balance;
}
return money;
},
payMoney() {
let money = 0;
if (this.payInfo) {
money = this.payInfo.pay_money;
if (this.balanceDeduct && this.isBalance && this.balanceUsable) {
money = this.payInfo.pay_money - this.balanceDeduct;
}
}
return money;
},
offlineShow() {
// 获取当前页面栈实例数组
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// 获取页面路由路径
let routePath = currentPage.route;
return this.$store.state.offlineWhiteList.length ? this.$store.state.offlineWhiteList.includes(routePath) : false
}
},
methods: {
// ========== 初始化支付工具类(核心:配置官方参数) ==========
initPayUtils() {
// 微信支付初始化(替换为你的微信官方参数)
this.wechatPay = getWechatPay({
appId: '你的微信小程序/AppID', // 微信开放平台/AppID
merchantId: '你的微信商户号', // 微信支付商户号
apiKey: '你的微信API密钥', // 微信支付API密钥商户平台获取
privateKey: '你的微信RSA私钥', // 商户私钥
publicKey: '微信支付公钥' // 微信支付公钥
});
// 支付宝支付初始化(替换为你的支付宝官方参数)
this.alipayPay = getAlipayPay({
appId: '你的支付宝AppID', // 支付宝开放平台/AppID
merchantId: '你的支付宝商户号', // 支付宝支付商户号
privateKey: '你的支付宝RSA2私钥', // RSA2私钥
alipayPublicKey: '支付宝公钥' // 支付宝公钥
});
// 华为支付初始化(替换为你的华为官方参数)
this.huaweiPay = getHuaweiPay({
appId: '你的华为AppID', // 华为开发者联盟AppID
merchantId: '你的华为商户号', // 华为支付商户号
publicKey: '华为支付公钥', // 华为支付公钥
privateKey: '你的华为商户私钥', // 华为商户私钥
env: 'sandbox' // 测试环境sandbox生产环境production
});
},
/**
* 父级页面onShow调用
*/
pageShow() {
if (this.payInfo) {
let offlinepay = uni.getStorageSync('offlinepay');
if (offlinepay) {
uni.removeStorageSync('offlinepay');
this.close()
}
} else {
uni.removeStorageSync('offlinepay');
}
},
close() {
this.$emit('close');
this.$refs.choosePaymentPopup.close();
},
// 使用余额
useBalance() {
this.isBalance = this.isBalance ? 0 : 1;
this.$emit('useBalance', this.isBalance)
},
confirm() {
if (this.payTypeList.length == 0 && this.payMoney > 0) {
this.$util.showToast({
title: '请选择支付方式!'
});
return;
}
if (this.resetPayComplete == false) {
this.$util.showToast({
title: '支付取消中,请稍后再试!'
});
return;
}
uni.showLoading({
title: '支付中...',
mask: true
});
if (this.repeatFlag) return;
this.repeatFlag = true;
this.pay();
uni.setStorageSync('pay_flag', 1);
},
getPayInfo(out_trade_no, callback) {
this.$api.sendRequest({
url: '/api/pay/info',
data: {
out_trade_no
},
success: res => {
if (res.code >= 0 && res.data) {
this.payInfo = res.data;
if (this.balanceConfig && this.balanceUsable) this.getMemberBalance();
setTimeout(() => {
this.$refs.choosePaymentPopup.open();
if (typeof callback == 'function') callback();
})
} else {
this.$util.showToast({
title: '未获取到支付信息!'
});
}
}
});
},
/**
* 获取余额配置
*/
getBalanceConfig() {
this.$api.sendRequest({
url: '/api/pay/getBalanceConfig',
data: {},
success: res => {
this.balanceConfig = res.data.balance_show;
}
});
},
/**
* 获取用户余额
*/
getMemberBalance() {
this.$api.sendRequest({
url: '/api/memberaccount/usablebalance',
success: res => {
if (res.code == 0 && res.data) {
this.balance = parseFloat(res.data.usable_balance);
}
}
})
},
/**
* 查询支付方式
*/
getPayType() {
this.$api.sendRequest({
url: '/api/pay/type',
success: res => {
if (res.code == 0) {
if (res.data.pay_type == '') {
this.payTypeList = [];
} else {
this.payTypeList = this.payTypeList.filter((val, key) => {
return res.data.pay_type.indexOf(val.type) != -1
});
}
}
}
});
},
// #ifdef H5
pay() {
var payType = this.payTypeList[this.payIndex];
var return_url = '';
if (this.payInfo.event == 'BlindboxGoodsOrderPayNotify') {
return_url = '/pages_promotion/blindbox/index?outTradeNo=';
} else {
return_url = '/pages_tool/pay/result?code=';
}
this.$api.sendRequest({
url: '/api/pay/pay',
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType ? payType.type : '',
return_url: encodeURIComponent(this.$config.h5Domain + return_url + this.payInfo.out_trade_no),
is_balance: this.isBalance
},
success: async res => { // 新增async支持异步调用
uni.hideLoading();
if (res.code >= 0) {
if (res.data.pay_success) {
this.paySuccess();
return;
}
switch (payType.type) {
// ========== 支付宝支付H5端符合官方RSA2规范 ==========
case 'alipay':
try {
this.repeatFlag = false;
const orderInfo = {
outTradeNo: this.payInfo.out_trade_no,
productName: '订单支付',
price: this.payMoney,
returnUrl: this.$config.h5Domain + return_url + this.payInfo.out_trade_no,
notifyUrl: this.$config.h5Domain + '/api/pay/alipay/notify'
};
// 创建支付宝订单带RSA2签名
const alipayRes = await this.alipayPay.h5Pay(orderInfo);
// 验证支付宝签名(官方规范)
const isAlipaySignValid = this.alipayPay.verifyResult(alipayRes, alipayRes.sign);
if (!isAlipaySignValid) {
this.$util.showToast({ title: '支付宝订单签名验证失败' });
return;
}
// 区分微信浏览器/普通浏览器
if (this.$util.isWeiXin()) {
var wx_alipay = encodeURIComponent(alipayRes.payUrl);
this.$util.redirectTo('/pages_tool/pay/wx_pay', {
wx_alipay: wx_alipay,
out_trade_no: this.payInfo.out_trade_no
}, '', 'redirectTo');
} else {
location.href = alipayRes.payUrl;
this.checkPayStatus();
}
} catch (error) {
this.$util.showToast({ title: '支付宝支付失败:' + error.message });
this.repeatFlag = false;
}
break;
// ========== 微信支付H5端符合官方JSSDK+签名规范) ==========
case 'wechatpay':
try {
this.repeatFlag = false;
const orderInfo = {
outTradeNo: this.payInfo.out_trade_no,
productName: '订单支付',
price: this.payMoney,
openid: this.$store.state.openid,
notifyUrl: this.$config.h5Domain + '/api/pay/wechat/notify',
returnUrl: this.$config.h5Domain + return_url + this.payInfo.out_trade_no,
url: uni.getSystemInfoSync().platform == 'ios' ? uni.getStorageSync('initUrl') : location.href
};
// 微信浏览器内JSSDK支付
if (this.$util.isWeiXin()) {
// 初始化JSSDK官方规范
await this.wechatPay.initJSSDK(orderInfo.url);
// 创建微信订单带HMAC-SHA256签名
const wxPayRes = await this.wechatPay.mpPay(orderInfo);
// 验证签名(官方规范)
const isWxSignValid = this.wechatPay.verifyResult(wxPayRes, wxPayRes.paySign);
if (!isWxSignValid) {
this.$util.showToast({ title: '微信订单签名验证失败' });
return;
}
// 唤起JSSDK支付
await this.wechatPay.h5Pay(wxPayRes);
this.paySuccess();
} else {
// 普通浏览器H5支付链接
const wxH5Res = await this.wechatPay.h5Pay(orderInfo);
console.log('普通浏览器微信支付链接:', wxH5Res.mweb_url);
location.href = wxH5Res.mweb_url;
this.checkPayStatus();
}
} catch (error) {
this.$util.showToast({ title: '微信支付失败:' + error.message });
this.resetpay();
this.repeatFlag = false;
}
break;
// ========== 华为支付H5端符合官方RSA签名规范 ==========
case 'huaweipay':
try {
this.repeatFlag = false;
const orderInfo = {
productId: 'PROD_' + this.payInfo.out_trade_no,
productName: '订单支付',
price: this.payMoney
};
// 创建华为订单带RSA签名
const huaweiRes = await this.huaweiPay.h5Pay(orderInfo);
// 验证签名(官方规范)
const isHuaweiSignValid = this.huaweiPay.verifySignature(JSON.stringify(huaweiRes), huaweiRes.sign);
if (!isHuaweiSignValid) {
this.$util.showToast({ title: '华为订单签名验证失败' });
return;
}
console.log('华为支付跳转链接:', huaweiRes.payUrl);
location.href = huaweiRes.payUrl;
this.checkPayStatus();
} catch (error) {
this.$util.showToast({ title: '华为支付失败:' + error.message });
this.repeatFlag = false;
}
break;
// ========== 线下支付(保留原有逻辑) ==========
case 'offlinepay':
this.$util.redirectTo('/pages_tool/pay/offlinepay', {
outTradeNo: this.payInfo.out_trade_no
});
this.repeatFlag = false;
break;
}
} else {
this.$util.showToast({
title: res.message
});
this.repeatFlag = false;
}
},
fail: res => {
uni.hideLoading();
this.$util.showToast({
title: 'request:fail'
});
this.repeatFlag = false;
}
});
},
checkPayStatus() {
this.timer = setInterval(() => {
this.$api.sendRequest({
url: '/api/pay/status',
data: {
out_trade_no: this.payInfo.out_trade_no
},
success: res => {
if (res.code == 0) {
if (res.data.pay_status == 2) {
clearInterval(this.timer);
this.paySuccess();
}
} else {
clearInterval(this.timer);
}
}
});
}, 1000);
},
// #endif
// #ifdef MP
pay() {
var payType = this.payTypeList[this.payIndex];
this.$api.sendRequest({
url: '/api/pay/pay',
data: {
out_trade_no: this.payInfo.out_trade_no,
pay_type: payType ? payType.type : '',
scene: uni.getStorageSync('is_test') ? 1175 : uni.getLaunchOptionsSync().scene,
is_balance: this.isBalance
},
success: async res => { // 新增async支持异步调用
uni.hideLoading();
if (res.code >= 0) {
if (res.data.pay_success) {
this.paySuccess();
this.repeatFlag = false;
return;
}
if (payType.type == 'offlinepay') {
this.$util.redirectTo('/pages_tool/pay/offlinepay', {
outTradeNo: this.payInfo.out_trade_no
});
this.repeatFlag = false;
} else {
try {
this.repeatFlag = false;
// ========== 华为支付小程序端跳转H5 ==========
if (payType.type == 'huaweipay') {
const orderInfo = {
productId: 'PROD_' + this.payInfo.out_trade_no,
productName: '订单支付',
price: this.payMoney
};
const huaweiRes = await this.huaweiPay.mpWeixinPay(orderInfo);
this.$util.redirectTo('/pages_tool/pay/wx_pay', {
wx_alipay: encodeURIComponent(huaweiRes.payUrl),
out_trade_no: this.payInfo.out_trade_no
});
} else {
// ========== 微信/支付宝小程序支付(符合官方验签规范) ==========
var payData = res.data.data;
// 验证支付参数签名(官方规范)
let isSignValid = false;
if (payType.type == 'wechatpay') {
isSignValid = this.wechatPay.verifyResult(payData, payData.paySign);
} else if (payType.type == 'alipay') {
isSignValid = this.alipayPay.verifyResult(payData, payData.sign);
}
if (!isSignValid) {
this.$util.showToast({ title: '支付参数签名验证失败' });
return;
}
// #ifdef MP-WEIXIN
var scene = uni.getStorageSync('is_test') ? 1175 : uni.getLaunchOptionsSync().scene;
if ([1175, 1176, 1177, 1191, 1195].indexOf(scene) != -1) {
uni.requestOrderPayment({
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success: res => {
this.paySuccess();
this.repeatFlag = false;
},
fail: res => {
this.flag = false;
if (res.errMsg == 'requestOrderPayment:fail cancel') {
this.$util.showToast({
title: '您已取消支付'
});
this.resetpay();
this.repeatFlag = false;
} else {
uni.showModal({
content: '支付失败,失败原因: ' + res.errMsg,
showCancel: false
});
setTimeout(() => {
this.close();
this.repeatFlag = false;
}, 1500)
}
}
});
return
}
// #endif
uni.requestPayment({
provider: payType.provider,
...payData,
success: res => {
this.paySuccess();
this.repeatFlag = false;
},
fail: res => {
this.flag = false;
if (res.errMsg == 'requestPayment:fail cancel') {
this.$util.showToast({
title: '您已取消支付'
});
this.resetpay();
this.repeatFlag = false;
} else {
uni.showModal({
content: '支付失败,失败原因: ' + res.errMsg,
showCancel: false
});
setTimeout(() => {
this.close();
this.repeatFlag = false;
}, 1500)
}
}
});
}
} catch (error) {
this.$util.showToast({ title: '支付失败:' + error.message });
this.repeatFlag = false;
}
}
} else {
this.$util.showToast({
title: res.message
});
this.repeatFlag = false;
}
},
fail: res => {
uni.hideLoading();
this.$util.showToast({
title: 'request:fail'
});
this.repeatFlag = false;
}
});
},
// #endif
/**
* 支付成功之后跳转
*/
paySuccess() {
if (this.payInfo.event == 'BlindboxGoodsOrderPayNotify') {
this.$util.redirectTo('/pages_promotion/blindbox/index', {
outTradeNo: this.payInfo.out_trade_no
}, 'redirectTo');
} else if (this.payInfo.return_url) {
if (this.payInfo.return_url.indexOf('http://') != -1 || this.payInfo.return_url.indexOf('https://') != -1) location.replace(this.payInfo.return_url);
else this.$util.redirectTo(this.payInfo.return_url, {}, 'redirectTo');
} else {
this.$util.redirectTo('/pages_tool/pay/result', {
code: this.payInfo.out_trade_no
}, 'redirectTo');
}
},
/**
* 重置支付单据
*/
resetpay() {
this.resetPayComplete = false;
this.$api.sendRequest({
url: '/api/pay/resetpay',
data: {
out_trade_no: this.payInfo.out_trade_no,
},
success: res => {
if (res.code == 0) {
this.getPayInfo(res.data, () => {
this.resetPayComplete = true;
});
} else {
this.resetPayComplete = true;
}
},
fail: res => {
this.resetPayComplete = true;
}
})
}
},
// #ifdef H5
deactivated() {
clearInterval(this.timer);
},
// #endif
};
</script>
<style lang="scss" scoped>
.popup {
width: 75vw;
background: #fff;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
.popup-header {
display: flex;
border-bottom: 2rpx solid $color-line;
position: relative;
padding: 40rpx;
.tit {
flex: 1;
font-size: $font-size-toolbar;
line-height: 1;
text-align: center;
}
.iconfont {
line-height: 1;
position: absolute;
right: 30rpx;
top: 50%;
transform: translate(0, -50%);
color: $color-tip;
font-size: $font-size-toolbar;
}
}
.popup-body {
height: calc(100% - 250rpx);
&.safe-area {
height: calc(100% - 270rpx);
}
}
.popup-footer {
height: 100rpx;
.confirm-btn {
height: 72rpx;
line-height: 72rpx;
color: #fff;
text-align: center;
margin: 20rpx 30rpx 0;
border-radius: $border-radius;
}
&.bottom-safe-area {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
}
.choose-payment-popup {
.payment-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 90rpx;
margin: 0 30rpx;
border-bottom: 2rpx solid $color-line;
padding: 20rpx 0;
&:nth-child(2) {
padding-top: 0;
}
&:last-child {
border-bottom: none;
}
.iconfont {
font-size: 64rpx;
}
.icon-yue {
color: #faa218;
}
.icon-weixin1 {
color: #24af41;
}
.icon-yuezhifu {
color: #f9a647;
}
.icon-zhifubaozhifu- {
color: #00a0e9;
}
.icon-checkboxblank {
font-size: 40rpx;
color: $color-line;
}
.icon-yuan_checked {
font-size: 40rpx;
}
.name {
margin-left: 20rpx;
font-size: $font-size-base;
flex: 1;
}
.info-wrap {
flex: 1;
margin-left: 20rpx;
.name {
margin-left: 0;
font-size: $font-size-base;
flex: 1;
}
.money {
color: $color-tip;
font-size: $font-size-tag;
}
}
.box {
flex: 1;
padding: 0 10rpx;
line-height: inherit;
text-align: right;
input {
font-size: $font-size-tag !important;
}
}
&.set-pay-password {
height: initial;
.box {
font-size: $font-size-tag !important;
}
}
}
.pay-money {
text-align: center;
padding: 20rpx 0 40rpx 0;
background-color: #fff;
font-weight: bold;
margin-top: 30rpx;
line-height: 1;
.unit {
margin-right: 4rpx;
font-size: $font-size-tag;
}
.money {
font-size: $font-size-toolbar;
}
}
}
.empty {
width: 100%;
text-align: center;
padding: 40rpx 0;
color: $color-sub;
font-size: $font-size-tag;
}
</style>