Rename tiers and get working

This commit is contained in:
Owen
2026-02-08 17:55:26 -08:00
parent c41e8be3e8
commit 81ef2db7f8
36 changed files with 326 additions and 175 deletions

View File

@@ -17,8 +17,8 @@ import { eq, and, ne } from "drizzle-orm";
export async function getOrgTierData(
orgId: string
): Promise<{ tier: "home_lab" | "starter" | "scale" | null; active: boolean }> {
let tier: "home_lab" | "starter" | "scale" | null = null;
): Promise<{ tier: "tier1" | "tier2" | "tier3" | null; active: boolean }> {
let tier: "tier1" | "tier2" | "tier3" | null = null;
let active = false;
if (build !== "saas") {
@@ -50,9 +50,9 @@ export async function getOrgTierData(
if (subscription) {
// Validate that subscription.type is one of the expected tier values
if (
subscription.type === "home_lab" ||
subscription.type === "starter" ||
subscription.type === "scale"
subscription.type === "tier1" ||
subscription.type === "tier2" ||
subscription.type === "tier3"
) {
tier = subscription.type;
active = true;

View File

@@ -22,7 +22,7 @@ export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
if (build === "saas") {
const { tier, active } = await getOrgTierData(orgId);
return (tier == "home_lab" || tier == "starter" || tier == "scale") && active;
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active;
}
return false;

View File

@@ -17,7 +17,7 @@ import { getOrgTierData } from "#private/lib/billing";
export async function isSubscribed(orgId: string): Promise<boolean> {
if (build === "saas") {
const { tier, active } = await getOrgTierData(orgId);
return (tier == "home_lab" || tier == "starter" || tier == "scale") && active;
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active;
}
return false;

View File

@@ -39,7 +39,7 @@ export async function verifyValidSubscription(
}
const { tier, active } = await getOrgTierData(orgId);
if ((tier == "home_lab" || tier == "starter" || tier == "scale") && active) {
if ((tier == "tier1" || tier == "tier2" || tier == "tier3") && active) {
return next(
createHttpError(
HttpCode.FORBIDDEN,

View File

@@ -35,7 +35,7 @@ const changeTierSchema = z.strictObject({
});
const changeTierBodySchema = z.strictObject({
tier: z.enum(["home_lab", "starter", "scale"])
tier: z.enum(["tier1", "tier2", "tier3"])
});
export async function changeTier(
@@ -93,9 +93,9 @@ export async function changeTier(
eq(subscriptions.customerId, customer.customerId),
eq(subscriptions.status, "active"),
or(
eq(subscriptions.type, "home_lab"),
eq(subscriptions.type, "starter"),
eq(subscriptions.type, "scale")
eq(subscriptions.type, "tier1"),
eq(subscriptions.type, "tier2"),
eq(subscriptions.type, "tier3")
)
)
)
@@ -112,11 +112,11 @@ export async function changeTier(
// Get the target tier's price set
let targetPriceSet: FeaturePriceSet;
if (tier === "home_lab") {
if (tier === "tier1") {
targetPriceSet = getHomeLabFeaturePriceSet();
} else if (tier === "starter") {
} else if (tier === "tier2") {
targetPriceSet = getStarterFeaturePriceSet();
} else if (tier === "scale") {
} else if (tier === "tier3") {
targetPriceSet = getScaleFeaturePriceSet();
} else {
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid tier"));
@@ -148,11 +148,11 @@ export async function changeTier(
);
// Determine if we're switching between different products
// home_lab uses HOME_LAB product, starter/scale use USERS product
// tier1 uses TIER1 product, tier2/tier3 use USERS product
const currentTier = subscription.type;
const switchingProducts =
(currentTier === "home_lab" && (tier === "starter" || tier === "scale")) ||
((currentTier === "starter" || currentTier === "scale") && tier === "home_lab");
(currentTier === "tier1" && (tier === "tier2" || tier === "tier3")) ||
((currentTier === "tier2" || currentTier === "tier3") && tier === "tier1");
let updatedSubscription;
@@ -189,7 +189,7 @@ export async function changeTier(
}
);
} else {
// Same product, different price tier (starter <-> scale)
// Same product, different price tier (tier2 <-> tier3)
// We can simply update the price
logger.info(
`Updating price from ${currentTier} to ${tier} for subscription ${subscription.subscriptionId}`

View File

@@ -31,7 +31,7 @@ const createCheckoutSessionSchema = z.strictObject({
});
const createCheckoutSessionBodySchema = z.strictObject({
tier: z.enum(["home_lab", "starter", "scale"]),
tier: z.enum(["tier1", "tier2", "tier3"]),
});
export async function createCheckoutSession(
@@ -83,11 +83,11 @@ export async function createCheckoutSession(
}
let lineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
if (tier === "home_lab") {
if (tier === "tier1") {
lineItems = await getLineItems(getHomeLabFeaturePriceSet(), orgId);
} else if (tier === "starter") {
} else if (tier === "tier2") {
lineItems = await getLineItems(getStarterFeaturePriceSet(), orgId);
} else if (tier === "scale") {
} else if (tier === "tier3") {
lineItems = await getLineItems(getScaleFeaturePriceSet(), orgId);
} else {
return next(

View File

@@ -78,16 +78,10 @@ export async function getOrgUsage(
// Get usage for org
const usageData = [];
const sites = await usageService.getUsage(
orgId,
FeatureId.SITES
);
const users = await usageService.getUsageDaily(orgId, FeatureId.USERS);
const domains = await usageService.getUsageDaily(
orgId,
FeatureId.DOMAINS
);
const remoteExitNodes = await usageService.getUsageDaily(
const sites = await usageService.getUsage(orgId, FeatureId.SITES);
const users = await usageService.getUsage(orgId, FeatureId.USERS);
const domains = await usageService.getUsage(orgId, FeatureId.DOMAINS);
const remoteExitNodes = await usageService.getUsage(
orgId,
FeatureId.REMOTE_EXIT_NODES
);

View File

@@ -21,7 +21,7 @@ import {
} from "@server/lib/billing/features";
import Stripe from "stripe";
export type SubscriptionType = "home_lab" | "starter" | "scale" | "license";
export type SubscriptionType = "tier1" | "tier2" | "tier3" | "license";
export function getSubType(fullSubscription: Stripe.Response<Stripe.Subscription>): SubscriptionType | null {
// Determine subscription type by checking subscription items
@@ -41,21 +41,21 @@ export function getSubType(fullSubscription: Stripe.Response<Stripe.Subscription
// Check if price ID matches home lab tier
const homeLabPrices = Object.values(getHomeLabFeaturePriceSet());
if (homeLabPrices.includes(priceId)) {
return "home_lab";
return "tier1";
}
// Check if price ID matches starter tier
const starterPrices = Object.values(getStarterFeaturePriceSet());
if (starterPrices.includes(priceId)) {
return "starter";
// Check if price ID matches tier2 tier
const tier2Prices = Object.values(getStarterFeaturePriceSet());
if (tier2Prices.includes(priceId)) {
return "tier2";
}
// Check if price ID matches scale tier
const scalePrices = Object.values(getScaleFeaturePriceSet());
if (scalePrices.includes(priceId)) {
return "scale";
// Check if price ID matches tier3 tier
const tier3Prices = Object.values(getScaleFeaturePriceSet());
if (tier3Prices.includes(priceId)) {
return "tier3";
}
}
return null;
}
}

View File

@@ -91,6 +91,7 @@ export async function handleSubscriptionCreated(
}
return {
stripeSubscriptionItemId: item.id,
subscriptionId: subscription.id,
planId: item.plan.id,
priceId: item.price.id,
@@ -132,7 +133,7 @@ export async function handleSubscriptionCreated(
return;
}
if (type === "home_lab" || type === "starter" || type === "scale") {
if (type === "tier1" || type === "tier2" || type === "tier3") {
logger.debug(
`Handling SAAS subscription lifecycle for org ${customer.orgId} with type ${type}`
);

View File

@@ -76,7 +76,7 @@ export async function handleSubscriptionDeleted(
}
const type = getSubType(fullSubscription);
if (type == "home_lab" || type == "starter" || type == "scale") {
if (type == "tier1" || type == "tier2" || type == "tier3") {
logger.debug(
`Handling SaaS subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}`
);

View File

@@ -82,6 +82,7 @@ export async function handleSubscriptionUpdated(
// Upsert subscription items
if (Array.isArray(fullSubscription.items?.data)) {
const itemsToUpsert = fullSubscription.items.data.map((item) => ({
stripeSubscriptionItemId: item.id,
subscriptionId: subscription.id,
planId: item.plan.id,
priceId: item.price.id,
@@ -237,7 +238,7 @@ export async function handleSubscriptionUpdated(
}
// --- end usage update ---
if (type === "home_lab" || type === "starter" || type === "scale") {
if (type === "tier1" || type === "tier2" || type === "tier3") {
logger.debug(
`Handling SAAS subscription lifecycle for org ${customer.orgId} with type ${type}`
);

View File

@@ -14,8 +14,8 @@
import {
freeLimitSet,
homeLabLimitSet,
starterLimitSet,
scaleLimitSet,
tier2LimitSet,
tier3LimitSet,
limitsService,
LimitSet
} from "@server/lib/billing";
@@ -24,16 +24,16 @@ import { SubscriptionType } from "./hooks/getSubType";
function getLimitSetForSubscriptionType(subType: SubscriptionType | null): LimitSet {
switch (subType) {
case "home_lab":
case "tier1":
return homeLabLimitSet;
case "starter":
return starterLimitSet;
case "scale":
return scaleLimitSet;
case "tier2":
return tier2LimitSet;
case "tier3":
return tier3LimitSet;
case "license":
// License subscriptions use starter limits by default
// License subscriptions use tier2 limits by default
// This can be adjusted based on your business logic
return starterLimitSet;
return tier2LimitSet;
default:
return freeLimitSet;
}

View File

@@ -224,7 +224,7 @@ export async function createRemoteExitNode(
});
if (numExitNodeOrgs) {
await usageService.updateDaily(
await usageService.updateCount(
orgId,
FeatureId.REMOTE_EXIT_NODES,
numExitNodeOrgs.length

View File

@@ -106,7 +106,7 @@ export async function deleteRemoteExitNode(
});
if (numExitNodeOrgs) {
await usageService.updateDaily(
await usageService.updateCount(
orgId,
FeatureId.REMOTE_EXIT_NODES,
numExitNodeOrgs.length