import { redirect, notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { getSessionUser } from "@/lib/session"; import { getOrgBilling, getOrgBillingConfig } from "@/lib/db"; import { BillingSettingsForm } from "@/components/settings/billing-form"; import { SavedCardSection } from "@/components/settings/saved-card-section"; /** * /settings/billing — customer-side billing details management. * * Owner-only by visibility: non-owner members get a 404 (same * response as if the page didn't exist). The link to this page * is also hidden from non-owners on /billing and elsewhere, but * the page itself enforces too — a non-owner who learns the URL * still gets 404, not 403, so the page's existence doesn't leak. * * First-time visitors see an empty form. Subsequent visits see * the current values, editable. Save creates or updates via the * shared upsert path; the row's existence drives whether the * monthly issuance cron will pick this org up. * * Phase 9: also renders the saved-card section (Set up auto-pay / * Visa dot-dot-dot 4242, expires MM/YY / Update card / Disable * auto-pay / Remove card) when billing info is on file, plus a * footer note explaining that bank transfer is available on request. */ export default async function BillingSettingsPage() { const user = await getSessionUser(); if (!user) redirect("/login"); // Non-owners get a 404 — see comment above. if (!user.roles.includes("owner")) notFound(); const t = await getTranslations("settingsBilling"); const [existing, config] = await Promise.all([ getOrgBilling(user.orgId), getOrgBillingConfig(user.orgId), ]); return (

{t("title")}

{user.isPersonal ? t("subtitlePersonal") : t("subtitle")}

{/* Phase 9: saved-card section. Only shown once billing info exists — without an address Stripe can't create the customer object, so the "Set up auto-pay" button would fail anyway. We give a clear hint up there if the form is empty (no need to surface the card UI). */} {existing && (
)}
); }