Context
Phase 1.3 sub-plan business review (CONV-33, Finance HIGH 2) surfaced a gap: tier upgrade/downgrade (T1 ↔ T2 ↔ T3) had no policy for prorated charges or refunds. Decision required to:
- Lock the customer-facing billing experience (predictable, no surprise refunds / charges).
- Align with Stripe primitives (proration_behavior, subscription schedules) — avoid building custom proration math.
- Keep Stage 1 revenue recognition simple (cash-basis per § 1.8.C v4.18 amendment).
- Not encourage cyclic up-down gaming (subscribe T3, downgrade after 1 day to get pro-rata refund).
Two reasonable options on the table:
- (A) Stripe proration on both directions. Customer-friendly, symmetric. Stripe handles all the maths via
proration_behavior=create_prorations. Adds credit notes to the customer balance on downgrade — surfacing as «invoice credit» on next renewal. - (B) Stripe proration on upgrade, end-of-period switch on downgrade (no refund). Asymmetric. Upgrade is immediate + prorated; downgrade is scheduled via Stripe subscription schedule to take effect at
current_period_end. No refund / credit for the unused higher-tier period.
Decision
Option B — asymmetric: upgrade prorated, downgrade end-of-period (no refund).
Operational rules:
- Upgrade (T1 → T2, T1 → T3, T2 → T3): Stripe API call
subscriptions.update({ items, proration_behavior: 'create_prorations' }). Customer is charged the prorated difference immediately; new tier limits + features unlock instantly. Invoice email lands within seconds. - Downgrade (T3 → T2, T3 → T1, T2 → T1): create Stripe
SubscriptionSchedulewith phase change atcurrent_period_end. Until renewal: customer keeps current (higher) tier features. At renewal: tier flips. No refund / credit issued for the unused period at the higher tier. UI message: «Your tier will change to {New Tier} on {renewal_date}. Until then you keep {Current Tier} access.» - Trial → Paid (Path A signup, § 1.8.A): treated as a fresh subscription — full new-tier period, no proration relative to the (free) trial window.
- Free Guest → Paid (§ 1.8.E + § 2.8): treated as a fresh subscription; same as Trial → Paid.
- Suspension restore (§ 1.8.D): customer pays the outstanding invoice (which represents the suspended period); no separate proration logic. Cash-basis recognition applies (per § 1.8.C v4.18 amendment).
- Refunds initiated by support (e.g. legitimate complaint, accidental upgrade): handled out-of-band via Stripe Dashboard. Operator dashboard (Phase 1.4.7) logs the manual refund with mandatory reason in audit log (
pii_class = sensitive). Out of scope for self-serve. - Reverse-invite ownership transfer (§ 1.5.F + § 1.4.C): new Owner's billing period starts fresh from acceptance — no carryover of old Owner's prior trial/Free Guest state. Stripe customer object can be reused (linked to new Owner) OR a new customer is created; defaults to new customer for clean audit trail.
Alternatives Considered
- (A) Stripe proration on both directions. Rejected:
- Surfaces refund/credit as a customer balance, requiring explanation in the Billing UI (Stage 1 doesn't have a credit-balance widget).
- Encourages cyclic upgrade/downgrade gaming.
- Asymmetric model is the industry default (Notion, Linear, Figma all do upgrade-prorated, downgrade-at-period-end).
- (C) Lock tier for full billing period (no upgrade mid-period either). Rejected: hostile to growth — customer hitting a tier limit shouldn't have to wait for renewal. Upgrade-now is essential.
- (D) Custom proration math (bypass Stripe defaults). Rejected: high maintenance, tax / VAT edge cases compound the bug surface.
Consequences
- Customer experience: upgrades feel responsive (instant unlock + immediate prorated charge); downgrades feel «honest» (you keep what you paid for through the end of the cycle). Aligns with industry-standard SaaS expectations.
- Revenue recognition: cash-basis Stage 1 (per § 1.8.C v4.18); each Stripe invoice → revenue event on
payment_intent.succeeded. Downgrade has no impact on the current period's recognised revenue. - Implementation: Phase 1.3.11 (Billing tenant model) uses Stripe
Subscription+SubscriptionScheduleprimitives. UI: tier-switch modal computes preview via StripePOST /v1/invoices/create_previewendpoint (replaces deprecatedGET /v1/invoices/upcomingper Stripe 2025-03-31 changelog; v1.1 amendment CONV-35) to show «You'll be charged {amount} today» on upgrade, «No charge today — switches on {date}» on downgrade. - No refund path in self-serve → CS load: ~5-10% of downgrades may surface refund requests via support. Acceptable Stage 1; revisit if volume > 50/month.
- Reverse-invite billing handover (§ 1.5.F + § 1.4.C) has clean semantics — new Owner starts a fresh billing clock, no inherited credits / debits.
Revisit trigger
- If downgrade-refund support tickets exceed 50/month consistently → reconsider option A.
- If accrual-basis revenue recognition switch (Stage 2 pre-audit) requires symmetric proration for deferred-revenue tracking.
- If a competitive analysis shows symmetric proration as a meaningful market signal.
Cross-references
- Phase 1.3 § 1.8.F (Tier upgrade / downgrade — operational rules).
- Phase 1.3 § 1.8.B (Trial → Free Guest — Stripe customer cleanup).
- Phase 1.3 § 1.8.C (Suspended — chargeback overrides grace; cash-basis recognition).
- ADR 0008 (Tier model — skeleton; pricing per tier).
- ADR 0006 (Chargeback auto-freeze).
- Learning «PaymentProvider abstraction для кипрской компании» (2026-04-29) — Stripe Stage 1; Paddle / Checkout.com Stage 2/3.