mirror of
https://gitee.com/bootx/dax-pay-h5
synced 2026-08-12 15:35:39 +08:00
feat(cashier): 收银台接入真实支付并调整码牌 path 为 /h/:code
移动/PC 收银台对接订单查询、支付项与发起支付;码牌路由 CODE_PAY 与后端 getCodeLink 对齐为 /h/:code。
This commit is contained in:
@@ -1,46 +1,35 @@
|
||||
<script lang="ts" setup>
|
||||
import { showToast } from 'vant'
|
||||
import type { CashierItemPublic, GatewayOrderInfo } from '@/shared/api/gateway'
|
||||
import { showNotify, showSuccessToast } from 'vant'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cashierPay, getGatewayOrder, listCashierItems } from '@/shared/api/gateway'
|
||||
|
||||
defineOptions({ name: 'CashierPage' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const orderNo = route.params.orderNo as string
|
||||
|
||||
// 演示订单数据(mock,不连后端)
|
||||
const order = computed(() => ({
|
||||
title: t('cashier.demoOrderTitle'),
|
||||
orderNo: route.params.orderNo as string,
|
||||
amount: 0.01,
|
||||
// 15 分钟后过期
|
||||
expiredTime: Date.now() + 15 * 60 * 1000,
|
||||
}))
|
||||
const loading = ref(true)
|
||||
const paying = ref(false)
|
||||
const loadError = ref('')
|
||||
const order = ref<GatewayOrderInfo>({})
|
||||
const payMethods = ref<CashierItemPublic[]>([])
|
||||
const selectId = ref<string>('')
|
||||
|
||||
// 演示支付方式(mock)— name 由 i18n 按 icon 渲染
|
||||
interface PayMethod {
|
||||
id: string
|
||||
icon: 'wechat' | 'alipay' | 'union_pay'
|
||||
recommend?: boolean
|
||||
}
|
||||
const payMethods: PayMethod[] = [
|
||||
{ id: '1', icon: 'wechat', recommend: true },
|
||||
{ id: '2', icon: 'alipay' },
|
||||
{ id: '3', icon: 'union_pay' },
|
||||
]
|
||||
|
||||
// 支付方式名称(跟随语言)
|
||||
function methodName(item: PayMethod) {
|
||||
return t(`cashier.method.${item.icon}`)
|
||||
}
|
||||
|
||||
// 选中的支付方式(默认推荐项)
|
||||
const selectId = ref<string>(payMethods[0]!.id)
|
||||
|
||||
// 倒计时(原生 setInterval,项目无 @vueuse/core)
|
||||
const remainSeconds = ref(0)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const amountYuan = computed(() => {
|
||||
if (!order.value.amount) {
|
||||
return '0.00'
|
||||
}
|
||||
return (order.value.amount / 100).toFixed(2)
|
||||
})
|
||||
|
||||
const countdown = computed(() => {
|
||||
const s = remainSeconds.value
|
||||
const h = String(Math.floor(s / 3600)).padStart(2, '0')
|
||||
@@ -49,103 +38,264 @@ const countdown = computed(() => {
|
||||
return { h, m, s: sec }
|
||||
})
|
||||
|
||||
function startCountdown() {
|
||||
remainSeconds.value = Math.max(0, Math.floor((order.value.expiredTime - Date.now()) / 1000))
|
||||
timer = setInterval(() => {
|
||||
if (remainSeconds.value > 0) {
|
||||
remainSeconds.value--
|
||||
}
|
||||
else if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
const paid = computed(() => order.value.status === 'paid')
|
||||
|
||||
/**
|
||||
* 根据 UA 识别客户端环境(与后端 ClientEnvEnum 对齐)
|
||||
*/
|
||||
function detectClientEnv(): string {
|
||||
const ua = navigator.userAgent.toLowerCase()
|
||||
if (ua.includes('micromessenger')) {
|
||||
return 'wechat_pay'
|
||||
}
|
||||
if (ua.includes('alipayclient') || ua.includes('alipay')) {
|
||||
return 'alipay'
|
||||
}
|
||||
if (ua.includes('unionpay') || ua.includes('cloudpay') || ua.includes('upwallet')) {
|
||||
return 'union_pay'
|
||||
}
|
||||
if (ua.includes('aweme') || ua.includes('toutiao') || ua.includes('douyin')) {
|
||||
return 'douyin'
|
||||
}
|
||||
return 'browser'
|
||||
}
|
||||
|
||||
// 发起支付(演示)
|
||||
function pay() {
|
||||
const selected = payMethods.find(it => it.id === selectId.value)
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: t('cashier.demoPayStarted', { name: selected ? methodName(selected) : '' }),
|
||||
})
|
||||
/**
|
||||
* 支付项展示名: 优先后端 name, 否则按 icon 走 i18n
|
||||
*/
|
||||
function methodName(item: CashierItemPublic) {
|
||||
if (item.name) {
|
||||
return item.name
|
||||
}
|
||||
const icon = item.icon || 'wechat'
|
||||
const key = `cashier.method.${icon}`
|
||||
const label = t(key)
|
||||
return label === key ? icon : label
|
||||
}
|
||||
|
||||
onMounted(startCountdown)
|
||||
/**
|
||||
* 图标 class 后缀(未知 icon 回退 wechat 样式)
|
||||
*/
|
||||
function iconClass(icon?: string) {
|
||||
const known = ['wechat', 'alipay', 'union_pay', 'douyin']
|
||||
if (icon && known.includes(icon)) {
|
||||
return icon
|
||||
}
|
||||
if (icon === 'wechat_pay') {
|
||||
return 'wechat'
|
||||
}
|
||||
return 'wechat'
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadPage()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 加载订单 + 支付项
|
||||
*/
|
||||
async function loadPage() {
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
order.value = await getGatewayOrder(orderNo)
|
||||
if (order.value.expiredTime) {
|
||||
const exp = new Date(order.value.expiredTime).getTime()
|
||||
remainSeconds.value = Math.max(0, Math.floor((exp - Date.now()) / 1000))
|
||||
timer = setInterval(() => {
|
||||
if (remainSeconds.value > 0) {
|
||||
remainSeconds.value--
|
||||
}
|
||||
else if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
if (!paid.value) {
|
||||
const clientEnv = detectClientEnv()
|
||||
payMethods.value = await listCashierItems({
|
||||
orderNo,
|
||||
cashierType: 'h5',
|
||||
clientEnv,
|
||||
})
|
||||
// 默认选中推荐项, 否则第一项
|
||||
const recommend = payMethods.value.find(i => i.recommend)
|
||||
selectId.value = (recommend || payMethods.value[0])?.id || ''
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
loadError.value = e?.message || t('cashier.loadFail')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起收银台支付
|
||||
*/
|
||||
async function pay() {
|
||||
if (paying.value || paid.value || !selectId.value) {
|
||||
return
|
||||
}
|
||||
paying.value = true
|
||||
try {
|
||||
const result = await cashierPay({
|
||||
orderNo,
|
||||
itemId: selectId.value,
|
||||
cashierType: 'h5',
|
||||
clientEnv: detectClientEnv(),
|
||||
device: 'mobile',
|
||||
})
|
||||
if (result?.status === 'success') {
|
||||
order.value.status = 'paid'
|
||||
showSuccessToast(t('cashier.paid'))
|
||||
redirectIfNeeded()
|
||||
return
|
||||
}
|
||||
if (result?.payBody && result.payBodyType === 'url') {
|
||||
window.location.href = result.payBody
|
||||
return
|
||||
}
|
||||
startPoll()
|
||||
}
|
||||
catch (e: any) {
|
||||
showNotify({ type: 'danger', message: e?.message || t('cashier.payFail') })
|
||||
}
|
||||
finally {
|
||||
paying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startPoll() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const latest = await getGatewayOrder(orderNo)
|
||||
order.value = latest
|
||||
if (latest.status === 'paid') {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
showSuccessToast(t('cashier.paid'))
|
||||
redirectIfNeeded()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 忽略轮询错误
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function redirectIfNeeded() {
|
||||
if (order.value.returnUrl) {
|
||||
setTimeout(() => {
|
||||
window.location.href = order.value.returnUrl!
|
||||
}, 1200)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="cashier">
|
||||
<!-- 顶部订单信息 -->
|
||||
<div class="cashier__header enter-y">
|
||||
<div class="cashier__price">
|
||||
<span class="cashier__currency">¥</span>
|
||||
<span class="cashier__amount">{{ order.amount }}</span>
|
||||
</div>
|
||||
<div class="cashier__countdown">
|
||||
<span class="cashier__countdown-label">{{ t('cashier.remainTime') }}</span>
|
||||
<span class="cashier__countdown-time">
|
||||
{{ countdown.h }}:{{ countdown.m }}:{{ countdown.s }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="cashier__detail">
|
||||
<div class="cashier__detail-row">
|
||||
<span>{{ t('cashier.orderTitle') }}</span>
|
||||
<span>{{ order.title }}</span>
|
||||
</div>
|
||||
<div class="cashier__detail-row">
|
||||
<span>{{ t('cashier.orderNo') }}</span>
|
||||
<span>{{ order.orderNo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" class="cashier__header enter-y">
|
||||
<van-loading color="#5d9dfe" size="24px" />
|
||||
</div>
|
||||
|
||||
<!-- 支付方式选择 -->
|
||||
<div class="cashier__body enter-y">
|
||||
<div class="cashier__section-title">
|
||||
{{ t('cashier.selectMethod') }}
|
||||
</div>
|
||||
<div class="cashier__list">
|
||||
<div
|
||||
v-for="item in payMethods"
|
||||
:key="item.id"
|
||||
class="cashier__item"
|
||||
:class="{ 'cashier__item--active': item.id === selectId }"
|
||||
@click="selectId = item.id"
|
||||
>
|
||||
<div class="cashier__item-info">
|
||||
<div class="cashier__pay-icon" :class="`cashier__pay-icon--${item.icon}`">
|
||||
<!-- 内联 SVG 品牌图标:微信支付 / 支付宝 / 银联 -->
|
||||
<svg v-if="item.icon === 'wechat'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-322.3-227.1C205.2 150.8 64 271.4 64 420.2c0 81.1 43.6 146.4 116.6 197.4l-29.2 88.4 102.2-52.6c36.5 7.2 65.9 14.6 101.9 14.6 5.4 0 10.8-.2 16.2-.5-3.4-11.8-5.3-24.1-5.3-36.9 0-153.7 129.4-253.2 323.7-253.2zM544.9 267.6c21.5 0 35.7 14.2 35.7 35.7 0 21.3-14.2 35.8-35.7 35.8s-42.9-14.5-42.9-35.8c0-21.5 21.4-35.7 42.9-35.7zM269.3 338.3c-21.5 0-43.3-14.2-43.3-35.7 0-21.3 21.8-35.8 43.3-35.8 21.3 0 35.6 14.5 35.6 35.8 0 21.5-14.3 35.7-35.6 35.7z" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'alipay'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M843.1 608.6c-23.4-49.7-54.2-105.8-91.7-166.2-37.8-60.4-77.1-115.3-117.6-164.4-44.6-12.3-91.7-18.8-140.4-18.8-101.3 0-194.4 34.4-266.9 91.9-83.3 66-133.5 162.7-133.5 269.5 0 103.9 49.5 199.1 133.7 265.7 76.2 60.2 174.3 95.3 280.1 95.3 138.6 0 262.2-57.3 339-147.1-33.9-26-99-62.7-202.9-99.3-29.4 39.5-72.9 64.8-128.1 64.8-99.5 0-176.4-72.2-176.4-176.4 0-99.1 71.5-177 176.4-177 78.2 0 137.6 41.6 161.8 103.9 73.6 30.9 132.3 56.9 167.4 76.5l72.1 36.1z" />
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm130.4 590.4c-39.1 27.8-86.8 43.6-138.4 43.6-130.4 0-236-105.6-236-236s105.6-236 236-236c45.6 0 88.2 13 124 35.4l-58.5 58.5c-19.4-9-40.9-14-63.5-14-85.2 0-154.4 69.2-154.4 154.4S384.8 544.3 470 544.3c52.6 0 98.9-26.4 126.4-66.6l70.6 70.6c-7.8 2.1-15.8 4-24.6 6.7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cashier__pay-name">
|
||||
{{ methodName(item) }}
|
||||
<span v-if="item.recommend" class="cashier__recommend">{{ t('cashier.recommend') }}</span>
|
||||
</div>
|
||||
<div v-else-if="loadError" class="cashier__header enter-y">
|
||||
<p class="cashier__error">
|
||||
{{ loadError }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 顶部订单信息 -->
|
||||
<div class="cashier__header enter-y">
|
||||
<div class="cashier__price">
|
||||
<span class="cashier__currency">¥</span>
|
||||
<span class="cashier__amount">{{ amountYuan }}</span>
|
||||
</div>
|
||||
<div v-if="remainSeconds > 0 && !paid" class="cashier__countdown">
|
||||
<span class="cashier__countdown-label">{{ t('cashier.remainTime') }}</span>
|
||||
<span class="cashier__countdown-time">
|
||||
{{ countdown.h }}:{{ countdown.m }}:{{ countdown.s }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="cashier__detail">
|
||||
<div class="cashier__detail-row">
|
||||
<span>{{ t('cashier.orderTitle') }}</span>
|
||||
<span>{{ order.title || t('cashier.demoOrderTitle') }}</span>
|
||||
</div>
|
||||
<div class="cashier__detail-row">
|
||||
<span>{{ t('cashier.orderNo') }}</span>
|
||||
<span>{{ order.orderNo }}</span>
|
||||
</div>
|
||||
<div class="cashier__radio" :class="{ 'cashier__radio--checked': item.id === selectId }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部支付按钮 -->
|
||||
<div class="cashier__footer enter-y">
|
||||
<button class="cashier__pay-btn" @click="pay">
|
||||
{{ t('cashier.payNow') }} ¥{{ order.amount }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- 已支付 -->
|
||||
<div v-if="paid" class="cashier__body enter-y">
|
||||
<div class="cashier__section-title">
|
||||
{{ t('cashier.paid') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支付方式选择 -->
|
||||
<div v-else class="cashier__body enter-y">
|
||||
<div class="cashier__section-title">
|
||||
{{ t('cashier.selectMethod') }}
|
||||
</div>
|
||||
<div v-if="!payMethods.length" class="cashier__empty">
|
||||
{{ t('cashier.emptyItems') }}
|
||||
</div>
|
||||
<div v-else class="cashier__list">
|
||||
<div
|
||||
v-for="item in payMethods"
|
||||
:key="item.id"
|
||||
class="cashier__item"
|
||||
:class="{ 'cashier__item--active': item.id === selectId }"
|
||||
@click="selectId = item.id"
|
||||
>
|
||||
<div class="cashier__item-info">
|
||||
<div class="cashier__pay-icon" :class="`cashier__pay-icon--${iconClass(item.icon)}`">
|
||||
<svg v-if="iconClass(item.icon) === 'wechat'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-322.3-227.1C205.2 150.8 64 271.4 64 420.2c0 81.1 43.6 146.4 116.6 197.4l-29.2 88.4 102.2-52.6c36.5 7.2 65.9 14.6 101.9 14.6 5.4 0 10.8-.2 16.2-.5-3.4-11.8-5.3-24.1-5.3-36.9 0-153.7 129.4-253.2 323.7-253.2zM544.9 267.6c21.5 0 35.7 14.2 35.7 35.7 0 21.3-14.2 35.8-35.7 35.8s-42.9-14.5-42.9-35.8c0-21.5 21.4-35.7 42.9-35.7zM269.3 338.3c-21.5 0-43.3-14.2-43.3-35.7 0-21.3 21.8-35.8 43.3-35.8 21.3 0 35.6 14.5 35.6 35.8 0 21.5-14.3 35.7-35.6 35.7z" />
|
||||
</svg>
|
||||
<svg v-else-if="iconClass(item.icon) === 'alipay'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M843.1 608.6c-23.4-49.7-54.2-105.8-91.7-166.2-37.8-60.4-77.1-115.3-117.6-164.4-44.6-12.3-91.7-18.8-140.4-18.8-101.3 0-194.4 34.4-266.9 91.9-83.3 66-133.5 162.7-133.5 269.5 0 103.9 49.5 199.1 133.7 265.7 76.2 60.2 174.3 95.3 280.1 95.3 138.6 0 262.2-57.3 339-147.1-33.9-26-99-62.7-202.9-99.3-29.4 39.5-72.9 64.8-128.1 64.8-99.5 0-176.4-72.2-176.4-176.4 0-99.1 71.5-177 176.4-177 78.2 0 137.6 41.6 161.8 103.9 73.6 30.9 132.3 56.9 167.4 76.5l72.1 36.1z" />
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm130.4 590.4c-39.1 27.8-86.8 43.6-138.4 43.6-130.4 0-236-105.6-236-236s105.6-236 236-236c45.6 0 88.2 13 124 35.4l-58.5 58.5c-19.4-9-40.9-14-63.5-14-85.2 0-154.4 69.2-154.4 154.4S384.8 544.3 470 544.3c52.6 0 98.9-26.4 126.4-66.6l70.6 70.6c-7.8 2.1-15.8 4-24.6 6.7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cashier__pay-name">
|
||||
{{ methodName(item) }}
|
||||
<span v-if="item.recommend" class="cashier__recommend">{{ t('cashier.recommend') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cashier__radio" :class="{ 'cashier__radio--checked': item.id === selectId }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部支付按钮 -->
|
||||
<div v-if="!paid" class="cashier__footer enter-y">
|
||||
<button class="cashier__pay-btn" :disabled="paying || !selectId" @click="pay">
|
||||
{{ paying ? t('cashier.paying') : `${t('cashier.payNow')} ¥${amountYuan}` }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -168,6 +318,20 @@ onUnmounted(() => {
|
||||
padding-bottom: 80px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
|
||||
&__error {
|
||||
color: @danger;
|
||||
font-size: 14px;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
color: @text-sub;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
&__header {
|
||||
// header 加主色调浅渐变,与金额蓝色呼应
|
||||
background: linear-gradient(180deg, #f0f6ff 0%, #ffffff 100%);
|
||||
@@ -305,6 +469,10 @@ onUnmounted(() => {
|
||||
&--union_pay {
|
||||
background: #e60012;
|
||||
}
|
||||
|
||||
&--douyin {
|
||||
background: #111;
|
||||
}
|
||||
}
|
||||
|
||||
&__pay-name {
|
||||
@@ -364,6 +532,10 @@ onUnmounted(() => {
|
||||
background: @primary-dark;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CashierItemPublic, GatewayOrderInfo } from '@/shared/api/gateway'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cashierPay, getGatewayOrder, listCashierItems } from '@/shared/api/gateway'
|
||||
|
||||
defineOptions({ name: 'PcCashier' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const orderNo = route.params.orderNo as string
|
||||
|
||||
// 演示订单数据(mock,不连后端)
|
||||
const order = computed(() => ({
|
||||
title: t('cashier.demoOrderTitle'),
|
||||
orderNo: route.params.orderNo as string,
|
||||
amount: 0.01,
|
||||
expiredTime: Date.now() + 15 * 60 * 1000,
|
||||
}))
|
||||
const loading = ref(true)
|
||||
const paying = ref(false)
|
||||
const loadError = ref('')
|
||||
const order = ref<GatewayOrderInfo>({})
|
||||
const payMethods = ref<CashierItemPublic[]>([])
|
||||
const selectId = ref<string>('')
|
||||
const payBody = ref('')
|
||||
const showQrcode = ref(false)
|
||||
|
||||
// 演示支付方式(mock)— name 由 i18n 按 icon 渲染
|
||||
interface PayMethod {
|
||||
id: string
|
||||
icon: 'wechat' | 'alipay' | 'union_pay'
|
||||
recommend?: boolean
|
||||
}
|
||||
const payMethods: PayMethod[] = [
|
||||
{ id: '1', icon: 'wechat', recommend: true },
|
||||
{ id: '2', icon: 'alipay' },
|
||||
{ id: '3', icon: 'union_pay' },
|
||||
]
|
||||
|
||||
// 支付方式名称(跟随语言)
|
||||
function methodName(item: PayMethod) {
|
||||
return t(`cashier.method.${item.icon}`)
|
||||
}
|
||||
|
||||
const selectId = ref<string>(payMethods[0]!.id)
|
||||
|
||||
// 倒计时(原生 setInterval,项目无 @vueuse/core)
|
||||
const remainSeconds = ref(0)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const amountYuan = computed(() => {
|
||||
if (!order.value.amount) {
|
||||
return '0.00'
|
||||
}
|
||||
return (order.value.amount / 100).toFixed(2)
|
||||
})
|
||||
|
||||
const paid = computed(() => order.value.status === 'paid')
|
||||
|
||||
const countdown = computed(() => {
|
||||
const s = remainSeconds.value
|
||||
const h = String(Math.floor(s / 3600)).padStart(2, '0')
|
||||
@@ -46,135 +41,235 @@ const countdown = computed(() => {
|
||||
return { h, m, s: sec }
|
||||
})
|
||||
|
||||
function startCountdown() {
|
||||
remainSeconds.value = Math.max(0, Math.floor((order.value.expiredTime - Date.now()) / 1000))
|
||||
timer = setInterval(() => {
|
||||
if (remainSeconds.value > 0) {
|
||||
remainSeconds.value--
|
||||
}
|
||||
else if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
function methodName(item?: CashierItemPublic | null) {
|
||||
if (!item) {
|
||||
return ''
|
||||
}
|
||||
if (item.name) {
|
||||
return item.name
|
||||
}
|
||||
const icon = item.icon || 'wechat'
|
||||
const key = `cashier.method.${icon}`
|
||||
const label = t(key)
|
||||
return label === key ? icon : label
|
||||
}
|
||||
|
||||
// 支付后展示二维码占位
|
||||
const showQrcode = ref(false)
|
||||
|
||||
function pay() {
|
||||
showQrcode.value = true
|
||||
function iconClass(icon?: string) {
|
||||
if (icon === 'wechat' || icon === 'wechat_pay') {
|
||||
return 'wechat'
|
||||
}
|
||||
if (icon === 'alipay') {
|
||||
return 'alipay'
|
||||
}
|
||||
if (icon === 'union_pay') {
|
||||
return 'union_pay'
|
||||
}
|
||||
return 'wechat'
|
||||
}
|
||||
|
||||
onMounted(startCountdown)
|
||||
onMounted(async () => {
|
||||
await loadPage()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
})
|
||||
|
||||
async function loadPage() {
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
order.value = await getGatewayOrder(orderNo)
|
||||
if (order.value.expiredTime) {
|
||||
const exp = new Date(order.value.expiredTime).getTime()
|
||||
remainSeconds.value = Math.max(0, Math.floor((exp - Date.now()) / 1000))
|
||||
timer = setInterval(() => {
|
||||
if (remainSeconds.value > 0) {
|
||||
remainSeconds.value--
|
||||
}
|
||||
else if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
if (!paid.value) {
|
||||
// WEB 收银台: cashierType=web, clientEnv 不传
|
||||
payMethods.value = await listCashierItems({
|
||||
orderNo,
|
||||
cashierType: 'web',
|
||||
})
|
||||
const recommend = payMethods.value.find(i => i.recommend)
|
||||
selectId.value = (recommend || payMethods.value[0])?.id || ''
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
loadError.value = e?.message || t('cashier.loadFail')
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function pay() {
|
||||
if (paying.value || paid.value || !selectId.value) {
|
||||
return
|
||||
}
|
||||
paying.value = true
|
||||
try {
|
||||
const result = await cashierPay({
|
||||
orderNo,
|
||||
itemId: selectId.value,
|
||||
cashierType: 'web',
|
||||
device: 'pc',
|
||||
})
|
||||
if (result?.status === 'success') {
|
||||
order.value.status = 'paid'
|
||||
return
|
||||
}
|
||||
if (result?.payBody) {
|
||||
if (result.payBodyType === 'url') {
|
||||
window.location.href = result.payBody
|
||||
return
|
||||
}
|
||||
// 二维码类: 展示 payBody 文本(可后续接二维码组件)
|
||||
payBody.value = result.payBody
|
||||
showQrcode.value = true
|
||||
startPoll()
|
||||
return
|
||||
}
|
||||
startPoll()
|
||||
}
|
||||
catch (e: any) {
|
||||
loadError.value = e?.message || t('cashier.payFail')
|
||||
}
|
||||
finally {
|
||||
paying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startPoll() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const latest = await getGatewayOrder(orderNo)
|
||||
order.value = latest
|
||||
if (latest.status === 'paid') {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
}
|
||||
if (latest.returnUrl) {
|
||||
window.location.href = latest.returnUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
}, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pc-cashier">
|
||||
<div class="pc-cashier__box">
|
||||
<!-- 订单头部 -->
|
||||
<div class="pc-cashier__header">
|
||||
<div class="pc-cashier__countdown">
|
||||
<span class="pc-cashier__countdown-label">{{ t('cashier.remainTimeShort') }}</span>
|
||||
<span class="pc-cashier__countdown-time">
|
||||
{{ countdown.h }}:{{ countdown.m }}:{{ countdown.s }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pc-cashier__title">
|
||||
{{ order.title }}
|
||||
</div>
|
||||
<div class="pc-cashier__price">
|
||||
<span class="pc-cashier__price-label">{{ t('cashier.payableAmount') }}</span>
|
||||
<p>
|
||||
<span>¥</span>{{ order.amount }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="pc-cashier__order-no">
|
||||
{{ t('cashier.orderNoLabel') }}{{ order.orderNo }}
|
||||
</div>
|
||||
<div v-if="loading" class="pc-cashier__content">
|
||||
{{ t('cashier.paying') }}
|
||||
</div>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<div class="pc-cashier__content">
|
||||
<!-- 支付方式网格(支付前) -->
|
||||
<div v-if="!showQrcode" class="pc-cashier__grid">
|
||||
<div
|
||||
v-for="item in payMethods"
|
||||
:key="item.id"
|
||||
class="pc-cashier__method"
|
||||
:class="{ 'pc-cashier__method--active': item.id === selectId }"
|
||||
@click="selectId = item.id"
|
||||
>
|
||||
<span class="pc-cashier__method-icon" :class="`pc-cashier__method-icon--${item.icon}`">
|
||||
<!-- 内联 SVG 品牌图标:微信支付 / 支付宝 / 银联 -->
|
||||
<svg v-if="item.icon === 'wechat'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-322.3-227.1C205.2 150.8 64 271.4 64 420.2c0 81.1 43.6 146.4 116.6 197.4l-29.2 88.4 102.2-52.6c36.5 7.2 65.9 14.6 101.9 14.6 5.4 0 10.8-.2 16.2-.5-3.4-11.8-5.3-24.1-5.3-36.9 0-153.7 129.4-253.2 323.7-253.2zM544.9 267.6c21.5 0 35.7 14.2 35.7 35.7 0 21.3-14.2 35.8-35.7 35.8s-42.9-14.5-42.9-35.8c0-21.5 21.4-35.7 42.9-35.7zM269.3 338.3c-21.5 0-43.3-14.2-43.3-35.7 0-21.3 21.8-35.8 43.3-35.8 21.3 0 35.6 14.5 35.6 35.8 0 21.5-14.3 35.7-35.6 35.7z" />
|
||||
</svg>
|
||||
<svg v-else-if="item.icon === 'alipay'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M843.1 608.6c-23.4-49.7-54.2-105.8-91.7-166.2-37.8-60.4-77.1-115.3-117.6-164.4-44.6-12.3-91.7-18.8-140.4-18.8-101.3 0-194.4 34.4-266.9 91.9-83.3 66-133.5 162.7-133.5 269.5 0 103.9 49.5 199.1 133.7 265.7 76.2 60.2 174.3 95.3 280.1 95.3 138.6 0 262.2-57.3 339-147.1-33.9-26-99-62.7-202.9-99.3-29.4 39.5-72.9 64.8-128.1 64.8-99.5 0-176.4-72.2-176.4-176.4 0-99.1 71.5-177 176.4-177 78.2 0 137.6 41.6 161.8 103.9 73.6 30.9 132.3 56.9 167.4 76.5l72.1 36.1z" />
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm130.4 590.4c-39.1 27.8-86.8 43.6-138.4 43.6-130.4 0-236-105.6-236-236s105.6-236 236-236c45.6 0 88.2 13 124 35.4l-58.5 58.5c-19.4-9-40.9-14-63.5-14-85.2 0-154.4 69.2-154.4 154.4S384.8 544.3 470 544.3c52.6 0 98.9-26.4 126.4-66.6l70.6 70.6c-7.8 2.1-15.8 4-24.6 6.7z" />
|
||||
</svg>
|
||||
<div v-else-if="loadError" class="pc-cashier__content">
|
||||
{{ loadError }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<!-- 订单头部 -->
|
||||
<div class="pc-cashier__header">
|
||||
<div v-if="remainSeconds > 0 && !paid" class="pc-cashier__countdown">
|
||||
<span class="pc-cashier__countdown-label">{{ t('cashier.remainTimeShort') }}</span>
|
||||
<span class="pc-cashier__countdown-time">
|
||||
{{ countdown.h }}:{{ countdown.m }}:{{ countdown.s }}
|
||||
</span>
|
||||
<span class="pc-cashier__method-name">{{ methodName(item) }}</span>
|
||||
<span v-if="item.recommend" class="pc-cashier__recommend">{{ t('cashier.recommend') }}</span>
|
||||
</div>
|
||||
<div class="pc-cashier__title">
|
||||
{{ order.title || t('cashier.demoOrderTitle') }}
|
||||
</div>
|
||||
<div class="pc-cashier__price">
|
||||
<span class="pc-cashier__price-label">{{ t('cashier.payableAmount') }}</span>
|
||||
<p>
|
||||
<span>¥</span>{{ amountYuan }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="pc-cashier__order-no">
|
||||
{{ t('cashier.orderNoLabel') }}{{ order.orderNo }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码占位(支付后) -->
|
||||
<div v-else class="pc-cashier__qrcode">
|
||||
<div class="pc-cashier__qrcode-box">
|
||||
<!-- 四角 L 型装饰角标,提升扫码区仪式感 -->
|
||||
<span class="pc-cashier__corner pc-cashier__corner--tl" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--tr" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--bl" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--br" />
|
||||
<svg viewBox="0 0 100 100" class="pc-cashier__qrcode-svg" aria-hidden="true">
|
||||
<rect width="100" height="100" fill="#fff" />
|
||||
<g fill="#1d2129">
|
||||
<rect x="8" y="8" width="24" height="24" />
|
||||
<rect x="14" y="14" width="12" height="12" fill="#fff" />
|
||||
<rect x="18" y="18" width="4" height="4" />
|
||||
<rect x="68" y="8" width="24" height="24" />
|
||||
<rect x="74" y="14" width="12" height="12" fill="#fff" />
|
||||
<rect x="78" y="18" width="4" height="4" />
|
||||
<rect x="8" y="68" width="24" height="24" />
|
||||
<rect x="14" y="74" width="12" height="12" fill="#fff" />
|
||||
<rect x="18" y="78" width="4" height="4" />
|
||||
<rect x="40" y="8" width="6" height="6" /><rect x="54" y="8" width="6" height="6" />
|
||||
<rect x="40" y="20" width="6" height="6" /><rect x="54" y="20" width="6" height="6" />
|
||||
<rect x="8" y="40" width="6" height="6" /><rect x="20" y="40" width="6" height="6" />
|
||||
<rect x="40" y="40" width="6" height="6" /><rect x="54" y="40" width="6" height="6" />
|
||||
<rect x="68" y="40" width="6" height="6" /><rect x="86" y="40" width="6" height="6" />
|
||||
<rect x="40" y="54" width="6" height="6" /><rect x="68" y="54" width="6" height="6" />
|
||||
<rect x="86" y="54" width="6" height="6" />
|
||||
<rect x="40" y="68" width="6" height="6" /><rect x="54" y="68" width="6" height="6" />
|
||||
<rect x="68" y="68" width="6" height="6" /><rect x="86" y="68" width="6" height="6" />
|
||||
<rect x="40" y="80" width="6" height="6" /><rect x="68" y="80" width="6" height="6" />
|
||||
<rect x="86" y="80" width="6" height="6" />
|
||||
</g>
|
||||
</svg>
|
||||
<!-- 内容区 -->
|
||||
<div class="pc-cashier__content">
|
||||
<div v-if="paid" class="pc-cashier__method-name">
|
||||
{{ t('cashier.paid') }}
|
||||
</div>
|
||||
<div class="pc-cashier__qrcode-tip">
|
||||
<p class="pc-cashier__qrcode-title">
|
||||
{{ t('cashier.qrcodePayDemo') }}
|
||||
</p>
|
||||
<p class="pc-cashier__qrcode-sub">
|
||||
{{ t('cashier.qrcodeTip', { name: methodName(payMethods.find(i => i.id === selectId)!) }) }}
|
||||
</p>
|
||||
<!-- 支付方式网格(支付前) -->
|
||||
<div v-else-if="!showQrcode" class="pc-cashier__grid">
|
||||
<div
|
||||
v-for="item in payMethods"
|
||||
:key="item.id"
|
||||
class="pc-cashier__method"
|
||||
:class="{ 'pc-cashier__method--active': item.id === selectId }"
|
||||
@click="selectId = item.id"
|
||||
>
|
||||
<span class="pc-cashier__method-icon" :class="`pc-cashier__method-icon--${iconClass(item.icon)}`">
|
||||
<svg v-if="iconClass(item.icon) === 'wechat'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M690.1 377.4c5.9 0 11.8.2 17.6.5-24.4-128.7-158.3-227.1-322.3-227.1C205.2 150.8 64 271.4 64 420.2c0 81.1 43.6 146.4 116.6 197.4l-29.2 88.4 102.2-52.6c36.5 7.2 65.9 14.6 101.9 14.6 5.4 0 10.8-.2 16.2-.5-3.4-11.8-5.3-24.1-5.3-36.9 0-153.7 129.4-253.2 323.7-253.2zM544.9 267.6c21.5 0 35.7 14.2 35.7 35.7 0 21.3-14.2 35.8-35.7 35.8s-42.9-14.5-42.9-35.8c0-21.5 21.4-35.7 42.9-35.7zM269.3 338.3c-21.5 0-43.3-14.2-43.3-35.7 0-21.3 21.8-35.8 43.3-35.8 21.3 0 35.6 14.5 35.6 35.8 0 21.5-14.3 35.7-35.6 35.7z" />
|
||||
</svg>
|
||||
<svg v-else-if="iconClass(item.icon) === 'alipay'" viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M843.1 608.6c-23.4-49.7-54.2-105.8-91.7-166.2-37.8-60.4-77.1-115.3-117.6-164.4-44.6-12.3-91.7-18.8-140.4-18.8-101.3 0-194.4 34.4-266.9 91.9-83.3 66-133.5 162.7-133.5 269.5 0 103.9 49.5 199.1 133.7 265.7 76.2 60.2 174.3 95.3 280.1 95.3 138.6 0 262.2-57.3 339-147.1-33.9-26-99-62.7-202.9-99.3-29.4 39.5-72.9 64.8-128.1 64.8-99.5 0-176.4-72.2-176.4-176.4 0-99.1 71.5-177 176.4-177 78.2 0 137.6 41.6 161.8 103.9 73.6 30.9 132.3 56.9 167.4 76.5l72.1 36.1z" />
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 1024 1024" width="22" height="22" aria-hidden="true">
|
||||
<path fill="#fff" d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm130.4 590.4c-39.1 27.8-86.8 43.6-138.4 43.6-130.4 0-236-105.6-236-236s105.6-236 236-236c45.6 0 88.2 13 124 35.4l-58.5 58.5c-19.4-9-40.9-14-63.5-14-85.2 0-154.4 69.2-154.4 154.4S384.8 544.3 470 544.3c52.6 0 98.9-26.4 126.4-66.6l70.6 70.6c-7.8 2.1-15.8 4-24.6 6.7z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="pc-cashier__method-name">{{ methodName(item) }}</span>
|
||||
<span v-if="item.recommend" class="pc-cashier__recommend">{{ t('cashier.recommend') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码/支付参数展示 -->
|
||||
<div v-else class="pc-cashier__qrcode">
|
||||
<div class="pc-cashier__qrcode-box">
|
||||
<span class="pc-cashier__corner pc-cashier__corner--tl" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--tr" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--bl" />
|
||||
<span class="pc-cashier__corner pc-cashier__corner--br" />
|
||||
<p class="pc-cashier__qrcode-sub" style="word-break: break-all; padding: 8px; font-size: 12px;">
|
||||
{{ payBody }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="pc-cashier__qrcode-tip">
|
||||
<p class="pc-cashier__qrcode-title">
|
||||
{{ t('cashier.qrcodePayDemo') }}
|
||||
</p>
|
||||
<p class="pc-cashier__qrcode-sub">
|
||||
{{ t('cashier.qrcodeTip', { name: methodName(payMethods.find(i => i.id === selectId)) }) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 立即支付按钮(支付前) -->
|
||||
<button v-if="!showQrcode" class="pc-cashier__pay-btn" @click="pay">
|
||||
{{ t('cashier.payNow') }}
|
||||
</button>
|
||||
<!-- 立即支付按钮(支付前) -->
|
||||
<button
|
||||
v-if="!showQrcode && !paid"
|
||||
class="pc-cashier__pay-btn"
|
||||
:disabled="paying || !selectId"
|
||||
@click="pay"
|
||||
>
|
||||
{{ paying ? t('cashier.paying') : t('cashier.payNow') }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 网关聚合扫码 API
|
||||
* 网关聚合扫码 / 收银台 API
|
||||
*/
|
||||
import { RequestEnum } from '@/shared/enums/httpEnum'
|
||||
import { http } from '@/shared/utils/http/axios'
|
||||
@@ -26,7 +26,7 @@ export interface GatewayOrderInfo {
|
||||
returnUrl?: string
|
||||
}
|
||||
|
||||
/** 聚合支付结果 */
|
||||
/** 聚合/收银台支付结果 */
|
||||
export interface AggregatePayResult {
|
||||
orderId?: string | number
|
||||
bizOrderNo?: string
|
||||
@@ -36,6 +36,16 @@ export interface AggregatePayResult {
|
||||
payBodyType?: string
|
||||
}
|
||||
|
||||
/** 收银台支付项(公开字段) */
|
||||
export interface CashierItemPublic {
|
||||
/** 支付项ID(后端 Long, 前端用 string 避免精度问题) */
|
||||
id: string
|
||||
name?: string
|
||||
icon?: string
|
||||
recommend?: boolean
|
||||
sortNo?: number
|
||||
}
|
||||
|
||||
/** 查询网关订单 */
|
||||
export function getGatewayOrder(orderNo: string): Promise<GatewayOrderInfo> {
|
||||
return http.request<GatewayOrderInfo>({
|
||||
@@ -63,3 +73,47 @@ export function aggregatePay(data: {
|
||||
isShowMessage: false,
|
||||
})
|
||||
}
|
||||
|
||||
/** 收银台支付项列表 */
|
||||
export function listCashierItems(params: {
|
||||
orderNo: string
|
||||
cashierType: string
|
||||
clientEnv?: string
|
||||
}): Promise<CashierItemPublic[]> {
|
||||
return http.request<CashierItemPublic[]>({
|
||||
url: '/client/gateway/cashier/items',
|
||||
method: RequestEnum.GET,
|
||||
params,
|
||||
}, {
|
||||
isShowMessage: false,
|
||||
}).then((list) => {
|
||||
// id 统一为 string
|
||||
return (list || []).map(item => ({
|
||||
...item,
|
||||
id: String(item.id),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/** 收银台发起支付 */
|
||||
export function cashierPay(data: {
|
||||
orderNo: string
|
||||
itemId: string | number
|
||||
cashierType: string
|
||||
clientEnv?: string
|
||||
openId?: string
|
||||
device?: string
|
||||
clientIp?: string
|
||||
}): Promise<AggregatePayResult> {
|
||||
return http.request<AggregatePayResult>({
|
||||
url: '/client/gateway/cashier/pay',
|
||||
method: RequestEnum.POST,
|
||||
data: {
|
||||
...data,
|
||||
// 后端 Long; 字符串数字可被 Jackson 反序列化
|
||||
itemId: data.itemId,
|
||||
},
|
||||
}, {
|
||||
isShowMessage: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "WeChat Pay",
|
||||
"alipay": "Alipay",
|
||||
"union_pay": "UnionPay"
|
||||
}
|
||||
},
|
||||
"loadFail": "Failed to load order",
|
||||
"payFail": "Failed to start payment",
|
||||
"paid": "Payment successful",
|
||||
"paying": "Paying...",
|
||||
"emptyItems": "No payment methods available"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "Pembayaran WeChat",
|
||||
"alipay": "Alipay",
|
||||
"union_pay": "UnionPay"
|
||||
}
|
||||
},
|
||||
"loadFail": "Gagal memuat pesanan",
|
||||
"payFail": "Gagal memulai pembayaran",
|
||||
"paid": "Pembayaran berhasil",
|
||||
"paying": "Membayar...",
|
||||
"emptyItems": "Tidak ada metode pembayaran"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "ウィーチャットペイ",
|
||||
"alipay": "アリペイ",
|
||||
"union_pay": "銀聯"
|
||||
}
|
||||
},
|
||||
"loadFail": "注文の読み込みに失敗しました",
|
||||
"payFail": "支払いの開始に失敗しました",
|
||||
"paid": "支払い成功",
|
||||
"paying": "支払い中...",
|
||||
"emptyItems": "利用可能な支払い方法がありません"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "위챗페이",
|
||||
"alipay": "알리페이",
|
||||
"union_pay": "유니온페이"
|
||||
}
|
||||
},
|
||||
"loadFail": "주문 로드 실패",
|
||||
"payFail": "결제 시작 실패",
|
||||
"paid": "결제 성공",
|
||||
"paying": "결제 중...",
|
||||
"emptyItems": "사용 가능한 결제 수단이 없습니다"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "WeChat Pay",
|
||||
"alipay": "Alipay",
|
||||
"union_pay": "UnionPay"
|
||||
}
|
||||
},
|
||||
"loadFail": "Gagal memuatkan pesanan",
|
||||
"payFail": "Gagal memulakan pembayaran",
|
||||
"paid": "Pembayaran berjaya",
|
||||
"paying": "Membayar...",
|
||||
"emptyItems": "Tiada kaedah pembayaran"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "วีแชต เพย์",
|
||||
"alipay": "อาลีเพย์",
|
||||
"union_pay": "ยูเนี่ยนเพย์"
|
||||
}
|
||||
},
|
||||
"loadFail": "โหลดคำสั่งซื้อไม่สำเร็จ",
|
||||
"payFail": "เริ่มชำระเงินไม่สำเร็จ",
|
||||
"paid": "ชำระเงินสำเร็จ",
|
||||
"paying": "กำลังชำระเงิน...",
|
||||
"emptyItems": "ไม่มีช่องทางชำระเงิน"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "WeChat trả tiền",
|
||||
"alipay": "Alipay",
|
||||
"union_pay": "UnionPay"
|
||||
}
|
||||
},
|
||||
"loadFail": "Không tải được đơn hàng",
|
||||
"payFail": "Không thể bắt đầu thanh toán",
|
||||
"paid": "Thanh toán thành công",
|
||||
"paying": "Đang thanh toán...",
|
||||
"emptyItems": "Không có phương thức thanh toán"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "微信支付",
|
||||
"alipay": "支付宝",
|
||||
"union_pay": "银联支付"
|
||||
}
|
||||
},
|
||||
"loadFail": "加载订单失败",
|
||||
"payFail": "支付发起失败",
|
||||
"paid": "支付成功",
|
||||
"paying": "支付中...",
|
||||
"emptyItems": "暂无可用支付方式"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "微信支付",
|
||||
"alipay": "支付寶",
|
||||
"union_pay": "銀聯支付"
|
||||
}
|
||||
},
|
||||
"loadFail": "載入訂單失敗",
|
||||
"payFail": "支付發起失敗",
|
||||
"paid": "支付成功",
|
||||
"paying": "支付中...",
|
||||
"emptyItems": "暫無可用支付方式"
|
||||
}
|
||||
|
||||
@@ -16,5 +16,10 @@
|
||||
"wechat": "微信支付",
|
||||
"alipay": "支付寶",
|
||||
"union_pay": "銀聯支付"
|
||||
}
|
||||
},
|
||||
"loadFail": "載入訂單失敗",
|
||||
"payFail": "支付發起失敗",
|
||||
"paid": "支付成功",
|
||||
"paying": "支付中...",
|
||||
"emptyItems": "暫無可用支付方式"
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ export const RoutePath = {
|
||||
CASHIER: '/cashier/:orderNo',
|
||||
/** 聚合扫码支付(跨端:两端各实现) */
|
||||
AGGREGATE: '/aggregate/:orderNo',
|
||||
/** 码牌支付(移动独占) */
|
||||
CODE_PAY: '/code-pay/:code',
|
||||
/** 码牌支付 H5(移动独占, path 与后端 getCodeLink /h/{code} 一致) */
|
||||
CODE_PAY: '/h/:code',
|
||||
/** 商户对账单(PC 独占) */
|
||||
MERCHANT_STATEMENT: '/merchant/statement',
|
||||
/** 协议展示页(跨端:用户协议/隐私政策,链接可独立分享) */
|
||||
|
||||
Reference in New Issue
Block a user