Skip to content

Freeze subscription credits during payment recovery#957

Merged
ross0x01 merged 1 commit into
mainfrom
codex/freeze-past-due-credits
Jul 24, 2026
Merged

Freeze subscription credits during payment recovery#957
ross0x01 merged 1 commit into
mainfrom
codex/freeze-past-due-credits

Conversation

@ross0x01

@ross0x01 ross0x01 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • freeze each paid monthly bucket at its remaining balance when a refreshed subscription-cycle invoice fails and the subscription is past_due
  • store Stripe subscription, invoice, and transition time in the bucket so duplicate and out-of-order payment events cannot mint credits
  • atomically restore the full paid allocation only after a newer invoice.paid; leave the webhook unprocessed when Redis cannot persist the transition so Stripe retries
  • add structured delinquency/stale-transition logs and regression coverage

Revenue behavior

  • users keep only credits earned from the already-paid cycle during Stripe recovery; the failed renewal does not create a new included allocation
  • existing prepaid extra-usage credit remains spendable
  • a successful recovery payment starts the new allocation exactly once

Validation

  • corepack pnpm jest --runInBand (296 suites, 2,933 tests)
  • corepack pnpm typecheck
  • corepack pnpm lint (passes with 5 existing frontend warnings)
  • corepack pnpm exec prettier --check app/api/subscription/webhook/route.ts app/api/subscription/webhook/__tests__/route.test.ts lib/rate-limit/token-bucket.ts lib/rate-limit/index.ts lib/rate-limit/__tests__/token-bucket.integration.test.ts
  • parsed both embedded Redis scripts with the local Lua interpreter
  • corepack pnpm exec next build --webpack

Manual verification

  1. In Stripe test mode, consume part of a paid test account allowance, then fail a subscription-cycle renewal. Confirm the subscription can remain in its recovery state but the included balance does not refill.
  2. Retry the same failure webhook and send an older payment event. Confirm neither changes the held balance.
  3. Pay the failed invoice. Confirm the new paid allocation is restored once, and replaying invoice.paid does not refill it again.

Summary by CodeRabbit

  • New Features

    • Added automatic credit holds when subscription renewals become past due.
    • Added credit restoration after successful invoice payments.
    • Improved handling of duplicate, stale, and out-of-order billing events to prevent incorrect credit changes.
    • Added billing event timing support for more accurate credit transitions.
  • Bug Fixes

    • Prevented stale payment failures from freezing credits.
    • Ensured failed credit holds do not incorrectly mark webhook events as processed.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hackerai Ready Ready Preview, Comment Jul 24, 2026 2:28am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f4fdf30-af26-4460-94ee-b8177009c597

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd0b8a and 9d60f67.

📒 Files selected for processing (5)
  • app/api/subscription/webhook/__tests__/route.test.ts
  • app/api/subscription/webhook/route.ts
  • lib/rate-limit/__tests__/token-bucket.integration.test.ts
  • lib/rate-limit/index.ts
  • lib/rate-limit/token-bucket.ts

📝 Walkthrough

Walkthrough

The PR adds atomic Redis transitions for delinquency credit freezes and paid-cycle bucket resets, integrates them into Stripe subscription webhooks with event timestamps and outcome logging, and expands unit and integration coverage for idempotency, stale events, and failure handling.

Changes

Billing credit transitions

Layer / File(s) Summary
Atomic token-bucket transitions
lib/rate-limit/token-bucket.ts, lib/rate-limit/index.ts
Adds billing transition types, Redis Lua scripts, TTL handling, and exported functions for delinquency freezes and paid-cycle resets.
Transition integration coverage
lib/rate-limit/__tests__/token-bucket.integration.test.ts
Tests allocation preservation, Redis arguments, outcome mapping, idempotency, limiter bypasses, and error propagation.
Webhook freeze and reset integration
app/api/subscription/webhook/route.ts
Passes Stripe event timestamps, performs per-user paid resets, freezes qualifying past_due renewals, and logs transition outcomes.
Webhook behavior coverage
app/api/subscription/webhook/__tests__/route.test.ts
Updates mocks and reset expectations, parameterizes failure events, and covers freeze, stale-event, analytics, and credit-hold failure behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Stripe
  participant Webhook
  participant RateLimit
  participant Redis
  participant PostHog
  Stripe->>Webhook: invoice.payment_failed
  Webhook->>RateLimit: freezeRateLimitBucketForDelinquency
  RateLimit->>Redis: execute delinquency freeze script
  Redis-->>RateLimit: transition outcome
  Webhook->>PostHog: log processed or stale outcome
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: freezing subscription credits during payment recovery after failed renewals.
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.
✨ 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 codex/freeze-past-due-credits

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

@ross0x01
ross0x01 merged commit 96a0ae1 into main Jul 24, 2026
5 checks passed
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.

1 participant