Billing
Billing displays real server state with a next action, not placeholder statistics.
Surface contract
Primary object: the subscription row and the invoices the payment processor actually sent; a free plan is a real row, not a placeholder.
Actions
- Starts the payment processor’s own checkout for the configured plan price (POST /v1/billing/checkout). The charge, currency and renewal terms are the processor’s; owner/admin only, and nothing is charged until checkout completes.
- Adds the 100-job/month intelligence add-on as a second line item of the same processor checkout (POST /v1/billing/checkout with addon: intelligence). The catalog offers it on free and pro only; the server refuses an unknown or ineligible add-on, and the subscription row’s Add-ons column shows whether it is active.
- Opens the processor’s billing portal (POST /v1/billing/portal) for invoices, payment methods and cancellation. When no billing customer is linked the server answers 422 with that fact instead of opening a fake portal.
- CancelProvider credentialsCancellation is completed in the processor’s billing portal so the effective date and refund rules are the processor’s own; this build has no local cancel endpoint, so it sends you to the portal rather than pretending to cancel.
- View invoiceEach invoice row links to the processor-hosted invoice URL recorded by the verified webhook; the page never builds an invoice document itself.
Required states and how this surface reaches them
- loading
- the session probe and each list show Loading… until the control plane answers.
- ready
- the server’s own records render as returned; the primary object is named before its details.
- empty
- no invoice exists until a verified webhook records one; the subscription endpoint records a real free row for an unprovisioned organization.
- error
- the control plane’s status and detail are shown; no placeholder row replaces them.
- offline
- no control plane is reachable; nothing is rendered rather than invented.
- permission_denied
- a 401 returns to sign-in with this path; a 403 states that the role cannot read this surface.
- not_configured
- without STRIPE_SECRET_KEY (or the configured plan/add-on price) checkout and portal answer 503 naming the missing secret; the page shows setup, never a fake checkout.
- budget_paused
- a subscription in a dunning grace or suspended state is reported by the server as it is; usage caps are shown on the usage surface.
- requires_approval
- mutations the server reserves for an explicit human owner/admin are refused with its own detail and never auto-approved here.
Scope: workspace membership (owner/admin for changes). Plan and invoice state come from verified Stripe webhooks; this page never fabricates a receipt, a price or a subscription state.
Subscription
checkingLoading…
Invoices
checkingLoading…
Rows come from the authenticated control plane; this page never renders placeholder records. Actions report the server's own response, including refusals.