fix(subscriptions): expire trial subs past end_date, not just active
The subscription-expiry cron filtered status="active" only, so trials never transitioned out of "trial" after end_date. Every access gate keys off status, so trials past end_date kept full product access for free (revenue leak) and inflated the active-trial count. On prod: 105 stuck trials, 3528 brand grants still live. - expiry processor now sweeps status IN (active, trial) past end_date (lt() still skips NULL end_date, so perpetual subs are untouched) - add "trial" to SubscriptionStatus union — it was used in the DB and code but missing from the type (both subscription.ts and user.ts) Proven read-only on prod: old WHERE caught 0, fixed catches 105. Revocation uses the existing set-expired + delete-userBrands path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Job } from "bullmq";
|
||||
import { and, eq, lt } from "drizzle-orm";
|
||||
import { and, eq, inArray, lt } from "drizzle-orm";
|
||||
import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
||||
import { userBrands, userSubscriptions } from "../../database/schema/core";
|
||||
|
||||
@@ -13,11 +13,20 @@ export async function processSubscriptionExpiry(
|
||||
|
||||
const now = new Date();
|
||||
|
||||
// Find active subscriptions where endDate has passed
|
||||
// Find active OR trial subscriptions whose endDate has passed.
|
||||
// NOTE: "trial" was previously omitted here, so trials never expired — their
|
||||
// status stayed "trial" forever past end_date, and every access gate keys off
|
||||
// status, so those users kept full access for free (revenue leak). `lt` skips
|
||||
// NULL end_date rows, so perpetual/active subs without an end_date are untouched.
|
||||
const expiredSubs = await db
|
||||
.select({ id: userSubscriptions.id, userId: userSubscriptions.userId })
|
||||
.from(userSubscriptions)
|
||||
.where(and(eq(userSubscriptions.status, "active"), lt(userSubscriptions.endDate, now)));
|
||||
.where(
|
||||
and(
|
||||
inArray(userSubscriptions.status, ["active", "trial"]),
|
||||
lt(userSubscriptions.endDate, now),
|
||||
),
|
||||
);
|
||||
|
||||
if (expiredSubs.length === 0) {
|
||||
console.log("[subscription-expiry] No expired subscriptions found");
|
||||
|
||||
Reference in New Issue
Block a user