import { client } from "@/api/rpcclient"; import DownloadIcon from "@/components/icons/DownloadIcon.vue"; import ListIcon from "@/components/icons/ListIcon.vue"; import BaseTable from "@/components/ui/BaseTable.vue"; import { useAppToast } from "@/composables/useAppToast"; import { getApiErrorMessage, getStatusStyles } from "@/lib/utils"; import { PaymentHistoryItem } from "@/server/gen/proto/app/v1/common"; import { useAuthStore } from "@/stores/auth"; import { useQuery } from "@pinia/colada"; import { useTranslation } from "i18next-vue"; import type { ColumnDef } from "@tanstack/vue-table"; const normalizeHistoryStatus = (status?: string) => { switch ((status || '').toLowerCase()) { case 'success': case 'succeeded': case 'paid': return 'success'; case 'failed': case 'error': case 'canceled': case 'cancelled': return 'failed'; case 'pending': case 'processing': default: return 'pending'; } }; const PaymentHistory = defineComponent({ name: 'PaymentHistory', setup() { const auth = useAuthStore(); const { t } = useTranslation(); const toast = useAppToast(); const downloadingInvoiceId = ref(null); const formatTermLabel = (months: number) => t('settings.billing.termOption', { months }); const formatPaymentMethodLabel = (value?: string) => { switch ((value || '').toLowerCase()) { case 'topup': return t('settings.billing.paymentMethod.topup'); case 'wallet': default: return t('settings.billing.paymentMethod.wallet'); } }; const mapHistoryItem = (item: PaymentHistoryItem) => { const details: string[] = []; if (item.kind !== 'wallet_topup' && item.termMonths) { details.push(formatTermLabel(item.termMonths)); } if (item.kind !== 'wallet_topup' && item.paymentMethod) { details.push(formatPaymentMethodLabel(item.paymentMethod)); } if (item.kind !== 'wallet_topup' && item.expiresAt) { details.push(t('settings.billing.history.validUntil', { date: auth.formatHistoryDate(item.expiresAt) })); } return { id: item.id || '', date: auth.formatHistoryDate(item.createdAt), amount: item.amount || 0, plan: item.kind === 'wallet_topup' ? t('settings.billing.walletTopup') : (item.planName || t('settings.billing.unknownPlan')), status: normalizeHistoryStatus(item.status), invoiceId: item.invoiceId || '-', currency: item.currency || 'USD', kind: item.kind || 'subscription', details, }; }; const { data, isLoading } = useQuery({ key: ['paymentHistory'], query: () => client.listPaymentHistory().then(res => (res.payments || []).map(mapHistoryItem)), }); const handleDownloadInvoice = async (item: PaymentHistoryItem) => { if (!item.id) return; downloadingInvoiceId.value = item.id; toast.add({ severity: 'info', summary: t('settings.billing.toast.downloadingSummary'), detail: t('settings.billing.toast.downloadingDetail', { invoiceId: item.invoiceId }), life: 2000, }); try { const response = await client.downloadInvoice({ id: item.id }); const content = response.content || ''; const contentType = response.contentType || 'text/plain;charset=utf-8'; const filename = response.filename || `${item.invoiceId}.txt`; const blob = new Blob([content], { type: contentType }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); URL.revokeObjectURL(url); toast.add({ severity: 'success', summary: t('settings.billing.toast.downloadedSummary'), detail: t('settings.billing.toast.downloadedDetail', { invoiceId: item.invoiceId }), life: 3000, }); } catch (error) { console.error(error); toast.add({ severity: 'error', summary: t('settings.billing.toast.downloadFailedSummary'), detail: getApiErrorMessage(error, t('settings.billing.toast.downloadFailedDetail')), life: 5000, }); } finally { downloadingInvoiceId.value = null; } }; const columns: ColumnDef>[] = [ { accessorKey: 'date', header: t('settings.billing.table.date'), cell: ({ getValue }) => (

{getValue()}

), meta: { headerClass: 'col-span-3', cellClass: 'col-span-3', }, }, { accessorKey: 'amount', header: t('settings.billing.table.amount'), cell: ({ row }) => (

{auth.formatMoney(row.original.amount)}

), meta: { headerClass: 'col-span-2', cellClass: 'col-span-2', }, }, { accessorKey: 'plan', header: t('settings.billing.table.plan'), cell: ({ row }) => ( <>

{row.original.plan}

{row.original.details?.length ? (

{row.original.details.join(' ยท ')}

) : null} ), meta: { headerClass: 'col-span-3', cellClass: 'col-span-3', }, }, { accessorKey: 'status', header: t('settings.billing.table.status'), cell: ({ row }) => ( {t('settings.billing.status.' + row.original.status)} ), meta: { headerClass: 'col-span-2', cellClass: 'col-span-2', }, }, { accessorKey: 'invoice', enableSorting: false, header: t('settings.billing.table.invoice'), cell: ({ row }) => ( ), meta: { headerClass: 'col-span-2 flex justify-center', cellClass: 'col-span-2 justify-center', }, }, ]; return () => (

{t('settings.billing.paymentHistory')}

{t('settings.billing.paymentHistorySubtitle')}

{{ loading: () => (
{Array.from({ length: 3 }).map((_, index) => (
))}
), empty: () => (

{t('settings.billing.noPaymentHistory')}

) }}
); } }); export default PaymentHistory;