Skip to content

Add Stripe billing with free/pro tiers and invite-assigned plans - #792

Merged
kody-bot merged 11 commits into
mainfrom
cursor/stripe-billing-and-invite-plans-5d94
Jul 20, 2026
Merged

kody-bot merged 11 commits into
mainfrom
cursor/stripe-billing-and-invite-plans-5d94

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 20, 2026 •

Copy link
Copy Markdown
Owner

What

Lightweight (gratitext-style, no SDK, no webhooks) Stripe billing plus the invite/plan plumbing to run cohorts on different tiers:

  • Two public tiers — new free plan (tight limits: 5 packages, 3 jobs, 5 email sends/day, 64 MB storage, …) and pro at $5/mo; partner remains as a manual grant. Plan order: free < pro < partner. The earlier personal/$2.50 tier was removed in favor of a single paid tier (its Stripe product/price/link are deactivated).
  • Invite-assigned plans — invites.plan (migration 0065) is applied to users.plan when the invite is consumed at signup (password + social). NULL keeps today's legacy/unlimited behavior, so existing invites are unchanged and legacy cohorts still work by default. Admin invites UI gets a plan select.
  • Stripe billing (packages/worker/src/billing/, migration 0066) — raw-fetch Stripe client with schema validation and a 10s request timeout; payment-link checkout; /account/billing settings page; /account/billing/success links stripe_customer_id after verifying a signed client_reference_id; /account/billing/portal opens the Stripe billing portal; users.stripe_plan cache refreshed on page view (60s staleness) and by an hourly cron lane (25 users/sweep).
  • Effective plan — getUserPlan resolves max(users.plan, users.stripe_plan); a NULL manual plan (legacy/unlimited) is never downgraded by a subscription. Enforcement hot path stays a single D1 row read — Stripe is never called during entitlement checks.
  • Fully optional — without STRIPE_SECRET_KEY, billing degrades to manual plans (banner on the billing page; cron/portal/success no-op safely).

Pricing (data-grounded)

Free $0 / Pro $5/mo, one paid tier. Benchmarks: IFTTT Pro $3.99, Make ~$10, Val Town Pro $10 legacy ($25 new, incl. $10 LLM credits), Zapier/n8n/Pipedream $20–29. Kody is BYO-agent (zero inference cost); worst-case per-user Cloudflare cost is ~$1–2/mo (DO duration), so $5 holds healthy margin while staying far from the $20 ChatGPT anchor. Live Stripe product/price/payment link are created and committed as production vars in wrangler.jsonc.

Security

Independent review flagged that a raw stable user id in client_reference_id is forgeable (it's just SHA-256(email)); fixed by signing it with COOKIE_SECRET (HMAC). The success endpoint also refuses to replace an established customer linkage and rejects customers already linked to another account (unique partial index enforced). Checkout session ids are redacted from error logs.

Production rollout

  • STRIPE_SECRET_KEY syncs via the deploy workflow (--set-from-env-optional); the STRIPE_SECRET_KEY GitHub Actions secret must be added for billing to activate in production — until then everything runs in manual-plans mode.
  • Price id / payment link for production are committed in wrangler.jsonc (non-secret; coupling documented inline).

Testing

  • npm run validate green locally (format, lint, typecheck, unit, Playwright E2E, MCP E2E).
  • Unit coverage: billing config/client/sync (incl. forged-reference rejection, replay/overwrite guard, already-linked customer), free-tier limits, effective-plan matrix, invite plan roundtrip, signup plan application.
  • Manual E2E against a local Stripe API mock (video): free-plan user → checkout return links customer and shows Pro → billing portal opens. Separately verified: a different logged-in user replaying a checkout URL is rejected by the HMAC guard, and admin invite → signup lands the new user on the free plan.

billing_final_two_tier_demo.mp4

Billing page on free plan (two tiers)
Invite created with free plan

System recap — adds a new primitive (high risk)

Mode: recap · Base: main @ 4f84d184 · Head: 76a4159e

Classification: adds — new billing primitive (Stripe checkout/link/sync); entitlements, app-sessions, and d1-app-db extended.

Primitives touched

Primitive Group Impact
billing auth adds — payment-link checkout, customer linking, subscription→plan sync
entitlements auth extends — free plan, personal removed; effective plan = max(manual, stripe); NULL never downgraded
app-sessions auth extends — signup applies invites.plan to the new user
d1-app-db storage extends — invites.plan; users.stripe_customer_id (unique) / stripe_plan / stripe_plan_refreshed_at
app-ui surfaces extends — /account/billing page + admin invite plan select
email assistant composes — inbound limit read now uses effective plan
jobs-cron runtime composes — hourly stripe_plan_refresh lane

System map

Checkout flows from the billing UI through the new billing primitive into D1, and entitlements read the synced plan on every enforcement check.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app"]:::extended
	billing["billing<br/>Stripe billing"]:::added
	d1AppDb["d1-app-db<br/>D1 app database"]:::extended
	entitlements["entitlements<br/>Plans & entitlements"]:::extended
	appSessions["app-sessions<br/>Browser sessions"]:::extended
	jobsCron["jobs-cron<br/>Scheduled lanes"]:::touched
	stripe["Stripe API"]:::untouched
	appUi -->|"/account/billing + success/portal routes"| billing
	billing -->|"checkout session verify + subscriptions list (10s timeout)"| stripe
	billing -->|"stripe_customer_id / stripe_plan columns"| d1AppDb
	appSessions -->|"signup applies invites.plan"| d1AppDb
	entitlements -->|"SELECT plan, stripe_plan (single row read)"| d1AppDb
	jobsCron -->|"hourly stripe_plan_refresh lane"| billing
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

users:   plan                          → plan, stripe_customer_id (unique), stripe_plan, stripe_plan_refreshed_at
invites: code, note, max_uses, …       → + plan (nullable; applied at signup)
plans:   partner, personal, pro        → free, partner, pro (effective = max(manual, stripe); NULL wins; pro = $5/mo)

Invariants

Per-user isolation: checkout linking binds client_reference_id = HMAC(stable user id, COOKIE_SECRET); a customer can never link to two accounts (unique partial index + pre-check) and an established linkage is never replaced by a later session.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added an account Billing page with current plan details, cancellation dates, and Pro subscription management (including portal access).
    • Introduced Stripe subscription syncing with customer linking and scheduled plan refresh for updated billing entitlements.
    • Admins can set an invite plan; the selected plan is applied during signup.
    • Updated entitlement plan logic to support Free, Pro, and Partner tiers with Stripe-aware “effective plan” enforcement.
  • Documentation
    • Added and expanded Stripe billing environment setup and configuration guidance.
  • Bug Fixes
    • Corrected plan names/labels and usage-limit enforcement to match the new plan tier semantics.

…sync

- 'free' plan tier with tight limits (data-grounded pricing: free / $2.50
  personal / $10 pro, benchmarked against IFTTT, Make, Val Town)
- invites.plan column applied to users at signup (password + social)
- users.stripe_customer_id / stripe_plan cache with checkout-session
  linking guarded by client_reference_id and hourly cron refresh
- getUserPlan resolves the effective plan as max(manual, stripe), with
  NULL legacy/unlimited never downgraded
- /account/billing page routes, payment-link config, deploy secret sync
Wire AccountBillingRoute into the client router and account nav so users can view their plan, open Stripe checkout links, and manage subscriptions.
- /account/billing client route with plan cards (free / $2.50 personal /
  $5 pro), payment links, portal link, and nav entry
- billing module unit tests (config, client, sync) plus entitlements
  coverage for the free tier and effective-plan resolution
- test schemas updated for the new users billing columns
- docs: entitlements architecture, env vars, setup manifest
- regenerated worker-configuration.d.ts for the new production vars
- Manage subscription now forces a full-page navigation so the SPA
  router does not intercept the server-only /account/billing/portal
  redirect route
- plan cards distinguish 'Current plan' (exact match) from 'Included in
  your plan' (higher-ranked or legacy/unlimited plan)
Independent review findings:
- client_reference_id is now an HMAC of the stable user id keyed by
  COOKIE_SECRET instead of the raw email hash, so it cannot be derived
  by an attacker who knows a victim's email
- an established stripe_customer_id is never silently replaced by a
  later checkout session (GET endpoint; first-link or same-customer only)
- checkout session ids are redacted from Stripe error logs
- documented the portal redirect trust assumption and the committed
  production price id / payment link coupling
@kody-bot
kody-bot marked this pull request as ready for review July 20, 2026 00:22
@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-792.kody-a99.workers.dev

Worker: kody-pr-792
D1: kody-pr-792-db
KV: kody-pr-792-oauth-kv

Mocks:

One free tier plus one paid tier is easier to justify and communicate.
$5/mo sits between IFTTT Pro ($3.99) and Make (~$10), stays far from
the $20 ChatGPT anchor, and covers worst-case per-user infra (~$1-2/mo
of DO duration) with real margin. The Stripe Personal product, price,
and payment link are deactivated; parsePlanName already treats stored
'personal' values as NULL so no data migration is needed.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b4311405-bd7c-49f3-9617-88d21e68d9c7

📥 Commits

Reviewing files that changed from the base of the PR and between 76a4159 and ca0358e.

📒 Files selected for processing (3)
  • packages/worker/migrations/0067-personal-plan-to-pro.sql
  • packages/worker/src/app/account-billing-data.ts
  • packages/worker/src/billing/subscription-sync.ts
📝 Walkthrough

Walkthrough

This change adds invite-associated plans, replaces personal with free, resolves entitlements from manual and Stripe plans, introduces Stripe billing and subscription synchronization, adds account billing routes, and updates deployment, schema, documentation, tests, and generated Worker typings.

Changes

Invite Plans and Stripe Billing

Layer / File(s) Summary
Invite plan propagation
packages/worker/src/app/invites.ts, packages/worker/src/app/handlers/auth*.ts, packages/worker/src/app/handlers/admin-invites.ts, packages/worker/migrations/0065-invite-plans.sql, packages/worker/client/routes/admin-invites.tsx, e2e/*
Invites accept nullable validated plans, persist them, display them in admin flows, copy them to new users during password and OAuth signup, and include plan values in audit events and end-to-end verification.
Effective entitlement resolution
packages/worker/src/entitlements/*, packages/worker/src/email/inbound.ts, packages/worker/src/app/admin-user-usage-data.ts, packages/worker/src/mcp/*, packages/worker/src/*test.ts
Plan names now use free, pro, and partner; entitlement resolution combines manual and Stripe plans while preserving unlimited behavior for null manual plans, and affected consumers and tests use the updated limits.
Stripe billing services
packages/worker/src/billing/*, packages/worker/migrations/0066-stripe-billing.sql, packages/worker/src/db.ts
Stripe configuration, REST requests, response validation, payment-link references, checkout customer linking, subscription-plan refresh, cancellation tracking, uniqueness checks, and stale-account synchronization are implemented and tested.
Billing application flow
packages/worker/src/app/account-billing-data.ts, packages/worker/src/app/handlers/account-billing.ts, packages/worker/client/routes/account-billing.tsx, packages/worker/src/app/{routes,router}.ts, packages/worker/client/routes/index.tsx
Authenticated page, API, checkout-success, and portal handlers expose billing data and Stripe actions; the client route renders plan status, payment links, cancellation state, and subscription management.
Deployment and configuration support
.github/workflows/deploy.yml, packages/worker/{.env.example,env-schema.ts,wrangler.jsonc}, docs/contributing/*, packages/worker/worker-configuration.d.ts
Stripe environment variables, production Worker variables, secret synchronization, architecture documentation, examples, and regenerated Cloudflare runtime typings are updated.
Scheduled synchronization
packages/worker/src/index.ts, packages/worker/src/index.workers.test.ts
The scheduled handler runs the stale Stripe-plan refresh lane and verifies its invocation.
Schema and test fixtures
packages/worker/src/community/community-flow-test-schema.ts, packages/worker/src/entitlements/test-schema.ts, packages/worker/src/app/*test.ts
Application and test schemas include Stripe billing columns, while database mocks and fixtures cover invite plans, effective plans, billing linkage, and refreshed subscription state.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • kentcdodds/kody#615: Updates the invite-gated signup and invite-consumption flow that this change extends with plan propagation.
  • kentcdodds/kody#619: Introduces the nullable manual-plan entitlement behavior extended here with Stripe-aware effective plans.
  • kentcdodds/kody#773: Modifies the OAuth invite-consumption flow touched here for invite-associated plans.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.66% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: Stripe billing, the free/pro tier model, and invite-assigned plans.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/stripe-billing-and-invite-plans-5d94

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/worker/src/app/handlers/auth.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate invite-plan consumption logic across password and OAuth signup. Both handlers implement identical consumedInvitePlan derivation, invite-release reset, and conditional plan insertion; the shared root cause is that invite-plan-to-user-creation logic isn't factored into a single helper.

  • packages/worker/src/app/handlers/auth.ts#L202-254: extract a shared helper (e.g. in packages/worker/src/app/invites.ts) that consumes the invite, derives consumedInvitePlan via parsePlanName, and returns the fields to spread into user creation, plus a release() closure; call it here.
  • packages/worker/src/app/handlers/auth-provider.ts#L437-484: call the same shared helper here instead of re-implementing the state machine.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/app/handlers/auth.ts` at line 1, Extract the duplicated
invite-plan state machine from the password signup flow and OAuth signup flow
into a shared helper, such as in invites.ts. The helper should consume the
invite, derive consumedInvitePlan through parsePlanName, return user-creation
fields including conditional plan data, and expose a release() closure for
resetting the invite; update the handlers around auth.ts and auth-provider.ts to
call this helper and remove their duplicate logic.
packages/worker/src/app/account-billing-data.ts (1)

75-102: 🧹 Nitpick | 🔵 Trivial

On-render Stripe refresh adds request-path latency.

The staleness path calls refreshStripePlanForUser (a blocking listSubscriptions call to Stripe) during page/API render. It's correctly gated to 60s and wrapped so failures degrade gracefully, and the hourly cron lane provides a backstop — so this is acceptable. If billing-page p95 latency becomes a concern, consider serving the cached stripe_plan immediately and refreshing out-of-band (e.g. waitUntil) instead of awaiting Stripe inline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/worker/src/app/account-billing-data.ts` around lines 75 - 102, The
current staleness path awaits refreshStripePlanForUser during request rendering;
if reducing billing-page latency, return the cached stripePlan immediately and
move the Stripe refresh to an out-of-band waitUntil task, preserving the
existing 60-second staleness gate and graceful error logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/src/billing/stripe-client.ts`:
- Around line 88-120: Update stripeRequest to create a request timeout and pass
its AbortSignal through the fetch RequestInit. Ensure the signal automatically
aborts slow or hung Stripe requests while preserving the existing URL, headers,
method, and body handling.

---

Nitpick comments:
In `@packages/worker/src/app/account-billing-data.ts`:
- Around line 75-102: The current staleness path awaits refreshStripePlanForUser
during request rendering; if reducing billing-page latency, return the cached
stripePlan immediately and move the Stripe refresh to an out-of-band waitUntil
task, preserving the existing 60-second staleness gate and graceful error
logging.

In `@packages/worker/src/app/handlers/auth.ts`:
- Line 1: Extract the duplicated invite-plan state machine from the password
signup flow and OAuth signup flow into a shared helper, such as in invites.ts.
The helper should consume the invite, derive consumedInvitePlan through
parsePlanName, return user-creation fields including conditional plan data, and
expose a release() closure for resetting the invite; update the handlers around
auth.ts and auth-provider.ts to call this helper and remove their duplicate
logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 53e0912c-fa43-485b-9122-f9e5466872a7

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed468b and 94ed7bc.

📒 Files selected for processing (64)
  • .github/workflows/deploy.yml
  • docs/contributing/architecture/authentication.md
  • docs/contributing/architecture/entitlements.md
  • docs/contributing/architecture/primitives.yaml
  • docs/contributing/environment-variables.md
  • docs/contributing/setup-manifest.md
  • e2e/invite-signup-verification.spec.ts
  • packages/worker/.env.example
  • packages/worker/client/routes/account-billing.tsx
  • packages/worker/client/routes/account-management-components.tsx
  • packages/worker/client/routes/admin-insights.tsx
  • packages/worker/client/routes/admin-invites.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/migrations/0065-invite-plans.sql
  • packages/worker/migrations/0066-stripe-billing.sql
  • packages/worker/src/app/account-billing-data.ts
  • packages/worker/src/app/admin-invites-data.ts
  • packages/worker/src/app/admin-user-usage-data.node.test.ts
  • packages/worker/src/app/admin-user-usage-data.ts
  • packages/worker/src/app/handlers/account-billing.ts
  • packages/worker/src/app/handlers/admin-invites.ts
  • packages/worker/src/app/handlers/admin-users.node.test.ts
  • packages/worker/src/app/handlers/auth-handler.node.test.ts
  • packages/worker/src/app/handlers/auth-provider.ts
  • packages/worker/src/app/handlers/auth.ts
  • packages/worker/src/app/invites.node.test.ts
  • packages/worker/src/app/invites.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/billing/billing-config.node.test.ts
  • packages/worker/src/billing/billing-config.ts
  • packages/worker/src/billing/stripe-client.node.test.ts
  • packages/worker/src/billing/stripe-client.ts
  • packages/worker/src/billing/subscription-sync.ts
  • packages/worker/src/billing/subscription-sync.workers.test.ts
  • packages/worker/src/community/community-flow-test-schema.ts
  • packages/worker/src/db.ts
  • packages/worker/src/email/inbound-entitlements.workers.test.ts
  • packages/worker/src/email/inbound.ts
  • packages/worker/src/email/outbound.workers.test.ts
  • packages/worker/src/entitlements/entitlements-service.workers.test.ts
  • packages/worker/src/entitlements/entitlements.node.test.ts
  • packages/worker/src/entitlements/plans.ts
  • packages/worker/src/entitlements/service.ts
  • packages/worker/src/entitlements/test-schema.ts
  • packages/worker/src/env-schema.ts
  • packages/worker/src/index.ts
  • packages/worker/src/index.workers.test.ts
  • packages/worker/src/jobs/service.node.test.ts
  • packages/worker/src/mcp/capabilities/admin/admin-capabilities.node.test.ts
  • packages/worker/src/mcp/capabilities/admin/admin-user-usage.ts
  • packages/worker/src/mcp/capabilities/email/email-usage-get.workers.test.ts
  • packages/worker/src/mcp/capabilities/packages/save-package-entitlements.node.test.ts
  • packages/worker/src/mcp/capabilities/packages/save-package-private-visibility.node.test.ts
  • packages/worker/src/mcp/capabilities/repo/repo-open-session.node.test.ts
  • packages/worker/src/mcp/capabilities/services/service-start.node.test.ts
  • packages/worker/src/mcp/executor.node.test.ts
  • packages/worker/src/mcp/secrets/service.node.test.ts
  • packages/worker/src/package-registry/service.node.test.ts
  • packages/worker/src/package-runtime/package-workflows.node.test.ts
  • packages/worker/src/storage-runner.workers.test.ts
  • packages/worker/worker-configuration.d.ts
  • packages/worker/wrangler.jsonc

Comment thread packages/worker/src/billing/stripe-client.ts
@cursor cursor Bot changed the title Add Stripe billing, free tier, and invite-assigned plans Add Stripe billing with free/pro tiers and invite-assigned plans Jul 20, 2026
Comment thread packages/worker/src/app/account-billing-data.ts
Comment thread packages/worker/src/entitlements/plans.ts
Comment thread packages/worker/src/billing/subscription-sync.ts Outdated
…nal plan migration

- billing page always refreshes subscription state on view so a
  scheduled cancellation date is never hidden by the stored cache
- a failed plan refresh right after checkout linking no longer surfaces
  as a checkout error (page view and cron converge shortly)
- migration 0067 maps stored 'personal' plans to 'pro' so accounts
  granted the removed tier keep enforcement instead of falling back to
  legacy/unlimited

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ca0358e. Configure here.

<label mix={css(fieldCss)}>
<span mix={css(fieldLabelCss)}>Plan</span>
<select name="plan" disabled={isMutating} mix={css(inputCss)}>
<option value="">Legacy / tierless (free)</option>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Invite default mislabeled as free

Medium Severity

The create-invite plan dropdown labels the empty option as “Legacy / tierless (free)”, but an empty value stores a null invite plan, which signup applies as a null users.plan (legacy/unlimited), not the enforced free tier. Admins can pick that option believing they assigned free limits while new users remain tierless.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ca0358e. Configure here.

'missing_customer',
'The checkout session did not include a Stripe customer.',
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Checkout success skips payment check

Medium Severity

The billing success handler links stripe_customer_id after fetching a checkout session but never verifies the session is completed or paid. It only checks HMAC client_reference_id and presence of customer, so an unpaid or incomplete session can still establish linkage and trigger the “already linked to a different customer” guard on a later successful checkout.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ca0358e. Configure here.


export function parseStoredStripePlan(value: string | null | undefined) {
return parsePlanName(value)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unused exported billing helper

Low Severity

parseStoredStripePlan is exported from the new subscription sync module but nothing in the repository imports or calls it, so it is dead code left over from the billing work.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ca0358e. Configure here.

@kody-bot
kody-bot merged commit 9aede04 into main Jul 20, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/stripe-billing-and-invite-plans-5d94 branch July 20, 2026 02:34
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.

3 participants