Skip to content

fix: disable auto top-up after 7 days - #1918

Merged
steebchen merged 6 commits into
mainfrom
disable-failed-autotopup
Mar 29, 2026
Merged

steebchen merged 6 commits into
mainfrom
disable-failed-autotopup

Conversation

@steebchen

@steebchen steebchen commented Mar 28, 2026 •

Copy link
Copy Markdown
Member

Summary

  • track when an org's payment failure streak started
  • disable auto top-up in the worker after 7 days of failed auto top-up attempts
  • reset failure tracking after a successful top-up or when auto top-up is manually re-enabled

Validation

  • pnpm exec vitest run apps/worker/src/worker.spec.ts --no-file-parallelism
  • pnpm build
  • pnpm test:unit (fails in unrelated existing suites, including apps/gateway/src/videos/videos.spec.ts and apps/api/src/routes/beacon.spec.ts in this workspace)

Summary by CodeRabbit

Release Notes

  • New Features

    • Auto top-up now automatically disables after 7 days of consecutive payment failures.
    • Enhanced audit logging separates payment-related changes from general organization updates for clearer tracking.
  • Bug Fixes

    • Payment failure counters and timestamps now properly reset when auto top-up is re-enabled.

Copilot AI review requested due to automatic review settings March 28, 2026 20:06
@coderabbitai

coderabbitai Bot commented Mar 28, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bb63afad-6164-4afa-9c78-aeef9b54c921

📥 Commits

Reviewing files that changed from the base of the PR and between fc9a58e and 3f18768.

⛔ Files ignored due to path filters (4)
  • apps/code/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/playground/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/ui/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • ee/admin/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
📒 Files selected for processing (6)
  • apps/worker/src/worker.spec.ts
  • apps/worker/src/worker.ts
  • packages/db/migrations/1774784729_hard_maximus.sql
  • packages/db/migrations/meta/1774784729_snapshot.json
  • packages/db/migrations/meta/_journal.json
  • packages/db/src/schema.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/db/migrations/meta/_journal.json
  • packages/db/migrations/1774784729_hard_maximus.sql
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/db/src/schema.ts
  • apps/worker/src/worker.ts
  • apps/worker/src/worker.spec.ts

Walkthrough

The changes implement automatic disablement of auto top-ups after 7 days of persistent payment failures. A new paymentFailureStartedAt timestamp tracks failure initiation, and the worker process disables auto top-ups when this threshold is exceeded. Payment failure metrics are reset upon successful payments or re-enabling auto top-ups. Audit logging differentiates between organization.update and payment.auto_topup.* events.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
packages/db/src/schema.ts, packages/db/migrations/1774784729_hard_maximus.sql, packages/db/migrations/meta/_journal.json
Added paymentFailureStartedAt nullable timestamp column to organization table and new audit actions payment.auto_topup.update/payment.auto_topup.disable.
Type Definitions
packages/db/src/types.ts
Updated SerializedOrganization to exclude paymentFailureStartedAt from serialized shape.
API Route Handling
apps/api/src/routes/organization.ts
Modified update handler to reset payment failure fields when enabling auto top-up, segregate audit events (separate payment.auto_topup.update for auto-topup field changes), and use stringified value comparison for threshold/amount detection.
API Route Tests
apps/api/src/routes/organization.spec.ts
Added test suite verifying PATCH /orgs/{id} produces correct audit log entries for auto-topup and organization field updates with expected metadata changes.
Payment Handling
apps/api/src/stripe.ts
Reset paymentFailureStartedAt alongside failure counters on successful top-ups/payment intents; initialize paymentFailureStartedAt on failed intents.
Worker Auto-topup Processing
apps/worker/src/worker.ts
Exported processAutoTopUp function; added 7-day disable threshold with early-exit logic that disables auto-topup, resets failure metrics, and emits payment.auto_topup.disable audit events.
Worker Tests
apps/worker/src/worker.spec.ts
Restructured database cleanup; added processAutoTopUp test suite covering 7-day failure threshold scenarios and auto-topup disablement with audit log assertions.
Gateway Rate-limit Tests
apps/gateway/src/lib/rate-limit.spec.ts
Extended test fixtures to include paymentFailureStartedAt: null field in mock organization data.
Testing Utilities
apps/api/src/testing.ts
Updated deleteAll() to clear auditLog table alongside other cleanup operations.

Sequence Diagram

sequenceDiagram
    participant Worker as Worker Process
    participant Org as Organization DB
    participant AuditLog as Audit Log
    
    Worker->>Org: Query organizations with paymentFailureStartedAt set
    Org-->>Worker: Return orgs in failure state
    
    Worker->>Worker: Check if (now - paymentFailureStartedAt) >= 7 days
    
    alt Failure Threshold Exceeded
        Worker->>Org: Disable autoTopUpEnabled, reset failure counts/timestamps
        Worker->>AuditLog: Create payment.auto_topup.disable entry with failure metadata
        AuditLog-->>Worker: Audit logged
        Worker->>Worker: Emit warning, continue to next org
    else Within 7-Day Window
        Worker->>Worker: Preserve failure state, process auto-topup normally
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • rcogal
  • smakosh
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly describes the main change: automatically disabling auto top-up after 7 days of payment failures, which is the core objective of this PR.

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

✨ 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 disable-failed-autotopup

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 and usage tips.

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

ℹ️ 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 packages/db/src/schema.ts
referralEarnings: decimal().notNull().default("0"),
paymentFailureCount: integer().notNull().default(0),
lastPaymentFailureAt: timestamp(),
paymentFailureStartedAt: timestamp(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add migration for paymentFailureStartedAt column

This patch adds paymentFailureStartedAt to the Drizzle schema, but no new file under packages/db/migrations/ is included to actually add payment_failure_started_at in existing databases. In environments that rely on migration files (e.g., runMigrations() in packages/db/src/migrate.ts, invoked from apps/api/src/serve.ts when RUN_MIGRATIONS=true), the new worker/API queries in this commit will hit column does not exist at runtime when reading/updating organization records.

Useful? React with 👍 / 👎.

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.

Pull request overview

Adds “payment failure streak start” tracking and uses it to automatically disable auto top-up after 7 days of continued payment failures, with resets on successful top-up or manual re-enable.

Changes:

  • Add paymentFailureStartedAt to the organization schema/type surface.
  • Track/reset paymentFailureStartedAt in Stripe webhook handling and org updates.
  • Update the worker’s auto top-up loop to disable auto top-up after 7 days of failures, and add worker test coverage for this behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/db/src/types.ts Excludes paymentFailureStartedAt from SerializedOrganization like other internal billing fields.
packages/db/src/schema.ts Adds paymentFailureStartedAt column to organization.
apps/worker/src/worker.ts Disables auto top-up after 7 days since paymentFailureStartedAt; exports processAutoTopUp for tests.
apps/worker/src/worker.spec.ts Adds tests covering the 7-day disable behavior and non-disable behavior before 7 days.
apps/gateway/src/lib/rate-limit.spec.ts Updates mocked organization objects to include paymentFailureStartedAt.
apps/api/src/stripe.ts Resets paymentFailureStartedAt on success; sets it on payment failures.
apps/api/src/routes/organization.ts Resets failure tracking when auto top-up is manually re-enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/db/src/schema.ts
Comment on lines 148 to +151
referralEarnings: decimal().notNull().default("0"),
paymentFailureCount: integer().notNull().default(0),
lastPaymentFailureAt: timestamp(),
paymentFailureStartedAt: timestamp(),

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

The schema adds organization.paymentFailureStartedAt, and application code now reads/writes this column (worker + Stripe webhook). There is no corresponding SQL migration in packages/db/migrations, so deployments will fail at runtime with "column does not exist" errors. Add a migration that adds this column (and consider backfilling it for existing orgs with paymentFailureCount > 0).

Copilot uses AI. Check for mistakes.
Comment thread apps/api/src/stripe.ts
Comment on lines 1032 to 1042
@@ -1035,6 +1038,7 @@ async function handlePaymentIntentFailed(
.set({
paymentFailureCount: newFailureCount,
lastPaymentFailureAt: new Date(),
paymentFailureStartedAt: failureStartedAt,
})

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

failureStartedAt currently falls back to new Date() when paymentFailureStartedAt is null. For existing rows (new column) that already have paymentFailureCount > 0/lastPaymentFailureAt populated, this effectively “restarts” the streak on the next failure and can delay auto top-up disabling. Consider falling back to organization.lastPaymentFailureAt (if present) before new Date() so the streak start is preserved as best as possible for pre-existing data.

Copilot uses AI. Check for mistakes.
Comment thread apps/worker/src/worker.ts
Comment on lines +221 to +223
if (
org.paymentFailureStartedAt &&
Date.now() - org.paymentFailureStartedAt.getTime() >=

Copilot AI Mar 28, 2026

Copy link

Choose a reason for hiding this comment

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

The disable-after-7-days check relies solely on org.paymentFailureStartedAt. For existing organizations that already have paymentFailureCount > 0/lastPaymentFailureAt set (from before this column existed), paymentFailureStartedAt will be null and auto top-up will never be disabled until another failure occurs. Consider initializing/backfilling paymentFailureStartedAt (e.g., from lastPaymentFailureAt) when it’s missing but failures are present, or ensure the migration backfills it.

Suggested change
if (
org.paymentFailureStartedAt &&
Date.now() - org.paymentFailureStartedAt.getTime() >=
// Backfill paymentFailureStartedAt for legacy organizations if needed
let paymentFailureStartedAt = org.paymentFailureStartedAt;
if (
!paymentFailureStartedAt &&
org.paymentFailureCount > 0 &&
org.lastPaymentFailureAt
) {
await db
.update(tables.organization)
.set({
paymentFailureStartedAt: org.lastPaymentFailureAt,
})
.where(eq(tables.organization.id, org.id));
paymentFailureStartedAt = org.lastPaymentFailureAt;
}
if (
paymentFailureStartedAt &&
Date.now() - paymentFailureStartedAt.getTime() >=

Copilot uses AI. Check for mistakes.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/worker/src/worker.spec.ts`:
- Line 128: Wrap the multiplication expression used for the date math in the
paymentFailureStartedAt assignment (and the similar expression around line 167)
with parentheses so the subtraction and multiplication operators are explicitly
grouped; for example, change new Date(Date.now() - 8 * 24 * 60 * 60 * 1000) to
use new Date(Date.now() - (8 * 24 * 60 * 60 * 1000)) to satisfy the
no-mixed-operators rule and clarify precedence for the paymentFailureStartedAt
value.
🪄 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: c193f849-b5fc-4e6d-b4fc-35d77b4bedbc

📥 Commits

Reviewing files that changed from the base of the PR and between 4e6335d and f65e3b3.

📒 Files selected for processing (7)
  • apps/api/src/routes/organization.ts
  • apps/api/src/stripe.ts
  • apps/gateway/src/lib/rate-limit.spec.ts
  • apps/worker/src/worker.spec.ts
  • apps/worker/src/worker.ts
  • packages/db/src/schema.ts
  • packages/db/src/types.ts

Comment thread apps/worker/src/worker.spec.ts Outdated

@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: 154efd912a

ℹ️ 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".

@@ -0,0 +1 @@
ALTER TABLE "organization" ADD COLUMN "payment_failure_started_at" timestamp;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Register this migration in the Drizzle journal

Adding 1774728743_solid_randall.sql alone is not enough for this repo’s migration flow: in this commit, packages/db/migrations/meta/_journal.json still ends at 1774721979_left_mimic, so the new payment_failure_started_at migration is not tracked/applied on upgraded environments. That leaves the new worker/API updates touching organization.paymentFailureStartedAt vulnerable to column does not exist failures after deploy.

Useful? React with 👍 / 👎.

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

ℹ️ 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/stripe.ts
// Calculate new failure count and check if we should send an email
const previousFailureCount = organization.paymentFailureCount ?? 0;
const previousFailureAt = organization.lastPaymentFailureAt;
const failureStartedAt = organization.paymentFailureStartedAt ?? new Date();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Scope failure streak to auto top-up intents

paymentFailureStartedAt is initialized for every payment_intent.payment_failed event, including non-auto-top-up failures (e.g., manual top-ups or subscription-related payment intents resolved via customer lookup). With this commit’s worker logic (apps/worker/src/worker.ts) disabling auto top-up after 7 days based on this field, unrelated payment failures can now shut off auto top-up for an organization even when auto top-up itself hasn’t been failing. Restrict setting this streak start (and the associated counters used for disablement) to verified auto top-up failures only (such as events carrying the auto top-up transaction metadata).

Useful? React with 👍 / 👎.

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

ℹ️ 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/stripe.ts
// Calculate new failure count and check if we should send an email
const previousFailureCount = organization.paymentFailureCount ?? 0;
const previousFailureAt = organization.lastPaymentFailureAt;
const failureStartedAt = organization.paymentFailureStartedAt ?? new Date();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict failure streak updates to auto top-up intents

This assignment updates the 7-day disable timer for every payment_intent.payment_failed event that resolves an organization, not just failed auto top-ups. Fresh evidence in this commit: the transactionId-missing path explicitly handles manual/non-auto payments ("for manual top-ups or payments without transactionId") and still flows into this unconditional paymentFailureStartedAt update, while processAutoTopUp now disables auto top-up once that timestamp is 7 days old. A failed non-auto payment can therefore disable auto top-up even when auto top-up itself was healthy.

Useful? React with 👍 / 👎.

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/routes/organization.spec.ts`:
- Around line 99-106: The test currently uses toMatchObject which allows extra
fields; change the assertion on orgAuditLogs[0]?.metadata to a strict equality
check so no extra keys (e.g., auto-top-up) can sneak into organization.update.
Replace the toMatchObject call with an exact deep equality (e.g.,
expect(orgAuditLogs[0]?.metadata).toEqual({ changes: { name: { old: "Test
Organization", new: "Renamed Organization" } } })) or equivalent strict check,
ensuring you reference the metadata -> changes -> name structure exactly.
🪄 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: 78f1f1a3-4259-4282-909b-a934353fd3d0

📥 Commits

Reviewing files that changed from the base of the PR and between cfa8653 and fc9a58e.

⛔ Files ignored due to path filters (4)
  • apps/code/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/playground/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/ui/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • ee/admin/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
📒 Files selected for processing (6)
  • apps/api/src/routes/organization.spec.ts
  • apps/api/src/routes/organization.ts
  • apps/api/src/testing.ts
  • apps/worker/src/worker.spec.ts
  • apps/worker/src/worker.ts
  • packages/db/src/schema.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/db/src/schema.ts
  • apps/api/src/routes/organization.ts
  • apps/worker/src/worker.spec.ts
  • apps/worker/src/worker.ts

Comment on lines +99 to +106
expect(orgAuditLogs[0]?.metadata).toMatchObject({
changes: {
name: {
old: "Test Organization",
new: "Renamed Organization",
},
},
});

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.

⚠️ Potential issue | 🟡 Minor

Assert exact organization.update change keys to enforce separation.

Line 99 currently uses toMatchObject, which permits extra fields. A regression where auto-top-up fields leak into organization.update would still pass.

🔧 Tighten the assertion
-		expect(orgAuditLogs[0]?.metadata).toMatchObject({
-			changes: {
-				name: {
-					old: "Test Organization",
-					new: "Renamed Organization",
-				},
-			},
-		});
+		const orgChanges = (orgAuditLogs[0]?.metadata as {
+			changes: Record<string, unknown>;
+		}).changes;
+		expect(Object.keys(orgChanges)).toEqual(["name"]);
+		expect(orgChanges).toMatchObject({
+			name: {
+				old: "Test Organization",
+				new: "Renamed Organization",
+			},
+		});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/routes/organization.spec.ts` around lines 99 - 106, The test
currently uses toMatchObject which allows extra fields; change the assertion on
orgAuditLogs[0]?.metadata to a strict equality check so no extra keys (e.g.,
auto-top-up) can sneak into organization.update. Replace the toMatchObject call
with an exact deep equality (e.g., expect(orgAuditLogs[0]?.metadata).toEqual({
changes: { name: { old: "Test Organization", new: "Renamed Organization" } } }))
or equivalent strict check, ensuring you reference the metadata -> changes ->
name structure exactly.

@steebchen
steebchen added this pull request to the merge queue Mar 29, 2026
@steebchen
steebchen removed this pull request from the merge queue due to a manual request Mar 29, 2026
@steebchen
steebchen merged commit a7e7682 into main Mar 29, 2026
16 of 17 checks passed
@steebchen
steebchen deleted the disable-failed-autotopup branch March 29, 2026 15:23
@coderabbitai coderabbitai Bot mentioned this pull request Apr 13, 2026
12 tasks
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