Skip to content

Commit 6d07a01

Browse files
committed
fix(coding-plans): bound billing lifecycle sweep
1 parent d3f0508 commit 6d07a01

1 file changed

Lines changed: 11 additions & 3 deletions

File tree

apps/web/src/lib/coding-plans/billing-lifecycle-cron.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'server-only';
22

33
import { addDays, addHours } from 'date-fns';
4-
import { and, eq, inArray, lte, sql } from 'drizzle-orm';
4+
import { and, asc, eq, inArray, lte, sql } from 'drizzle-orm';
55
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
66

77
import { maybePerformAutoTopUp } from '@/lib/autoTopUp';
@@ -20,6 +20,7 @@ import type * as schema from '@kilocode/db/schema';
2020

2121
const logInfo = sentryLogger('coding-plans-billing-cron', 'info');
2222
const logError = sentryLogger('coding-plans-billing-cron', 'error');
23+
const BILLING_LIFECYCLE_SWEEP_LIMIT = 1_000;
2324

2425
export type CodingPlanCronSummary = {
2526
renewals: number;
@@ -111,7 +112,9 @@ async function sweepCancelAtPeriodEnd(
111112
eq(coding_plan_subscriptions.cancel_at_period_end, true),
112113
lte(coding_plan_subscriptions.current_period_end, nowIso)
113114
)
114-
);
115+
)
116+
.orderBy(asc(coding_plan_subscriptions.current_period_end), asc(coding_plan_subscriptions.id))
117+
.limit(BILLING_LIFECYCLE_SWEEP_LIMIT);
115118

116119
for (const row of rows) {
117120
try {
@@ -196,7 +199,9 @@ async function sweepRenewals(
196199
eq(coding_plan_subscriptions.cancel_at_period_end, false),
197200
lte(coding_plan_subscriptions.credit_renewal_at, nowIso)
198201
)
199-
);
202+
)
203+
.orderBy(asc(coding_plan_subscriptions.credit_renewal_at), asc(coding_plan_subscriptions.id))
204+
.limit(BILLING_LIFECYCLE_SWEEP_LIMIT);
200205

201206
for (const selectedRow of rows) {
202207
const row: RenewalRow = {
@@ -260,6 +265,9 @@ async function processRenewal(
260265
selectedRow: RenewalRow,
261266
nowIso: string
262267
): Promise<RenewalResult> {
268+
// Renewal processing has one durable outcome per due term, guarded by row locks
269+
// and an idempotency key: charge and extend, start a single auto-top-up grace
270+
// window, wait for in-flight grace recovery, or terminate and queue revocation.
263271
return database.transaction(async tx => {
264272
await tx.execute(
265273
sql`SELECT id FROM coding_plan_subscriptions WHERE id = ${selectedRow.id} FOR UPDATE`

0 commit comments

Comments
 (0)