Skip to content

fix(devpass): keep billing usable after plan ends - #3445

Merged
steebchen merged 3 commits into
mainfrom
atlanta-v2
Aug 7, 2026
Merged

steebchen merged 3 commits into
mainfrom
atlanta-v2

Conversation

@steebchen

@steebchen steebchen commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Problem

When a DevPass subscription ends, the Stripe subscription.deleted handler resets every devPlan* column on the org, including devPlan: "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:

  • past invoices and their PDF receipts
  • self-refunds (an unused Reset Pass stays refundable for 7 days after the plan is gone)
  • billing details used on those invoices
  • account settings / delete account

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

  • Shell (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.
  • Billing page: renders a "No active plan" state that drops everything requiring a live subscription (payment method, tier changes, cancel/resume — those endpoints reject an inactive plan) and keeps invoices and billing details.
  • Settings page: hides the routing / service-tier section, whose PATCH /dev-plans/settings returns 400 without an active plan. Account details and delete-account stay.
  • API: /dev-plans/status gains hasBillingHistory. Ending a plan clears every devPlan* column, so the billing transactions are the only lasting record that the user was ever a subscriber — one indexed findFirst on transaction.

Two deliberate calls on how hasBillingHistory is defined, both raised in review:

  • It matches DEV_PLAN_INVOICE_TYPES plus LEGACY_DEV_PLAN_TX_TYPES. Dev plans were billed as subscription_* 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.
  • Transaction status is not filtered. Every dev-plan invoice row is written completed today, and GET /dev-plans/invoices renders pending/failed charges with a status badge — so filtering here would hide a page that has content. hasBillingHistory answers "does the billing page have something to show", so it mirrors the invoice list.

Refund eligibility itself is unchanged: plan payments correctly report plan_inactive once 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

  • New specs in apps/api/src/routes/dev-plans.spec.ts cover hasBillingHistory: false before the first charge, true after a plan payment, still true after the plan ends, true for a legacy subscription_start row and for an unsettled charge, and unaffected by non-invoice bookkeeping rows (dev_plan_cancel). Full file: 26 passed.
  • pnpm format, pnpm lint, pnpm build all clean.
  • Driven locally against a seeded DevPass org with devPlan forced to none:

Billing after the plan ended — invoices, receipts and refunds still reachable

Billing page with no active plan: sidebar intact, No active plan card, invoices with Invoice/Refund actions, billing details

Usage tab becomes the plan chooser, nav still exposes Billing

Dashboard with no active plan: sidebar shows Plans/Billing/Profile/Settings with the plan chooser in the content area

🤖 Generated with Claude Code

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>
Copilot AI lite review requested due to automatic review settings August 6, 2026 21:16

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The /dev-plans/status endpoint now reports persistent billing history from qualifying invoice transactions. The dashboard uses plan status and billing history to control navigation, plan selection, billing content, and settings visibility.

Changes

DevPass billing history

Layer / File(s) Summary
Billing history status API
apps/api/src/routes/dev-plans.ts, apps/api/src/routes/dev-plans.spec.ts
The status endpoint returns hasBillingHistory based on qualifying invoice transactions. Tests cover active, ended, unpaid, and bookkeeping-only states.
Plan-aware dashboard access
apps/code/src/app/dashboard/DashboardShell.tsx
The dashboard keeps billing, profile, and settings routes available without an active plan. It updates navigation and plan chooser headings using billing history.
Billing and settings views
apps/code/src/app/dashboard/(main)/billing/BillingClient.tsx, apps/code/src/app/dashboard/(main)/settings/page.tsx
Billing displays history, invoices, and details without live-plan controls. Settings hides DevPlanSettings when no plan is active.

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
Loading

Possibly related PRs

Suggested reviewers: copilot, smakosh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 and concisely describes the main change: preserving billing access after a DevPass plan ends.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch atlanta-v2

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
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between af282cf and fe23bcc.

📒 Files selected for processing (5)
  • apps/api/src/routes/dev-plans.spec.ts
  • apps/api/src/routes/dev-plans.ts
  • apps/code/src/app/dashboard/(main)/billing/BillingClient.tsx
  • apps/code/src/app/dashboard/(main)/settings/page.tsx
  • apps/code/src/app/dashboard/DashboardShell.tsx

Comment thread apps/api/src/routes/dev-plans.spec.ts
Comment thread apps/api/src/routes/dev-plans.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 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".

Comment thread apps/api/src/routes/dev-plans.ts Outdated
const billingTransaction = await db.query.transaction.findFirst({
where: {
organizationId: { eq: personalOrg.id },
type: { in: [...DEV_PLAN_INVOICE_TYPES] },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

steebchen and others added 2 commits August 7, 2026 15:17
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>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@steebchen
steebchen added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit a6d70ba Aug 7, 2026
12 checks passed
@steebchen
steebchen deleted the atlanta-v2 branch August 7, 2026 14:02
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.

2 participants