feat 收银台接口对接

This commit is contained in:
DaxPay
2025-03-27 22:11:13 +08:00
parent cacf6c2113
commit 75b228e866
8 changed files with 345 additions and 247 deletions

View File

@@ -27,7 +27,7 @@ export const DaxPayRoute: RouteRecordRaw = {
}, },
}, },
{ {
path: 'cashier/:code', path: '/cashier/:code',
name: 'CashierCode', name: 'CashierCode',
component: () => import('@/views/daxpay/cashier/Cashier.vue'), component: () => import('@/views/daxpay/cashier/Cashier.vue'),
meta: { meta: {
@@ -59,7 +59,7 @@ export const DaxPayRoute: RouteRecordRaw = {
}, },
}, },
{ {
path: 'paySuccess', path: '/paySuccess',
name: 'PaySuccess', name: 'PaySuccess',
component: () => import('@/views/daxpay/result/PaySuccess.vue'), component: () => import('@/views/daxpay/result/PaySuccess.vue'),
meta: { meta: {
@@ -67,12 +67,12 @@ export const DaxPayRoute: RouteRecordRaw = {
}, },
}, },
{ {
path: 'payFail', path: '/payFail',
name: 'payFail', name: 'payFail',
component: () => import('@/views/daxpay/result/PayFail.vue'), component: () => import('@/views/daxpay/result/PayFail.vue'),
meta: { meta: {
title: '支付失败页面', title: '支付失败页面',
}, },
} },
], ],
} }

View File

@@ -109,9 +109,9 @@ const transform: AxiosTransform = {
if (!isUrlStr && joinPrefix) { if (!isUrlStr && joinPrefix) {
config.url = `${urlPrefix}${config.url}` config.url = `${urlPrefix}${config.url}`
} }
console.log(config.url)
if (!isUrlStr && apiUrl && isString(apiUrl)) { if (!isUrlStr && apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`
} }
const params = config.params || {} const params = config.params || {}
const data = config.data || false const data = config.data || false

View File

@@ -0,0 +1,87 @@
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
/**
* 获取订单和收银台配置信息
*/
export function getOrderAndConfig(orderNo) {
return http.request<Result<OrderAndConfig>>({
url: '/unipay/gateway/getOrderAndConfig',
method: 'GET',
params: { orderNo, cashierType: 'h5' },
})
}
/**
* 收银台配置
*/
export interface OrderAndConfig {
/** 订单信息 */
order: GatewayOrder
/** 收银台配置信息 */
config: GatewayPayConfig
/** 收银台分类配置信息 */
groupConfigs: CashierGroupConfig[]
}
/**
* 订单信息
*/
export interface GatewayOrder {
/** 商户订单号 */
bizOrderNo?: string
/** 订单号 */
orderNo?: string
/** 标题 */
title?: string
/** 描述 */
description?: string
/** 金额(元) */
amount?: number
}
/**
* 聚合支付配置信息
*/
export interface GatewayPayConfig {
/** 收银台名称 */
name?: string
/** PC收银台是否同时显示聚合收银码 */
aggregateShow?: boolean
/** h5收银台自动升级聚合支付 */
h5AutoUpgrade?: boolean
}
/**
* 收银台分类配置
*/
export interface CashierGroupConfig {
/** 主键 */
id?: string
/** 名称 */
name?: string
/** 配置项列表 */
items?: CashierConfig[]
}
/**
* 收银台配置项
*/
export interface CashierConfig {
/** 主键 */
id?: string
/** 发起调用的类型 */
callType?: string
/** 名称 */
name?: string
/** 支付通道 */
channel?: string
/** 支付方式 */
payMethod?: string
/** 其他支付方式 */
otherMethod?: string
/** 图标 */
icon?: string
/** 是否推荐 */
recommend?: boolean
}

View File

@@ -1,125 +1,123 @@
<template> <template>
<div class="cashier"> <div v-if="orderAndConfig" class="cashier">
<div class="cash_topBox"> <div class="cash_topBox">
<div class="payPrice"> <div class="payPrice">
<span class="unit"></span> <span class="unit"></span>
<div class="price">1299.8</div> <div class="price">
{{ orderAndConfig.order.amount }}
</div>
</div> </div>
<div class="excessTime"> <div class="excessTime">
<span class="exTitle">剩余支付时间</span> <span class="exTitle">剩余支付时间</span>
<span class="number">{{ downTime.currentMiute }}</span> <span class="number">{{ orderTime.currentMiute }}</span>
<span class="point">:</span> <span class="point">:</span>
<span class="number">{{ downTime.currentSeconds }}</span> <span class="number">{{ orderTime.currentSeconds }}</span>
</div> </div>
<div class="payMessItem"> <div class="payMessItem">
<div class="itemTitle">标题:</div> <div class="itemTitle">
<div class="itemContent">商业版 1288 预购版</div> 标题:
</div>
<div class="itemContent">
{{ orderAndConfig.order.title }}
</div>
</div> </div>
<div class="payMessItem"> <div class="payMessItem">
<div class="itemTitle">订单编号:</div> <div class="itemTitle">
<div class="itemContent">207084835007084835066</div> 订单编号:
</div>
<div class="itemContent">
{{ orderAndConfig.order.orderNo }}
</div>
</div> </div>
</div> </div>
<div class="cash_bodyBox"> <div class="cash_bodyBox">
<h2>请选择支付方式</h2> <h2>请选择支付方式</h2>
<div class="payGoupList"> <div class="payGoupList">
<div <div
v-for="item in orderAndConfig.groupConfigs[0].items"
:key="item.id"
class="payMethodsItem" class="payMethodsItem"
v-for="item in paymentMethods"
:key="item"
@click="payTypeClick(item)" @click="payTypeClick(item)"
> >
<div class="itemType"> <div class="itemType">
<img :src="item.icon" alt="" /> <img :src="item.icon" alt="">
<p>{{ item.label }}</p> <p>{{ item.name }}</p>
<span v-if="item.recommended"> 推荐</span> <span v-if="item.recommend"> 推荐</span>
</div> </div>
<div class="selectBox"> <div class="selectBox">
<img <img
v-if="item.value == payType" v-if="item.id === selectId"
src="@/assets/images/selected-arrow-icon.png" src="@/assets/images/selected-arrow-icon.png"
alt="" alt=""
/> >
</div> </div>
</div> </div>
</div> </div>
<div class="payBtnBox">支付<span>1299.8</span></div> <div class="payBtnBox">
支付<span>{{ orderAndConfig.order.amount }}</span>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 导入图片文件 // 导入图片文件
import zfbPay from "@/assets/images/zfb_pay.png"; import { onMounted, onUnmounted, reactive, ref } from 'vue'
import wxPay from "@/assets/images/new_wx_pay.png"; import { useRoute } from 'vue-router'
import quickPay from "@/assets/images/quick_pay.png"; import type { OrderAndConfig } from '@/views/daxpay/cashier/Cashier.api'
import { ref, reactive, onMounted, onUnmounted } from "vue"; import { getOrderAndConfig } from '@/views/daxpay/cashier/Cashier.api'
//点击支付类型 const route = useRoute()
const payType = ref("alipay");
const payTypeClick = (item) => {
console.log(item);
payType.value = item.value;
};
//支付方式列表 const { code: orderNo } = route.params
const paymentMethods = ref([ const orderAndConfig = ref<OrderAndConfig>()
{
value: "alipay", // 选中的支付方式
label: "支付宝", const selectId = ref()
icon: zfbPay, function payTypeClick(item) {
recommended: true, selectId.value = item.id
}, }
{
value: "wechat", // 倒计时对象
label: "微信", const orderTime = reactive({
icon: wxPay, totalTme: 300, // 总共时间
recommended: false, currentMinute: '05', // 当前分钟
}, currentSeconds: '00', // 当前秒数
{ })
value: "quickpay",
label: "快捷支付", const { pause, resume } = useIntervalFn(() => {
icon: quickPay, orderTime.totalTme--
recommended: false, orderTime.currentMinute = formatTime(Math.floor(orderTime.totalTme / 60))
}, orderTime.currentSeconds = formatTime(Math.floor(orderTime.totalTme % 60))
]); }, 1000)
function formatTime(time: number){
// 格式化时间
return time.toString().padStart(2, '0')
}
//倒计时对象
const downTime = reactive({
timer: null,
totalTme: 300, //总共时间
currentMiute: "05", //当前分钟
currentSeconds: "00", //当前秒数
formatTime: (time: number) => {
//格式化时间
return time.toString().padStart(2, "0");
},
startCountdown: () => {
//每秒渲染时间函数
if (downTime.totalTme == 0) {
clearInterval(timer);
downTime.timer = null;
}
downTime.totalTme--;
downTime.currentMiute = downTime.formatTime(Math.floor(downTime.totalTme / 60));
downTime.currentSeconds = downTime.formatTime(Math.floor(downTime.totalTme % 60));
},
});
onMounted(() => { onMounted(() => {
downTime.timer = setInterval(() => { init()
downTime.startCountdown(); })
}, 1000);
});
onUnmounted(() => { onUnmounted(() => {
if (timer) { pause()
clearInterval(timer); })
downTime.timer = null;
} /**
}); * 初始化
*/
function init() {
getOrderAndConfig(orderNo).then(({ data }) => {
orderAndConfig.value = data
// orderTime.totalTme = data.order.expireTime
resume()
})
}
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.cashier { .cashier {
font-family: "Microsoft YaHei", "微软雅黑", sans-serif; font-family: 'Microsoft YaHei', '微软雅黑', sans-serif;
width: 100%; width: 100%;
height: 100vh; height: 100vh;
background-color: #f5f5f5; background-color: #f5f5f5;

View File

@@ -7,18 +7,18 @@
</van-loading> </van-loading>
</div> </div>
</van-overlay> </van-overlay>
<van-cell-group inset title="订单信息"> <van-cell-group inset title="订单信息">
<van-cell title="金额" title-style="font-size: 22px;color: red"> <van-cell title="金额" title-style="font-size: 22px;color: red">
<template #default> <template #default>
<span style="font-size: 22px;color: red">{{ aggregateInfo.order.amount }}</span> <span style="font-size: 22px;color: red">{{ aggregateInfo.order.amount }}</span>
</template> </template>
</van-cell> </van-cell>
<van-field label="标题" :model-value="aggregateInfo.order.title" readonly /> <van-field label="标题" :model-value="aggregateInfo.order.title" readonly />
<van-field label="订单号" :model-value="aggregateInfo.order.bizOrderNo" readonly /> <van-field label="订单号" :model-value="aggregateInfo.order.bizOrderNo" readonly />
<van-field label="支付号" :model-value="aggregateInfo.order.orderNo" readonly /> <van-field label="支付号" :model-value="aggregateInfo.order.orderNo" readonly />
<van-field label="描述" rows="2" type="textarea" :model-value="aggregateInfo.order.description" readonly /> <van-field label="描述" rows="2" type="textarea" :model-value="aggregateInfo.order.description" readonly />
</van-cell-group> </van-cell-group>
<van-submit-bar safe-area-inset-bottom :price="(aggregateInfo.order.amount || 0)*100" button-text="支付" @submit="pay" /> <van-submit-bar safe-area-inset-bottom :price="(aggregateInfo.order.amount || 0) * 100" button-text="支付" @submit="pay" />
</div> </div>
</template> </template>
@@ -59,7 +59,7 @@ async function initData() {
await getAggregateConfig(orderNo, CheckoutAggregateEnum.ALI).then(({ data }) => { await getAggregateConfig(orderNo, CheckoutAggregateEnum.ALI).then(({ data }) => {
aggregateInfo.value = data aggregateInfo.value = data
}).catch((res) => { }).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true }) router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
}) })
// 判断是否自动拉起支付 // 判断是否自动拉起支付
if (aggregateInfo.value.aggregateConfig.autoLaunch) { if (aggregateInfo.value.aggregateConfig.autoLaunch) {

View File

@@ -1,116 +1,119 @@
<template> <template>
<div class="payFail"> <div class="payFail">
<div class="payLogo"> <div class="payLogo">
<img src="@/assets/images/fail1.png" alt="" /> <img src="@/assets/images/fail1.png" alt="">
<p>支付失败</p> <p>支付失败</p>
<span>订单超时自动关闭请重新发起支付</span> <span>订单超时自动关闭请重新发起支付</span>
</div> </div>
<div class="payPrice"> <div class="payPrice">
<span class="unit"></span> <span class="unit"></span>
<div class="price">793.21</div> <div class="price">
</div> 793.21
<div class="payMessBox"> </div>
<div class="payMessItem"> </div>
<div class="itemTitle">支付标题</div> <div class="payMessBox">
<div class="itemContent">商业版 1288 预购版</div> <div class="payMessItem">
</div> <div class="itemTitle">
<div class="payMessItem"> 支付标题
<div class="itemTitle">订单编号</div> </div>
<div class="itemContent">20708483506</div> <div class="itemContent">
</div> 商业版 1288 预购版
</div> </div>
</div>
<div class="payBtnBox"> <div class="payMessItem">
关闭 <div class="itemTitle">
订单编号
</div>
<div class="itemContent">
20708483506
</div>
</div> </div>
</div> </div>
</template>
<script setup lang="ts"></script> <div class="payBtnBox">
关闭
</div>
</div>
</template>
<script setup lang="ts"></script>
<style scoped lang="less"> <style scoped lang="less">
.payFail { .payFail {
padding: 3.5rem 0rem; padding: 3.5rem 0rem;
width: 100%; width: 100%;
height: 100vh; height: 100vh;
background-color: #fff; background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
gap: 3rem;
position: relative;
.payLogo {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.25rem;
align-items: center; align-items: center;
gap: 3rem; img {
position: relative; width: 3.125rem;
.payLogo { height: 3.125rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
align-items: center;
img{
width: 3.125rem;
height: 3.125rem;
}
p{
font-size:1.5rem;
color: #e74e4e;
font-weight: 700;
}
span{
font-size:.75rem;
letter-spacing: 2px;
font-size: .875rem;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
}
} }
.payPrice{ p {
display: flex; font-size: 1.5rem;
gap: .315rem; color: #e74e4e;
align-items: center; font-weight: 700;
.unit{
font-size:.75rem;
transform: translateY(.525rem);
}
.price{
font-size:2rem;
font-weight: 700;
}
} }
.payMessBox{ span {
width: 100%; letter-spacing: 2px;
padding: 0px 1.25rem; font-size: 0.875rem;
display: flex; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
flex-direction: column;
gap: .625rem;
.payMessItem{
width: 100%;
display: flex;
justify-content: space-between;
.itemTitle{
font-size:1rem;
color: #797d81;
}
.itemContent{
font-size:1rem;
font-weight: 500;
font-size: "微软雅黑";
}
}
} }
.payBtnBox{
width: 90%;
margin: 0 auto;
background-color: #e74e4e;
color: #fff;
height: 3.25rem;
position: absolute;
bottom:3.75rem;
text-align: center;
line-height: 3.25rem;
border-radius:.625rem;
}
} }
</style> .payPrice {
display: flex;
gap: 0.315rem;
align-items: center;
.unit {
font-size: 0.75rem;
transform: translateY(0.525rem);
}
.price {
font-size: 2rem;
font-weight: 700;
}
}
.payMessBox {
width: 100%;
padding: 0 1.25rem;
display: flex;
flex-direction: column;
gap: 0.625rem;
.payMessItem {
width: 100%;
display: flex;
justify-content: space-between;
.itemTitle {
font-size: 1rem;
color: #797d81;
}
.itemContent {
font-size: 1rem;
font-weight: 500;
}
}
}
.payBtnBox {
width: 90%;
margin: 0 auto;
background-color: #e74e4e;
color: #fff;
height: 3.25rem;
position: absolute;
bottom: 3.75rem;
text-align: center;
line-height: 3.25rem;
border-radius: 0.625rem;
}
}
</style>

View File

@@ -1,30 +1,44 @@
<template> <template>
<div class="paySuccess"> <div class="paySuccess">
<div class="payLogo"> <div class="payLogo">
<img src="@/assets/images/success1.png" alt="" /> <img src="@/assets/images/success1.png" alt="">
<p>支付成功</p> <p>支付成功</p>
</div> </div>
<div class="payPrice"> <div class="payPrice">
<span class="unit"></span> <span class="unit"></span>
<div class="price">793.21</div> <div class="price">
793.21
</div>
</div> </div>
<div class="payMessBox"> <div class="payMessBox">
<div class="payMessItem"> <div class="payMessItem">
<div class="itemTitle">支付标题</div> <div class="itemTitle">
<div class="itemContent">商业版 1288 预购版</div> 支付标题
</div>
<div class="itemContent">
商业版 1288 预购版
</div>
</div> </div>
<div class="payMessItem"> <div class="payMessItem">
<div class="itemTitle">订单编号</div> <div class="itemTitle">
<div class="itemContent">20708483506</div> 订单编号
</div>
<div class="itemContent">
20708483506
</div>
</div> </div>
<div class="payMessItem"> <div class="payMessItem">
<div class="itemTitle">支付时间</div> <div class="itemTitle">
<div class="itemContent">2021-06-25 11:21</div> 支付时间
</div>
<div class="itemContent">
2021-06-25 11:21
</div>
</div> </div>
</div> </div>
<div class="payBtnBox"> <div class="payBtnBox">
完成 完成
</div> </div>
</div> </div>
</template> </template>
@@ -47,66 +61,62 @@
flex-direction: column; flex-direction: column;
gap: 1.25rem; gap: 1.25rem;
align-items: center; align-items: center;
img{ img {
width: 3.125rem; width: 3.125rem;
height: 3.125rem; height: 3.125rem;
} }
p{ p {
font-size:1.5rem; font-size: 1.5rem;
color: #0d6eff; color: #0d6eff;
font-weight: 700; font-weight: 700;
} }
} }
.payPrice{ .payPrice {
display: flex; display: flex;
gap: .315rem; gap: 0.315rem;
align-items: center; align-items: center;
.unit{ .unit {
font-size:.75rem; font-size: 0.75rem;
transform: translateY(.525rem); transform: translateY(0.525rem);
} }
.price{ .price {
font-size:2rem; font-size: 2rem;
font-weight: 700; font-weight: 700;
} }
} }
.payMessBox{ .payMessBox {
width: 100%; width: 100%;
padding: 0px 1.25rem; padding: 0px 1.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: .625rem; gap: 0.625rem;
.payMessItem{ .payMessItem {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.itemTitle{ .itemTitle {
font-size:1rem; font-size: 1rem;
color: #797d81; color: #797d81;
} }
.itemContent{ .itemContent {
font-size:1rem; font-size: 1rem;
font-weight: 500; font-weight: 500;
font-size: "微软雅黑"; font-size: '微软雅黑';
} }
} }
} }
.payBtnBox{ .payBtnBox {
width: 90%; width: 90%;
margin: 0 auto; margin: 0 auto;
background-color: #0d6eff; background-color: #0d6eff;
color: #fff; color: #fff;
height: 3.25rem; height: 3.25rem;
position: absolute; position: absolute;
bottom:3.75rem; bottom: 3.75rem;
text-align: center; text-align: center;
line-height: 3.25rem; line-height: 3.25rem;
border-radius:.625rem; border-radius: 0.625rem;
} }
} }
</style> </style>

2
types/config.d.ts vendored
View File

@@ -15,6 +15,6 @@ export interface GlobEnvConfig {
VITE_GLOB_API_URL: string VITE_GLOB_API_URL: string
// 接口前缀 // 接口前缀
VITE_GLOB_API_URL_PREFIX?: string VITE_GLOB_API_URL_PREFIX?: string
//项目简称 // 项目简称
VITE_GLOB_APP_SHORT_NAME: string VITE_GLOB_APP_SHORT_NAME: string
} }