Skip to content

[codex] fix credit-only billing for former subscribers#2355

Open
riderx wants to merge 6 commits into
mainfrom
codex/credit-only-billing
Open

[codex] fix credit-only billing for former subscribers#2355
riderx wants to merge 6 commits into
mainfrom
codex/credit-only-billing

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 28, 2026

Summary (AI generated)

  • Treat orgs with active usage credits and no active subscription entitlement as credit-only during plan checks.
  • Consume credits against zero included usage for credit-only orgs, so former subscribers do not keep using stale plan allowances before credits are debited.
  • Skip the immediate failed-payment Bento event when an org already has active usage credits.
  • Add a regression test for former subscribers whose usage is under the old plan limit but should still consume credits.
  • Refresh generated read-replica schema and add a scoped typo exclusion for an existing committed migration filename required by CI.

Motivation (AI generated)

Former subscribers could buy credits after stopping subscription payments, but the cron plan check still used their old Stripe product_id limits as the baseline. That meant credits were not consumed until usage exceeded the old plan, and users could still receive payment-related nudges even though they had active credits available.

Business Impact (AI generated)

This makes prepaid credits usable for returning or failed-payment customers, reduces incorrect payment emails, and preserves expected access for customers who have already bought credits.

Test Plan (AI generated)

  • bun run lint:backend
  • bun run typecheck:backend
  • bun run supabase:with-env -- bunx vitest run tests/credit-only-billing.test.ts
  • bun run supabase:with-env -- bunx vitest run tests/plugin-credits-flag.test.ts
  • bun run supabase:with-env -- bunx vitest run tests/credit-only-billing.test.ts tests/plugin-credits-flag.test.ts
  • typos .
  • bash scripts/check-supabase-migration-order.sh
  • MAIN_SUPABASE_DB_URL=postgresql://postgres:postgres@127.0.0.1:56572/postgres bun run readreplicate:check-schema
  • Commit hook typechecks: bun run cli:typecheck && bun run typecheck:backend && bun run typecheck:frontend
  • GitHub CI: Run tests, Run Playwright tests, CLI tests, CodeQL, SonarCloud, Socket, DeepSec, dead code, and CLI path checks

Generated with AI

Summary by CodeRabbit

  • Bug Fixes

    • Improved failed payment handling for organizations with active usage credits by conditionally managing payment failure tracking based on credit availability.
  • New Features

    • Enhanced billing logic to better support credit-only mode organizations with improved credit overage limit management and notification handling.
  • Tests

    • Added integration test suite for credit-only billing scenarios, validating credit consumption and overage tracking.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Warning

Review limit reached

@riderx, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 35 minutes and 24 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e6d0adf6-bdaa-4597-9f07-95d424f24a1f

📥 Commits

Reviewing files that changed from the base of the PR and between de78115 and 98033b4.

📒 Files selected for processing (1)
  • tests/credit-only-billing.test.ts
📝 Walkthrough

Walkthrough

This PR implements credit-only billing mode for organizations, enabling conditional handling of plan overages and failed payment events based on active Stripe subscription status. It adds entitlement detection helpers, extends org billing data queries, updates plan checking to enforce zero-limit overages when credit-only, and conditionally emits failed payment events only for organizations without active usage credits.

Changes

Credit-only Billing Implementation

Layer / File(s) Summary
Credit-only entitlement types and helpers
supabase/functions/_backend/utils/plans.ts
New TypeScript interfaces StripeInfoForPlanCheck and OrgWithCustomerInfo type Stripe subscription status, trial, and anchor fields. Helper functions isFutureTimestamp, hasActivePlanEntitlement, and isCreditOnlyBillingOrg determine whether an org should operate in credit-only mode based on active subscription or trial status.
Org billing data and plan check updates
supabase/functions/_backend/utils/plans.ts
getOrgWithCustomerInfo now fetches has_usage_credits and additional Stripe fields (trial_at, subscription_anchor_start, subscription_anchor_end). userAbovePlan accepts expanded org billing fields and a forceCreditMode parameter, derives creditOnlyMode, and forces all per-metric limits to 0 when in credit-only mode for overage computation.
Organization event and notification handling
supabase/functions/_backend/utils/plans.ts, supabase/functions/_backend/triggers/stripe_event.ts
handleOrgNotificationsAndEvents adds a dedicated code path for onboarded credit-only orgs. stripe_event.ts introduces orgHasActiveUsageCredits helper to query org credits status and conditionally logs a skip message or emits org:failed_payment event depending on credit availability.
Credit-only billing integration test
tests/credit-only-billing.test.ts
New Vitest suite validates the credit-only billing scenario: seeds initial app/org/customer setup, simulates a failed subscription with prior MAU usage and credit grants, triggers the cron stats endpoint, and asserts that credits are consumed and overage events for MAU include expected prior limit/usage values.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Cap-go/capgo#2059: Both PRs center on the orgs.has_usage_credits flag: this PR reads it to alter Stripe "failed payment"/credit-only entitlement logic, while the related PR adds the trigger-backed computation of that same flag and tests endpoints gated by it.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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
Title check ✅ Passed The title accurately and concisely describes the main purpose of the changeset—fixing credit-only billing for former subscribers.
Description check ✅ Passed The description includes a comprehensive summary, motivation, business impact, and detailed test plan with checkmarks showing all tests passed, meeting the template requirements.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 28, 2026

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing codex/credit-only-billing (98033b4) with main (4fec358)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx marked this pull request as ready for review May 28, 2026 16:46
@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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@tests/credit-only-billing.test.ts`:
- Around line 4-10: Replace direct use of BASE_URL with the test helper
getEndpointUrl(path) so the test routes through the harness's environment-based
backend routing; locate occurrences of BASE_URL in
tests/credit-only-billing.test.ts (including the referenced spot and line ~85)
and change calls that build endpoint strings (e.g., `${BASE_URL}/...`) to use
getEndpointUrl('/...') instead, preserving the same path and any query params or
headers passed to executeSQL/fetchWithRetry.
- Line 43: The test "consumes credits for a former subscriber even when usage is
under the old plan limit" is using it(...) but per repository policy tests in
tests/**/*.test.ts must be run in parallel; change the call to
it.concurrent(...) for that test case so it runs concurrently (locate the
anonymous test with the exact title string in tests/credit-only-billing.test.ts
and replace the it(...) invocation with it.concurrent(...)).
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fcf33288-82fe-488e-9370-ce94069b351e

📥 Commits

Reviewing files that changed from the base of the PR and between 4fec358 and de78115.

📒 Files selected for processing (3)
  • supabase/functions/_backend/triggers/stripe_event.ts
  • supabase/functions/_backend/utils/plans.ts
  • tests/credit-only-billing.test.ts

Comment thread tests/credit-only-billing.test.ts Outdated
Comment thread tests/credit-only-billing.test.ts Outdated
@riderx riderx deployed to deepsec-pr May 28, 2026 16:51 — with GitHub Actions Active
Copy link
Copy Markdown

@42Clownfish 42Clownfish left a comment

Choose a reason for hiding this comment

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

Reviewed the credit-only billing change. No blocker from my pass.

What I checked:

  • The entitlement split is conservative: trial time must be in the future, succeeded subscriptions remain plan-entitled, and failed/former subscriptions with active usage credits move into the zero-included-usage credit path.
  • userAbovePlan only forces limits to zero in credit-only mode, so active subscribers keep the normal plan limits while former subscribers consume prepaid credits against actual usage.
  • The failed-payment webhook path now skips the Bento failed-payment event only when the org has active usage credits; database status update still runs afterward.
  • The new regression test covers the important case: a former subscriber under the old MAU plan limit still gets a credit consumption and overage event with limit: 0.
  • CodeRabbit's earlier test-harness comments appear addressed in the latest commit, and current checks are green.

Residual risk I would keep in mind after merge: this relies on orgs.has_usage_credits being fresh when the Stripe failed-payment event arrives. If the computed flag can lag behind a just-created credit grant, the email suppression path could still fire once. I do not see that as a blocker for this PR, but it is the operational edge I would monitor.

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