diff --git a/apps/web/src/features/purchases/components/LicensesView.tsx b/apps/web/src/features/purchases/components/LicensesView.tsx
index 838994948..fcfc60524 100644
--- a/apps/web/src/features/purchases/components/LicensesView.tsx
+++ b/apps/web/src/features/purchases/components/LicensesView.tsx
@@ -1,11 +1,12 @@
/**
* LicensesView — list of user's purchased licenses with download links (v0.401 M2)
+ * v0.403 F1: Invoice download link
*/
import { useState, useEffect } from 'react';
import { marketplaceService } from '@/services/marketplaceService';
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
-import { Download, FileAudio } from 'lucide-react';
+import { Download, FileAudio, FileText } from 'lucide-react';
import { EmptyState } from '@/components/ui/empty-state';
import { Skeleton } from '@/components/ui/skeleton';
@@ -72,16 +73,32 @@ export function LicensesView() {
Purchased {item.order?.created_at ? new Date(item.order.created_at).toLocaleDateString() : '—'}
-
+
+
+
+
))}
diff --git a/apps/web/src/features/purchases/pages/purchases-page/PurchasesView.tsx b/apps/web/src/features/purchases/pages/purchases-page/PurchasesView.tsx
index 7bb45f4a8..61ed6561d 100644
--- a/apps/web/src/features/purchases/pages/purchases-page/PurchasesView.tsx
+++ b/apps/web/src/features/purchases/pages/purchases-page/PurchasesView.tsx
@@ -1,5 +1,6 @@
import { RefundRequestModal } from '@/components/commerce/modals/RefundRequestModal';
import toast from '@/utils/toast';
+import { marketplaceService } from '@/services/marketplaceService';
import { usePurchasesView } from './usePurchasesView';
import { PurchasesViewHeader } from './PurchasesViewHeader';
import { PurchasesViewList } from './PurchasesViewList';
@@ -19,6 +20,15 @@ export function PurchasesView({ initialPurchases }: PurchasesViewProps = {}) {
handleDownload,
} = usePurchasesView(initialPurchases ?? undefined);
+ const handleInvoiceDownload = async (orderId: string) => {
+ try {
+ await marketplaceService.downloadInvoice(orderId);
+ toast.success('Invoice downloaded');
+ } catch {
+ toast.error('Failed to download invoice');
+ }
+ };
+
if (loading) {
return ;
}
@@ -34,6 +44,7 @@ export function PurchasesView({ initialPurchases }: PurchasesViewProps = {}) {
setActiveDownloadId={setActiveDownloadId}
onDownloadFormat={handleDownload}
onLicense={() => toast('License document opened')}
+ onInvoiceDownload={handleInvoiceDownload}
onRefund={setRefundOrderId}
/>
diff --git a/apps/web/src/features/purchases/pages/purchases-page/PurchasesViewItem.tsx b/apps/web/src/features/purchases/pages/purchases-page/PurchasesViewItem.tsx
index 5ca49c3c9..d4624f4de 100644
--- a/apps/web/src/features/purchases/pages/purchases-page/PurchasesViewItem.tsx
+++ b/apps/web/src/features/purchases/pages/purchases-page/PurchasesViewItem.tsx
@@ -9,6 +9,7 @@ interface PurchasesViewItemProps {
onToggleDownload: () => void;
onDownloadFormat: (format: string) => void;
onLicense: () => void;
+ onInvoiceDownload: (orderId: string) => void;
onRefund: () => void;
}
@@ -20,6 +21,7 @@ export function PurchasesViewItem({
onToggleDownload,
onDownloadFormat,
onLicense,
+ onInvoiceDownload,
onRefund,
}: PurchasesViewItemProps) {
const product = purchase.product as { title: string; coverUrl?: string; type?: string };
@@ -86,6 +88,17 @@ export function PurchasesViewItem({
License
+ }
+ onClick={() => onInvoiceDownload(purchase.orderId)}
+ title="Download Invoice"
+ >
+ Invoice
+
+