Commit graph

1 commit

Author SHA1 Message Date
screentinker c3a5261057
fix(subscription): make the trial-expiry auto-downgrade actually fire (#228)
getUserPlan()'s auto-downgrade was guarded on `subscription_status !== 'active'`,
but that column DEFAULTs to 'active' and is only ever changed by Stripe webhook
events. For trial users who never touch Stripe — the entire population it's meant
to catch — the condition was always false, so the downgrade never ran and every
signup kept Pro free forever.

Re-key the guard on the real signals:
  - trial expired (!trial_active), AND
  - stripe_subscription_id IS NULL (never paid), AND
  - plan_id === trial_plan (still on the plan the trial granted), AND
  - plan_name !== 'free'

The plan_id === trial_plan clause is load-bearing: it protects comped / hand-
granted plans (e.g. a manually-set enterprise plan, where plan_id !== trial_plan)
from being silently downgraded. Grandfathered accounts (trial_started IS NULL)
never enter the block at all, so the ~home cohort is untouched. Added a comment
documenting the subscription_status-default trap so it isn't reintroduced.

Enforcement stays forward-only/lazy — the downgrade happens in the resolver on a
user's next request; no mass update here.

Downstream (deviceSocket.checkDeviceAccess, traced, unchanged): a genuinely-
expired free-tier trial now resolves to free and its device-limit block correctly
caps it to 1 device; grandfathered home (2 devices) and paid users are not
blocked. NOTE: the separate "Trial Expired" screen branch there is a pre-existing
dead condition (it needs trial_started set AND plan_name='free' at once, but the
downgrade clears trial_started) — left as-is per scope; flagged for follow-up.

Tests (new trial-expiry.test.js — there was none, which is how this shipped):
lapsed trial downgrades; comped enterprise (plan_id!=trial_plan) not downgraded;
grandfathered home (trial_started NULL) not downgraded; paid user not downgraded;
in-window trial not downgraded; plus a regression pinning that subscription_status
='active' no longer shields a lapsed trial. Suite 563/563.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:51:46 -05:00