Skip to content

feat(code): gate reset passes on usage, add refunds - #3120

Merged
smakosh merged 4 commits into
mainfrom
devpass-exhausted-allowance-promo
Jul 18, 2026
Merged

smakosh merged 4 commits into
mainfrom
devpass-exhausted-allowance-promo

Conversation

@smakosh

@smakosh smakosh commented Jul 18, 2026 •

Copy link
Copy Markdown
Member

What

Combines two related Reset Pass safeguards (this PR now includes #3119):

A Reset Pass lifts the weekly premium cap, but the unlocked spend still draws from the plan's monthly credit pool — so near or at the end of the pool a pass delivers little or nothing. This PR gates purchase/redeem on cycle usage, replaces the pass card with an upgrade/PAYG promo once the pool is fully exhausted, and adds a self-refund path for unused passes.

Cycle-usage gates (server + mirrored in UI)

  • Purchase rejected with 400 above 95% cycle usage — the card is never charged. Buy button disabled with an explanatory tooltip.
  • Redeem rejected above 90% cycle usage so a pass can't be burned against an empty pool; the pass keeps until credits renew. Redeem button disabled with tooltip.
  • Thresholds + getDevPlanCycleUsageFraction live in @llmgateway/shared, shared by API and dashboard.
  • The gated card is not a dead end: once redeeming is blocked (>90%), it shows a visible explanation and a primary CTA — upgrade to the next tier (Lite/Pro) or PAYG credits on llmgateway.io (Max) — matching the 100% promo. PostHog event devpass_reset_gate_upgrade_clicked.

100% exhausted → promo instead of passes

When the monthly allowance is fully used ($87 Lite / $237 Pro / $537 Max), the Reset Pass card is hidden entirely and replaced with a passport-style promo:

  • Lite/Pro → upgrade CTA to the next tier (/dashboard/billing), highlighting that upgrading starts a fresh billing cycle immediately with the full new allowance.
  • Max → PAYG CTA to llmgateway.io, highlighting it works with the same coding agents — just swap the API key and buy credits.
  • Usage-bar and weekly-meter copy adjusted per state; PostHog events devpass_allowance_exhausted_viewed / ..._promo_clicked.

Self-refund of unused passes (7 days)

  • dev_plan_reset_pass is now self-refundable with a 7-day window (vs 14 for plan payments), only while the tier-bound inventory still holds a pass; a redeemed pass returns pass_already_used.
  • Refund flows through the existing charge.refunded webhook bookkeeping (claws back the pass, leaves the DevPass subscription untouched); works after plan cancellation since purchased passes survive it.
  • DevPass billing history offers the refund on eligible rows with pass-specific dialog copy; ineligible rows explain why (redeemed / 7-day window expired / already refunded). Docs updated.

Testing

  • dev-plans-reset-passes.spec.ts (32), self-refund.spec.ts (30), packages/shared/dev-plans.spec.ts (8) — 70/70 passing.
  • Full pnpm build (17 tasks) + pnpm format pass.
  • All dashboard states verified live; demo videos in session (promo cases + refund cases).

https://claude.ai/code/session_011UftFFQDksranqTiWPNMBb

Summary by CodeRabbit

  • New Features
    • Added an “allowance reached” card and updated dashboard usage messaging/CTAs when monthly credits are exhausted.
    • Added cycle-usage boundary gating for Reset Pass purchase/redemption (redeem ~90%, purchase ~95%); buttons/copy adjust accordingly.
  • Bug Fixes
    • Blocked Reset Pass purchase/redemption when monthly usage is above the configured thresholds, with clearer boundary behavior.
    • Improved self-refund eligibility to reject already-used passes and updated refund UI/tooltips accordingly.
  • Documentation
    • Refreshed Reset Pass docs to describe the new monthly threshold behavior and refund timing.
  • Tests
    • Expanded coverage for cycle-usage gates and self-refund eligibility reasons.

claude and others added 2 commits July 18, 2026 12:13
Block buying a Reset Pass above 95% of the monthly cycle credit
allowance and redeeming one above 90%, since a pass restores a
weekly cap with nothing left to spend against. Purchases were
already tracked in the transaction table; unused passes can now
be self-refunded within 7 days of purchase (tier-bound inventory
must still hold the pass), leaving the DevPass plan untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E18J9zTY3MiR6SedFgY4aN
When the monthly credit allowance (the hard ceiling, not the weekly
premium cap) is exhausted, Reset Passes can't unlock anything: they
only reset the weekly premium window and never add credits. Selling
one at that point is a no-op purchase.

- API: reject reset-pass purchase with 400 when the monthly pool is
  exhausted (weekly-cap-only exhaustion still allows purchase)
- Dashboard: hide the Reset Pass card in that state and show a promo
  instead: Lite/Pro get an upgrade CTA to the next tier (upgrading
  starts a fresh cycle instantly with the full new allowance); Max
  gets a PAYG CTA to llmgateway.io highlighting that it works with
  the same coding agents via an API key swap + credits
- Weekly meter copy no longer points at the hidden pass card
- PostHog events for promo views/clicks

Claude-Session: https://claude.ai/code/session_011UftFFQDksranqTiWPNMBb
@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Reset Pass purchase and redemption now enforce monthly usage thresholds. The dashboard handles exhausted allowances with upgrade or PAYG messaging, and Reset Pass refunds now support dedicated eligibility rules and user-facing states.

Changes

Reset Pass cycle-usage controls

Layer / File(s) Summary
Shared usage contract and API gates
packages/shared/src/dev-plans.ts, packages/shared/src/index.ts, apps/api/src/routes/dev-plans.ts, apps/api/src/routes/dev-plans-reset-passes.spec.ts, packages/shared/src/dev-plans.spec.ts
Adds shared usage thresholds and blocks purchases above 95% or redemptions above 90%, with boundary and helper tests.
Allowance exhaustion dashboard flow
apps/code/src/app/dashboard/components/ResetPassCard.tsx, apps/code/src/app/dashboard/components/UsageOverview.tsx, apps/code/src/app/dashboard/components/AllowanceExhaustedCard.tsx
Applies cycle gates to Reset Pass controls and renders upgrade or PAYG content when monthly credits are exhausted.
Reset Pass refund eligibility and presentation
apps/api/src/lib/self-refund.ts, apps/api/src/lib/self-refund.spec.ts, apps/api/src/routes/organization.ts, apps/api/src/routes/dev-plans.ts, apps/code/src/app/dashboard/components/DevPassInvoices.tsx, apps/ui/src/...
Adds seven-day Reset Pass refund eligibility, pass-state checks, pass_already_used, and reset-pass-specific refund messaging.
Behavior documentation
apps/docs/content/learn/reset-passes.mdx
Documents the 90% redemption and 95% purchase restrictions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard
  participant UsageOverview
  participant ResetPassAPI
  participant OrganizationCredits
  participant Stripe
  Dashboard->>UsageOverview: Display monthly usage
  UsageOverview->>ResetPassAPI: Purchase or redeem Reset Pass
  ResetPassAPI->>OrganizationCredits: Read credits used and limit
  OrganizationCredits-->>ResetPassAPI: Return usage fraction
  ResetPassAPI->>Stripe: Create payment intent for allowed purchase
  ResetPassAPI-->>Dashboard: Return success or threshold rejection
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% 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: usage-based gating for reset passes and adding refunds.
✨ 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 devpass-exhausted-allowance-promo

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.

…ction-yc2kdk' into devpass-exhausted-allowance-promo

# Conflicts:
#	apps/api/src/routes/dev-plans-reset-passes.spec.ts
#	apps/api/src/routes/dev-plans.ts
#	apps/code/src/app/dashboard/components/UsageOverview.tsx
@smakosh smakosh changed the title feat(code): promote upgrade when usage is full feat(code): gate reset passes on usage, add refunds Jul 18, 2026

@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: 1

🧹 Nitpick comments (1)
apps/ui/src/app/dashboard/[orgId]/org/transactions/page.tsx (1)

27-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding "dev_plan_reset_pass" to the Transaction["type"] union.

You've added "pass_already_used" to the refund.reason union, but the type union (lines 7-11) does not include "dev_plan_reset_pass". Since pass_already_used is specific to reset passes, it might be beneficial to also include "dev_plan_reset_pass" in the type union for TypeScript completeness, in case this transaction type is encountered via URL navigation or an API response.

💡 Proposed addition to `type`
 	type:
 		| "credit_topup"
 		| "subscription_start"
 		| "subscription_cancel"
-		| "subscription_end";
+		| "subscription_end"
+		| "dev_plan_reset_pass";
🤖 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 `@apps/ui/src/app/dashboard/`[orgId]/org/transactions/page.tsx around lines 27
- 28, Extend the Transaction["type"] union near the existing transaction type
literals to include "dev_plan_reset_pass", keeping the new value aligned with
the related refund.reason support for reset-pass transactions.
🤖 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/docs/content/learn/reset-passes.mdx`:
- Line 64: Update the refund eligibility wording in the reset-pass documentation
to refer only to an eligible unused pass, rather than implying every unredeemed
pass purchased within seven days qualifies. Mention or link to the refund flow’s
inventory and amount eligibility checks while preserving the existing seven-day,
payment-method, passport, and DevPass plan behavior.

---

Nitpick comments:
In `@apps/ui/src/app/dashboard/`[orgId]/org/transactions/page.tsx:
- Around line 27-28: Extend the Transaction["type"] union near the existing
transaction type literals to include "dev_plan_reset_pass", keeping the new
value aligned with the related refund.reason support for reset-pass
transactions.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 21b597d6-9482-43db-acf1-a35ee479fc3a

📥 Commits

Reviewing files that changed from the base of the PR and between 6a3e23c and e29dcdf.

📒 Files selected for processing (14)
  • apps/api/src/lib/self-refund.spec.ts
  • apps/api/src/lib/self-refund.ts
  • apps/api/src/routes/dev-plans-reset-passes.spec.ts
  • apps/api/src/routes/dev-plans.ts
  • apps/api/src/routes/organization.ts
  • apps/code/src/app/dashboard/components/DevPassInvoices.tsx
  • apps/code/src/app/dashboard/components/ResetPassCard.tsx
  • apps/code/src/app/dashboard/components/UsageOverview.tsx
  • apps/docs/content/learn/reset-passes.mdx
  • apps/ui/src/app/dashboard/[orgId]/org/transactions/page.tsx
  • apps/ui/src/components/billing/transactions-client.tsx
  • packages/shared/src/dev-plans.spec.ts
  • packages/shared/src/dev-plans.ts
  • packages/shared/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/code/src/app/dashboard/components/UsageOverview.tsx

**Buy a pass** shows a confirmation dialog, then charges the payment method saved on your DevPass — no checkout redirect, no subscription change, no proration. Each purchase emails a PDF invoice and appears in the DevPass billing history alongside plan charges.
**Buy a pass** shows a confirmation dialog, then charges the payment method saved on your DevPass — no checkout redirect, no subscription change, no proration. Each purchase emails a PDF invoice and appears in the DevPass billing history alongside plan charges. Purchases are paused once you've used more than 95% of your monthly credit allowance — a pass would give you almost nothing to use at that point — and open up again when your credits renew.

An unused pass can be returned: for 7 days after purchase, a purchased pass that hasn't been redeemed can be self-refunded from the billing history. The refund goes back to your payment method, the pass is removed from your passport, and your DevPass plan itself is unaffected.

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the additional refund eligibility checks.

This wording implies every unredeemed pass within seven days is refundable, but the refund flow also applies inventory and amount checks. Describe it as an eligible unused pass and mention or link to those conditions.

Based on the refund implementation’s additional inventory and amount eligibility rules.

🤖 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 `@apps/docs/content/learn/reset-passes.mdx` at line 64, Update the refund
eligibility wording in the reset-pass documentation to refer only to an eligible
unused pass, rather than implying every unredeemed pass purchased within seven
days qualifies. Mention or link to the refund flow’s inventory and amount
eligibility checks while preserving the existing seven-day, payment-method,
passport, and DevPass plan behavior.

When the 90%/95% cycle-usage gates disable redeeming/buying, the
Reset Pass card previously offered no way forward — just disabled
buttons with hover-only tooltips. Now the gated card shows a visible
explanation line and a primary CTA: upgrade to the next tier
(Lite/Pro) or PAYG credits on llmgateway.io (Max), matching the promo
already shown at 100% exhaustion. Adds a
devpass_reset_gate_upgrade_clicked PostHog event.

Claude-Session: https://claude.ai/code/session_011UftFFQDksranqTiWPNMBb
@smakosh
smakosh added this pull request to the merge queue Jul 18, 2026
Merged via the queue into main with commit 2f7d502 Jul 18, 2026
23 checks passed
@smakosh
smakosh deleted the devpass-exhausted-allowance-promo branch July 18, 2026 13:20
steebchen added a commit that referenced this pull request Jul 19, 2026
Sync with main's Reset Pass refunds (#3120/#3124): the reset-pass
refunded-purchase attribution and the new admin devpass refund
queries only matched credit_refund rows; match subscription_refund
too since plan refunds are now recorded with that type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
smakosh pushed a commit that referenced this pull request Oct 11, 2026
Sync with main's Reset Pass refunds (#3120/#3124): the reset-pass
refunded-purchase attribution and the new admin devpass refund
queries only matched credit_refund rows; match subscription_refund
too since plan refunds are now recorded with that type.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants