Repository navigation
feat(migrate): end of a prepaid Studio year, "Keep Studio" renewal - #545
Merged
Merged
Conversation
Provision with billing.collector migrate reserves the grant with no Polar call. POST /api/migrate/grants/activate starts a prepaid year from the payment. Billing state prepaid wins while prepaid_until is ahead; overage is locked during it. Revoke ends the prepaid year. Migration 055 is additive (045 revoked CHECK widened).
Revoke read the grant before an activate wrote the prepaid year, so it skipped ending it and left a refunded year live. It now also ends the year on the fresh row the revoke mark returns.
A prepaid Studio year (paid with the Migrate order) now has an end the
owner can see and act on:
- Billing shows "Prepaid through {date}" instead of a monthly price; from
30 days before the end it offers "Keep Studio" on Starter or Pro yearly.
The yearly checkout carries a trial that ends with the prepaid year, so
nothing is charged before it ends. It is tagged migrate_renewal_grant_id,
never treated as a trial or a grant redeem.
- A sweep every 6 h mails the owner at T-30, T-7 and the end (cursor in
migrate_grants.prepaid_reminder_stage, migration 056) and ends the year
through endPrepaidYear. A year with a renewal gets no "choose a plan"
mail.
- Overage stays locked during the year with its own reason and copy; the
limit-reached text, the usage alert next step and the banner say so.
- Usage alerts read the prepaid plan and date (prepaid_plan, prepaid_until).
- The canceled mail is not sent while the prepaid year still runs; the
activation mail at renewal conversion is the yearly "prepaid-renewed"
mail.
- The usage period of a renewal trial stays the calendar month.
- Attach refuses a grant in the last 30 days of a prepaid year with no
renewal; with a renewal the renewal plan must cover the grant.
- Revoke cancels a "Keep Studio" renewal with the year, and a repeated
revoke takes off a year an earlier call could not.
- The claim page states the prepaid year and date for a prepaid grant.
- docs/PAYMENT_PROVIDERS.md: the prepaid year section.
… mail with the button - A plain subscription that reuses the account row a "Keep Studio" renewal wrote drops its `prepaid_renewal` trial origin on created/updated, so its activation is not told as the yearly renewal. - The T-30 window ends at 30 days, when "Keep Studio" opens, not 30.5: the mail's button works when it arrives.
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.
Stacked on #542 (prepaid Studio year core). Review that first; this PR's base is its branch and moves to
mainonce it merges.What this adds
Billing page and banner
Keep Studio (
POST /api/billing/keep-studio,server/utils/migrate-prepaid-renewal.ts)clamp(ceil(days left), 1, 30)), so nothing is charged before the year ends.migrate_renewal_grant_id, nevermigrate_grant_id: the webhook links it to the grant (renewal_subscription_id), no redeem, no duplicate check.trial_origin: 'prepaid_renewal'is not a trial for trial context or trial reminders.prepaid_not_active,renewal_not_open,renewal_scheduled. An ordinary checkout during a prepaid year now answers with the prepaid message.interval: 'year'is refused (not configured there).Year-end sweep (
server/plugins/migrate-prepaid-sweep.ts, every 6 h)migrate_grants.prepaid_reminder_stage(migration 056, CHECK 0–3).endPrepaidYear, then the ended mail.prepaid-ending,prepaid-ended,prepaid-renewed.Honest copy
prepaid(settings GET/PATCH, usage, usage panel).prepaid_plan/prepaid_until(F2).prepaid-renewedmail.Revoke residual
Attach
attach_prepaid_ending).Claim page
Docs
docs/PAYMENT_PROVIDERS.md: "Prepaid Studio year".Known edges (not in this PR)
Tests