Billing UI is mock data and shows every org a fabricated trial #3

Open
opened 2026-08-19 22:06:53 -04:00 by logan · 2 comments
Owner

lib/billing.ts:142-151 exports SAMPLE_SUBSCRIPTION, a hardcoded fixture that regenerates a fresh "7 days left" trial on every page load. The checkout and portal calls just throw. /settings/billing renders the fixture and admits "Demo data" on the page itself.

The real organization document sets subscription_status: None (routers/links.py:182-183, whose own comment notes that nothing reads or enforces these fields) and nothing ever writes anything else. The trial banner is not reading that field at all — it reads the fixture, unconditionally, for every org.

Billing was deliberately descoped pending a pricing decision (BUSINESS_MODEL.md §9), so this is not a request to build Stripe. The immediate problem is narrower: the UI actively misrepresents account state.

Minimum acceptable interim fix: stop presenting a fabricated trial as if it were real.

`lib/billing.ts:142-151` exports `SAMPLE_SUBSCRIPTION`, a hardcoded fixture that regenerates a fresh "7 days left" trial on every page load. The checkout and portal calls just `throw`. `/settings/billing` renders the fixture and admits "Demo data" on the page itself. The real organization document sets `subscription_status: None` (`routers/links.py:182-183`, whose own comment notes that nothing reads or enforces these fields) and nothing ever writes anything else. The trial banner is not reading that field at all — it reads the fixture, unconditionally, for every org. Billing was deliberately descoped pending a pricing decision (`BUSINESS_MODEL.md` §9), so this is not a request to build Stripe. The immediate problem is narrower: the UI actively misrepresents account state. Minimum acceptable interim fix: stop presenting a fabricated trial as if it were real.
Author
Owner

Correcting this issue — I overstated it when filing, and the overstatement is the reason it looked urgent.

The fixture is real: lib/billing.ts:142 still defines SAMPLE_SUBSCRIPTION with a rolling 7-day trial, and getCurrentSubscription() returns it unconditionally. But it is not rendered anywhere except /settings/billing, and that page already says so on itself: "Demo data — this page isn't connected to a live payment processor." There is no trial banner in the app chrome, no nav badge, nothing on the dashboard. Grepping trial across app/ and components/ returns exactly two hits, both inside app/settings/billing/page.tsx.

So the accurate framing is "a demo page labelled as a demo page", not "the UI actively misrepresents account state to every org". That is a much weaker reason to act.

Leaving this open and folded into the descoped billing work rather than patching it tonight — building a half-real subscription state before the pricing decision exists would just be a second fixture. The TODO(billing) comments in lib/billing.ts already name the endpoints the real implementation needs (GET /org/subscription, /org/usage, /org/invoices).

Worth keeping in view: routers/links.py:182-183 writes subscription_status: None on the org document and nothing ever reads or enforces it, so the backend has a stub too.

Correcting this issue — I overstated it when filing, and the overstatement is the reason it looked urgent. The fixture is real: `lib/billing.ts:142` still defines `SAMPLE_SUBSCRIPTION` with a rolling 7-day trial, and `getCurrentSubscription()` returns it unconditionally. But it is **not** rendered anywhere except `/settings/billing`, and that page already says so on itself: *"Demo data — this page isn't connected to a live payment processor."* There is no trial banner in the app chrome, no nav badge, nothing on the dashboard. Grepping `trial` across `app/` and `components/` returns exactly two hits, both inside `app/settings/billing/page.tsx`. So the accurate framing is "a demo page labelled as a demo page", not "the UI actively misrepresents account state to every org". That is a much weaker reason to act. Leaving this open and folded into the descoped billing work rather than patching it tonight — building a half-real subscription state before the pricing decision exists would just be a second fixture. The `TODO(billing)` comments in `lib/billing.ts` already name the endpoints the real implementation needs (`GET /org/subscription`, `/org/usage`, `/org/invoices`). Worth keeping in view: `routers/links.py:182-183` writes `subscription_status: None` on the org document and nothing ever reads or enforces it, so the backend has a stub too.
Author
Owner

Referenced by board minutes #42 (2026-08-23).

The board ruled (Decision 4) that pricing comes off the public site entirely, replaced with "Pricing in development - contact us", rather than keeping the invented $0/$79/custom catalog behind a below-the-fold disclaimer. The CMO's softer alternative was explicitly overruled: the number is not merely unwired, it is invented, and BUSINESS_MODEL.md §3.1 - now ratified - says so in writing.

This issue (fabricated trial state shown to every org) is the same class of problem on the authenticated side. The public-surface half is tracked in #46; keep this one for the in-app billing UI.

**Referenced by board minutes #42 (2026-08-23).** The board ruled (Decision 4) that **pricing comes off the public site entirely**, replaced with "Pricing in development - contact us", rather than keeping the invented `$0/$79/custom` catalog behind a below-the-fold disclaimer. The CMO's softer alternative was explicitly overruled: the number is not merely unwired, it is *invented*, and `BUSINESS_MODEL.md` §3.1 - now ratified - says so in writing. This issue (fabricated trial state shown to every org) is the same class of problem on the authenticated side. The public-surface half is tracked in **#46**; keep this one for the in-app billing UI.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#3