Merge pull request #4629 from yan9651688/fix/issue-4609-plan-currency-symbol

fix(payment): show plan prices with configured currency symbols
This commit is contained in:
Wesley Liddick
2026-07-20 15:32:11 +08:00
committed by GitHub
4 changed files with 129 additions and 5 deletions
@@ -29,9 +29,9 @@
</template>
<template #cell-price="{ value, row }">
<div class="text-sm">
<span class="font-medium text-gray-900 dark:text-white">${{ (value ?? 0).toFixed(2) }}</span>
<span class="font-medium text-gray-900 dark:text-white">{{ planCurrencySymbol(row.currency) }}{{ (value ?? 0).toFixed(2) }}</span>
<span v-if="row.currency" class="ml-1 text-xs text-gray-400">{{ row.currency }}</span>
<span v-if="row.original_price" class="ml-1 text-xs text-gray-400 line-through">${{ row.original_price.toFixed(2) }}</span>
<span v-if="row.original_price" class="ml-1 text-xs text-gray-400 line-through">{{ planCurrencySymbol(row.currency) }}{{ row.original_price.toFixed(2) }}</span>
</div>
</template>
<template #cell-validity_days="{ value, row }">
@@ -91,11 +91,16 @@ import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import Icon from '@/components/icons/Icon.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
import PlanEditDialog from './PlanEditDialog.vue'
import { currencySymbol } from '@/components/payment/currency'
import { platformTextClass } from '@/utils/platformColors'
const { t } = useI18n()
const appStore = useAppStore()
function planCurrencySymbol(currency?: string): string {
return currencySymbol(currency || 'USD')
}
// ==================== Groups ====================
const groups = ref<AdminGroup[]>([])
@@ -0,0 +1,106 @@
import { flushPromises, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import AdminPaymentPlansView from '../AdminPaymentPlansView.vue'
const { getPlans, getConfig, getGroups } = vi.hoisted(() => ({
getPlans: vi.fn(),
getConfig: vi.fn(),
getGroups: vi.fn(),
}))
vi.mock('@/api/admin/payment', () => ({
adminPaymentAPI: {
getPlans,
getConfig,
},
}))
vi.mock('@/api/admin', () => ({
default: {
groups: {
getAll: getGroups,
},
},
}))
vi.mock('vue-i18n', async (importOriginal) => {
const actual = await importOriginal<typeof import('vue-i18n')>()
return {
...actual,
useI18n: () => ({
t: (key: string) => key,
}),
}
})
const DataTableStub = {
props: ['data'],
template: `
<div>
<div v-for="row in data" :key="row.id">
<slot name="cell-price" :value="row.price" :row="row" />
</div>
</div>
`,
}
describe('AdminPaymentPlansView', () => {
beforeEach(() => {
getGroups.mockResolvedValue([])
getConfig.mockResolvedValue({ data: {} })
getPlans.mockResolvedValue({
data: [
{
id: 1,
name: 'CNY plan',
group_id: 1,
price: 499,
original_price: 599,
currency: 'CNY',
validity_days: 30,
validity_unit: 'day',
sort_order: 0,
for_sale: true,
features: [],
},
{
id: 2,
name: 'Legacy plan',
group_id: 1,
price: 10,
original_price: 0,
currency: '',
validity_days: 30,
validity_unit: 'day',
sort_order: 0,
for_sale: true,
features: [],
},
],
})
})
it('uses the configured currency symbol and keeps legacy prices in USD', async () => {
const wrapper = mount(AdminPaymentPlansView, {
global: {
plugins: [createPinia()],
stubs: {
AppLayout: { template: '<div><slot /></div>' },
DataTable: DataTableStub,
ConfirmDialog: true,
GroupBadge: true,
Icon: true,
PlanEditDialog: true,
},
},
})
await flushPromises()
expect(wrapper.text()).toContain('¥499.00CNY')
expect(wrapper.text()).toContain('¥599.00')
expect(wrapper.text()).toContain('$10.00')
})
})