import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { getSessionUser } from "@/lib/session"; import { getOrgBilling, getOrgBillingConfig } from "@/lib/db"; import { listTenants } from "@/lib/k8s"; import { BackLink } from "@/components/ui/back-link"; import { OrgPaymentModeList } from "@/components/admin/billing/org-payment-mode-list"; /** * /admin/billing/orgs — list of orgs with their payment mode * settings. * * Phase 9b-2. The customer's /settings/billing only exposes the * saved-card flow (auto-pay). Bank-transfer mode is admin-only — * customer must contact support to request it, admin flips the * pay_by_invoice flag here. Also exposes the auto_charge_enabled * pause-switch for support situations. * * The page is intentionally minimal: org name, country, current * mode, has-saved-card indicator, and toggles. Detail-level work * (open balances, invoice list) is on the existing pages * (/admin/billing, /admin/billing/invoices). */ export default async function AdminOrgsPaymentModePage() { const user = await getSessionUser(); if (!user) redirect("/login"); if (!user.isPlatform) redirect("/dashboard"); const t = await getTranslations("adminBilling"); // Same org-discovery pattern as /api/admin/billing/orgs: tenant // labels are the source of truth for org membership. We dedupe by // org id since one org can own many tenants. const tenants = await listTenants().catch(() => []); const orgIds = new Set(); for (const tnt of tenants) { const oid = tnt.metadata.labels?.["pieced.ch/zitadel-org-id"]; if (oid) orgIds.add(oid); } const orgs = await Promise.all( Array.from(orgIds).map(async (oid) => { const [billing, cfg] = await Promise.all([ getOrgBilling(oid).catch(() => null), getOrgBillingConfig(oid), ]); return { zitadelOrgId: oid, companyName: billing?.companyName ?? null, country: billing?.country ?? null, hasSavedCard: !!cfg.stripeDefaultPaymentMethodId, cardLabel: cfg.stripePmBrand && cfg.stripePmLast4 ? `${cfg.stripePmBrand} •••• ${cfg.stripePmLast4}` : null, payByInvoice: !!cfg.payByInvoice, autoChargeEnabled: cfg.autoChargeEnabled !== false, }; }) ); // Sort: orgs with billing first (most actionable), then by name. orgs.sort((a, b) => { if (!!a.companyName !== !!b.companyName) { return a.companyName ? -1 : 1; } return (a.companyName ?? a.zitadelOrgId).localeCompare( b.companyName ?? b.zitadelOrgId ); }); return (

{t("orgsPageTitle")}

{t("orgsPageSubtitle")}

); }