Repository navigation
fix(devpass): keep billing usable after plan ends - #3445
Conversation
When a DevPass subscription ended, the dashboard shell replaced every page with the plan chooser, so past subscribers lost access to their invoices, invoice PDFs, Reset Pass self-refunds and billing details. A self-refund made it worse: it cancels immediately, bouncing the user off the very page they just used. The shell now keeps its chrome for anyone who has ever been billed and for the billing/profile/settings routes, and only swaps the usage overview for the plan chooser. Billing renders a "No active plan" state that drops the controls needing a live subscription (payment method, tier changes, cancel/resume) and keeps invoices and billing details. Settings hides the routing/service-tier section, whose endpoint rejects an inactive plan. /dev-plans/status gains hasBillingHistory, since ending a plan clears every devPlan* column and the invoices are the only lasting record of a past subscriber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
WalkthroughThe ChangesDevPass billing history
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant DashboardShell
participant DevPlansStatus
participant BillingClient
DashboardShell->>DevPlansStatus: Request plan status
DevPlansStatus-->>DashboardShell: Return devPlan and hasBillingHistory
DashboardShell->>BillingClient: Render billing route or plan chooser
BillingClient-->>DashboardShell: Display billing history and plan controls by status
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@apps/api/src/routes/dev-plans.spec.ts`:
- Around line 1179-1187: Add pending and failed dev_plan_start transaction cases
to the getStatus test coverage, asserting each returns hasBillingHistory: false.
Keep the existing completed non-invoice bookkeeping case, and use the same
transaction setup and getStatus helper to verify non-completed invoice
transactions are excluded.
In `@apps/api/src/routes/dev-plans.ts`:
- Around line 1821-1827: Update the billing history query in
apps/api/src/routes/dev-plans.ts lines 1821-1827 to require status "completed"
alongside DEV_PLAN_INVOICE_TYPES, so only successful invoice payments count. Add
pending and failed invoice-type cases in apps/api/src/routes/dev-plans.spec.ts
lines 1179-1187 and assert both return hasBillingHistory: false.
🪄 Autofix
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: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: caff730b-db97-4df3-ac35-503f334319d5
📒 Files selected for processing (5)
apps/api/src/routes/dev-plans.spec.tsapps/api/src/routes/dev-plans.tsapps/code/src/app/dashboard/(main)/billing/BillingClient.tsxapps/code/src/app/dashboard/(main)/settings/page.tsxapps/code/src/app/dashboard/DashboardShell.tsx
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe23bccc85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const billingTransaction = await db.query.transaction.findFirst({ | ||
| where: { | ||
| organizationId: { eq: personalOrg.id }, | ||
| type: { in: [...DEV_PLAN_INVOICE_TYPES] }, |
There was a problem hiding this comment.
Include legacy DevPass payments in history
Because /dev-plans/status now decides whether an inactive user keeps dashboard chrome by checking only DEV_PLAN_INVOICE_TYPES, it misses pre-rename DevPass purchases recorded as subscription_start (documented in apps/api/src/utils/devpass-filter.ts:64). For any former DevPass subscriber whose only payment is one of those legacy rows and whose subscription has since been cleared to devPlan: "none", hasBillingHistory stays false, so they still get the full-width plan chooser instead of the sidebar/billing/settings access this change is meant to restore. Include the legacy DevPass payment rows in the history check; the query is already scoped to the user's devpass org.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 1b846c8 — good catch. hasBillingHistory now matches DEV_PLAN_INVOICE_TYPES plus LEGACY_DEV_PLAN_TX_TYPES; the query is already scoped to the user's devpass-kind personal org, which is the pairing that constant's own doc comment requires. Added a spec covering a legacy subscription_start row on an ended plan.
Dev plans were billed as `subscription_*` before the DevPass rename, and admin revenue still counts those rows on devpass-kind orgs as DevPass revenue. A past subscriber whose only payment is a legacy row reported no billing history, so the dashboard still locked them out of billing. Status stays unfiltered on purpose: every dev-plan invoice row is written `completed` today, and the invoice list renders pending/failed charges with a status badge — filtering here would hide a page that has content. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Problem
When a DevPass subscription ends, the Stripe
subscription.deletedhandler resets everydevPlan*column on the org, includingdevPlan: "none". The dashboard shell gated on exactly that (hasActivePlan) and replaced all page content with the plan chooser — so a past subscriber could no longer reach:A self-refund made it sharper: refunding a plan payment cancels the subscription immediately, so the user was bounced off the very page they had just used.
Approach
DashboardShell): keep the sidebar chrome for anyone who has ever been billed, and for the billing/profile/settings routes themselves. Only the usage overview is swapped for the plan chooser, which now renders inside the content area with the nav item relabelled "Plans". Brand-new visitors (no plan, no billing history) still land on the focused, full-width chooser, so the signup funnel is unchanged.PATCH /dev-plans/settingsreturns 400 without an active plan. Account details and delete-account stay./dev-plans/statusgainshasBillingHistory. Ending a plan clears everydevPlan*column, so the billing transactions are the only lasting record that the user was ever a subscriber — one indexedfindFirstontransaction.Two deliberate calls on how
hasBillingHistoryis defined, both raised in review:DEV_PLAN_INVOICE_TYPESplusLEGACY_DEV_PLAN_TX_TYPES. Dev plans were billed assubscription_*before the DevPass rename, and admin revenue still counts those rows on devpass-kind orgs as DevPass revenue; the query is already scoped to the devpass personal org, which is the pairing that constant requires.completedtoday, andGET /dev-plans/invoicesrenders pending/failed charges with a status badge — so filtering here would hide a page that has content.hasBillingHistoryanswers "does the billing page have something to show", so it mirrors the invoice list.Refund eligibility itself is unchanged: plan payments correctly report
plan_inactiveonce the subscription is gone (there is nothing left to cancel), while unused Reset Passes remain refundable, which is exactly why the page has to stay reachable.Verification
apps/api/src/routes/dev-plans.spec.tscoverhasBillingHistory: false before the first charge, true after a plan payment, still true after the plan ends, true for a legacysubscription_startrow and for an unsettled charge, and unaffected by non-invoice bookkeeping rows (dev_plan_cancel). Full file: 26 passed.pnpm format,pnpm lint,pnpm buildall clean.devPlanforced tonone:Billing after the plan ended — invoices, receipts and refunds still reachable
Usage tab becomes the plan chooser, nav still exposes Billing
🤖 Generated with Claude Code