The invoice page reads Stripe directly so the portal never stores payment data
The portal invoices page does not query the database for payment records. It queries Stripe. The page is a server component that authenticates with Clerk, looks up the client record, reads the stripeCustomerId field, and passes it to getClientInvoices. That function hits the Stripe API directly — listing PaymentIntents filtered to the customer ID, pulling succeeded and pending statuses. The response maps into a flat array of invoice objects with six fields — id, description from the metadata, formatted date, amount in the smallest currency unit, the currency code, status, and an optional receipt URL. The InvoiceTable component formats the amount using Intl.NumberFormat with the style set to currency and the currency code passed from the PaymentIntent itself. A GBP payment renders as £250.00. A USD payment would render as $250.00. A EUR payment would render as €250.00. The formatter divides by one hundred because Stripe stores amounts in the smallest unit — pence, cents, centimes. The table uses semantic HTML — thead with scope attributes on every th, tbody with consistent row structure, and an aria-label on the table element. The status column renders a pill badge — emerald for paid, orange for pending, red for failed — using a config record that maps status strings to Tailwind classes. The receipt column conditionally renders a link to Stripe's hosted receipt page. If Stripe has no receipt URL for that payment, the cell is empty. The link opens in a new tab with noopener noreferrer and uses an aria-label that includes the description so screen readers can distinguish between multiple receipt links. On mobile, the date column hides completely using a responsive class — hidden on base, table-cell on sm — because the description and amount are more important on a narrow screen. No payment data lives in the database. No PCI compliance burden. No sync job keeping records in step. The portal reads Stripe in real time and formats the response. If a refund happens in the Stripe dashboard, the next page load reflects it immediately.
Comments coming soon
Sign in with TikTok to leave a comment. Coming soon.