Twelfth section of #269's change. When the grant that holds a ladder
ranks below the tier a paused subscription pays for, the paid plan's
card now offers Resume plan before Cancel plan. Resume plan is a
console act (entitlements.ResumeSubscription, run by
fulfillment.ResumePlan from PostResume): in one transaction under the
pool row it confers the subscription's own tier over the grant and
writes the reconcile requests, and the outbox's reconcile then clears
the pause at Stripe after the commit, so the member's request makes no
Stripe call. It takes no subscription's advisory lock, so an ending act
or a reconcile holding that lock goes first; a second submit confers
once; over a bundle grant it ends the grant on its other ladder too. It
is refused when its conditions no longer hold: a suspended subscription
answers as a past-due or paused plan does, and a scheduled move, a rung
the subscription or another one already holds, or a Stripe mapping the
outbox cannot reach answers the unavailable-plan sentence.
Its confirmation reads "Resume <paid>?" and "Your subscription replaces
the grant. Nothing is charged now; then <price>." A grant marked to
resume delivers again when the subscription ends.
Rides along, by ruling: the current card a grant holds now states its
cost like the others, reading "Granted until <date>" when the grant has
an end date and "Granted" when it has none (the grant's valid_until,
read with the positions in ListLadderPositionsByPool). Under a grant of
the subscription's own tier, where the paid card is the current card,
its paused line carries the date: "Paused while Plus is granted until
Feb 20, 2099". The organization type's default plan's card, which
states no cost either, is #289. Also by ruling, the switch on the
granted tier's own card reads "Pay for this plan" and its confirmation
"Pay for <tier>?" (Move.OwnTier); the tiers above keep "Upgrade".
docs/stripe.md, docs/plan-management.md,
docs/models/plan-ladders-transitions.md and docs/database-locks.md say
the same.
Tests: TestResumeSubscription_* (taking the ladder from the grant,
refusals, an ending act first, a second submit, a bundle grant, a
marked grant resuming), TestResumePlan_* (no Stripe call, the act's
refusals mapped, an unreachable mapping, a failed clear keeping the
ladder), TestPostResume, TestResumeConfirmation,
TestRenderPlansAfterMoveAnswersResume, TestGrantedCurrentCard,
TestSwitchLabelUnderAGrant, TestOffer rows, and TestPaidPlanCardUnderAGrant
rows for the dated own-tier lines. TestMemberPlansGolden gains four
states (paid-standard-under-granted-basic and three dated ones); nine
states gain "Granted" on the granted current card, and
paid-retired-plus-under-granted-pro's switch reads "Pay for this plan".
The routes goldens gain the resume route.
TestResumePlan_AFailedClearKeepsTheLadder runs the drift sweep where the
task named collectionDrift; the sweep reaches the same decision.
Full suite on reset databases: 5090 passed, 14 skipped, none failed.
make screens: no page changed; no demo state reaches the paid card.
Breaks, all CAUGHT (43):
- Resume: the rank test dropped from rule 3a.1
- Resume: the console-pause test dropped from rule 3a.1
- Resume: the scheduled-move test dropped from rule 3a.1
- Resume: the bundle test dropped from rule 3a.1
- Resume: the suspension test dropped from rule 3a.1
- Resume: the GrantHolds test dropped from rule 3a.1
- ResumeSubscription: the status test dropped
- ResumeSubscription: the materialization dropped
- ResumePlan: the act's refusal mapping dropped
- ResumeSubscription: the marker test dropped (a behavior alone passes)
- ResumeSubscription: the rank test dropped
- ResumeSubscription: the scheduled-change read dropped (cancel_at_period_end alone)
- ResumeSubscription: the holds test dropped
- ResumeSubscription: the reconcile requests dropped
- ResumeSubscription: the reads moved above LockPools
- ResumeSubscription: the reconcile's advisory lock on the subscription's Stripe id taken after LockPools
- ResumePlan: the reachability test dropped
- Resume plan: the trigger's declaration removed
- Resume plan: the confirmation's first sentence dropped
- Resume plan: the control not drawn (CanResume ignored by the template)
- Resume plan: CanResume not counted in plansOfferPaidMove
- Resume plan: the handler's decision gate dropped
- Granted card: GrantedUntil never set (TestGrantedCurrentCard)
- Granted card: GrantedUntil never set (TestPaidPlanCardUnderAGrant)
- Granted card: GrantedUntil never set (TestMemberPlansGolden/granted-plus-until)
- Granted card: the template's two branches removed
- Granted card: setGranted called only when a subscription pays on the ladder
- Granted card: the default_sourced test dropped
- Granted card: pos.GrantID.Valid dropped from setGranted
- Granted card: a clock read added (a passed end date drawn as Granted)
- Granted card: setGranted called after the paid card's return
- Granted card: setGranted also called on the paid card under a grant of another tier
- Granted card: the date left out of the plain paused branch (TestPaidPlanCardUnderAGrant)
- Granted card: the date left out of the plain paused branch (TestMemberPlansGolden/granted-plus-own-tier-until)
- Granted card: the date left out of the payment-due paused branch
- Granted card: the date left out of the downgrade's paused branch
- Granted card: the date drawn on a line that does not read Paused (the renews branch)
- Granted card: the query's column selected as NULL (the join left out)
- Granted card: JOIN core.grants in place of the LEFT JOIN (the new test)
- Granted card: JOIN core.grants in place of the LEFT JOIN (the live-positions test)
- renderPlansAfterMove: ErrResumeUnavailable dropped from the unavailable-plan case
- moveLabel: the own-tier case reverted to Upgrade
- planMoveMessage: the own-tier heading reverted to Switch to