Skip to content

feat(admin): allow removing an admin-granted subscription#1723

Open
giladresisi wants to merge 2 commits into
mainfrom
feat/remove-admin-granted-subscription
Open

feat(admin): allow removing an admin-granted subscription#1723
giladresisi wants to merge 2 commits into
mainfrom
feat/remove-admin-granted-subscription

Conversation

@giladresisi

Copy link
Copy Markdown
Collaborator

The impersonation bar could grant a free/admin subscription but not undo it. Adds a super-admin Remove Subscription action next to the grant selector.

  • New POST /billing/remove-subscription (super-admin): downgrades the org to FREE, drops the local subscription row via the existing deleteSubscription path, and clears the org's fake paymentId — all with no Stripe calls. Clearing paymentId matters: admin grants store the user id there, and leaving it makes later Stripe flows error with "No such customer".
  • Guarded to admin-granted subscriptions only: any cus_... paymentId is a real Stripe customer and is rejected (use cancel-subscription instead), so a real paid subscription can never be removed here.
  • /self returns adminGrantedSubscription; the impersonation bar shows the Remove control only for admin grants, and the billing page hides the Cancel button for them.

Validated by impersonation both ways: an admin-granted account shows Remove and hides the Cancel controls; a real cus_ subscriber shows Cancel (working) and hides Remove.

Split from #1668. Independent of #1721, though they touch adjacent code — either merge order resolves automatically.

🤖 Generated with Claude Code

The impersonation bar could grant a free/admin subscription but not undo it.
Adds a super-admin "Remove Subscription" action next to the grant selector.

- New POST /billing/remove-subscription (super-admin) →
  removeAdminGrantedSubscription: downgrades the org to FREE, drops the local
  subscription row via the existing deleteSubscription path, and clears the
  org's fake paymentId — all with NO Stripe calls. Clearing paymentId is
  required: admin grants store the user id in paymentId, and leaving it would
  make later Stripe flows (checkout, discount checks) call Stripe with a
  non-existent customer and error ("No such customer").
- Guarded to admin-granted subscriptions only: those store the user id in
  Organization.paymentId, so any paymentId starting with `cus_` is a real
  Stripe customer and is rejected (use cancel-subscription instead) — a real
  paid subscription can never be removed here.
- /self now returns `adminGrantedSubscription`, and the impersonation bar shows
  the remove control only when it is true, so a genuine Stripe subscription is
  never accidentally removed.
- Since an admin-granted subscription has no Stripe subscription to cancel, the
  now-redundant Cancel controls are hidden when `adminGrantedSubscription` is
  true: the impersonation bar's "Cancel Subscription" (Remove is used instead)
  and the billing page's self-service "Cancel subscription" button. Their
  backend endpoints (in the previous commit) already reject non-`cus_` orgs
  with a clean 400 as defense-in-depth.

Validated by impersonating an admin-granted account: the Remove control shows
while both Cancel controls (impersonation bar and billing page) are hidden;
impersonating a real `cus_` subscriber hides Remove and still shows the Cancel
controls, which continue to work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@postiz-contribution postiz-contribution Bot added the contribution:approved Approved contributor label Jul 14, 2026
@postiz-contribution

Copy link
Copy Markdown

Contribution-checker quality warning
Heuristic score: 0/100 (low). This is a non-blocking warning surfaced by the project's quality settings.

Heuristics that flagged:

  • Excessive inline code references: 8 inline refs (>3)
  • PR doesn't use the repo's PR template: 5 required checkbox items missing (max 1, match ≥80%)
  • AI watermark phrase: Matched: "Generated with Claude Code"
  • Commit message too long: Longest: 1915 chars
  • Excessive added comments: 14 added comment lines (ratio 0.15)

If this is a genuine contribution, please add detail to your PR description and tighten the diff scope before reviewers look at it.

@postiz-agent

postiz-agent Bot commented Jul 14, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Comment on lines +128 to +131
adminGrantedSubscription:
// @ts-ignore
!!organization?.subscription &&
!organization?.paymentId?.startsWith('cus_'),

This comment was marked as outdated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by followup commit

Lifetime-deal orgs have a subscription row but a null paymentId, so
!paymentId?.startsWith('cus_') was true for them and the impersonation
bar showed a Remove Subscription button that always fails server-side.
Require a non-null paymentId, which only the admin-grant path sets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contribution:approved Approved contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant