mirror of
https://gitee.com/bootx/dax-pay-h5
synced 2026-08-07 05:45:45 +08:00
feat(code-pay): 补齐云闪付/抖音码牌端页与金额上限及商户简称展示
This commit is contained in:
@@ -161,6 +161,38 @@ const routeModuleList: Array<RouteRecordRaw> = [
|
||||
},
|
||||
],
|
||||
},
|
||||
// 码牌支付-云闪付端(静态段须在 /h/:code 之前注册)
|
||||
{
|
||||
path: RoutePath.CODE_PAY_UNION,
|
||||
name: 'CodePayUnion',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '码牌支付',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'CodePayUnionPage',
|
||||
component: () => import('@/mobile/views/code-pay/union-pay/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 码牌支付-抖音端
|
||||
{
|
||||
path: RoutePath.CODE_PAY_DOUYIN,
|
||||
name: 'CodePayDouyin',
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '码牌支付',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'CodePayDouyinPage',
|
||||
component: () => import('@/mobile/views/code-pay/douyin/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 码牌支付入口分发(跨端:PC 为扫码引导页)
|
||||
{
|
||||
path: RoutePath.CODE_PAY,
|
||||
|
||||
@@ -70,6 +70,7 @@ onMounted(() => {
|
||||
<CodePayShell
|
||||
v-else
|
||||
:merchant-name="info.name"
|
||||
:mch-short-name="info.mchShortName"
|
||||
:display-amount="displayAmount"
|
||||
:amount-type="info.amountType"
|
||||
:description="description"
|
||||
|
||||
151
src/mobile/views/code-pay/douyin/index.vue
Normal file
151
src/mobile/views/code-pay/douyin/index.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 码牌支付-抖音端(黑白主视觉)
|
||||
*/
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CodePayShell from '../shared/CodePayShell.vue'
|
||||
import { useCodePayPage } from '../shared/useCodePayPage'
|
||||
|
||||
defineOptions({ name: 'CodePayDouyin' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const code = route.params.code as string
|
||||
|
||||
const {
|
||||
loading,
|
||||
paying,
|
||||
loadError,
|
||||
info,
|
||||
description,
|
||||
setDescription,
|
||||
displayAmount,
|
||||
paid,
|
||||
orderNo,
|
||||
authRedirecting,
|
||||
init,
|
||||
onInput,
|
||||
onDelete,
|
||||
pay,
|
||||
closePage,
|
||||
} = useCodePayPage({ code, clientEnv: 'douyin' })
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code-pay-page">
|
||||
<!-- 加载 / 授权中:品牌顶栏 + 上浮卡 -->
|
||||
<template v-if="loading || authRedirecting">
|
||||
<div class="code-pay-page__brand code-pay-page__brand--douyin" />
|
||||
<div class="code-pay-page__card">
|
||||
<van-loading color="#161823" size="28px" />
|
||||
<!-- 正在授权 / 加载中 -->
|
||||
<p class="code-pay-page__tip">
|
||||
{{ authRedirecting ? t('codePay.authorizing') : t('codePay.loading') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 加载失败 -->
|
||||
<template v-else-if="loadError">
|
||||
<div class="code-pay-page__brand code-pay-page__brand--douyin" />
|
||||
<div class="code-pay-page__card">
|
||||
<div class="code-pay-page__error-icon">
|
||||
<van-icon name="warning-o" size="40" color="#fa8c16" />
|
||||
</div>
|
||||
<p class="code-pay-page__error-title">
|
||||
{{ t('codePay.loadFailTitle') }}
|
||||
</p>
|
||||
<p class="code-pay-page__error">
|
||||
{{ loadError }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CodePayShell
|
||||
v-else
|
||||
:merchant-name="info.name"
|
||||
:mch-short-name="info.mchShortName"
|
||||
:display-amount="displayAmount"
|
||||
:amount-type="info.amountType"
|
||||
:description="description"
|
||||
:paying="paying"
|
||||
:paid="paid"
|
||||
:order-no="orderNo"
|
||||
brand-color="#161823"
|
||||
brand-dark="#000000"
|
||||
@update:description="setDescription"
|
||||
@input="onInput"
|
||||
@delete="onDelete"
|
||||
@pay="pay"
|
||||
@close="closePage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.code-pay-page {
|
||||
min-height: 100%;
|
||||
min-height: 100dvh;
|
||||
background: #f5f7fa;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__brand {
|
||||
height: 120px;
|
||||
|
||||
// 抖音黑白主视觉(近黑 → 纯黑)
|
||||
&--douyin {
|
||||
background: linear-gradient(135deg, #161823 0%, #000 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
margin: -48px 16px 0;
|
||||
padding: 40px 20px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 6%);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__error-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: rgb(250 140 22 / 10%);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__error-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__error {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,8 @@
|
||||
/**
|
||||
* 码牌支付入口分发页
|
||||
*
|
||||
* 二维码恒为 /h/:code; 按 UA replace 到微信/支付宝端页, 其它环境提示用钱包扫码。
|
||||
* 二维码恒为 /h/:code; 按 UA replace 到微信/支付宝/云闪付/抖音端页,
|
||||
* 其它环境(browser 等)提示用钱包扫码。
|
||||
* 不支持页:四钱包图标 + 加粗标题 + 小号副文案(与聚合 unsupported 一致)。
|
||||
*/
|
||||
import { onMounted, ref } from 'vue'
|
||||
@@ -32,17 +33,22 @@ const walletIcons = [
|
||||
{ src: douyinSvg, altKey: 'codePay.wallet.douyin' },
|
||||
]
|
||||
|
||||
/** clientEnv → 码牌分端路由 name */
|
||||
const ENV_ROUTE_NAME: Record<string, string> = {
|
||||
wechat: 'CodePayWechat',
|
||||
alipay: 'CodePayAlipay',
|
||||
union_pay: 'CodePayUnion',
|
||||
douyin: 'CodePayDouyin',
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const env = detectClientEnv()
|
||||
if (env === 'wechat') {
|
||||
router.replace({ name: 'CodePayWechat', params: { code } })
|
||||
const routeName = ENV_ROUTE_NAME[env]
|
||||
if (routeName) {
|
||||
router.replace({ name: routeName, params: { code } })
|
||||
return
|
||||
}
|
||||
if (env === 'alipay') {
|
||||
router.replace({ name: 'CodePayAlipay', params: { code } })
|
||||
return
|
||||
}
|
||||
// 云闪付/抖音/浏览器: 提示用钱包扫码
|
||||
// browser 等非钱包宿主: 提示用钱包扫码
|
||||
redirecting.value = false
|
||||
unsupported.value = true
|
||||
})
|
||||
|
||||
@@ -13,6 +13,8 @@ defineOptions({ name: 'CodePayShell' })
|
||||
const props = withDefaults(defineProps<{
|
||||
/** 商户/码牌名称 */
|
||||
merchantName?: string
|
||||
/** 商户简称(副标题「向{name}付款」) */
|
||||
mchShortName?: string
|
||||
/** 展示金额(元字符串) */
|
||||
displayAmount: string
|
||||
/** 金额类型 */
|
||||
@@ -31,6 +33,7 @@ const props = withDefaults(defineProps<{
|
||||
brandDark?: string
|
||||
}>(), {
|
||||
merchantName: '',
|
||||
mchShortName: '',
|
||||
amountType: 'random',
|
||||
description: '',
|
||||
paying: false,
|
||||
@@ -41,8 +44,8 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 金额键盘输入 */
|
||||
'input': [key: string]
|
||||
/** 金额键盘输入(Vant 数字键可能为 number) */
|
||||
'input': [key: string | number]
|
||||
/** 金额删除 */
|
||||
'delete': []
|
||||
/** 确认支付 */
|
||||
@@ -60,6 +63,13 @@ const remarkDraft = ref('')
|
||||
/** 是否固定金额布局 */
|
||||
const isFixed = computed(() => props.amountType === 'fixed')
|
||||
|
||||
/** 副标题:向{商户简称}付款 */
|
||||
const payToText = computed(() => {
|
||||
const name = props.mchShortName || t('codePay.merchantDefault')
|
||||
// 向{name}付款
|
||||
return t('codePay.payToMerchant', { name })
|
||||
})
|
||||
|
||||
/**
|
||||
* 打开备注弹窗
|
||||
*/
|
||||
@@ -151,9 +161,9 @@ function onPay() {
|
||||
<div class="code-pay-shell__merchant-name">
|
||||
{{ merchantName || t('codePay.merchantDefault') }}
|
||||
</div>
|
||||
<!-- 向商户付款 -->
|
||||
<!-- 向{商户简称}付款 -->
|
||||
<div class="code-pay-shell__merchant-sub">
|
||||
{{ t('codePay.payToMerchant') }}
|
||||
{{ payToText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,7 +226,7 @@ function onPay() {
|
||||
:show="!paying"
|
||||
:z-index="100"
|
||||
@close="onPay"
|
||||
@input="(k: string) => emit('input', k)"
|
||||
@input="(k: string | number) => emit('input', k)"
|
||||
@delete="emit('delete')"
|
||||
/>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
getCodePayInfo,
|
||||
} from '@/shared/api/code-pay'
|
||||
import { invokeJsapiByEnv } from '@/shared/pay/jsapi'
|
||||
import { yuanToFen } from '@/shared/utils/pay-amount'
|
||||
import { isAmountOverMax, yuanToFen } from '@/shared/utils/pay-amount'
|
||||
import { clearPayOpenId, getPayOpenId } from '@/shared/utils/pay-openid'
|
||||
import {
|
||||
redirectToPayUrl,
|
||||
@@ -25,7 +25,7 @@ export interface UseCodePayPageOptions {
|
||||
/** 码牌编码 */
|
||||
code: string
|
||||
/** 写死的客户端环境 */
|
||||
clientEnv: 'wechat' | 'alipay'
|
||||
clientEnv: 'wechat' | 'alipay' | 'union_pay' | 'douyin'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,32 +107,43 @@ export function useCodePayPage(options: UseCodePayPageOptions) {
|
||||
|
||||
/**
|
||||
* 金额键盘输入
|
||||
* Vant NumberKeyboard 数字键 @input 传 number,须统一转 string,否则 amount 被污染后 indexOf 崩溃
|
||||
*/
|
||||
function onInput(key: string) {
|
||||
function onInput(key: string | number) {
|
||||
if (paying.value || paid.value) {
|
||||
return
|
||||
}
|
||||
if (key === '.' && amount.value.includes('.')) {
|
||||
// 统一为字符串(Vant 数字键为 number)
|
||||
const k = String(key)
|
||||
// 保证 amount 始终是字符串
|
||||
let current = String(amount.value)
|
||||
if (k === '.' && current.includes('.')) {
|
||||
return
|
||||
}
|
||||
// 最多两位小数
|
||||
const dot = amount.value.indexOf('.')
|
||||
if (dot >= 0 && amount.value.length - dot > 2 && key !== '.') {
|
||||
const dot = current.indexOf('.')
|
||||
if (dot >= 0 && current.length - dot > 2 && k !== '.') {
|
||||
return
|
||||
}
|
||||
if (amount.value === '0' && key !== '.') {
|
||||
amount.value = key
|
||||
if (current === '0' && k !== '.') {
|
||||
current = k
|
||||
}
|
||||
else {
|
||||
amount.value += key
|
||||
current += k
|
||||
}
|
||||
// 上限:对齐后端 amount @Max(9999999999 分),超限则忽略本次按键
|
||||
if (isAmountOverMax(current)) {
|
||||
return
|
||||
}
|
||||
amount.value = current
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
if (paying.value || paid.value) {
|
||||
return
|
||||
}
|
||||
amount.value = amount.value.slice(0, -1) || '0'
|
||||
// String 加固:防止历史脏值导致 slice 失败
|
||||
amount.value = String(amount.value).slice(0, -1) || '0'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,11 +215,16 @@ export function useCodePayPage(options: UseCodePayPageOptions) {
|
||||
}
|
||||
let amountFen: number | undefined
|
||||
if (info.value.amountType === 'random') {
|
||||
amountFen = yuanToFen(amount.value)
|
||||
amountFen = yuanToFen(String(amount.value))
|
||||
if (!amountFen) {
|
||||
showNotify({ type: 'warning', message: t('codePay.amountZero') })
|
||||
return
|
||||
}
|
||||
// 提交前再拦一层(与键盘上限一致)
|
||||
if (isAmountOverMax(String(amount.value))) {
|
||||
showNotify({ type: 'warning', message: t('codePay.amountOverMax') })
|
||||
return
|
||||
}
|
||||
}
|
||||
if (info.value.needOpenId && !openId.value) {
|
||||
showNotify({ type: 'warning', message: t('codePay.needAuth') })
|
||||
|
||||
151
src/mobile/views/code-pay/union-pay/index.vue
Normal file
151
src/mobile/views/code-pay/union-pay/index.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 码牌支付-云闪付端(品牌红)
|
||||
*/
|
||||
import { onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CodePayShell from '../shared/CodePayShell.vue'
|
||||
import { useCodePayPage } from '../shared/useCodePayPage'
|
||||
|
||||
defineOptions({ name: 'CodePayUnion' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const code = route.params.code as string
|
||||
|
||||
const {
|
||||
loading,
|
||||
paying,
|
||||
loadError,
|
||||
info,
|
||||
description,
|
||||
setDescription,
|
||||
displayAmount,
|
||||
paid,
|
||||
orderNo,
|
||||
authRedirecting,
|
||||
init,
|
||||
onInput,
|
||||
onDelete,
|
||||
pay,
|
||||
closePage,
|
||||
} = useCodePayPage({ code, clientEnv: 'union_pay' })
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="code-pay-page">
|
||||
<!-- 加载 / 授权中:品牌顶栏 + 上浮卡 -->
|
||||
<template v-if="loading || authRedirecting">
|
||||
<div class="code-pay-page__brand code-pay-page__brand--union" />
|
||||
<div class="code-pay-page__card">
|
||||
<van-loading color="#E21836" size="28px" />
|
||||
<!-- 正在授权 / 加载中 -->
|
||||
<p class="code-pay-page__tip">
|
||||
{{ authRedirecting ? t('codePay.authorizing') : t('codePay.loading') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 加载失败 -->
|
||||
<template v-else-if="loadError">
|
||||
<div class="code-pay-page__brand code-pay-page__brand--union" />
|
||||
<div class="code-pay-page__card">
|
||||
<div class="code-pay-page__error-icon">
|
||||
<van-icon name="warning-o" size="40" color="#fa8c16" />
|
||||
</div>
|
||||
<p class="code-pay-page__error-title">
|
||||
{{ t('codePay.loadFailTitle') }}
|
||||
</p>
|
||||
<p class="code-pay-page__error">
|
||||
{{ loadError }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CodePayShell
|
||||
v-else
|
||||
:merchant-name="info.name"
|
||||
:mch-short-name="info.mchShortName"
|
||||
:display-amount="displayAmount"
|
||||
:amount-type="info.amountType"
|
||||
:description="description"
|
||||
:paying="paying"
|
||||
:paid="paid"
|
||||
:order-no="orderNo"
|
||||
brand-color="#E21836"
|
||||
brand-dark="#C41230"
|
||||
@update:description="setDescription"
|
||||
@input="onInput"
|
||||
@delete="onDelete"
|
||||
@pay="pay"
|
||||
@close="closePage"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.code-pay-page {
|
||||
min-height: 100%;
|
||||
min-height: 100dvh;
|
||||
background: #f5f7fa;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__brand {
|
||||
height: 120px;
|
||||
|
||||
// 云闪付品牌红
|
||||
&--union {
|
||||
background: linear-gradient(135deg, #e21836 0%, #c41230 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&__card {
|
||||
margin: -48px 16px 0;
|
||||
padding: 40px 20px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 6%);
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__tip {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__error-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: rgb(250 140 22 / 10%);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__error-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
&__error {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -70,6 +70,7 @@ onMounted(() => {
|
||||
<CodePayShell
|
||||
v-else
|
||||
:merchant-name="info.name"
|
||||
:mch-short-name="info.mchShortName"
|
||||
:display-amount="displayAmount"
|
||||
:amount-type="info.amountType"
|
||||
:description="description"
|
||||
|
||||
@@ -74,6 +74,18 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('@/pc/views/code-pay/Index.vue'),
|
||||
meta: { title: '码牌支付' },
|
||||
},
|
||||
{
|
||||
path: RoutePath.CODE_PAY_UNION,
|
||||
name: 'PcCodePayUnion',
|
||||
component: () => import('@/pc/views/code-pay/Index.vue'),
|
||||
meta: { title: '码牌支付' },
|
||||
},
|
||||
{
|
||||
path: RoutePath.CODE_PAY_DOUYIN,
|
||||
name: 'PcCodePayDouyin',
|
||||
component: () => import('@/pc/views/code-pay/Index.vue'),
|
||||
meta: { title: '码牌支付' },
|
||||
},
|
||||
{
|
||||
path: RoutePath.CODE_PAY,
|
||||
name: 'PcCodePay',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* PC 码牌落地:引导手机钱包扫码(Web 居中白卡,对齐 PC 聚合 unsupported)
|
||||
* 码牌收款仅在微信/支付宝移动端完成,PC 只做扫码提示。
|
||||
* 码牌收款在微信/支付宝/云闪付/抖音移动端完成,PC 只做扫码提示。
|
||||
*/
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
@@ -14,7 +14,7 @@ defineOptions({ name: 'PcCodePay' })
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
// 路由 /h/:code 或 /h/wechat|alipay/:code
|
||||
// 路由 /h/:code 或 /h/wechat|alipay|union-pay|douyin/:code
|
||||
const code = (route.params.code as string) || ''
|
||||
|
||||
/** 四钱包图标 */
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface CodePayInfo {
|
||||
code?: string
|
||||
/** 码牌名称 */
|
||||
name?: string
|
||||
/** 商户简称(展示用; 后端简称空时已回退全称) */
|
||||
mchShortName?: string
|
||||
/** 金额类型 random-自定义 / fixed-固定 */
|
||||
amountType?: AmountType
|
||||
/** 固定金额(分, amountType=fixed 时返回) */
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "Add remark",
|
||||
"remarkPlaceholder": "Enter payment remark",
|
||||
"amountZero": "Amount cannot be 0",
|
||||
"amountOverMax": "Amount cannot exceed ¥99,999,999.99",
|
||||
"confirmPay": "Confirm Payment",
|
||||
"demoPay": "Demo: code {code} payment ¥{amount}",
|
||||
"loadFail": "Failed to load QR code info, please try again later",
|
||||
"loadFailTitle": "Load failed",
|
||||
"merchantDefault": "Merchant",
|
||||
"payToMerchant": "Pay to merchant",
|
||||
"payToMerchant": "Pay to {name}",
|
||||
"paySuccess": "Payment successful",
|
||||
"payFail": "Payment failed, please retry",
|
||||
"payLaunched": "Payment launched",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "Tambah catatan",
|
||||
"remarkPlaceholder": "Masukkan catatan pembayaran",
|
||||
"amountZero": "Jumlah tidak boleh 0",
|
||||
"amountOverMax": "Jumlah tidak boleh melebihi ¥99.999.999,99",
|
||||
"confirmPay": "Konfirmasi Pembayaran",
|
||||
"demoPay": "Demo: kode {code} bayar ¥{amount}",
|
||||
"loadFail": "Gagal memuat info kode QR, coba lagi nanti",
|
||||
"loadFailTitle": "Gagal memuat",
|
||||
"merchantDefault": "Merchant",
|
||||
"payToMerchant": "Bayar ke merchant",
|
||||
"payToMerchant": "Bayar ke {name}",
|
||||
"paySuccess": "Pembayaran berhasil",
|
||||
"payFail": "Pembayaran gagal, coba lagi",
|
||||
"payLaunched": "Pembayaran diluncurkan",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "備考を追加",
|
||||
"remarkPlaceholder": "支払い備考を入力",
|
||||
"amountZero": "金額は0にできません",
|
||||
"amountOverMax": "金額は 99999999.99 元を超えられません",
|
||||
"confirmPay": "支払いを確認",
|
||||
"demoPay": "デモ:コード {code} 支払い ¥{amount}",
|
||||
"loadFail": "コード情報の読み込みに失敗しました。しばらくしてから再試行してください",
|
||||
"loadFailTitle": "読み込み失敗",
|
||||
"merchantDefault": "加盟店",
|
||||
"payToMerchant": "加盟店へ支払い",
|
||||
"payToMerchant": "{name} へ支払い",
|
||||
"paySuccess": "支払い成功",
|
||||
"payFail": "支払いに失敗しました。再試行してください",
|
||||
"payLaunched": "支払いを開始しました",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "비고 추가",
|
||||
"remarkPlaceholder": "결제 비고를 입력하세요",
|
||||
"amountZero": "금액은 0일 수 없습니다",
|
||||
"amountOverMax": "금액은 99999999.99 원을 초과할 수 없습니다",
|
||||
"confirmPay": "결제 확인",
|
||||
"demoPay": "데모: 코드 {code} 결제 ¥{amount}",
|
||||
"loadFail": "코드 정보를 불러오지 못했습니다. 잠시 후 다시 시도하세요",
|
||||
"loadFailTitle": "불러오기 실패",
|
||||
"merchantDefault": "가맹점",
|
||||
"payToMerchant": "가맹점에 결제",
|
||||
"payToMerchant": "{name}에 결제",
|
||||
"paySuccess": "결제 성공",
|
||||
"payFail": "결제 실패, 다시 시도하세요",
|
||||
"payLaunched": "결제가 시작되었습니다",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "Tambah catatan",
|
||||
"remarkPlaceholder": "Masukkan catatan pembayaran",
|
||||
"amountZero": "Jumlah tidak boleh 0",
|
||||
"amountOverMax": "Jumlah tidak boleh melebihi ¥99,999,999.99",
|
||||
"confirmPay": "Sahkan Pembayaran",
|
||||
"demoPay": "Demo: kod {code} bayar ¥{amount}",
|
||||
"loadFail": "Gagal memuatkan info kod QR, cuba lagi kemudian",
|
||||
"loadFailTitle": "Gagal memuatkan",
|
||||
"merchantDefault": "Pedagang",
|
||||
"payToMerchant": "Bayar kepada pedagang",
|
||||
"payToMerchant": "Bayar kepada {name}",
|
||||
"paySuccess": "Pembayaran berjaya",
|
||||
"payFail": "Pembayaran gagal, cuba lagi",
|
||||
"payLaunched": "Pembayaran dilancarkan",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "เพิ่มหมายเหตุ",
|
||||
"remarkPlaceholder": "กรอกหมายเหตุการชำระเงิน",
|
||||
"amountZero": "จำนวนเงินต้องไม่เป็น 0",
|
||||
"amountOverMax": "จำนวนเงินต้องไม่เกิน ¥99,999,999.99",
|
||||
"confirmPay": "ยืนยันการชำระเงิน",
|
||||
"demoPay": "สาธิต: รหัส {code} ชำระ ¥{amount}",
|
||||
"loadFail": "โหลดข้อมูลรหัส QR ไม่สำเร็จ โปรดลองอีกครั้ง",
|
||||
"loadFailTitle": "โหลดไม่สำเร็จ",
|
||||
"merchantDefault": "ร้านค้า",
|
||||
"payToMerchant": "ชำระให้ร้านค้า",
|
||||
"payToMerchant": "ชำระให้ {name}",
|
||||
"paySuccess": "ชำระเงินสำเร็จ",
|
||||
"payFail": "ชำระเงินไม่สำเร็จ โปรดลองอีกครั้ง",
|
||||
"payLaunched": "เปิดการชำระเงินแล้ว",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "Thêm ghi chú",
|
||||
"remarkPlaceholder": "Nhập ghi chú thanh toán",
|
||||
"amountZero": "Số tiền không được là 0",
|
||||
"amountOverMax": "Số tiền không được vượt quá ¥99.999.999,99",
|
||||
"confirmPay": "Xác nhận thanh toán",
|
||||
"demoPay": "Demo: mã {code} thanh toán ¥{amount}",
|
||||
"loadFail": "Tải thông tin mã QR thất bại, vui lòng thử lại",
|
||||
"loadFailTitle": "Tải thất bại",
|
||||
"merchantDefault": "Merchant",
|
||||
"payToMerchant": "Thanh toán cho merchant",
|
||||
"payToMerchant": "Thanh toán cho {name}",
|
||||
"paySuccess": "Thanh toán thành công",
|
||||
"payFail": "Thanh toán thất bại, vui lòng thử lại",
|
||||
"payLaunched": "Đã mở thanh toán",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "添加备注",
|
||||
"remarkPlaceholder": "请输入支付备注内容",
|
||||
"amountZero": "金额不可为0",
|
||||
"amountOverMax": "金额不能超过 99999999.99 元",
|
||||
"confirmPay": "确认支付",
|
||||
"demoPay": "演示:码牌 {code} 支付 ¥{amount}",
|
||||
"loadFail": "码牌信息加载失败, 请稍后重试",
|
||||
"loadFailTitle": "加载失败",
|
||||
"merchantDefault": "收款商户",
|
||||
"payToMerchant": "向商户付款",
|
||||
"payToMerchant": "向{name}付款",
|
||||
"paySuccess": "支付成功",
|
||||
"payFail": "支付失败, 请重试",
|
||||
"payLaunched": "支付已拉起",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "新增備註",
|
||||
"remarkPlaceholder": "請輸入支付備註內容",
|
||||
"amountZero": "金額不可為 0",
|
||||
"amountOverMax": "金額不能超過 99999999.99 元",
|
||||
"confirmPay": "確認支付",
|
||||
"demoPay": "示範:碼牌 {code} 支付 ¥{amount}",
|
||||
"loadFail": "碼牌資訊載入失敗,請稍後重試",
|
||||
"loadFailTitle": "載入失敗",
|
||||
"merchantDefault": "收款商戶",
|
||||
"payToMerchant": "向商戶付款",
|
||||
"payToMerchant": "向{name}付款",
|
||||
"paySuccess": "支付成功",
|
||||
"payFail": "支付失敗,請重試",
|
||||
"payLaunched": "支付已拉起",
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
"addRemark": "新增備註",
|
||||
"remarkPlaceholder": "請輸入支付備註內容",
|
||||
"amountZero": "金額不可為 0",
|
||||
"amountOverMax": "金額不能超過 99999999.99 元",
|
||||
"confirmPay": "確認支付",
|
||||
"demoPay": "示範:碼牌 {code} 支付 ¥{amount}",
|
||||
"loadFail": "碼牌資訊載入失敗,請稍後重試",
|
||||
"loadFailTitle": "載入失敗",
|
||||
"merchantDefault": "收款商戶",
|
||||
"payToMerchant": "向商戶付款",
|
||||
"payToMerchant": "向{name}付款",
|
||||
"paySuccess": "支付成功",
|
||||
"payFail": "支付失敗,請重試",
|
||||
"payLaunched": "支付已拉起",
|
||||
|
||||
@@ -47,6 +47,10 @@ export const RoutePath = {
|
||||
CODE_PAY_WECHAT: '/h/wechat/:code',
|
||||
/** 码牌支付-支付宝端(Mobile 收款;PC 同入口扫码引导) */
|
||||
CODE_PAY_ALIPAY: '/h/alipay/:code',
|
||||
/** 码牌支付-云闪付端(Mobile 收款;PC 同入口扫码引导) */
|
||||
CODE_PAY_UNION: '/h/union-pay/:code',
|
||||
/** 码牌支付-抖音端(Mobile 收款;PC 同入口扫码引导) */
|
||||
CODE_PAY_DOUYIN: '/h/douyin/:code',
|
||||
/** 商户对账单(PC 独占) */
|
||||
MERCHANT_STATEMENT: '/merchant/statement',
|
||||
/** 协议展示页(跨端:用户协议/隐私政策,链接可独立分享) */
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
* 支付金额工具:分 ↔ 元
|
||||
*/
|
||||
|
||||
/**
|
||||
* 支付金额上限(分),对齐后端 NormalPayParam @Max(9999999999)
|
||||
* 即 99999999.99 元
|
||||
*/
|
||||
export const MAX_PAY_AMOUNT_FEN = 9_999_999_999
|
||||
|
||||
/**
|
||||
* 支付金额上限(元,展示/比较用)
|
||||
*/
|
||||
export const MAX_PAY_AMOUNT_YUAN = MAX_PAY_AMOUNT_FEN / 100
|
||||
|
||||
/**
|
||||
* 分 → 元字符串(两位小数)
|
||||
* 后端 Long 全局序列化为字符串(防 JS 精度),故需兼容字符串入参
|
||||
@@ -28,3 +39,15 @@ export function yuanToFen(yuan: string): number {
|
||||
}
|
||||
return Math.round(n * 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 元字符串是否超过支付上限(含中间输入态如 "100.",按数值比较)
|
||||
*/
|
||||
export function isAmountOverMax(yuan: string): boolean {
|
||||
const n = Number(yuan)
|
||||
if (!Number.isFinite(n)) {
|
||||
return false
|
||||
}
|
||||
// 与分精度对齐:先转分再比,避免浮点误差
|
||||
return Math.round(n * 100) > MAX_PAY_AMOUNT_FEN
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user