feat(payment): balance recharge multiplier and refund amount separation
- Add balance_recharge_multiplier system setting (e.g. 1.2 = charge 100 get 120) - Separate order_amount (credited balance) from pay_amount (actual payment) - Refund calculates gateway amount proportionally from pay_amount - Frontend shows both amounts in order details, payment status, refund dialog - Admin settings UI for configuring recharge multiplier
This commit is contained in:
@@ -19,11 +19,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('payment.orders.amount') }}</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">${{ order.amount.toFixed(2) }}</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ order.order_type === 'balance' ? '$' : '¥' }}{{ order.amount.toFixed(2) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('payment.orders.payAmount') }}</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">${{ order.pay_amount.toFixed(2) }}</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">¥{{ order.pay_amount.toFixed(2) }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('payment.orders.paymentMethod') }}</p>
|
||||
@@ -73,7 +73,7 @@
|
||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>
|
||||
<span class="text-red-600 dark:text-red-400">{{ t('payment.admin.refundAmount') }}:</span>
|
||||
<span class="ml-1 font-medium text-red-700 dark:text-red-300">${{ order.refund_amount.toFixed(2) }}</span>
|
||||
<span class="ml-1 font-medium text-red-700 dark:text-red-300">{{ order.order_type === 'balance' ? '$' : '¥' }}{{ order.refund_amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div v-if="order.refund_reason" class="col-span-2">
|
||||
<span class="text-red-600 dark:text-red-400">{{ t('payment.admin.refundReason') }}:</span>
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
|
||||
<template #cell-amount="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ row.order_type === 'balance' ? '$' : '¥' }}{{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">
|
||||
({{ t('payment.orders.payAmount') }}: ${{ row.pay_amount.toFixed(2) }})
|
||||
({{ t('payment.orders.payAmount') }}: ¥{{ row.pay_amount.toFixed(2) }})
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,11 +35,15 @@
|
||||
</div>
|
||||
<div class="mt-1 flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.amount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ order?.pay_amount?.toFixed(2) }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ order?.order_type === 'balance' ? '$' : '¥' }}{{ order?.amount?.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="mt-1 flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.payAmount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">¥{{ order?.pay_amount?.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div v-if="actuallyRefunded > 0" class="mt-1 flex justify-between text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.admin.alreadyRefunded') }}</span>
|
||||
<span class="font-medium text-red-600 dark:text-red-400">${{ actuallyRefunded.toFixed(2) }}</span>
|
||||
<span class="font-medium text-red-600 dark:text-red-400">{{ order?.order_type === 'balance' ? '$' : '¥' }}{{ actuallyRefunded.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,7 +70,7 @@
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-3 text-sm dark:bg-dark-700">
|
||||
<div class="text-gray-500 dark:text-gray-400">{{ t('payment.admin.orderAmount') }}</div>
|
||||
<div class="mt-1 font-semibold text-gray-900 dark:text-white">${{ order?.pay_amount?.toFixed(2) }}</div>
|
||||
<div class="mt-1 font-semibold text-gray-900 dark:text-white">{{ order?.order_type === 'balance' ? '$' : '¥' }}{{ order?.amount?.toFixed(2) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -91,7 +95,7 @@
|
||||
<div>
|
||||
<label class="input-label">{{ t('payment.admin.refundAmount') }}</label>
|
||||
<div class="relative">
|
||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">$</span>
|
||||
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">{{ order?.order_type === 'balance' ? '$' : '¥' }}</span>
|
||||
<input
|
||||
v-model.number="form.amount"
|
||||
type="number"
|
||||
@@ -103,7 +107,7 @@
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('payment.admin.maxRefundable') }}: ${{ maxRefundable.toFixed(2) }}
|
||||
{{ t('payment.admin.maxRefundable') }}: {{ order?.order_type === 'balance' ? '$' : '¥' }}{{ maxRefundable.toFixed(2) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -200,12 +204,12 @@ const actuallyRefunded = computed(() => {
|
||||
|
||||
const maxRefundable = computed(() => {
|
||||
if (!props.order) return 0
|
||||
return props.order.pay_amount - actuallyRefunded.value
|
||||
return props.order.amount - actuallyRefunded.value
|
||||
})
|
||||
|
||||
const balanceInsufficient = computed(() => {
|
||||
if (props.userBalance == null || !props.order) return false
|
||||
return props.userBalance < props.order.pay_amount
|
||||
return props.userBalance < props.order.amount
|
||||
})
|
||||
|
||||
watch(() => props.show, (val) => {
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</template>
|
||||
<template #cell-amount="{ value, row }">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">(${{ row.pay_amount.toFixed(2) }})</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ row.order_type === 'balance' ? '$' : '¥' }}{{ value.toFixed(2) }}</span>
|
||||
<span v-if="row.pay_amount !== value" class="ml-1 text-xs text-gray-500">(¥{{ row.pay_amount.toFixed(2) }})</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-payment_type="{ value }">
|
||||
|
||||
@@ -45,7 +45,11 @@
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.amount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ paidOrder.pay_amount.toFixed(2) }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ paidOrder.order_type === 'balance' ? '$' : '¥' }}{{ paidOrder.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.payAmount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">¥{{ paidOrder.pay_amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.amount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ paidOrder.pay_amount.toFixed(2) }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ paidOrder.order_type === 'balance' ? '$' : '¥' }}{{ paidOrder.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.payAmount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">¥{{ paidOrder.pay_amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.orderId') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">#{{ orderId }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<div v-if="amount > 0" class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.amount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">${{ payAmount.toFixed(2) }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ orderType === 'balance' ? '$' : '¥' }}{{ amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('payment.orders.payAmount') }}</span>
|
||||
<span class="font-medium text-gray-900 dark:text-white">¥{{ payAmount.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,7 +40,7 @@
|
||||
<div class="card overflow-hidden">
|
||||
<div class="bg-gradient-to-br from-[#635bff] to-[#4f46e5] px-6 py-5 text-center">
|
||||
<p class="text-sm font-medium text-indigo-200">{{ t('payment.actualPay') }}</p>
|
||||
<p class="mt-1 text-3xl font-bold text-white">${{ payAmount.toFixed(2) }}</p>
|
||||
<p class="mt-1 text-3xl font-bold text-white">¥{{ payAmount.toFixed(2) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stripe Payment Element -->
|
||||
@@ -75,7 +79,9 @@ const POPUP_METHODS = new Set(['alipay', 'wechat_pay'])
|
||||
|
||||
const props = defineProps<{
|
||||
orderId: number
|
||||
amount: number
|
||||
clientSecret: string
|
||||
orderType?: 'balance' | 'subscription'
|
||||
publishableKey: string
|
||||
payAmount: number
|
||||
}>()
|
||||
|
||||
Reference in New Issue
Block a user