mirror of
https://gitee.com/bootx/dax-pay-h5.git
synced 2025-10-14 14:10:26 +00:00
346 lines
7.9 KiB
Vue
346 lines
7.9 KiB
Vue
<template>
|
||
<div v-if="show">
|
||
<div class="container">
|
||
<div class="payName">
|
||
<span>付款给</span>
|
||
<span class="paytext">{{ cashierInfo?.name }}</span>
|
||
</div>
|
||
<div class="amount-display">
|
||
<p class="title">
|
||
金额
|
||
</p>
|
||
<p style="font-size: 32px">
|
||
¥{{ amount }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="notes">
|
||
<div class="remark" @click="showRemark = true">
|
||
<div v-if="!description">
|
||
添加备注
|
||
</div>
|
||
<div v-else style="max-width: 75vw">
|
||
<van-text-ellipsis :content="`备注: ${description}`" />
|
||
<div />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- loading -->
|
||
<div v-if="loading" id="loadingMask" class="loadingMask hide">
|
||
<div class="content">
|
||
<img class="loadingImg" src="@/assets/images/loading.png" alt="">
|
||
<div class="loadingTxt">
|
||
处理中,请耐心等待
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<van-dialog
|
||
v-model:show="showRemark"
|
||
title="支付备注"
|
||
confirm-button-text="保存"
|
||
cancel-button-text="清除"
|
||
confirm-button-color="#4CAF50"
|
||
cancel-button-color="red"
|
||
show-cancel-button
|
||
@cancel="description = ''"
|
||
>
|
||
<van-field
|
||
v-model="description"
|
||
rows="2"
|
||
autosize
|
||
label=""
|
||
type="textarea"
|
||
:maxlength="50"
|
||
placeholder="请输入支付备注内容"
|
||
show-word-limit
|
||
/>
|
||
</van-dialog>
|
||
<van-number-keyboard
|
||
:show="!loading"
|
||
theme="custom"
|
||
extra-key="."
|
||
close-button-text="付款"
|
||
@close="pay"
|
||
@input="input"
|
||
@delete="del"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { showNotify } from 'vant'
|
||
import type {
|
||
CashierPayParam,
|
||
GatewayCashierConfig,
|
||
WxJsapiSignResult,
|
||
} from '../CashierCode.api'
|
||
|
||
import {
|
||
auth,
|
||
cashierPay,
|
||
generateAuthUrl,
|
||
getCashierCodeConfig,
|
||
} from '../CashierCode.api'
|
||
|
||
import { AggregateEnum, CashierCodeTypeEnum, GatewayCallTypeEnum } from '@/enums/daxpay/DaxPayEnum'
|
||
import router from '@/router'
|
||
import { useKeyboard } from '@/hooks/daxpay/useKeyboard'
|
||
|
||
const route = useRoute()
|
||
const { code: cashierCode } = route.params
|
||
const { code: authCode } = route.query
|
||
|
||
const show = ref<boolean>(false)
|
||
const showRemark = ref<boolean>(false)
|
||
const loading = ref<boolean>(false)
|
||
const cashierInfo = ref<GatewayCashierConfig>({})
|
||
const amount = ref<string>('0')
|
||
const description = ref<string>('')
|
||
const openId = ref<string>('')
|
||
|
||
const { input, del } = useKeyboard(amount)
|
||
|
||
onMounted(() => {
|
||
init()
|
||
})
|
||
|
||
/**
|
||
* 初始化
|
||
*/
|
||
async function init() {
|
||
loading.value = true
|
||
getCashierCodeConfig(cashierCode, AggregateEnum.WECHAT)
|
||
.then((res) => {
|
||
if (res.code) {
|
||
router.replace({ name: 'payFail', query: { msg: res.msg } })
|
||
return
|
||
}
|
||
loading.value = false
|
||
const data = res.data
|
||
cashierInfo.value = data as any
|
||
// 判断是否需要获取OpenId
|
||
if (data.needOpenId) {
|
||
// 如果不是重定向跳转过来, 跳转到到重定向地址
|
||
if (!authCode) {
|
||
// 重定向跳转到微信授权地址
|
||
generateAuthUrl(cashierCode, CashierCodeTypeEnum.WECHAT_PAY).then((res) => {
|
||
if (res.code) {
|
||
router.replace({ name: 'payFail', query: { msg: res.msg } })
|
||
return
|
||
}
|
||
const url = res.data
|
||
location.replace(url)
|
||
}).catch((res) => {
|
||
router.replace({ name: 'payFail', query: { msg: res.message } })
|
||
})
|
||
}
|
||
else {
|
||
// 认证获取OpenId
|
||
auth({ cashierCode: cashierCode as string, cashierType: CashierCodeTypeEnum.WECHAT_PAY, authCode: authCode as string })
|
||
.then((res) => {
|
||
if (res.code) {
|
||
router.push({ name: 'payFail', query: { msg: res.msg } })
|
||
return
|
||
}
|
||
openId.value = res.data.openId as string
|
||
show.value = true
|
||
}).catch((res) => {
|
||
router.push({ name: 'payFail', query: { msg: res.message }, replace: true })
|
||
})
|
||
}
|
||
}
|
||
else {
|
||
show.value = true
|
||
}
|
||
})
|
||
.catch((error) => {
|
||
router.push({ name: 'payFail', query: { msg: error } })
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 微信jsapi方式支付
|
||
*/
|
||
function pay() {
|
||
const amountValue = Number(amount.value)
|
||
if (amountValue === 0) {
|
||
showNotify({ type: 'warning', message: '金额不可为0' })
|
||
return
|
||
}
|
||
|
||
loading.value = true
|
||
const from = {
|
||
amount: amountValue,
|
||
cashierCode,
|
||
openId: openId.value,
|
||
cashierType: CashierCodeTypeEnum.WECHAT_PAY,
|
||
description: description.value,
|
||
} as CashierPayParam
|
||
cashierPay(from)
|
||
.then((res) => {
|
||
if (res.code) {
|
||
router.push({ name: 'payFail', query: { msg: res.msg } })
|
||
return
|
||
}
|
||
loading.value = false
|
||
// 根据类型拉起对应的支付。 支持跳转和jsapi
|
||
if (cashierInfo.value?.callType === GatewayCallTypeEnum.jsapi) {
|
||
const json = JSON.parse(res.data.payBody)
|
||
jsapiPay(json)
|
||
}
|
||
if (cashierInfo.value?.callType === GatewayCallTypeEnum.link) {
|
||
location.replace(res.data.payBody as any)
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 拉起Jsapi支付窗口
|
||
*/
|
||
function jsapiPay(data: WxJsapiSignResult) {
|
||
const form = {
|
||
appId: data.appId, // 公众号ID,由商户传入
|
||
timeStamp: data.timeStamp, // 时间戳,自1970年以来的秒数
|
||
nonceStr: data.nonceStr, // 随机串
|
||
package: data.package, // 预支付ID
|
||
signType: data.signType, // 微信签名方式:
|
||
paySign: data.paySign, // 微信签名
|
||
}
|
||
// 使用微信JsSdk拉起支付
|
||
WeixinJSBridge.invoke('getBrandWCPayRequest', form, (res) => {
|
||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||
// 跳转到成功页面
|
||
router.replace({
|
||
path: '/paySuccess',
|
||
query: { title: '支付成功' },
|
||
})
|
||
}
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
@color: #4caf50;
|
||
|
||
:deep(.van-key--blue) {
|
||
background: @color;
|
||
}
|
||
.container {
|
||
background: linear-gradient(to bottom, #07c160, #07c160, #ffffff); // 从蓝色渐变到白色
|
||
width: 100%;
|
||
padding: 40px;
|
||
height: 40%;
|
||
border-radius: 10px;
|
||
text-align: center;
|
||
color: white;
|
||
|
||
.payName {
|
||
margin: 5px 0;
|
||
font-size: 16px;
|
||
|
||
.paytext {
|
||
font-size: 20px;
|
||
margin-left: 5px;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.amount-display {
|
||
background-color: white;
|
||
color: @color;
|
||
border-radius: 20px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
display: flex;
|
||
gap: 1.875rem;
|
||
align-items: center;
|
||
|
||
p {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 20px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.notes {
|
||
width: 100%;
|
||
height: 10%;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
|
||
.remark {
|
||
color: @color;
|
||
cursor: pointer;
|
||
}
|
||
}
|
||
|
||
/* loading */
|
||
.loadingMask {
|
||
position: fixed;
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0 auto;
|
||
top: 0;
|
||
right: 0;
|
||
left: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 99;
|
||
border-radius: 0 0 0.2rem 0.2rem;
|
||
|
||
.content {
|
||
position: absolute;
|
||
width: 15rem;
|
||
border-radius: 0.2rem;
|
||
// box-shadow:
|
||
// 0px 12px 48px 16px rgba(0, 0, 0, 0.03),
|
||
// 0px 9px 28px 0px rgba(0, 0, 0, 0.05),
|
||
// 0px 6px 16px -8px rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.loadingImg {
|
||
width: 1.6rem;
|
||
height: 1.6rem;
|
||
margin-top: 2rem;
|
||
animation: 1.6s linear ratate infinite;
|
||
}
|
||
|
||
.loadingTxt {
|
||
font-size: 1.125rem;
|
||
color: #22242e;
|
||
margin-top: 1.2rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
@keyframes ratate {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
40% {
|
||
transform: rotate(144deg);
|
||
}
|
||
|
||
80% {
|
||
transform: rotate(288deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
}
|
||
</style>
|