# Account Billing Page Contract v1

Route: `/account/billing`
Owner: Billing frontend contract pilot (`B-BILLING-FRONTEND-CONTRACT-PILOT-001`)
Persona: authenticated personal user
Status: pilot contract for existing page behavior

This billing page contract records the current `/account/billing` surface without
redesigning it. The page is a personal billing overview: it shows ledger-derived
balances, recent usage, recent refund requests, and links to Stripe-managed
checkout and payment-method pages. Tenant finance operations remain in
`/platform/finance`.

## Data Dependencies

- `GET /api/v1/v3/account/overview`
  - Used for account attention items and page shell context.
- `GET /api/v1/billing/balance`
  - Canonical balance source is `balances[]`, one ledger-derived amount per
    currency.
  - Compatibility field `balance` is accepted for older responses and is treated
    as the only displayed balance when `balances[]` is absent or empty.
- `GET /api/v1/billing/financial-posture`
  - Shows the user's current credit exhaustion or delinquency posture.
  - Includes non-destructive effects, selected action, affected workloads/apps,
    balance snapshot, and correlation evidence for support/debugging.
- `GET /api/v1/billing/usage`
  - Provides read-only usage ledger rows for allocations and app runtimes.
- `GET /api/v1/billing/refunds`
  - Provides recent refund requests and correlation evidence.

No direct database access, local billing math, or cross-domain joins are allowed
in the page. Currency, invoice, refund, and balance facts must stay API-owned.

## Actions

- Add funds
  - Calls checkout session creation with the entered amount in minor units.
  - Redirects to the provider URL returned by the API.
  - The page must not infer credit success before webhook reconciliation updates
    the ledger.
- Manage payment methods
  - Calls customer portal session creation and redirects to the provider URL.
- Refresh
  - Invalidates the billing React Query key and refetches balance, usage, and
    refunds.
- Usage filters and refund filters
  - Browser-local only; they do not mutate backend state.

## Permissions And Financial Restrictions

- The page requires an authenticated user session and bearer token.
- Personal users may view only their own balance, usage, and refunds.
- Tenant, operator, and admin finance actions are out of scope and must stay on
  the platform finance surface.
- Budget, invoice, refund policy, minimum deposit, and maximum deposit rules are
  backend-owned financial restrictions. The frontend may display API-provided
  state, but it must not hardcode policy values or decide eligibility locally.
- Credit exhaustion posture is API-owned. The page may label `restricted` as
  launches disabled and guide users to add funds or contact finance, but it must
  not locally decide when to release, suspend, or restore workloads.
- No auth token, payment reference, Stripe customer id, or provider secret may be
  placed in a URL query string, log line, or telemetry payload.

## Telemetry And Correlation

- Page view: `account.billing.viewed`
  - Properties: route, active currency count, usage row count, refund row count.
- Add funds intent: `account.billing.checkout.started`
  - Properties: amount_minor, currency when API-supported, generated
    correlation id or request id.
- Payment portal intent: `account.billing.portal.started`
  - Properties: generated correlation id or request id.
- Refresh intent: `account.billing.refreshed`
  - Properties: stale age if available.
- Financial posture viewed: `account.billing.financial_posture.viewed`
  - Properties: state, selected action, affected allocation count, affected app
    count, correlation id when present.
- Refund detail inspect: `account.billing.refund.selected`
  - Properties: refund id, status, correlation id.

Telemetry payloads must be sanitized before capture. Payment references may be
displayed in the authenticated detail panel when returned by the API, but should
not be copied into analytics.

## Error Boundary

- Loading uses the shared V3 loading state.
- API failures use `V3ErrorState` and its retry action.
- `401` or missing token is an authentication error and should route through the
  existing session/login handling.
- `403` must render a permission-denied state rather than hiding the page.
- Provider redirect creation failures remain on-page with retry; the page must
  not fabricate a successful checkout, portal, budget, invoice, or refund state.
- Error text should preserve backend `correlation_id` where available and must
  not expose secrets.

## Accessibility Checks

- Page title and section headings remain semantic and unique.
- Add funds, manage payment methods, refresh, filters, and row selection are
  keyboard reachable.
- Usage is a real table with readable headers.
- Refund row selection has visible selected state and detail content is reachable
  after selection.
- Loading and error states must be announced by shared primitives.
- Currency amounts must be locale-aware and include currency context when more
  than one balance is present.

## Refresh And Subscription Behavior

- React Query stale time: 30 seconds for overview and billing bundle.
- Manual refresh invalidates `["v3", "account", "billing"]`.
- No WebSocket or event-stream subscription is currently attached to this route.
- A future balance subscription must use a typed contract and must preserve the
  same `balances[]` canonical shape with `balance` compatibility until the
  backend removes the compatibility field.

## Theme, Query, And I18n Boundaries

- Styling must use existing product primitives and design tokens; do not add
  route-local brand colors or a billing-only design system.
- API access stays in `packages/web/src/lib/api/client.ts` and re-exported V3
  client helpers; page code should not construct backend URLs.
- Visible strings remain English for the current pilot, but money formatting
  must use locale-aware currency formatting. New durable billing copy should be
  moved into the chosen i18n/content contract before broad expansion.
