Basic billing page is working

This commit is contained in:
Owen
2026-02-06 17:41:20 -08:00
parent 6cfc7b7c69
commit e101ac341b
14 changed files with 451 additions and 644 deletions

View File

@@ -31,7 +31,6 @@ import {
import { eq, isNull, sql, not, and, desc } from "drizzle-orm";
import response from "@server/lib/response";
import { getUserDeviceName } from "@server/db/names";
import { isLicensedOrSubscribed } from "@server/private/lib/isLicencedOrSubscribed";
const paramsSchema = z.strictObject({
orgId: z.string()

View File

@@ -25,6 +25,7 @@ import {
getHomeLabFeaturePriceSet,
getScaleFeaturePriceSet,
getStarterFeaturePriceSet,
getLineItems,
FeatureId,
type FeaturePriceSet
} from "@server/lib/billing";
@@ -149,7 +150,7 @@ export async function changeTier(
// Determine if we're switching between different products
// home_lab uses HOME_LAB product, starter/scale use USERS product
const currentTier = subscription.type;
const switchingProducts =
const switchingProducts =
(currentTier === "home_lab" && (tier === "starter" || tier === "scale")) ||
((currentTier === "starter" || currentTier === "scale") && tier === "home_lab");
@@ -175,10 +176,9 @@ export async function changeTier(
}
// Add new items for the target tier
for (const [featureId, priceId] of Object.entries(targetPriceSet)) {
itemsToUpdate.push({
price: priceId
});
const newLineItems = await getLineItems(targetPriceSet, orgId);
for (const lineItem of newLineItems) {
itemsToUpdate.push(lineItem);
}
updatedSubscription = await stripe!.subscriptions.update(

View File

@@ -23,6 +23,8 @@ import config from "@server/lib/config";
import { fromError } from "zod-validation-error";
import stripe from "#private/lib/stripe";
import { getHomeLabFeaturePriceSet, getLineItems, getScaleFeaturePriceSet, getStarterFeaturePriceSet } from "@server/lib/billing";
import { usageService } from "@server/lib/billing/usageService";
import Stripe from "stripe";
const createCheckoutSessionSchema = z.strictObject({
orgId: z.string()
@@ -80,19 +82,21 @@ export async function createCheckoutSession(
);
}
let lineItems;
let lineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
if (tier === "home_lab") {
lineItems = getLineItems(getHomeLabFeaturePriceSet());
lineItems = await getLineItems(getHomeLabFeaturePriceSet(), orgId);
} else if (tier === "starter") {
lineItems = getLineItems(getStarterFeaturePriceSet());
lineItems = await getLineItems(getStarterFeaturePriceSet(), orgId);
} else if (tier === "scale") {
lineItems = getLineItems(getScaleFeaturePriceSet());
lineItems = await getLineItems(getScaleFeaturePriceSet(), orgId);
} else {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid plan")
);
}
logger.debug(`Line items: ${JSON.stringify(lineItems)}`)
const session = await stripe!.checkout.sessions.create({
client_reference_id: orgId, // So we can look it up the org later on the webhook
billing_address_collection: "required",

View File

@@ -16,3 +16,4 @@ export * from "./createPortalSession";
export * from "./getOrgSubscriptions";
export * from "./getOrgUsage";
export * from "./internalGetOrgTier";
export * from "./changeTier";

View File

@@ -151,6 +151,14 @@ if (build === "saas") {
billing.createCheckoutSession
);
authenticated.post(
"/org/:orgId/billing/change-tier",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.billing),
logActionAudit(ActionsEnum.billing),
billing.changeTier
);
authenticated.post(
"/org/:orgId/billing/create-portal-session",
verifyOrgAccess,

View File

@@ -26,7 +26,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, InferInsertModel } from "drizzle-orm";
import { build } from "@server/build";
import config from "@server/private/lib/config";
import config from "#private/lib/config";
const paramsSchema = z.strictObject({
orgId: z.string()