Handle trial limit lifecycle

This commit is contained in:
Owen
2026-05-03 14:30:34 -07:00
parent eb515a8f7f
commit 1a926a7127
3 changed files with 40 additions and 12 deletions

View File

@@ -16,6 +16,7 @@ import { customers, db, subscriptions } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { generateId } from "@server/auth/sessions/app"; import { generateId } from "@server/auth/sessions/app";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
export async function handleCustomerCreated( export async function handleCustomerCreated(
customer: Stripe.Customer customer: Stripe.Customer
@@ -62,6 +63,13 @@ export async function handleCustomerCreated(
expiresAt: trialExpiresAt, expiresAt: trialExpiresAt,
trial: true trial: true
}); });
// update to the business limits for the trial
await handleSubscriptionLifesycle(
customer.metadata.orgId,
"active",
"tier3"
);
}); });
logger.info(`Customer with ID ${customer.id} created successfully.`); logger.info(`Customer with ID ${customer.id} created successfully.`);

View File

@@ -44,7 +44,7 @@ function getLimitSetForSubscriptionType(
export async function handleSubscriptionLifesycle( export async function handleSubscriptionLifesycle(
orgId: string, orgId: string,
status: string, status: string,
subType: SubscriptionType | null subType: SubscriptionType | null = null
) { ) {
switch (status) { switch (status) {
case "active": case "active":

View File

@@ -24,13 +24,18 @@ import { fromError } from "zod-validation-error";
import { sendEmail } from "@server/emails"; import { sendEmail } from "@server/emails";
import NotifyTrialExpiring from "@server/emails/templates/NotifyTrialExpiring"; import NotifyTrialExpiring from "@server/emails/templates/NotifyTrialExpiring";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { handleSubscriptionLifesycle } from "../billing/subscriptionLifecycle";
const sendTrialNotificationParamsSchema = z.object({ const sendTrialNotificationParamsSchema = z.object({
orgId: z.string() orgId: z.string()
}); });
const sendTrialNotificationBodySchema = z.object({ const sendTrialNotificationBodySchema = z.object({
notificationType: z.enum(["trial_ending_5d", "trial_ending_24h", "trial_ended"]), notificationType: z.enum([
"trial_ending_5d",
"trial_ending_24h",
"trial_ended"
]),
orgName: z.string(), orgName: z.string(),
trialEndsAt: z.number(), trialEndsAt: z.number(),
billingLink: z.string().optional() billingLink: z.string().optional()
@@ -69,9 +74,7 @@ async function getOrgAdmins(orgId: string) {
) )
); );
const byUserId = new Map( const byUserId = new Map(admins.map((a) => [a.userId, a]));
admins.map((a) => [a.userId, a])
);
const orgAdmins = Array.from(byUserId.values()).filter( const orgAdmins = Array.from(byUserId.values()).filter(
(admin) => admin.email && admin.email.length > 0 (admin) => admin.email && admin.email.length > 0
); );
@@ -108,8 +111,12 @@ export async function sendTrialNotification(
} }
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
const { notificationType, orgName, trialEndsAt, billingLink: bodyBillingLink } = const {
parsedBody.data; notificationType,
orgName,
trialEndsAt,
billingLink: bodyBillingLink
} = parsedBody.data;
// Verify organization exists // Verify organization exists
const org = await db const org = await db
@@ -146,13 +153,17 @@ export async function sendTrialNotification(
bodyBillingLink ?? bodyBillingLink ??
`${config.getRawConfig().app.dashboard_url}/${orgId}/settings/billing`; `${config.getRawConfig().app.dashboard_url}/${orgId}/settings/billing`;
const trialEndsAtFormatted = new Date(trialEndsAt * 1000).toLocaleDateString( const trialEndsAtFormatted = new Date(
"en-US", trialEndsAt * 1000
{ year: "numeric", month: "long", day: "numeric" } ).toLocaleDateString("en-US", {
); year: "numeric",
month: "long",
day: "numeric"
});
let daysRemaining: number | null; let daysRemaining: number | null;
let subject: string; let subject: string;
let resetLimits = false;
if (notificationType === "trial_ending_5d") { if (notificationType === "trial_ending_5d") {
daysRemaining = 5; daysRemaining = 5;
@@ -163,6 +174,7 @@ export async function sendTrialNotification(
} else { } else {
daysRemaining = null; daysRemaining = null;
subject = "Your trial has ended"; subject = "Your trial has ended";
resetLimits = true;
} }
let emailsSent = 0; let emailsSent = 0;
@@ -201,6 +213,14 @@ export async function sendTrialNotification(
} }
} }
if (resetLimits) {
// this will only fire if they have not upgraded yet because when upgrading we delete the trial
await handleSubscriptionLifesycle(orgId, "cancled");
logger.debug(
`Trial ended for org ${orgId}, limits reset to free tier`
);
}
return response<SendTrialNotificationResponse>(res, { return response<SendTrialNotificationResponse>(res, {
data: { data: {
success: true, success: true,
@@ -221,4 +241,4 @@ export async function sendTrialNotification(
) )
); );
} }
} }