feat: download invoices

This commit is contained in:
Nevo David 2026-04-07 18:05:30 +07:00
parent d16945a9e2
commit c7867ab05e
2 changed files with 58 additions and 1 deletions

View file

@ -22,6 +22,8 @@ interface Charge {
refunded: boolean;
amount_refunded: number;
description: string | null;
receipt_url: string | null;
invoice_pdf: string | null;
}
const useCharges = () => {
@ -124,6 +126,7 @@ const ChargesModal: FC<{ close: () => void }> = ({ close }) => {
<th className="p-[8px]">{t('date', 'Date')}</th>
<th className="p-[8px]">{t('amount', 'Amount')}</th>
<th className="p-[8px]">{t('status', 'Status')}</th>
<th className="p-[8px] w-[50px]" />
</tr>
</thead>
<tbody>
@ -178,6 +181,34 @@ const ChargesModal: FC<{ close: () => void }> = ({ close }) => {
</span>
)}
</td>
<td className="p-[8px]">
{(charge.invoice_pdf || charge.receipt_url) && (
<a
href={charge.invoice_pdf || charge.receipt_url!}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="inline-flex items-center justify-center w-[28px] h-[28px] rounded-[4px] hover:bg-tableBorder transition-colors"
title={charge.invoice_pdf ? t('download_invoice', 'Download Invoice') : t('view_receipt', 'View Receipt')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="16"
height="16"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
</a>
)}
</td>
</tr>
))}
</tbody>

View file

@ -856,7 +856,7 @@ export class StripeService {
limit: 100,
});
return charges.data
const chargeList = charges.data
.filter((f) => f.status === 'succeeded')
.map((charge) => ({
id: charge.id,
@ -867,7 +867,33 @@ export class StripeService {
refunded: charge.refunded,
amount_refunded: charge.amount_refunded,
description: charge.description,
receipt_url: charge.receipt_url || null,
invoice: (charge as any).invoice || null,
}));
const invoiceIds = chargeList
.map((c) => c.invoice)
.filter((id): id is string => !!id && typeof id === 'string');
const invoicePdfMap: Record<string, string> = {};
for (const invoiceId of invoiceIds) {
try {
const inv = await stripe.invoices.retrieve(invoiceId);
if (inv.invoice_pdf) {
invoicePdfMap[invoiceId] = inv.invoice_pdf;
}
} catch {
// ignore if invoice can't be fetched
}
}
return chargeList.map((charge) => ({
...charge,
invoice_pdf:
charge.invoice && invoicePdfMap[charge.invoice as string]
? invoicePdfMap[charge.invoice as string]
: null,
}));
}
async refundCharges(organizationId: string, chargeIds: string[]) {