mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-08 05:56:38 +00:00
Working on complete auth flow
This commit is contained in:
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}:`,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user