Implement the ux-first-run change: a state-derived setup checklist on /operator/setup with a landing region that recedes once required steps are done, and empty states that distinguish blocked from empty across operator and member surfaces. Also add production deployment and environment reference docs, plus a config-key completeness test.
9.1 KiB
operator-billing-views
Purpose
Gives operators read-only visibility into the billing domain — accounts, subscriptions and their details, invoices with line items, payments, and payment methods — each attributed with its Stripe sync status, so billing state can be inspected and reconciled from the operator panel without reaching for the Stripe dashboard. These are views only: no mutation of billing state happens through this surface.
Requirements
Requirement: Operator can view billing accounts with Stripe sync status
The system SHALL provide a read-only view for operators to list all billing accounts with their associated organization, status, and Stripe customer sync status.
Scenario: Billing accounts list shows all accounts
- WHEN an operator with the operator-member role accesses the Billing > Accounts view
- THEN the system displays a table of all billing accounts with columns: account name, organization name, status, Stripe sync status, and creation date
Scenario: Billing account shows Stripe customer deep link when synced
- WHEN a billing account has a
stripe.customer_mappingsrecord withsync_status = 'synced' - THEN the system displays a link to the Stripe dashboard customer page using the format
https://dashboard.stripe.com/{mode}/customers/{stripe_customer_id}
Scenario: Billing account shows pending status
- WHEN a billing account has a
stripe.customer_mappingsrecord withsync_status = 'pending' - THEN the system displays a "Pending" badge and no deep link
Requirement: Operator can view subscriptions with Stripe sync status
The system SHALL provide a read-only view for operators to list all subscriptions with their status, billing account, period dates, and Stripe sync status.
Scenario: Subscriptions list shows all subscriptions
- WHEN an operator accesses the Billing > Subscriptions view
- THEN the system displays a table of all subscriptions with columns: subscription ID (truncated), billing account, status, current period, cancel at period end flag, and Stripe sync status
Scenario: Subscription shows Stripe deep link when synced
- WHEN a subscription has a
stripe.subscription_mappingsrecord withsync_status = 'synced' - THEN the system displays a link to the Stripe dashboard subscription page
Scenario: Subscription status is color-coded
- WHEN a subscription has status
activeortrialing - THEN the system displays a green/success badge
- WHEN a subscription has status
past_due,unpaid, orincomplete - THEN the system displays a red/danger badge
- WHEN a subscription has status
canceledorended - THEN the system displays a gray/secondary badge
Requirement: Operator can view subscription details
The system SHALL provide a detailed view of a subscription showing its items, changes history, and associated pool provision.
Scenario: Subscription detail shows items
- WHEN an operator views a subscription detail
- THEN the system displays all subscription items with their product name, price amount, currency, and quantity
Scenario: Subscription detail shows change history
- WHEN an operator views a subscription detail
- THEN the system displays the subscription change history ordered by change date descending, showing previous status, new status, and Stripe event ID
Requirement: Operator can view invoices with line items
The system SHALL provide a read-only view for operators to list all invoices with their status, amounts, and payment state.
Scenario: Invoices list shows all invoices
- WHEN an operator accesses the Billing > Invoices view
- THEN the system displays a table of all invoices with columns: invoice ID (truncated), billing account, status, amount due, amount paid, currency, due date, and paid date
Scenario: Invoice shows Stripe deep link when synced
- WHEN an invoice has a
stripe.invoice_mappingsrecord withsync_status = 'synced' - THEN the system displays a link to the Stripe dashboard invoice page
Scenario: Invoice status is color-coded
- WHEN an invoice has status
paidANDamount_due = amount_paid - THEN the system displays a green/success badge
- WHEN an invoice has status
openordraft - THEN the system displays a yellow/warning badge
- WHEN an invoice has status
voidoruncollectible - THEN the system displays a gray/secondary badge
Scenario: Invoice line items are viewable
- WHEN an operator clicks to view invoice details
- THEN the system displays all line items with description, quantity, amount, and period
Requirement: Operator can view payments
The system SHALL provide a read-only view for operators to list all payments with their status, amount, and payment method.
Scenario: Payments list shows all payments
- WHEN an operator accesses the Billing > Payments view
- THEN the system displays a table of all payments with columns: payment ID (truncated), invoice ID (truncated), billing account, status, amount, currency, and payment method (card brand + last4 if available)
Scenario: Payment shows Stripe deep link when synced
- WHEN a payment has a
stripe.payment_mappingsrecord withsync_status = 'synced' - THEN the system displays a link to the Stripe dashboard payment intent page
Scenario: Payment shows card details when available
- WHEN a payment has an associated payment method with card details
- THEN the system displays the card brand and last 4 digits (e.g., "Visa •••• 4242")
Requirement: Operator can view payment methods
The system SHALL provide a read-only view of payment methods associated with billing accounts.
Scenario: Payment methods are visible in billing account detail
- WHEN an operator views a billing account detail
- THEN the system displays all attached payment methods with type, card details (if applicable), and default status
Scenario: Detached payment methods are indicated
- WHEN a payment method has
detached_atset - THEN the system displays a "Detached" badge and shows the detached date
MODIFIED Requirements
None - this change only adds read-only views without modifying existing behavior.
Requirement: Stripe sync vocabulary is defined where it appears
Operator billing views that render a Stripe Sync state (such as Not Mapped or Synced) SHALL define the vocabulary in place — a visible legend or inline note stating that Not Mapped means the record has no linked Stripe object and what that implies — so an operator can tell deliberate absence from breakage without leaving the page.
Scenario: Not Mapped is explained on the page
- WHEN an operator views a billing table containing
Not Mappedbadges - THEN the page carries a visible definition of the sync vocabulary
- AND the definition states what an operator should conclude from (and do about) an unmapped record
Requirement: Billing views disclose projection recency
Because operator billing data is projected from processed webhook events, each operator billing view SHALL display when the most recent webhook event was processed (e.g. "data as of …"), and SHALL state when no webhook event has ever been processed. Stale data must be visibly stale rather than silently presented as current.
Scenario: Recency stamp renders with data
- WHEN an operator views a billing table and webhook events have been processed
- THEN the view shows the most recent processed event time
Scenario: No events ever processed is stated
- WHEN an operator views a billing table on a deployment where no webhook event has ever been processed
- THEN the view states that no billing events have been received, rather than showing rows or emptiness as if current
Requirement: Empty billing views distinguish unconfigured Stripe from no data
When the Stripe integration is not configured (its required settings are absent, per the same required-key completeness check the Stripe settings surface performs), an empty operator billing view SHALL state that billing data requires Stripe to be configured and SHALL link to the Stripe settings surface. When Stripe is configured but no webhook events have been processed, the existing no-events statement (see "Billing views disclose projection recency") applies instead. The two states SHALL NOT render identical copy: one is blocked on the operator, the other is waiting on the world.
Scenario: Unconfigured Stripe is named as the blocker
- WHEN an operator views an empty billing view on a deployment whose Stripe integration fails the required-key completeness check
- THEN the empty state states that billing data requires Stripe configuration
- AND links to the Stripe settings surface
Scenario: Configured but eventless stays a projection statement
- WHEN an operator views an empty billing view on a deployment where Stripe passes the required-key check but no webhook event has been processed
- THEN the empty state is the no-events-received projection statement, not the unconfigured-Stripe blocker copy