"use client"; import { useState } from "react"; import { useTranslations } from "next-intl"; interface Props { invoiceNumber: string; } /** * Pay-with-card button. Posts to /api/billing/invoices/[n]/pay, * which returns a Stripe Checkout Session URL; we redirect the * browser there. * * The button is rendered only by the parent for status='open' or * 'overdue' invoices — the API enforces this too, but pre-filtering * UI-side keeps the dead state out of the customer's face. */ export function PayInvoiceButton({ invoiceNumber }: Props) { const t = useTranslations("customerBilling"); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const onClick = async () => { setBusy(true); setError(null); try { const res = await fetch( `/api/billing/invoices/${encodeURIComponent(invoiceNumber)}/pay`, { method: "POST" } ); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.error ?? `HTTP ${res.status}`); } if (!data.url) { throw new Error("Payment session URL missing from response."); } // Hard navigation, not Next.js router — Stripe Checkout is a // separate origin and the browser needs to fully leave our app. window.location.href = data.url; } catch (e: any) { setError(e?.message ?? String(e)); setBusy(false); } }; return (
{error && ( {error} )}
); }