erio e761d38fd1 fix(payment): integrate recharge fee rate in order flow and fix UI display
Backend:
- Use cfg.RechargeFeeRate in order creation instead of hardcoded 0
- Remove dead getFeeRate stub method
- All amounts computed server-side: order_amount, pay_amount, fee_rate

Frontend - PaymentView:
- Read recharge_fee_rate from checkout-info API (not per-method)
- Show fee breakdown only when fee_rate > 0
- Show credited amount only when multiplier ≠ 1

Frontend - Order display (user + admin):
- Fix fee_rate * 100 bug (fee_rate is already a percentage)
- OrderTable: show pay_amount as primary, fee/credited as sub-lines
- AdminOrderDetail: full breakdown (base/fee/paid/credited)
- AdminRefundDialog: label "到账金额" for clarity
- PaymentResultView: show pay_amount with fee info

Types + i18n:
- Add recharge_fee_rate to CheckoutInfoResponse
- Add fee_rate to CreateOrderResult
- Add translations: creditedAmount, fee, baseAmount, includedInPayAmount
2026-04-15 01:27:24 +08:00

77 lines
3.0 KiB
Vue

<template>
<DataTable :columns="columns" :data="orders" :loading="loading">
<template #cell-id="{ value }">
<span class="font-mono text-sm">#{{ value }}</span>
</template>
<template #cell-out_trade_no="{ value }">
<span class="text-sm text-gray-900 dark:text-white">{{ value }}</span>
</template>
<template v-if="showUser" #cell-user_email="{ value, row }">
<div class="text-sm">
<span class="text-gray-900 dark:text-white">{{ value || row.user_name || '#' + row.user_id }}</span>
<span v-if="row.user_notes" class="ml-1 text-xs text-gray-400">({{ row.user_notes }})</span>
</div>
</template>
<template #cell-pay_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.fee_rate > 0" class="ml-1 text-xs text-gray-400" :title="t('payment.orders.fee') + ': ' + row.fee_rate + '%'">
({{ t('payment.orders.fee') }} {{ row.fee_rate }}%)
</span>
<div v-if="row.amount !== row.pay_amount" class="text-xs text-gray-500">
{{ t('payment.orders.creditedAmount') }}: {{ row.order_type === 'balance' ? '$' : '¥' }}{{ row.amount.toFixed(2) }}
</div>
</div>
</template>
<template #cell-payment_type="{ value }">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('payment.methods.' + value, value) }}</span>
</template>
<template #cell-status="{ value }">
<OrderStatusBadge :status="value" />
</template>
<template #cell-created_at="{ value }">
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatDate(value) }}</span>
</template>
<template #cell-actions="{ row }">
<slot name="actions" :row="row" />
</template>
</DataTable>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { PaymentOrder } from '@/types/payment'
import type { Column } from '@/components/common/types'
import DataTable from '@/components/common/DataTable.vue'
import OrderStatusBadge from '@/components/payment/OrderStatusBadge.vue'
const { t } = useI18n()
const props = defineProps<{
orders: PaymentOrder[]
loading: boolean
showUser?: boolean
}>()
function formatDate(dateStr: string) { return new Date(dateStr).toLocaleString() }
const columns = computed((): Column[] => {
const cols: Column[] = [
{ key: 'id', label: t('payment.orders.orderId') },
{ key: 'out_trade_no', label: t('payment.orders.orderNo') },
]
if (props.showUser) {
cols.push({ key: 'user_email', label: t('payment.admin.colUser') })
}
cols.push(
{ key: 'pay_amount', label: t('payment.orders.payAmount') },
{ key: 'payment_type', label: t('payment.orders.paymentMethod') },
{ key: 'status', label: t('payment.orders.status') },
{ key: 'created_at', label: t('payment.orders.createdAt') },
{ key: 'actions', label: t('common.actions') },
)
return cols
})
</script>