101 lines
2.5 KiB
TypeScript
101 lines
2.5 KiB
TypeScript
import { eq } from "drizzle-orm";
|
|
import { db } from "@/lib/db";
|
|
import { subscriptions, users } from "@/lib/schema";
|
|
|
|
const TRIAL_DAYS = 14;
|
|
|
|
export interface SubscriptionStatus {
|
|
isActive: boolean;
|
|
status: "trialing" | "active" | "expired" | "canceled" | "canceling";
|
|
daysRemainingInTrial: number | null;
|
|
trialEndsAt: Date | null;
|
|
subscriptionEndsAt: Date | null;
|
|
}
|
|
|
|
/**
|
|
* Check if a user has an active subscription or valid trial
|
|
*/
|
|
export async function checkSubscriptionStatus(userId: string): Promise<SubscriptionStatus> {
|
|
// Get user creation date
|
|
const [user] = await db
|
|
.select({ createdAt: users.createdAt })
|
|
.from(users)
|
|
.where(eq(users.id, userId))
|
|
.limit(1);
|
|
|
|
if (!user) {
|
|
return {
|
|
isActive: false,
|
|
status: "expired",
|
|
daysRemainingInTrial: null,
|
|
trialEndsAt: null,
|
|
subscriptionEndsAt: null,
|
|
};
|
|
}
|
|
|
|
// Get subscription record
|
|
const [subscription] = await db
|
|
.select()
|
|
.from(subscriptions)
|
|
.where(eq(subscriptions.userId, userId))
|
|
.limit(1);
|
|
|
|
const now = new Date();
|
|
const trialEndsAt = new Date(user.createdAt);
|
|
trialEndsAt.setDate(trialEndsAt.getDate() + TRIAL_DAYS);
|
|
|
|
// If user has active or canceling subscription, they can use the service
|
|
if (subscription?.status === "active" || subscription?.status === "canceling") {
|
|
// If subscription is marked as canceling, return canceling status
|
|
if (subscription.status === "canceling") {
|
|
return {
|
|
isActive: true,
|
|
status: "canceling",
|
|
daysRemainingInTrial: null,
|
|
trialEndsAt: null,
|
|
subscriptionEndsAt: subscription.currentPeriodEnd || null,
|
|
};
|
|
}
|
|
|
|
return {
|
|
isActive: true,
|
|
status: "active",
|
|
daysRemainingInTrial: null,
|
|
trialEndsAt: null,
|
|
subscriptionEndsAt: subscription.currentPeriodEnd || null,
|
|
};
|
|
}
|
|
|
|
// If still in trial period
|
|
if (now < trialEndsAt) {
|
|
const daysRemaining = Math.ceil(
|
|
(trialEndsAt.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
return {
|
|
isActive: true,
|
|
status: "trialing",
|
|
daysRemainingInTrial: daysRemaining,
|
|
trialEndsAt,
|
|
subscriptionEndsAt: null,
|
|
};
|
|
}
|
|
|
|
// Trial expired and no paid subscription
|
|
if (subscription?.status === "canceled") {
|
|
return {
|
|
isActive: false,
|
|
status: "canceled",
|
|
daysRemainingInTrial: null,
|
|
trialEndsAt,
|
|
subscriptionEndsAt: null,
|
|
};
|
|
}
|
|
|
|
return {
|
|
isActive: false,
|
|
status: "expired",
|
|
daysRemainingInTrial: null,
|
|
trialEndsAt,
|
|
subscriptionEndsAt: null,
|
|
};
|
|
}
|