Skip to content

fix(admin): don't hit Stripe for admin-granted subscriptions#1721

Open
giladresisi wants to merge 2 commits into
mainfrom
fix/admin-granted-stripe-guards
Open

fix(admin): don't hit Stripe for admin-granted subscriptions#1721
giladresisi wants to merge 2 commits into
mainfrom
fix/admin-granted-stripe-guards

Conversation

@giladresisi

Copy link
Copy Markdown
Collaborator

Admin-granted subscriptions store the user id in Organization.paymentId (not a real cus_... customer), so every billing path that fed paymentId to Stripe threw "No such customer" — the per-plan prorate call on billing-page load was flooding the error logs.

  • getCharges / prorate / checkDiscount / applyDiscount / finishTrial now skip Stripe for any non-cus_ paymentId (empty charges / zero price / no coupon / no-op).
  • /billing/portal, /billing/cancel and /billing/cancel-subscription reject with a clean 400 instead of a Stripe error.
  • /self exposes hasStripeCustomer, and the billing page only renders the "Update Payment Method / Invoices History" button when there is a real customer.

Validated by impersonating an admin-granted account (billing page loads with no Stripe error logs, portal button hidden) and a real cus_ subscriber (button shows, portal works).

Split from #1668.

🤖 Generated with Claude Code

Admin-granted subscriptions store the user id in Organization.paymentId (not a
real `cus_...` customer), so every billing path that fed paymentId to Stripe
threw "No such customer". Guard them all to skip Stripe when there is no real
`cus_` customer:

- getCharges returns no charges for any non-`cus_` paymentId.
- prorate returns a zero price (it fires on billing-page load per plan card,
  which was the main source of error logs).
- checkDiscount / applyDiscount return false (no coupon to offer/apply).
- finishTrial returns early (nothing to finish).
- /billing/portal, /billing/cancel and /billing/cancel-subscription reject with
  a clean 400 instead of a Stripe error (the UI also hides their buttons).

Also on the display side: /self exposes `hasStripeCustomer` (paymentId starts
with `cus_`), and the billing page only renders the "Update Payment Method /
Invoices History" button (which opens the Stripe billing portal) when it is
true. Admin-granted and never-subscribed orgs have no real customer, so the
button is hidden and the portal is never reached.

Validated by impersonating an admin-granted account (paymentId = user id): the
billing page now loads with no Stripe error logs (previously flooded by the
per-plan prorate call), and the Update Payment Method button is hidden. A real
`cus_` subscriber still sees the button and the portal opens normally.

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: 16 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: 1494 chars
  • Excessive added comments: 24 added comment lines (ratio 0.71)

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 thread libraries/nestjs-libraries/src/services/stripe.service.ts Outdated
Comment thread apps/backend/src/api/routes/billing.controller.ts
checkDiscount returned a pending Promise (always truthy), so the
eligibility guard in applyDiscount never fired: any cus_ customer
hitting POST /billing/apply-discount got the retention coupon
unconditionally, and customers with no active subscription crashed
on currentUserSubscription.data.id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread libraries/nestjs-libraries/src/services/stripe.service.ts
@nevo-david

Copy link
Copy Markdown
Contributor

Can you please elborate what is admin granted subscriptions?

@giladresisi

This comment was marked as duplicate.

@egelhaus

Copy link
Copy Markdown
Collaborator

Can you please elborate what is admin granted subscriptions?

Already resolved & clarified internally

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.

3 participants