Repository navigation
Conversation
… a plan [ENG-4035] A Pro trial only cancelled at expiry when no card was on file. Any saved card — on the subscription or just the customer — made Stripe invoice Pro at trial_end, so saving billing details silently bought the plan. Free trials now cancel at the exact second the trial ends (cancel_at === trial_end, no proration), so Stripe never invoices them and the deletion webhook moves the org to Hobby. An explicit upgrade clears cancel_at in the same error_if_incomplete update that ends the trial, so a declined or 3DS charge rolls back with the fallback intact. Trial -> Hobby clears it too, and scheduling a plan change on a trialing subscription now throws. Trial copy says the trial ends on Hobby without a charge; the ending modal CTA is now Upgrade. Entire-Checkpoint: 01M5KVSCHGMS3RRQAP6J89ENCX
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Ref ENG-4035
What & why
Was: A free Pro trial with any saved card — on the subscription or only the customer — was billed for Pro when the trial ended, so saving billing details silently bought the plan.
Now: Every new free trial ends on the free Hobby plan with no invoice, card or no card. Only an explicit upgrade whose payment succeeds keeps a paid plan.
cancel_at === trial_end, no proration).cancel_atin the sameerror_if_incompleteupdate, so a declined or 3DS charge rolls back with the fallback intact.Where to look
cancel_at— same paid update ends the trialBreaking changes
None — Cloud-only billing internals; no API, SDK, env var or self-hosting change.
Migrations & env
How this was tested
Rerun:
cd apps/web && npx vitest run modules/ee/billing/lib/free-trial-end.test.ts modules/ee/billing/lib/organization-billing.test.tsCoverage
cancel_at === trial_endcancel_atin the paid update; decline keeps itcancel_atcancel_at: ""free-trial-end.test.ts; mutatefree-trial-end.ts:28(cancel_at) totrialEndUnix + 1Stripe test-clock results (test mode, API 2026-02-25.clover)
7-day Pro trial ($89/mo + metered responses), clock advanced to trial end + 1 day.
card_declinedsubscription_payment_intent_requires_actioninvalid_canceled_subscription_fieldsScreenshots — Pro trial with 2 days left
Open gaps
Risks
cta: "upgrade"; dashboards filteringadd_payment_methodneed updating.cancel_at_period_end: falseclearscancel_at; scheduling on a trialing subscription now throws to prevent that.Note
AI model used —
claude-opus-5-5, reasoning effortunknown.