One Billing State Per Render, Asserted by the API
Why This Matters
Until today, the billing page looked at raw subscription fields and worked out for itself which banner to show. The API knew the truth and the page re-derived it, which is two implementations of the same rules, and two implementations always eventually disagree. As of this release there is one: the API computes exactly one billing state per render, with the dates and amounts that state needs, and the page renders what it is told. The page cannot invent a state the API never asserted; the build fails if a state exists that the page does not render.
What Changed
-
The billing status endpoint returns a precomputed state Nine states cover the whole subscription lifecycle, from the getting-started window through trialing, active, payment failure, scheduled cancellation, suspension, and the post-cancellation retention window. Each arrives with its dates and amounts attached: the trial end and first charge, the next billing date, the cancellation date, the payment retry schedule. API consumers get the same precomputed state the page uses; the reference documentation describes all nine.
-
Payment-failure copy corrected The previous failed-payment banner said the subscription would be cancelled if payment could not be collected. That is not what happens and never will be: retries run on a schedule, and if they are exhausted the account becomes read-only until the invoice is settled, with nothing deleted and nothing cancelled. The banner now states the actual retry schedule with its end date, and says plainly that access is unaffected while retries run. An open-ended “we’ll keep trying” reads as a threat; a dated window reads as what it is.
-
The retention promise, in writing When a subscription ends, the organisation keeps read-only access and full export for 12 months from the end date. The window spans a complete UK filing cycle, so the books are still there when the Self Assessment or Corporation Tax moment arrives. Resubscribing at any point during the window restores full access instantly, on the same plan, because nothing was ever archived. The billing page now shows both dates when a subscription has ended, and the API reference documents the window.
-
New states now have proper messaging A trial entering its final week gets an elevated reminder matching the email Stripe sends at the same moment. A suspended account gets three sentences: the fact, the remedy with a direct payment link, and the data promise. No guilt language anywhere.
-
Scheduled cancellation and payment failure shown together If a payment fails while a cancellation is already scheduled, both facts appear in one banner rather than fighting over the page.
Operational Impact
- The billing page shows exactly one state banner per render, asserted by the API
- Failed-payment messaging now matches configured behaviour: dated retries, access unaffected, never auto-cancelled
- Cancelled organisations see their end date and their retention date
- API consumers can read the same precomputed state from the billing status endpoint
Files Changed
Backend:
- Billing status endpoint: state computation added as a pure, testable function; response and schema extended
- Billing API reference: the state vocabulary and retention window documented
Frontend:
- Billing settings page: local state derivation removed; all banners render from the API’s state through an exhaustive switch the compiler verifies
- Three new banners (final-week trial reminder, suspended account, ended subscription with retention dates); payment-failure copy rewritten
The sentence the customer reads is the specification. Now the API writes the sentence, the page repeats it, and the compiler refuses to ship a state nobody wrote a sentence for.