Working on complete auth flow

This commit is contained in:
Owen
2026-02-04 16:32:39 -08:00
parent b5d76f73e8
commit 1bc4480d84
5 changed files with 125 additions and 22 deletions

View File

@@ -27,6 +27,7 @@ import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { AudienceIds, moveEmailToAudience } from "#private/lib/resend"; import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
import { getSubType } from "./getSubType"; import { getSubType } from "./getSubType";
import privateConfig from "#private/lib/config"; import privateConfig from "#private/lib/config";
import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
export async function handleSubscriptionCreated( export async function handleSubscriptionCreated(
subscription: Stripe.Subscription subscription: Stripe.Subscription
@@ -182,6 +183,33 @@ export async function handleSubscriptionCreated(
`Retrieved licenseId ${licenseId} from checkout session for subscription ${subscription.id}` `Retrieved licenseId ${licenseId} from checkout session for subscription ${subscription.id}`
); );
// Determine users and sites based on license type
const priceSet = getLicensePriceSet();
const subscriptionPriceId =
fullSubscription.items.data[0]?.price.id;
let numUsers: number;
let numSites: number;
if (subscriptionPriceId === priceSet[LicenseId.SMALL_LICENSE]) {
numUsers = 25;
numSites = 25;
} else if (
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE]
) {
numUsers = 50;
numSites = 50;
} else {
logger.error(
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
);
return;
}
logger.debug(
`License type determined: ${numUsers} users, ${numSites} sites for subscription ${subscription.id}`
);
const response = await fetch( const response = await fetch(
`${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/paid-for`, // this says enterprise but it does both `${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/paid-for`, // this says enterprise but it does both
{ {
@@ -193,7 +221,10 @@ export async function handleSubscriptionCreated(
"Content-Type": "application/json" "Content-Type": "application/json"
}, },
body: JSON.stringify({ body: JSON.stringify({
licenseId: parseInt(licenseId) licenseId: parseInt(licenseId),
paidFor: true,
users: numUsers,
sites: numSites
}) })
} }
); );

View File

@@ -24,11 +24,21 @@ import { eq, and } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { AudienceIds, moveEmailToAudience } from "#private/lib/resend"; import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
import { getSubType } from "./getSubType";
import stripe from "#private/lib/stripe";
export async function handleSubscriptionDeleted( export async function handleSubscriptionDeleted(
subscription: Stripe.Subscription subscription: Stripe.Subscription
): Promise<void> { ): Promise<void> {
try { 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 const [existingSubscription] = await db
.select() .select()
.from(subscriptions) .from(subscriptions)
@@ -64,6 +74,10 @@ export async function handleSubscriptionDeleted(
return; return;
} }
const type = getSubType(fullSubscription);
if (type === "saas") {
logger.debug(`Handling SaaS subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}`);
await handleSubscriptionLifesycle(customer.orgId, subscription.status); await handleSubscriptionLifesycle(customer.orgId, subscription.status);
const [orgUserRes] = await db const [orgUserRes] = await db
@@ -84,6 +98,10 @@ export async function handleSubscriptionDeleted(
moveEmailToAudience(email, AudienceIds.Churned); moveEmailToAudience(email, AudienceIds.Churned);
} }
} }
} else if (type === "license") {
logger.debug(`Handling license subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}`);
}
} catch (error) { } catch (error) {
logger.error( logger.error(
`Error handling subscription updated event for ID ${subscription.id}:`, `Error handling subscription updated event for ID ${subscription.id}:`,

View File

@@ -26,6 +26,7 @@ import logger from "@server/logger";
import { getFeatureIdByMetricId } from "@server/lib/billing/features"; import { getFeatureIdByMetricId } from "@server/lib/billing/features";
import stripe from "#private/lib/stripe"; import stripe from "#private/lib/stripe";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle"; import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { getSubType } from "./getSubType";
export async function handleSubscriptionUpdated( export async function handleSubscriptionUpdated(
subscription: Stripe.Subscription, subscription: Stripe.Subscription,
@@ -74,11 +75,6 @@ export async function handleSubscriptionUpdated(
}) })
.where(eq(subscriptions.subscriptionId, subscription.id)); .where(eq(subscriptions.subscriptionId, subscription.id));
await handleSubscriptionLifesycle(
existingCustomer.orgId,
subscription.status
);
// Upsert subscription items // Upsert subscription items
if (Array.isArray(fullSubscription.items?.data)) { if (Array.isArray(fullSubscription.items?.data)) {
const itemsToUpsert = fullSubscription.items.data.map((item) => ({ const itemsToUpsert = fullSubscription.items.data.map((item) => ({
@@ -141,14 +137,14 @@ export async function handleSubscriptionUpdated(
// This item has cycled // This item has cycled
const meterId = item.plan.meter; const meterId = item.plan.meter;
if (!meterId) { if (!meterId) {
logger.warn( logger.debug(
`No meterId found for subscription item ${item.id}. Skipping usage reset.` `No meterId found for subscription item ${item.id}. Skipping usage reset.`
); );
continue; continue;
} }
const featureId = getFeatureIdByMetricId(meterId); const featureId = getFeatureIdByMetricId(meterId);
if (!featureId) { if (!featureId) {
logger.warn( logger.debug(
`No featureId found for meterId ${meterId}. Skipping usage reset.` `No featureId found for meterId ${meterId}. Skipping usage reset.`
); );
continue; continue;
@@ -236,6 +232,20 @@ export async function handleSubscriptionUpdated(
} }
} }
// --- end usage update --- // --- end usage update ---
const type = getSubType(fullSubscription);
if (type === "saas") {
logger.debug(`Handling SAAS subscription lifecycle for org ${existingCustomer.orgId}`);
// we only need to handle the limit lifecycle for saas subscriptions not for the licenses
await handleSubscriptionLifesycle(
existingCustomer.orgId,
subscription.status
);
} else {
logger.debug(
`Subscription ${subscription.id} is of type ${type}. No lifecycle handling needed.`
);
}
} }
} catch (error) { } catch (error) {
logger.error( logger.error(

View File

@@ -6,6 +6,8 @@ export type GeneratedLicenseKey = {
createdAt: string; createdAt: string;
tier: string; tier: string;
type: string; type: string;
users: number;
sites: number;
}; };
export type ListGeneratedLicenseKeysResponse = GeneratedLicenseKey[]; export type ListGeneratedLicenseKeysResponse = GeneratedLicenseKey[];

View File

@@ -158,6 +158,48 @@ export default function GenerateLicenseKeysTable({
: t("licenseTierPersonal"); : t("licenseTierPersonal");
} }
}, },
{
accessorKey: "users",
friendlyName: t("users"),
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("users")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const users = row.original.users;
return users === -1 ? "∞" : users;
}
},
{
accessorKey: "sites",
friendlyName: t("sites"),
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() =>
column.toggleSorting(column.getIsSorted() === "asc")
}
>
{t("sites")}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => {
const sites = row.original.sites;
return sites === -1 ? "∞" : sites;
}
},
{ {
accessorKey: "terminateAt", accessorKey: "terminateAt",
friendlyName: t("licenseTableValidUntil"), friendlyName: t("licenseTableValidUntil"),