- Shipped
- August 11, 2026 at 6:29 PM UTC
- Author
- Kamo
- Commit
- d79656c
Three gaps in what shipped last: A group with a plan could never be removed. delete() refuses while a live subscription exists — correct, since the group row is the only thing pointing at the Stripe subscription — but there was no way to cancel, so the safeguard was a dead end. cancelPlan() releases the seats first, so the roster cannot go on claiming people hold seats that no longer exist. Adding someone the organization already buys a seat for is now refused unless takeOverFromOrg says otherwise. That is the second way to be charged twice for one person and nothing caught it: SubscriptionMember's uniques are per-subscription and AccountLicense declares none at all. Moving somebody from the organization's bill onto a group's is a real thing a manager does, so it is allowed — deliberately rather than by accident. BillingGroupScopeInterceptor binds every /api/billing/groups/{uuid}/** request to the caller's organization. The handlers already check, and this does not replace them; it exists because those checks are per-handler and a group now has sub-resources that spend money. The next route added inherits nothing unless something above it applies the rule. Deliberately narrower than the handlers: this settles which organization, not who within it may act, so the two cannot drift into disagreeing answers. An unknown group id passes through so the handler answers "not found" rather than confirming the id exists somewhere.