screentinker/server
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
..
config PiP overlay MVP: push image/web overlays to a device or group (#109) (#127) 2026-06-18 14:54:44 -05:00
db feat(content): subtitle/caption support as a content property (#223) 2026-07-23 12:33:35 -05:00
lib feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
middleware fix(subscription): make the trial-expiry auto-downgrade actually fire (#228) 2026-07-24 15:51:46 -05:00
player feat(content): subtitle/caption support as a content property (#223) 2026-07-23 12:33:35 -05:00
routes feat(stripe): enable promotion codes on checkout sessions (#227) 2026-07-23 23:44:18 -05:00
scripts fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
services feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
test fix(subscription): make the trial-expiry auto-downgrade actually fire (#228) 2026-07-24 15:51:46 -05:00
ws feat: transition engine — GL wipes across web, Tizen & Android (+ image↔video) (#204) 2026-07-20 16:45:32 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js feat(email): SMTP transport as an alternative to Microsoft Graph [#173] (#179) 2026-07-13 15:56:22 -05:00
package-lock.json chore(release): v1.9.14-beta1 2026-07-23 22:10:05 -05:00
package.json chore(release): v1.9.14-beta1 2026-07-23 22:10:05 -05:00
server.js feat(ui): show server URL in Add Display modal + GitHub Releases link on /download/apk (#210) 2026-07-23 10:25:00 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00