Commit 94732e1b authored by 郭志伟's avatar 郭志伟

feat(pay): 支付相关功能调整

parent c9bc8ed6
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -3,45 +3,10 @@ import config from '@/config'; ...@@ -3,45 +3,10 @@ import config from '@/config';
import { saDeviceId } from '@/service/sa.service'; import { saDeviceId } from '@/service/sa.service';
const { talosHost } = config; const { talosHost } = config;
const queryPayInfo = function(data) {
return request.post(`${talosHost}/open/checkout`, data);
};
const prepay = async function(data) {
const scDeviceId = await saDeviceId();
return request.post(`${talosHost}/open/checkout/prepay`, data, {
customHeader: {
scDeviceId
},
hideToast: true
});
};
const pay = async function(data) {
const scDeviceId = await saDeviceId();
return request.post(`${talosHost}/open/checkout/pay`, data, {
customHeader: {
scDeviceId
},
hideToast: true
});
};
const queryPayStatus = function(data) { const queryPayStatus = function(data) {
return request.post(`${talosHost}/open/checkout/pay_status/query`, data); return request.post(`${talosHost}/open/checkout/pay_status/query`, data);
}; };
const sendSms = function(data) {
return request.post(`${talosHost}/open/checkout/send_sms`, data);
};
const desSalt = function() {
return request.get(`${talosHost}/vcc/account/salt`);
};
const h5AppyUrl = function() {
return request.get(`${talosHost}/vcc/xyqb_mall/app_url`);
};
const getCoupon = async function(params) { const getCoupon = async function(params) {
const scDeviceId = await saDeviceId(); const scDeviceId = await saDeviceId();
return request.post(`${talosHost}/open/checkout/pay_coupon_list`, params, { return request.post(`${talosHost}/open/checkout/pay_coupon_list`, params, {
...@@ -51,43 +16,4 @@ const getCoupon = async function(params) { ...@@ -51,43 +16,4 @@ const getCoupon = async function(params) {
}); });
}; };
const ocrFaceId = function(params) { export { getCoupon, queryPayStatus };
return request.post(`${talosHost}/open/checkout/ocr_faceId`, params);
};
const getGoodsList = function(data) {
return request.get(`${talosHost}/vcc/xyqb/recommend/goods-list`, data, {
needScDeviceId: true,
hideLoading: true
});
};
// KA流程节点
const kaGetNextUrl = function() {
return request.get(`${talosHost}/api/kdsp/ka/process/get-next-url`);
};
const reissueContract = function(params) {
return request.post(`${talosHost}/open/checkout/payReissueContract`, params);
};
// 获取用户手机号
const getPhoneNumber = function() {
return request.get(`${talosHost}/api/kdsp/user/phone`);
};
export {
pay,
prepay,
sendSms,
desSalt,
h5AppyUrl,
getCoupon,
ocrFaceId,
queryPayInfo,
getGoodsList,
kaGetNextUrl,
queryPayStatus,
reissueContract,
getPhoneNumber
};
...@@ -4,5 +4,6 @@ let payHost = protocol + '//mapi-qa.liangkebang.net/pay'; ...@@ -4,5 +4,6 @@ let payHost = protocol + '//mapi-qa.liangkebang.net/pay';
let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址 let shenceHost = 'https://bn.xyqb.com/sa?project=default'; // 测试地址
let talosHost = 'http://talos-test1.liangkebang.net'; // 电商分期测试环境服务地址 let talosHost = 'http://talos-test1.liangkebang.net'; // 电商分期测试环境服务地址
let operatorHost = 'https://operator.liangkebang.com'; let operatorHost = 'https://operator.liangkebang.com';
const phobosHost = 'http://192.168.24.158:8080';
const toBHost = 'https://tob.liangkebang.net'; const toBHost = 'https://tob.liangkebang.net';
export default { talosHost, operatorHost, payHost, shenceHost, test: true, toBHost }; export default { talosHost, operatorHost, payHost, shenceHost, test: true, toBHost, phobosHost };
...@@ -5,6 +5,7 @@ const operatorHost = protocol + '//auth.quantgroup.cn'; ...@@ -5,6 +5,7 @@ const operatorHost = protocol + '//auth.quantgroup.cn';
const payHost = protocol + '//payapi.xyqb.com'; const payHost = protocol + '//payapi.xyqb.com';
const shenceHost = 'https://bn.xyqb.com/sa?project=production'; const shenceHost = 'https://bn.xyqb.com/sa?project=production';
const toBHost = 'https://tob.liangkebang.net'; const toBHost = 'https://tob.liangkebang.net';
const phobosHost = 'https://phobos.q-gp.com';
export default { export default {
// apiHost, // apiHost,
test: false, test: false,
...@@ -12,5 +13,6 @@ export default { ...@@ -12,5 +13,6 @@ export default {
talosHost, talosHost,
payHost, payHost,
toBHost, toBHost,
phobosHost,
operatorHost operatorHost
}; };
import cfg from '@/config/index';
import localStorage from '@/service/localStorage.service';
export default {
methods: {
toPay(orderNo) {
const redirectUrl = window.location.origin;
// this.$router.push({ name: 'pay', query: { orderNo: data.orderNo } });
window.location.replace(
`${cfg.phobosHost}?orderNo=${orderNo}&vccToken=${localStorage.get(
'vccToken'
)}&vccChannel=${localStorage.get('vccChannel')}&hideOrder=true&redirectUrl=${redirectUrl}`
);
}
}
};
...@@ -2,14 +2,12 @@ ...@@ -2,14 +2,12 @@
* @Description: 数据加密,aes加密数据主体,rsa加密aes密钥,aes密钥前端自己存储 * @Description: 数据加密,aes加密数据主体,rsa加密aes密钥,aes密钥前端自己存储
* @Date: 2020-12-08 11:08:28 * @Date: 2020-12-08 11:08:28
* @LastEditors: gzw * @LastEditors: gzw
* @LastEditTime: 2021-07-07 17:44:22 * @LastEditTime: 2021-10-25 18:04:59
*/ */
import { cipher as AES, util as UTIL, pki as PKI, md as SHA1 } from 'node-forge'; import { cipher as AES, util as UTIL, pki as PKI, md as SHA1 } from 'node-forge';
import uuidv1 from 'uuid/v1'; import uuidv1 from 'uuid/v1';
import { parseTime } from './utils.service'; import { parseTime } from './utils.service';
import { APP_ID, PUBLIC_KEY, PRIVATE_KEY } from '@/config/encrypt.config'; import { APP_ID, PUBLIC_KEY, PRIVATE_KEY } from '@/config/encrypt.config';
import CryptoJS from 'crypto-js';
import { desSalt } from '@/api/pay.api';
/** /**
* @description: 数据加密 * @description: 数据加密
...@@ -33,21 +31,6 @@ export function encryption(data = '') { ...@@ -33,21 +31,6 @@ export function encryption(data = '') {
}; };
} }
/**
* @description: 数据加密
* @message {String} message 数据源
* @return {String} 加密后的数据16进制
*/
export async function encryptByDESModeEBC(message) {
const [{ payPwdSalt }] = await desSalt();
var keyHex = CryptoJS.enc.Utf8.parse(payPwdSalt);
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString().toLocaleUpperCase();
}
/** /**
* @description: AES加密数据,默认CBC, Pki#cs7 * @description: AES加密数据,默认CBC, Pki#cs7
* @param {String} txt 数据源 * @param {String} txt 数据源
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
</div> </div>
</template> </template>
<script> <script>
import toPayMixins from '@/mixins/toPay.mixins';
import api from '@/api/recharge.api'; import api from '@/api/recharge.api';
import orderApi from '@/api/order.api'; import orderApi from '@/api/order.api';
import RechargeTop from './components/RechargeTop.vue'; import RechargeTop from './components/RechargeTop.vue';
...@@ -41,6 +42,7 @@ export default { ...@@ -41,6 +42,7 @@ export default {
RechargeList, RechargeList,
RechargeInput RechargeInput
}, },
mixins: [toPayMixins],
data() { data() {
return { return {
vipLife: [], vipLife: [],
...@@ -102,7 +104,7 @@ export default { ...@@ -102,7 +104,7 @@ export default {
if (error) { if (error) {
return; return;
} }
res?.orderNo && this.$router.push({ path: '/pay', query: { orderNo: res.orderNo } }); res?.orderNo && this.toPay(res.orderNo);
} }
} }
}; };
......
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
plain plain
type="primary" type="primary"
shape="circle" shape="circle"
@click="toPay" @click="toPay(orderNo)"
> >
付款</cr-button 付款</cr-button
> >
...@@ -127,6 +127,7 @@ ...@@ -127,6 +127,7 @@
import ListItem from '../orderList/components/ListItem.vue'; import ListItem from '../orderList/components/ListItem.vue';
import { setClipboardData } from '@/service/utils.service'; import { setClipboardData } from '@/service/utils.service';
import { isBack } from '@/service/routerStorage'; import { isBack } from '@/service/routerStorage';
import toPayMixins from '@/mixins/toPay.mixins';
import orderApi from '@/api/order.api'; import orderApi from '@/api/order.api';
import img11 from '@/assets/images/order/11.png'; import img11 from '@/assets/images/order/11.png';
import img21 from '@/assets/images/order/21.png'; import img21 from '@/assets/images/order/21.png';
...@@ -145,6 +146,7 @@ export default { ...@@ -145,6 +146,7 @@ export default {
components: { components: {
ListItem ListItem
}, },
mixins: [toPayMixins],
data() { data() {
return { return {
orderNo: '', orderNo: '',
...@@ -216,9 +218,6 @@ export default { ...@@ -216,9 +218,6 @@ export default {
this.$set(this.orderInfo.orderStatusInfo, `orderStatusText`, '交易关闭'); this.$set(this.orderInfo.orderStatusInfo, `orderStatusText`, '交易关闭');
} }
}, },
toPay() {
this.$router.push({ path: '/pay', query: { orderNo: this.orderNo } });
},
toGoods() { toGoods() {
this.$router.push({ this.$router.push({
path: '/vipLife' path: '/vipLife'
......
...@@ -24,6 +24,7 @@ import orderApi from '@/api/order.api'; ...@@ -24,6 +24,7 @@ import orderApi from '@/api/order.api';
import List from './components/List'; import List from './components/List';
import { isApp } from '@/service/validation.service'; import { isApp } from '@/service/validation.service';
import Cookie from '@/service/cookieStorage.service'; import Cookie from '@/service/cookieStorage.service';
import toPayMixins from '@/mixins/toPay.mixins';
const commonParams = { const commonParams = {
loading: false, loading: false,
finished: false, finished: false,
...@@ -36,6 +37,7 @@ export default { ...@@ -36,6 +37,7 @@ export default {
components: { components: {
List List
}, },
mixins: [toPayMixins],
data() { data() {
return { return {
tabOffset: isApp || Cookie.get('h') == '0' ? 0 : 48, tabOffset: isApp || Cookie.get('h') == '0' ? 0 : 48,
...@@ -94,7 +96,7 @@ export default { ...@@ -94,7 +96,7 @@ export default {
this.orderCancelPopup(); this.orderCancelPopup();
break; break;
case 'pay': case 'pay':
this.toPay(); this.toPay(this.currentOrder.orderNo);
break; break;
case 'notify': case 'notify':
this.orderNotify(); this.orderNotify();
...@@ -107,10 +109,6 @@ export default { ...@@ -107,10 +109,6 @@ export default {
break; break;
} }
}, },
toPay() {
this.$router.push({ path: '/pay', query: { orderNo: this.currentOrder.orderNo } });
},
toGoods() { toGoods() {
this.$router.push({ this.$router.push({
path: '/vipLife' path: '/vipLife'
......
<template>
<div class="contract">
<cr-checkbox
v-model="isCheck"
shape="round"
class="contract-checkbox"
btn-size="mini"
@change="change"
/>
<p class="contract-list">
勾选同意
<span
v-for="contract in contractList"
:key="contract.contractName"
@click="goview(contract.contractVisitUrl)"
>
{{ contract.contractName }}</span
>
</p>
</div>
</template>
<script>
export default {
name: 'Contract',
props: {
contractList: Array,
value: Boolean
},
data() {
return {
isCheck: this.value
};
},
methods: {
goview(url) {
window.location.href = url;
},
change(e) {
this.$emit('input', e);
}
}
};
</script>
<style lang="less">
.contract {
width: 100%;
position: relative;
display: flex;
align-items: flex-start;
color: @gray-5;
.text-10;
&-list {
color: @gray-5;
span {
color: @red;
}
}
}
</style>
<template>
<div class="pay-type-list">
<p class="type-title">{{ value.title }}</p>
<payGroupItem
v-if="value.isGroupPay"
v-model="payList"
:pay-type="payType"
:disabled="disabled"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:coupon-disabled="showCoupon && !payCouponCouldBeUsed"
/>
<template v-else>
<template v-for="(item, key) of payList">
<payCardItem
v-if="item.show"
:key="key"
:pay-type="payType"
:value="item"
:disabled="disabled"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:coupon-disabled="showCoupon && !payCouponCouldBeUsed"
/>
</template>
</template>
</div>
</template>
<script>
import payCardItem from './PayCardItem';
import payGroupItem from './PayGroupCard.vue';
export default {
name: 'PayCard',
components: {
payCardItem,
payGroupItem
},
provide() {
return {
payCard: this
};
},
props: {
value: Object,
payType: Number,
payText: String,
disabled: Boolean,
couponInfo: Object,
riskLimit: Boolean,
showCoupon: Boolean,
payCouponCouldBeUsed: Boolean,
single: { type: Boolean, default: false },
creditPay: { type: Boolean, default: false }
},
data() {
return {
payList: this?.value?.payList || {}
};
},
mounted() {},
methods: {}
};
</script>
<style lang="less">
.pay-type-list {
background-color: #fff;
border-radius: @border-radius-sm;
margin-top: @padding-sm;
.type-title {
height: 48px;
.text-16;
font-weight: bold;
padding-left: @padding-md;
position: relative;
line-height: 48px;
}
}
</style>
<template>
<div class="payCardItem">
<div
:class="['type-item', { 'b-t': !isGroupPay || value.payType === 1 }]"
@click="changePayType(value)"
>
<div class="type-item-content">
<div class="type-item-content-info">
<cr-image :src="value.icon" class="icon" mode="aspectFit" width="39" height="39" />
<div class="content-info-con">
<div class="content-info-tit">
<div class="content-info-tit-wrap">
<p class="content-info-tit">{{ value.name }}</p>
<p v-if="value.tagName" class="content-info-tit_tag">{{ value.tagName }}</p>
</div>
<p v-if="isGroupPay && value.payAmt" class="content-info-amount">
{{ value.payAmt }}
</p>
</div>
<p>{{ value.accountStatusDesc }}</p>
</div>
</div>
<div v-if="showCoupon && value.payType === 1" class="coupon">
<p class="couponDes" :class="{ disable: couponDisabled }" @click.stop="openCouponModal">
<span v-if="couponInfo.pickupId">
{{ `满${couponInfo.limitAmountNew}减${couponInfo.faceValueNew}元` }}
</span>
<span v-else>
未选择优惠券
</span>
<cr-icon type="arrow" class="selectArrow" size="15px" />
</p>
</div>
<p v-if="value.riskLimitDesc" class="limitDes">
{{ value.riskLimitDesc }}
</p>
</div>
<cr-checkbox
v-if="!isGroupPay"
v-model="value.isCheck"
shape="round"
class="type-item-checkbox"
checked-color="#EC1500"
:disabled="disabled || value.disabled"
@click.native="changePayType(value)"
/>
</div>
</div>
</template>
<script>
export default {
name: 'PayCardItem',
inject: ['payCard', 'pay'],
props: {
value: Object,
payType: Number,
disabled: Boolean,
couponInfo: Object,
riskLimit: Boolean,
showCoupon: Boolean,
isGroupPay: Boolean,
couponDisabled: Boolean
},
methods: {
changePayType({ payType, mergePayPretreatmentInfo }) {
if (this.disabled || this.value.disabled || this.isGroupPay) {
return;
}
this.$emit('click');
this.pay.changePayType(payType, mergePayPretreatmentInfo);
},
openCouponModal() {
if (this.couponDisabled) {
this.$toast('优惠券不可更改');
return;
}
this.pay.openCouponModal(this.pay.orderNo);
}
}
};
</script>
<style lang="less" scoped>
.payCardItem {
width: 100%;
padding-bottom: 2px;
}
.coupon {
margin-top: 4px;
height: 20px;
padding-left: 51px;
display: flex;
}
.couponDes {
display: inline-block;
color: @text-color-red;
.text-12;
border: @border-width-base solid @text-color-light;
border-radius: 3px;
padding: 0 0 0 3px;
&.disable {
color: @font-color-disabled;
border-color: @font-color-disabled;
}
text {
color: @text-color-red;
.text-12;
&.disable {
color: @font-color-disabled;
}
}
}
.limitDes {
color: #ed6a0c;
background: #fffbe8;
.text-11;
padding: 4px 8px;
margin: 8px 8px 8px 4px;
border-radius: 3px;
}
.type-item {
width: 100%;
padding: 12px 8px 12px 12px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&-content {
width: 100%;
}
&-content-info {
width: 100%;
display: flex;
align-items: center;
}
&-checkbox {
width: 18px;
}
}
.icon {
width: 39px;
height: 39px;
margin-right: @padding-sm;
}
.content-info-tit {
font-size: 15px;
color: @font-color-dark;
margin-bottom: 2px;
display: flex;
align-items: center;
justify-content: space-between;
&-wrap {
display: flex;
}
&_tag {
display: inline-flex;
align-items: center;
height: 16px;
border: @border-width-base solid @text-color-light;
border-radius: 3px;
font-size: 11px;
color: @text-color-light;
margin-left: @padding-unit;
padding: 0 2px;
}
}
.content-info-con {
flex: 1;
display: flex;
flex-direction: column;
.text-12;
color: @font-color-light;
}
.content-info-amount {
width: 100px;
text-align: left;
word-break: break-all;
&::before {
content: '¥';
}
.text-15;
color: @black;
}
.selectArrow {
vertical-align: text-top;
margin-left: -2px;
}
</style>
<template>
<div>
<div @click="changePayType(thirdPayInfo)">
<div class="groupCard">
<PayCardItem
:is-group-pay="true"
:value="creditPayInfo"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
:coupon-disabled="couponDisabled"
/>
</div>
<div class="dashed">
<p class="dashed-line" />
<cr-image
src="../../../assets/images/addicon.png"
width="24px"
height="24px"
class="dashed-icon"
/>
<cr-checkbox
v-model="thirdPayInfo.isCheck"
shape="round"
checked-color="#EC1500"
:disabled="disabled || thirdPayInfo.disabled"
class="dashed-checkbox"
@click.native="changePayType(thirdPayInfo)"
/>
</div>
<div class="groupCard">
<PayCardItem :is-group-pay="true" :value="thirdPayInfo" />
</div>
</div>
<p class="group-more b-t" @click="openMore">更多支付组合<cr-icon type="arrow" size="15px" /></p>
<cr-popup v-model="morePopup" round position="bottom" :style="{ height: '30%' }" closeable>
<p class="more-title">更换支付组合</p>
<PayCardItem
v-for="item in thirdPayList"
:key="item.payType"
:value="item"
:coupon-info="couponInfo"
:show-coupon="showCoupon"
:risk-limit="riskLimit"
@click="morePopup = false"
/>
</cr-popup>
</div>
</template>
<script>
import PayCardItem from './PayCardItem.vue';
export default {
name: 'PayGroupCard',
inject: ['payCard', 'pay'],
components: {
PayCardItem
},
props: {
value: Object,
payType: Number,
disabled: Boolean,
couponInfo: Object,
showCoupon: Boolean,
riskLimit: Boolean,
couponDisabled: Boolean
},
data() {
return {
morePopup: false,
thirdPayList: [],
creditPayInfo: this.value.creditPayInfo || {}
};
},
computed: {
thirdPayInfo: function() {
let temp = {};
for (const key in this.value) {
if (this.value[key].isRecommend || this.value[key].isCheck) {
const mergePayPretreatmentInfo = this.value[key]?.mergePayPretreatmentInfo || {};
temp = {
...this.value[key],
payAmt: mergePayPretreatmentInfo?.otherPayAmt
};
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.creditPayInfo.payAmt = mergePayPretreatmentInfo.creditPayAmt;
}
}
return temp;
}
},
mounted() {
for (const key in this.value) {
if (this.value[key].isGroupPay) {
this.thirdPayList.push(this.value[key]);
}
}
},
methods: {
changePayType({ payType, mergePayPretreatmentInfo }) {
if (this.disabled || this.creditPayInfo.disabled) {
return;
}
this.pay.changePayType(payType, mergePayPretreatmentInfo);
},
openMore() {
this.morePopup = true;
}
}
};
</script>
<style lang="less">
.groupCard {
width: 100%;
}
.group-more {
.text-13;
width: 335px;
height: 36px;
margin: auto;
display: flex;
color: #ec1500;
position: relative;
align-items: center;
justify-content: center;
}
.cr-popup--close-top-right {
z-index: 2;
}
.dashed {
width: 100%;
padding: 0 12px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&-line {
width: 310px;
border-bottom: 1px dashed #dcdcdc;
}
&-checkbox {
width: 18px;
}
&-icon {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
}
.more-title {
.text-16;
display: flex;
justify-content: center;
align-items: center;
height: 48px;
color: @black;
position: relative;
}
</style>
<template>
<cr-overlay :show="value">
<div class="sms-modal">
<div class="sms">
<p class="sms-icon"><cr-icon type="cross" color="#999999" @click="closeModal" /></p>
<p class="sms-title">请输入短信验证码</p>
<p class="sms-des">为保证您账户安全,此笔交易需要短信验证</p>
<p class="phone">已发送至 {{ phoneNumber }}</p>
<cr-authcode-field
span-size="24px"
type="number"
border-type="bottom"
:number="6"
height="40px"
span-color="#666"
input-color="#666"
input-size="20px"
class="sms-input"
:success="success"
/>
</div>
<div slot="footer" class="sms-action">
<p v-if="errorInfo" class="sms-error">{{ errorInfo }}</p>
<p v-if="!send" class="sms-set" @click="sendSms()">获取验证码</p>
<p v-else class="sms-set grey">{{ time }}s后重新获取验证码</p>
</div>
</div>
</cr-overlay>
</template>
<script>
import { sendSms as sendSmsApi, getPhoneNumber } from '@/api/pay.api';
export default {
props: {
value: {
type: Boolean,
default: true
},
orderNo: String,
flowOrderNo: String,
errorInfo: String
},
data() {
return {
send: false,
time: 60,
timer: null,
phoneNumber: ''
};
},
computed: {},
watch: {
value: function(val) {
if (val) {
this.sendSms(1);
}
},
errorInfo: function() {}
},
created() {
this.getPhone();
},
methods: {
closeModal() {
this.clearTimer();
this.$emit('close');
},
success(smsCode) {
this.$emit('submit', smsCode);
},
async sendSms() {
// 页面摧毁清掉定时器
if (this.send) return;
const [, error] = await sendSmsApi({ orderNo: this.orderNo, flowOrderNo: this.flowOrderNo });
if (!error) {
this.send = true;
this.timer = setInterval(() => {
--this.time;
if (!this.time) {
this.clearTimer();
}
}, 1000);
}
},
async getPhone() {
const [phoneNumber] = await getPhoneNumber();
this.phoneNumber = phoneNumber;
},
clearTimer() {
clearInterval(this.timer);
this.send = false;
this.time = 60;
}
}
};
</script>
<style lang="less">
.sms-modal {
box-sizing: border-box;
position: absolute;
left: 50%;
top: 26%;
margin-left: -150px;
padding: 12px 18px 24px;
width: 300px;
border-radius: 16px;
background: #fff;
}
.sms {
&-icon {
width: 100%;
height: 20px;
line-height: 20px;
text-align: right;
}
&-title {
text-align: center;
width: 100%;
font-size: 16px;
font-weight: bold;
}
&-des {
padding: 18px 0 0 8px;
display: flex;
justify-content: flex-start;
font-size: 12px;
color: #999999;
}
&-input {
margin: auto;
}
.phone {
padding-left: 8px;
margin-top: 8px;
color: #323233;
font-size: 14px;
}
&-sms {
margin-top: 4px;
font-size: 30px;
color: #333333;
&:before {
content: '¥';
.text-12;
}
}
&-input {
margin: 20px 0;
}
}
.sms-action {
width: 100%;
display: flex;
justify-content: space-between;
p {
font-size: 14px;
color: #666666;
}
.sms-error {
flex: 1;
color: #ee0a24;
}
.sms-set {
flex: 1;
text-align: right;
}
.grey {
color: #999999;
}
}
</style>
@import url('../../style/mixins');
.pay {
padding: @padding-xs;
}
.price-box {
background-color: @white;
height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.text-13;
color: @black;
border-radius: @border-radius-sm;
.price{
.text-30;
color: @font-color-red;
font-weight: bold;
margin-top: @padding-xs;
&:before{
content: '¥';
.text-14;
font-weight: bold;
}
}
.time{
display: flex;
align-items: center;
margin-top: @padding-unit;
}
}
.payBtn{
width: 100%;
position: fixed;
padding: @padding-xs @padding-lg;
box-sizing: border-box;
z-index: 99;
bottom: 0;
left:0;
background-color:@white;
.iphonex-fix-margin;
button{
.text-16;
width: 100%;
color: @white;
font-size: @line-height-sm;
border-radius: @border-radius-lx;
span{
font-weight: bold;
}
}
button[disabled] {
color: rgba(255,255,255,.6);
}
&-primary {
@primary-bg();
}
}
.btn{
height:50px;
width: 100%;
position: fixed;
padding: 0 @padding-lg;
box-sizing: border-box;
bottom: 0;
left:0;
background-color:@white;
display: flex;
align-items: center;
justify-content: center;
button{
flex:1;
color: @white;
font-size: @line-height-sm;
border-radius: @border-radius-lx;
}
button[disabled] {
color: rgba(255,255,255,.6);
}
&-primary {
@primary-bg();
}
&-default{
border: 1px solid @cherry-color-error;
color: @cherry-color-error !important;
margin-right: @padding-xs;
background-color: @white !important;
}
}
.placeholder{
width: 100%;
height: 100px;
}
\ No newline at end of file
<template>
<div class="pay">
<!-- 支付倒计时 -->
<div v-if="success" class="price-box">
<p class="price">{{ displayInfo.orderAmt }}</p>
<p v-if="!overtime" class="time">
支付剩余时间:
<cr-count-down :time="displayInfo.periodSeconds" @finish="timeup" />
</p>
<p v-else>支付超时,订单关闭</p>
</div>
<!-- 推荐信用支付 -->
<cr-pay-card
v-if="getObjectKey(creditPayList.payList).length"
v-model="creditPayList"
:pay-type="payType"
:disabled="overtime"
:risk-limit="riskLimit"
:show-coupon="showCoupon"
:coupon-info="selectedCoupon"
:has-pwd="creditPayInfo.hasPwd"
:pay-coupon-could-be-used="
displayInfo.payCouponInfo && displayInfo.payCouponInfo.payCouponCouldBeUsed
"
@changePayType="changePayType"
/>
<!-- 其他支付方式 -->
<cr-pay-card
v-if="getObjectKey(thirdPayList.payList).length"
v-model="thirdPayList"
:pay-type="payType"
:disabled="overtime"
:show-coupon="showCoupon"
:coupon-info="selectedCoupon"
:has-pwd="creditPayInfo.hasPwd"
:pay-coupon-could-be-used="
displayInfo && displayInfo.payCouponInfo && displayInfo.payCouponInfo.payCouponCouldBeUsed
"
@changePayType="changePayType"
/>
<!-- 支付按钮 -->
<p v-if="isReady && !overtime" class="payBtn">
<cr-contract
v-if="isShowProtocol"
ref="agreement"
v-model="isCheckAgreement"
:contract-list="payContractInfo.contractInfos"
/>
<cr-button type="primary" class="btn-primary" :disabled="!payType" @click="nextAction">
{{ accountS.text }}
</cr-button>
</p>
<p v-if="overtime" class="btn">
<cr-button v-if="isOrder" type="default" class="btn-default" @click="goOrderList"
>我的订单</cr-button
>
<cr-button type="primary" class="btn-primary" @click="goHome">返回商城</cr-button>
</p>
<p class="placeholder" />
<!-- 支付密码弹窗 -->
<cr-pwd-field
ref="pwd"
v-model="pwdModal"
:amount="amount"
@getData="getPwd"
@retrieveLink="retrieveLink"
/>
<!-- 短信验证弹窗 -->
<cr-sms-code-modal
v-model="smsModal"
:error-info="error"
:order-no="orderNo"
:flow-order-no="flowOrderNo"
@close="smsModal = false"
@submit="getSms"
/>
<!-- 享花券弹窗 -->
<cr-popup v-model="couponPopup" closeable round position="bottom">
<cr-coupon-list
no-render-disable-tab
:show-coupon="couponPopup"
:enable-list="payCouponList"
:value="selectedCoupon.id"
@change="handleSelectCoupon"
/>
</cr-popup>
</div>
</template>
<script>
import crPayCard from './components/PayCard';
import { payByWay } from '@/service/pay';
import crContract from './components/Contract.vue';
import crSmsCodeModal from './components/SmsModal';
import { isWechat } from '@/service/validation.service';
import { encryptByDESModeEBC } from '@/service/encrypt';
import localStorage from '@/service/localStorage.service';
import { throttle } from '@/service/utils.service';
import { goUrlExtends } from './extends';
import cookies from '@/service/cookieStorage.service';
import {
codeArr,
CREDIT_PAY,
PAY_SUCCESS,
creditStatus,
IS_THIRD_PAY,
IS_GROUP_PAY,
IS_CREDIT_PAY,
isDetentionFn,
havePayingOrder,
filterAllPayList,
PAYMENT_CODE_PAY, // 支付密码
ACCOUNT_APPLY_FAIL,
creditPayStatusType,
ACCOUNT_APPLY_SUCCESS,
SMS_VERIFICATION_CODE_PAY, //短信验证码
FACE_VERIFICATION_CODE_PAY
} from './STATIC_DATA';
import {
pay,
prepay,
getCoupon,
h5AppyUrl,
ocrFaceId,
queryPayInfo,
kaGetNextUrl,
reissueContract
} from '@/api/pay.api.js';
const VCC_CHANNEL = localStorage.get('vccChannel') || '';
let Current_Url = null;
export default {
components: {
crPayCard,
crContract,
crSmsCodeModal
},
provide() {
return {
pay: this
};
},
extends: goUrlExtends,
data() {
return {
isOrder: true,
error: '',
random: '',
orderNo: '',
payInfo: {},
success: false,
payType: null,
isReady: false,
overtime: false,
pwdModal: false,
smsModal: false,
showCoupon: false,
flowOrderNo: null,
couponPopup: false,
showGoFaceTip: false, // h5支付活体提示怎么处理
payTypeList: {},
creditPayList: {
title: '推荐信用支付',
payList: {}
},
thirdPayList: {
title: '其他支付方式',
payList: {}
},
displayInfo: {
payCouponInfo: {}
},
creditPayInfo: {},
accountS: {
text: '确认支付',
fn: this.pay
},
payCouponList: [],
isDetention: false,
selectedCoupon: {},
payContractInfo: {},
isCheckAgreement: false,
mergePayPretreatmentInfo: {},
tradeType: isWechat ? 'JSAPI' : 'MWEB'
};
},
computed: {
riskLimit() {
return this.isRiskLimit();
},
amount() {
return IS_THIRD_PAY(this.payType) || !this.selectedCoupon.faceValueNew
? this.displayInfo.orderAmt
: (this.displayInfo.orderAmt - this.selectedCoupon.faceValueNew).toFixed(2);
},
isShowProtocol() {
return (
!IS_THIRD_PAY(this.payType) &&
this.payContractInfo.payContractIsSign &&
this.creditPayInfo.accountStatus === ACCOUNT_APPLY_SUCCESS
);
},
canUseAmount() {
return (
((this.selectedCoupon?.faceValueNew && +this.selectedCoupon.faceValueNew) || 0) +
((this.displayInfo?.creditPayInfo?.canAmt && +this.displayInfo.creditPayInfo.canAmt) || 0) -
((this.displayInfo?.orderAmt && +this.displayInfo.orderAmt) || 0)
);
}
},
watch: {
payType() {
this.getBtnStatus(this.isRiskLimit());
}
},
mounted() {
this.getQuery();
this.setIsOrder();
this.orderNo = this.$route.query.orderNo || cookies.get('orderNo')?.orderNo;
cookies.set('orderNo', { orderNo: this.orderNo });
if (this.$route?.query?.ocrflag) {
this.payInfo = cookies.get('info') || {};
cookies.remove('ocrflag');
cookies.remove('info');
// 先支付再查询信息
this.pay(this.payInfo?.paramsData, 'ocr', cookies.get('ocrflag'));
}
!this.$route?.query?.ocrflag && this.$track.registeredEvents('h5_CheckOutCouterExposure');
const vccToken = localStorage.get('vccToken');
Current_Url = `${window.location.origin}/payWaiting?vccToken=${vccToken}&orderNo=${this.orderNo}`;
this.queryPayInfo();
this.getCouponList(this.orderNo);
},
methods: {
getQuery() {
this.returnUrl = cookies.get('returnUrl') || '';
if (localStorage.get('hideOrder')) {
this.isOrder = false;
}
},
setIsOrder() {
const { hideOrder } = this.$route.query || {};
if (hideOrder) {
localStorage.set('hideOrder', hideOrder);
}
},
/* 查询支付信息 */
async queryPayInfo() {
const [data, error] = await queryPayInfo({
orderNo: this.orderNo,
isUsedMergePayMethod: cookies.get('source') !== 'tob' // 是否需要组合支付,true需要。false不需要
});
if (error && codeArr.indexOf(error?.response?.businessCode) < 0) {
this.payResult('Fail');
return;
}
this.isReady = true;
const { displayInfo = {}, payMethods, creditQuotaInfo } = data || {};
this.overtime = displayInfo.periodSeconds <= 0;
this.success = true;
if (this.overtime) {
this.displayInfo.orderAmt = displayInfo.orderAmt;
return;
}
if (displayInfo.payCouponInfo && displayInfo.payCouponInfo.optimalPayCoupon) {
this.selectedCoupon = displayInfo.payCouponInfo.optimalPayCoupon;
this.selectedCoupon.id = this.selectedCoupon.pickupId;
this.showCoupon = displayInfo.payCouponInfo.optimalType === 3;
}
this.payContractInfo = displayInfo.payContractInfo || {};
for (const p of payMethods) {
// 默认支付方式
if (p.isDefault) {
this.payType = p.method;
}
for (const key in displayInfo) {
const payType = displayInfo[key].payType;
if (!payType) {
continue;
}
/* 三方支付 */
if (IS_THIRD_PAY(p.method) && p.method === payType) {
this.$set(this.thirdPayList.payList, key, {
...displayInfo[key],
...p,
show: true,
isCheck: p.isDefault || false
});
break;
}
/* 信用支付 */
if (
IS_CREDIT_PAY(p.method) &&
(p.method === payType || (p.method > 3 && p.method - 2 === payType))
) {
this.creditPayList.isGroupPay = IS_GROUP_PAY(p.method);
if (p.isDefault) {
this.mergePayPretreatmentInfo = p.mergePayPretreatmentInfo;
}
this.$set(this.creditPayList.payList, key, {
...displayInfo[key],
...p,
show: true,
payType: p.method,
isCheck: p.isDefault || false,
isGroupPay: IS_GROUP_PAY(p.method)
});
if (!('creditPayInfo' in this.creditPayList.payList)) {
this.$set(this.creditPayList.payList, 'creditPayInfo', {
...displayInfo['creditPayInfo'],
show: true,
isCheck: false
});
}
break;
}
}
}
if (this.payType === CREDIT_PAY && displayInfo.creditPayInfo.disabled) {
this.payType = null;
}
this.displayInfo = displayInfo || {};
this.creditPayInfo = displayInfo.creditPayInfo || {};
displayInfo.periodSeconds = displayInfo.periodSeconds * 1000;
this.getBtnStatus(this.isRiskLimit());
if (this.payInfo?.payType) {
this.selectedCoupon = this.payInfo.selectedCoupon;
this.changePayType(this.payInfo.payType, this.payInfo.mergePayPretreatmentInfo);
this.isShowProtocol && (this.isCheckAgreement = true);
this.setAmount();
}
if (creditQuotaInfo?.isFreezeCreditInOtherOrder && !this.payInfo?.payType) {
havePayingOrder.call(this);
}
},
isRiskLimit() {
const { accountStatus, riskManagementAmt } = this.creditPayInfo;
const { orderAmt } = this.displayInfo;
if (accountStatus === ACCOUNT_APPLY_SUCCESS) {
const coupon = +this.selectedCoupon.faceValueNew || 0;
return +riskManagementAmt + coupon < +orderAmt;
} else {
return false;
}
},
getBtnStatus(isRiskLimit) {
const { accountStatus } = this.creditPayInfo;
if (!accountStatus) return;
if (IS_CREDIT_PAY(this.payType)) {
// payType=1--享花卡支付,payType=2微信支付
if (accountStatus === ACCOUNT_APPLY_FAIL) {
this.accountS = {
text: '立即激活',
fn: this.goApply
};
} else if (creditStatus.indexOf(accountStatus) > -1) {
this.accountS = {
text: '立即开通',
fn: this.goApply
};
} else if (isRiskLimit) {
// 展示限额提示
this.accountS = {
text: '认证提额',
fn: this.goOcr
};
} else if (accountStatus === ACCOUNT_APPLY_SUCCESS) {
const finalCount = this.showCoupon
? (this.displayInfo.orderAmt - (this.selectedCoupon.faceValueNew || 0)).toFixed(2)
: this.displayInfo.orderAmt;
const txt = `实际支付${finalCount || '0.00'}`;
this.accountS = {
text: txt,
fn: this.pay
};
}
} else {
const txt = `确认支付`;
this.accountS = {
text: txt,
fn: this.pay
};
}
},
/* 获取优惠券信息 */
async getCouponList(orderNo) {
const [data] = await getCoupon({ orderNo: orderNo });
this.payCouponList = [];
if (data && data.coupons) {
data.coupons.forEach(item => {
if (item.couponCategory === 21) {
this.payCouponList.push({
...item,
id: item.pickupId,
pickupAble: 1
});
}
});
}
},
nextAction: throttle(function() {
this.$track.registeredEvents('h5_CheckOutCounterConfirmPayClick', {
order_id: this.orderNo,
pay_method: this.payType,
vcc_state: !IS_THIRD_PAY(this.payType) ? this.creditPayInfo?.accountStatus : '',
buttons_name: this.accountS.text
});
if (this.isShowProtocol) {
if (!this.isCheckAgreement) {
this.$toast('请仔细阅读并同意相关协议');
return;
}
this.reissueContract();
}
this.accountS.fn();
}, 1000),
/* 预支付 */
async pay(params, isOcr) {
// 组合支付的提示和享花卡支付逻辑
if (this.overtime) {
this.$toast('订单已超时!');
return;
}
this.error = '';
this.setAmount();
if (!params && !this.isDetention && IS_THIRD_PAY(this.payType) && !isOcr) {
this.isDetention = true;
isDetentionFn.call(this);
return;
}
this.isDetention = false;
/* 设置密码 */
if (IS_CREDIT_PAY(this.payType) && !this.creditPayInfo.hasPwd) {
this.retrieveLink();
return;
}
if (this.payType === CREDIT_PAY && this.canUseAmount < 0) {
this.$dialog({
message: '您的消费额度不足,请更换支付方式!',
confirmButtonText: '知道了',
showCancelButton: false,
confirmButtonColor: '#EC1500'
});
return;
}
const paramsData = {
...params,
quitUrl: Current_Url,
returnUrl: Current_Url,
ocrAuth: {
isH5: true
},
orderNo: this.orderNo,
payMethod: this.payType,
payCouponId: this.selectedCoupon.id || '',
mergePayPretreatmentId: this.mergePayPretreatmentInfo?.mergePayPretreatmentId
};
const [data, error] = params
? isOcr
? await pay(params)
: await pay(paramsData)
: await prepay(paramsData);
/* 支付失败 */
if (error) {
if (error?.response?.businessCode === '3001') {
/* 有享花卡未支付的订单 */
havePayingOrder.call(this);
return;
}
if (codeArr.indexOf(error?.response?.businessCode) < 0) {
this.payResult('Fail', error.message);
return;
}
error?.message && this.$toast(error?.message);
this.error = error?.message;
this.pwdModal && this.retrieve();
return;
}
const { creditPayInfo = {}, wxPayInfo = {}, flowOrderNo, aliPayInfo = {} } = data;
const creditPayStatus = creditPayInfo.creditPayStatus;
this.flowOrderNo = flowOrderNo;
if (wxPayInfo.mwebUrl) {
/* 微信支付 */
payByWay(this.tradeType, { ...wxPayInfo, url: wxPayInfo.mwebUrl });
return;
} else if (aliPayInfo.order_string) {
/* 支付宝支付 */
payByWay('ALIWEB', { ...aliPayInfo, url: aliPayInfo.order_string });
return;
} else if (creditPayStatus === PAY_SUCCESS) {
/* 信用支付成功 */
this.close();
this.payResult('Success');
return;
} else if (
creditPayStatus === PAYMENT_CODE_PAY ||
creditPayStatus === SMS_VERIFICATION_CODE_PAY
) {
/* 密码或者短信鉴权 */
this.close();
this[`${creditPayStatusType[creditPayStatus]}Modal`] = true;
return;
} else if (creditPayStatus === FACE_VERIFICATION_CODE_PAY) {
/* 人脸鉴权 */
cookies.set('info', {
paramsData: {
flowOrderNo: this.flowOrderNo,
...paramsData
},
payType: this.payType,
selectedCoupon: this.selectedCoupon,
isCheckAgreement: this.isCheckAgreement,
mergePayPretreatmentInfo: this.mergePayPretreatmentInfo
});
this.close();
this.goOcr();
}
},
payResult(type, error) {
/* 跳转支付结果页面 */
this.$router.replace({
name: `pay${type}`,
query: {
reason: error,
orderNo: this.orderNo,
payType: this.payType
}
});
},
/* 选择支付方式 */
changePayType(type, mergePayPretreatmentInfo) {
this.payType = type;
this.isDetention = false;
this.thirdPayList = filterAllPayList(type, this.thirdPayList);
this.creditPayList = filterAllPayList(type, this.creditPayList);
this.mergePayPretreatmentInfo = mergePayPretreatmentInfo || null;
},
openCouponModal() {
this.random = Math.random();
this.couponPopup = true;
this.$track.registeredEvents('h5_CheckOutCouterCouponBannerClick');
},
/* 选取优惠券 */
handleSelectCoupon(id, selectedCoupon) {
this.$track.registeredEvents('h5_CheckOutCounterCouponPopupWindowClick', {
coupon_type: '享花券',
check_or_not: id ? '勾选' : '取消',
coupon_id: id
});
this.selectedCoupon = selectedCoupon || {};
if (!('faceValueNew' in this.selectedCoupon) && 'faceValue' in this.selectedCoupon) {
this.selectedCoupon.faceValueNew = this.selectedCoupon.faceValue;
}
if (!('limitAmountNew' in this.selectedCoupon) && 'limitAmount' in this.selectedCoupon) {
this.selectedCoupon.limitAmountNew = this.selectedCoupon.limitAmount;
}
this.getBtnStatus(this.isRiskLimit());
this.couponPopup = false;
},
close() {
this.pwdModal = false;
this.smsModal = false;
},
/* 支付时间超时 */
timeup() {
this.overtime = true;
this.close();
},
/* 获取支付密码 */
async getPwd({ pwd }) {
this.retrieve();
this.pay({ flowOrderNo: this.flowOrderNo, password: await encryptByDESModeEBC(pwd) });
},
/* 短信验证码回调 */
getSms(e) {
this.pay({ flowOrderNo: this.flowOrderNo, smsAuthorizationCode: e });
},
/* 进入h5授信流程 */
async goApply() {
if (this.$route.query.ka) {
this.getKaGetNextUrl();
return;
}
const [{ url }] = await h5AppyUrl();
if (!url) return;
window.location.href = `${url}&returnUrl=${window.location.origin}/pay&from=pay`;
},
async getKaGetNextUrl() {
const [{ nextUrl }] = await kaGetNextUrl();
if (!nextUrl) return;
window.location.href = `${nextUrl}&returnUrl=${window.location.origin}/pay&from=pay`;
},
/* 走h5活体流程 */
async goOcr() {
const [res] = await ocrFaceId({
isH5: true,
type: 'xyqb',
orderNo: this.orderNo,
flowOrderNo: this.flowOrderNo,
callBackUrl: encodeURIComponent(`${window.location.href}&ocrflag=1`)
});
if (!res.callBackUrl) return;
window.location.href = res.callBackUrl;
},
/* 密码支付失败 */
retrieve() {
this.$refs.pwd.tips(false, this.error);
},
/* 忘记密码 */
retrieveLink() {
const { orderNo, hideOrder, returnUrl } = this.$route.query || {};
const redirectUrl = `${window.location.origin}${window.location.pathname}?orderNo=${orderNo}&hideOrder=${hideOrder}&returnUrl=${returnUrl}`;
let url = `${this.creditPayInfo.forgetPwdJumpUrl}&vccChannel=${VCC_CHANNEL}&redirectUrl=${redirectUrl}`;
url = url.replace('{token}', localStorage.get('vccToken'));
window.location.href = url;
},
getObjectKey(obj) {
return Object.keys(obj);
},
setAmount() {
const faceValue = +this.selectedCoupon?.faceValueNew || 0;
const orderAmt = +this.displayInfo?.orderAmt || 0;
const finalAmt =
IS_THIRD_PAY(this.payType) || !this.showCoupon
? orderAmt || '0.00'
: (orderAmt - faceValue || 0).toFixed(2);
const freeAmount =
!IS_THIRD_PAY(this.payType) && this.showCoupon ? faceValue || '0.00' : '0.00';
cookies.set('amount', { finalAmt, freeAmount });
},
async reissueContract() {
await reissueContract({
templateIdList: this.payContractInfo.contractInfos.map(item => item.contractId)
});
}
}
};
</script>
<style lang="less">
@import './index';
</style>
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
</template> </template>
<script> <script>
import { onKeyboardStateChange, clearKeyboard } from '@/service/utils.service'; import { onKeyboardStateChange, clearKeyboard } from '@/service/utils.service';
import toPayMixins from '@/mixins/toPay.mixins';
import rechargeApi from '@/api/recharge.api'; import rechargeApi from '@/api/recharge.api';
import orderApi from '@/api/order.api'; import orderApi from '@/api/order.api';
import tipsData from '@/api/tips'; import tipsData from '@/api/tips';
...@@ -57,6 +58,7 @@ export default { ...@@ -57,6 +58,7 @@ export default {
SkuList, SkuList,
AccountInput AccountInput
}, },
mixins: [toPayMixins],
data() { data() {
return { return {
account: '', account: '',
...@@ -196,7 +198,7 @@ export default { ...@@ -196,7 +198,7 @@ export default {
if (error) { if (error) {
return; return;
} }
res?.orderNo && this.$router.push({ path: '/pay', query: { orderNo: res.orderNo } }); res?.orderNo && this.toPay(res.orderNo);
}, 1000), }, 1000),
goOrderList() { goOrderList() {
this.$track.registeredEvents('h5_OtherRechargePageOrderSuspendedBtnClick', {}, () => { this.$track.registeredEvents('h5_OtherRechargePageOrderSuspendedBtnClick', {}, () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment