/* * This file is part of a proprietary work. * * Copyright (c) 2025 Fossorial, Inc. * All rights reserved. * * This file is licensed under the Fossorial Commercial License. * You may not use this file except in compliance with the License. * Unauthorized use, copying, modification, or distribution is strictly prohibited. * * This file is not licensed under the AGPLv3. */ import Stripe from "stripe"; import { subscriptions, db, subscriptionItems, customers, userOrgs, users } from "@server/db"; import { eq, and } from "drizzle-orm"; import logger from "@server/logger"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { AudienceIds, moveEmailToAudience } from "#private/lib/resend"; import { getSubType } from "./getSubType"; import stripe from "#private/lib/stripe"; import privateConfig from "#private/lib/config"; import { handleTierChange } from "../featureLifecycle"; export async function handleSubscriptionDeleted( subscription: Stripe.Subscription ): Promise { try { // Fetch the subscription from Stripe with expanded price.tiers const fullSubscription = await stripe!.subscriptions.retrieve( subscription.id, { expand: ["items.data.price.tiers"] } ); const [existingSubscription] = await db .select() .from(subscriptions) .where(eq(subscriptions.subscriptionId, subscription.id)) .limit(1); if (!existingSubscription) { logger.info( `Subscription with ID ${subscription.id} does not exist.` ); return; } await db .delete(subscriptions) .where(eq(subscriptions.subscriptionId, subscription.id)); await db .delete(subscriptionItems) .where(eq(subscriptionItems.subscriptionId, subscription.id)); // Lookup customer to get orgId const [customer] = await db .select() .from(customers) .where(eq(customers.customerId, subscription.customer as string)) .limit(1); if (!customer) { logger.error( `Customer with ID ${subscription.customer} not found for subscription ${subscription.id}.` ); return; } const type = getSubType(fullSubscription); if (type == "tier1" || type == "tier2" || type == "tier3") { logger.debug( `Handling SaaS subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}` ); await handleSubscriptionLifesycle( customer.orgId, subscription.status, type ); // Handle feature lifecycle for cancellation - disable all tier-specific features logger.info( `Disabling tier-specific features for org ${customer.orgId} due to subscription deletion` ); await handleTierChange(customer.orgId, null, type); const [orgUserRes] = await db .select() .from(userOrgs) .where( and( eq(userOrgs.orgId, customer.orgId), eq(userOrgs.isOwner, true) ) ) .innerJoin(users, eq(userOrgs.userId, users.userId)); if (orgUserRes) { const email = orgUserRes.user.email; if (email) { moveEmailToAudience(email, AudienceIds.Churned); } } } else if (type === "license") { logger.debug( `Handling license subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}` ); try { // WARNING: // this invalidates ALL OF THE ENTERPRISE LICENSES for this orgId await fetch( `${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/invalidate`, { method: "POST", headers: { "api-key": privateConfig.getRawPrivateConfig().server .fossorial_api_key!, "Content-Type": "application/json" }, body: JSON.stringify({ orgId: customer.orgId, }) } ); } catch (error) { logger.error( `Error notifying Fossorial API of license subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}:`, error ); } } } catch (error) { logger.error( `Error handling subscription updated event for ID ${subscription.id}:`, error ); } return; }