add subscription override column

This commit is contained in:
Owen
2026-08-10 11:15:27 -04:00
parent 3dc9c100e9
commit ed46afd81a
4 changed files with 37 additions and 12 deletions
+2 -1
View File
@@ -95,7 +95,8 @@ export const subscriptions = pgTable("subscriptions", {
billingCycleAnchor: bigint("billingCycleAnchor", { mode: "number" }), billingCycleAnchor: bigint("billingCycleAnchor", { mode: "number" }),
expiresAt: bigint("expiresAt", { mode: "number" }), expiresAt: bigint("expiresAt", { mode: "number" }),
trial: boolean("trial").default(false), trial: boolean("trial").default(false),
type: varchar("type", { length: 50 }) // tier1, tier2, tier3, or license type: varchar("type", { length: 50 }), // tier1, tier2, tier3, or license
override: boolean("override").default(false)
}); });
export const subscriptionItems = pgTable("subscriptionItems", { export const subscriptionItems = pgTable("subscriptionItems", {
+2 -1
View File
@@ -89,7 +89,8 @@ export const subscriptions = sqliteTable("subscriptions", {
expiresAt: integer("expiresAt"), expiresAt: integer("expiresAt"),
trial: integer("trial", { mode: "boolean" }).default(false), trial: integer("trial", { mode: "boolean" }).default(false),
billingCycleAnchor: integer("billingCycleAnchor"), billingCycleAnchor: integer("billingCycleAnchor"),
type: text("type") // tier1, tier2, tier3, or license type: text("type"), // tier1, tier2, tier3, or license
override: integer("override", { mode: "boolean" }).default(false)
}); });
export const subscriptionItems = sqliteTable("subscriptionItems", { export const subscriptionItems = sqliteTable("subscriptionItems", {
@@ -53,6 +53,15 @@ export async function handleSubscriptionDeleted(
return; return;
} }
// If the subscription has been manually overridden, we lock it down
// so Stripe can no longer change (or delete) its status locally.
if (existingSubscription.override === true) {
logger.info(
`Subscription ${subscription.id} is locked (override=true). Ignoring deletion event from Stripe.`
);
return;
}
await db await db
.delete(subscriptions) .delete(subscriptions)
.where(eq(subscriptions.subscriptionId, subscription.id)); .where(eq(subscriptions.subscriptionId, subscription.id));
@@ -68,13 +68,27 @@ export async function handleSubscriptionUpdated(
const type = getSubType(fullSubscription); const type = getSubType(fullSubscription);
const previousType = existingSubscription.type as SubscriptionType | null; const previousType = existingSubscription.type as SubscriptionType | null;
// If the subscription has been manually overridden, we lock the
// status down so Stripe webhooks can no longer change it.
const isLocked = existingSubscription.override === true;
if (isLocked) {
logger.info(
`Subscription ${subscription.id} is locked (override=true). Ignoring status change from Stripe (would have been ${subscription.status}).`
);
}
const effectiveStatus = isLocked
? existingSubscription.status
: subscription.status;
await db await db
.update(subscriptions) .update(subscriptions)
.set({ .set({
status: subscription.status, status: effectiveStatus,
canceledAt: subscription.canceled_at canceledAt: isLocked
? subscription.canceled_at ? existingSubscription.canceledAt
: null, : subscription.canceled_at
? subscription.canceled_at
: null,
updatedAt: Math.floor(Date.now() / 1000), updatedAt: Math.floor(Date.now() / 1000),
billingCycleAnchor: subscription.billing_cycle_anchor, billingCycleAnchor: subscription.billing_cycle_anchor,
type: type type: type
@@ -275,23 +289,23 @@ export async function handleSubscriptionUpdated(
// we only need to handle the limit lifecycle for saas subscriptions not for the licenses // we only need to handle the limit lifecycle for saas subscriptions not for the licenses
await handleSubscriptionLifesycle( await handleSubscriptionLifesycle(
customer.orgId, customer.orgId,
subscription.status, effectiveStatus,
type type
); );
// Handle feature lifecycle when subscription is canceled or becomes unpaid // Handle feature lifecycle when subscription is canceled or becomes unpaid
if ( if (
subscription.status === "canceled" || effectiveStatus === "canceled" ||
subscription.status === "unpaid" || effectiveStatus === "unpaid" ||
subscription.status === "incomplete_expired" effectiveStatus === "incomplete_expired"
) { ) {
logger.info( logger.info(
`Subscription ${subscription.id} for org ${customer.orgId} is ${subscription.status}, disabling paid features` `Subscription ${subscription.id} for org ${customer.orgId} is ${effectiveStatus}, disabling paid features`
); );
await handleTierChange(customer.orgId, null, previousType ?? undefined); await handleTierChange(customer.orgId, null, previousType ?? undefined);
} }
} else if (type === "license") { } else if (type === "license") {
if (subscription.status === "canceled" || subscription.status == "unpaid" || subscription.status == "incomplete_expired") { if (effectiveStatus === "canceled" || effectiveStatus == "unpaid" || effectiveStatus == "incomplete_expired") {
try { try {
// WARNING: // WARNING:
// this invalidates ALL OF THE ENTERPRISE LICENSES for this orgId // this invalidates ALL OF THE ENTERPRISE LICENSES for this orgId