Phase7: Void/Refund logic
Some checks failed
Build and Push / build (push) Failing after 52s

This commit is contained in:
2026-05-25 21:54:51 +02:00
parent 9cd9879a18
commit e15a668f8e
19 changed files with 2679 additions and 41 deletions

View File

@@ -1,23 +1,30 @@
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { getSessionUser } from "@/lib/session";
import { listInvoices, syncOverdueInvoices } from "@/lib/db";
import {
listCreditNotesForOrg,
listInvoices,
syncOverdueInvoices,
} from "@/lib/db";
import { CustomerInvoiceList } from "@/components/billing/customer-invoice-list";
import { CustomerCreditNoteList } from "@/components/billing/customer-credit-note-list";
import { RunningTotalWidget } from "@/components/billing/running-total-widget";
/**
* /billing — customer's billing home.
*
* Shows two things:
* Shows three things:
* 1. RunningTotalWidget — current calendar month's accruing cost
* (or the already-issued invoice for the current month, if
* that ran early).
* 2. CustomerInvoiceList — every issued invoice for this org,
* newest first. Status is reflected with a colored badge.
* 3. CustomerCreditNoteList — Phase 7. Credit notes (voids and
* refunds) for this org, with PDF download links. Hidden
* entirely when there are none (the common case).
*
* Anyone signed in can view this. The data is org-scoped; even
* non-owner team members see the same view. Phase 4 will add a
* "settings.payByInvoice" toggle visibility-gated to owners only.
* non-owner team members see the same view.
*/
export default async function CustomerBillingPage() {
const user = await getSessionUser();
@@ -31,10 +38,11 @@ export default async function CustomerBillingPage() {
console.warn("syncOverdueInvoices failed in /billing:", e);
}
const invoices = await listInvoices({
zitadelOrgId: user.orgId,
limit: 200,
});
// Parallel fetch — invoices + credit notes are independent.
const [invoices, creditNotes] = await Promise.all([
listInvoices({ zitadelOrgId: user.orgId, limit: 200 }),
listCreditNotesForOrg(user.orgId, 200),
]);
return (
<main className="max-w-5xl mx-auto px-6 py-8">
@@ -54,12 +62,24 @@ export default async function CustomerBillingPage() {
<RunningTotalWidget isOwner={user.roles.includes("owner")} />
</section>
<section className="animate-in animate-in-delay-2">
<section className="animate-in animate-in-delay-2 mb-8">
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("historyHeading")}
</h2>
<CustomerInvoiceList invoices={invoices} />
</section>
{/* Phase 7: credit-note section. CustomerCreditNoteList itself
returns null when there are no credit notes, so this whole
section disappears for orgs in normal operation. */}
{creditNotes.length > 0 && (
<section className="animate-in animate-in-delay-3">
<h2 className="text-xs font-semibold uppercase tracking-wider text-text-muted mb-3">
{t("creditNotesHeading")}
</h2>
<CustomerCreditNoteList creditNotes={creditNotes} />
</section>
)}
</main>
);
}