Skip to content

feat(billing): staff switch between managed and self-serve billing - #760

Merged
ussaama merged 2 commits into
mainfrom
feat/staff-billing-mode-switch
Jul 2, 2026
Merged

feat(billing): staff switch between managed and self-serve billing#760
ussaama merged 2 commits into
mainfrom
feat/staff-billing-mode-switch

Conversation

@ussaama

@ussaama ussaama commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Invariant: never orphan a live Mollie subscription. A live subscription
(payment_mode=="mollie" + subscription id) blocks tier and billing-mode changes
until the customer cancels. The customer billing page now reflects the real
subscription, not the status entitlement flag.

Backend

  • billing_account.has_live_mollie_subscription() predicate.
  • set_workspace_tier + set_managed: 409 before any write when it holds.
  • set_saas(SetSaasBody): to_free -> tier=free; else keep tier + required expires_at, which reverts to free via task_expire_workspace_tiers unless the customer subscribes. status stays "active".
  • get_billing_overview: +has_active_subscription, +has_payment_history.
  • admin rollup: payment_mode on BillingRow.

Frontend

  • Staff BillingModeControl (bidirectional, current tier, Free/keep-until-date modal, disabled+reason on live sub) in the account + external-workspace modals; ChangeTierControl disables Apply on live sub.
  • Customer BillingManager: paid tier with no live sub shows "No subscription" / "Ends " + Subscribe, never a false "Active"; cancelled subs keep Resume.

Docs: docs/billing_modes.md. Tests: test_managed_billing.py (33 pass, 1 skip).

  Invariant: never orphan a live Mollie subscription. A live subscription
  (payment_mode=="mollie" + subscription id) blocks tier and billing-mode changes
  until the customer cancels. The customer billing page now reflects the real
  subscription, not the `status` entitlement flag.

  Backend
  - billing_account.has_live_mollie_subscription() predicate.
  - set_workspace_tier + set_managed: 409 before any write when it holds.
  - set_saas(SetSaasBody): to_free -> tier=free; else keep tier + required
    expires_at, which reverts to free via task_expire_workspace_tiers unless the
    customer subscribes. status stays "active".
  - get_billing_overview: +has_active_subscription, +has_payment_history.
  - admin rollup: payment_mode on BillingRow.

  Frontend
  - Staff BillingModeControl (bidirectional, current tier, Free/keep-until-date
    modal, disabled+reason on live sub) in the account + external-workspace modals;
    ChangeTierControl disables Apply on live sub.
  - Customer BillingManager: paid tier with no live sub shows "No subscription" /
    "Ends <date>" + Subscribe, never a false "Active"; cancelled subs keep Resume.

  Docs: docs/billing_modes.md. Tests: test_managed_billing.py (33 pass, 1 skip).
@ussaama
ussaama merged commit c649a0b into main Jul 2, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant