Phase4: Stripe
Some checks failed
Build and Push / build (push) Failing after 38s

This commit is contained in:
2026-05-24 23:37:48 +02:00
parent 4a5ae0bb8b
commit a680d6de9f
13 changed files with 908 additions and 13 deletions

View File

@@ -0,0 +1,64 @@
"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<string | null>(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 (
<div className="flex flex-col items-end gap-1">
<button
onClick={onClick}
disabled={busy}
className="px-4 py-2 rounded-md bg-accent text-white text-sm font-medium hover:bg-accent-dim transition-colors disabled:opacity-50 cursor-pointer"
>
{busy ? t("redirectingToStripe") : t("payWithCard")}
</button>
{error && (
<span className="text-xs text-error max-w-[260px] text-right">
{error}
</span>
)}
</div>
);
}