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 { getSubType } from "./getSubType";
import privateConfig from "#private/lib/config";
import { getLicensePriceSet, LicenseId } from "@server/lib/billing/licenses";
export async function handleSubscriptionCreated(
subscription: Stripe.Subscription
@@ -182,6 +183,33 @@ export async function handleSubscriptionCreated(
`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(
`${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"
},
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 { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { AudienceIds, moveEmailToAudience } from "#private/lib/resend";
import { getSubType } from "./getSubType";
import stripe from "#private/lib/stripe";
export async function handleSubscriptionDeleted(
subscription: Stripe.Subscription
): Promise<void> {
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
.select()
.from(subscriptions)
@@ -64,25 +74,33 @@ export async function handleSubscriptionDeleted(
return;
}
await handleSubscriptionLifesycle(customer.orgId, subscription.status);
const type = getSubType(fullSubscription);
if (type === "saas") {
logger.debug(`Handling SaaS subscription deletion for orgId ${customer.orgId} and subscription ID ${subscription.id}`);
const [orgUserRes] = await db
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.orgId, customer.orgId),
eq(userOrgs.isOwner, true)
await handleSubscriptionLifesycle(customer.orgId, subscription.status);
const [orgUserRes] = await db
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.orgId, customer.orgId),
eq(userOrgs.isOwner, true)
)
)
)
.innerJoin(users, eq(userOrgs.userId, users.userId));
.innerJoin(users, eq(userOrgs.userId, users.userId));
if (orgUserRes) {
const email = orgUserRes.user.email;
if (orgUserRes) {
const email = orgUserRes.user.email;
if (email) {
moveEmailToAudience(email, AudienceIds.Churned);
if (email) {
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) {
logger.error(

View File

@@ -26,6 +26,7 @@ import logger from "@server/logger";
import { getFeatureIdByMetricId } from "@server/lib/billing/features";
import stripe from "#private/lib/stripe";
import { handleSubscriptionLifesycle } from "../subscriptionLifecycle";
import { getSubType } from "./getSubType";
export async function handleSubscriptionUpdated(
subscription: Stripe.Subscription,
@@ -74,11 +75,6 @@ export async function handleSubscriptionUpdated(
})
.where(eq(subscriptions.subscriptionId, subscription.id));
await handleSubscriptionLifesycle(
existingCustomer.orgId,
subscription.status
);
// Upsert subscription items
if (Array.isArray(fullSubscription.items?.data)) {
const itemsToUpsert = fullSubscription.items.data.map((item) => ({
@@ -141,14 +137,14 @@ export async function handleSubscriptionUpdated(
// This item has cycled
const meterId = item.plan.meter;
if (!meterId) {
logger.warn(
logger.debug(
`No meterId found for subscription item ${item.id}. Skipping usage reset.`
);
continue;
}
const featureId = getFeatureIdByMetricId(meterId);
if (!featureId) {
logger.warn(
logger.debug(
`No featureId found for meterId ${meterId}. Skipping usage reset.`
);
continue;
@@ -236,6 +232,20 @@ export async function handleSubscriptionUpdated(
}
}
// --- 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) {
logger.error(

View File

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

View File

@@ -158,6 +158,48 @@ export default function GenerateLicenseKeysTable({
: 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",
friendlyName: t("licenseTableValidUntil"),