Skip to content
34 changes: 18 additions & 16 deletions packages/features/ee/billing/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@ export const CHECKOUT_SESSION_TYPES = {

export type CheckoutSessionType = (typeof CHECKOUT_SESSION_TYPES)[keyof typeof CHECKOUT_SESSION_TYPES];

export enum BillingPlan {
INDIVIDUALS = "INDIVIDUALS",
TEAMS = "TEAMS",
ORGANIZATIONS = "ORGANIZATIONS",
ENTERPRISE = "ENTERPRISE",
PLATFORM_STARTER = "PLATFORM_STARTER",
PLATFORM_ESSENTIALS = "PLATFORM_ESSENTIALS",
PLATFORM_SCALE = "PLATFORM_SCALE",
PLATFORM_ENTERPRISE = "PLATFORM_ENTERPRISE",
UNKNOWN = "Unknown",
}
export const BILLING_PLANS = {
INDIVIDUALS: "INDIVIDUALS",
TEAMS: "TEAMS",
ORGANIZATIONS: "ORGANIZATIONS",
ENTERPRISE: "ENTERPRISE",
PLATFORM_STARTER: "PLATFORM_STARTER",
PLATFORM_ESSENTIALS: "PLATFORM_ESSENTIALS",
PLATFORM_SCALE: "PLATFORM_SCALE",
PLATFORM_ENTERPRISE: "PLATFORM_ENTERPRISE",
UNKNOWN: "Unknown",
} as const;

export type BillingPlan = (typeof BILLING_PLANS)[keyof typeof BILLING_PLANS];

export const PLATFORM_PLANS_MAP: Record<string, BillingPlan> = {
FREE: BillingPlan.PLATFORM_STARTER,
STARTER: BillingPlan.PLATFORM_STARTER,
ESSENTIALS: BillingPlan.PLATFORM_ESSENTIALS,
SCALE: BillingPlan.PLATFORM_SCALE,
ENTERPRISE: BillingPlan.PLATFORM_ENTERPRISE,
FREE: BILLING_PLANS.PLATFORM_STARTER,
STARTER: BILLING_PLANS.PLATFORM_STARTER,
ESSENTIALS: BILLING_PLANS.PLATFORM_ESSENTIALS,
SCALE: BILLING_PLANS.PLATFORM_SCALE,
ENTERPRISE: BILLING_PLANS.PLATFORM_ENTERPRISE,
};

export const PLATFORM_ENTERPRISE_SLUGS = process.env.PLATFORM_ENTERPRISE_SLUGS?.split(",") ?? [];
Expand Down
71 changes: 32 additions & 39 deletions packages/features/ee/billing/domain/billing-plans.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BillingPlan,
type BillingPlan,
BILLING_PLANS,
ENTERPRISE_SLUGS,
PLATFORM_ENTERPRISE_SLUGS,
PLATFORM_PLANS_MAP,
Expand Down Expand Up @@ -29,51 +30,43 @@ export class BillingPlanService {
isPlatformManaged: boolean;
};
}[]
) {
if (memberships.length === 0) return BillingPlan.INDIVIDUALS;
): Promise<BillingPlan> {
if (memberships.length === 0) return BILLING_PLANS.INDIVIDUALS;

for (const { team, user } of memberships) {
if (team.isPlatform || user.isPlatformManaged) {
if (PLATFORM_ENTERPRISE_SLUGS.includes(team.slug ?? "")) return BillingPlan.PLATFORM_ENTERPRISE;
if (PLATFORM_ENTERPRISE_SLUGS.includes(team.slug ?? "")) return BILLING_PLANS.PLATFORM_ENTERPRISE;
if (!team.platformBilling) continue;

return PLATFORM_PLANS_MAP[team.platformBilling.plan] ?? team.platformBilling.plan;
} else {
let teamMetadata;
try {
teamMetadata = teamMetadataStrictSchema.parse(team.metadata ?? {});
} catch {
teamMetadata = null;
}

let parentTeamMetadata;
try {
parentTeamMetadata = teamMetadataStrictSchema.parse(team.parent?.metadata ?? {});
} catch {
parentTeamMetadata = null;
}

if (
team.parent &&
team.parent.isOrganization &&
parentTeamMetadata?.subscriptionId &&
!team.parent.isPlatform
) {
return ENTERPRISE_SLUGS.includes(team.parent.slug ?? "")
? BillingPlan.ENTERPRISE
: BillingPlan.ORGANIZATIONS;
}

if (!teamMetadata?.subscriptionId) continue;
if (team.isOrganization) {
return ENTERPRISE_SLUGS.includes(team.slug ?? "")
? BillingPlan.ENTERPRISE
: BillingPlan.ORGANIZATIONS;
} else {
return BillingPlan.TEAMS;
}
}
const parentTeamMetadataResult = teamMetadataStrictSchema.safeParse(team.parent?.metadata ?? {});
const parentTeamMetadata = parentTeamMetadataResult.success ? parentTeamMetadataResult.data : null;
if (
team.parent &&
team.parent.isOrganization &&
parentTeamMetadata?.subscriptionId &&
!team.parent.isPlatform
) {
return ENTERPRISE_SLUGS.includes(team.parent.slug ?? "")
? BILLING_PLANS.ENTERPRISE
: BILLING_PLANS.ORGANIZATIONS;
}
const teamMetadataResult = teamMetadataStrictSchema.safeParse(team.metadata ?? {});
const teamMetadata = teamMetadataResult.success ? teamMetadataResult.data : null;
// (emrysal) if we do an early return on !teamMetadata?.subscriptionId here, the bundler is not smart enough to infer
// that it shouldn't clear out the BILLING_PLANS before the for() scope finishes.
if (team.isOrganization && teamMetadata?.subscriptionId) {
return ENTERPRISE_SLUGS.includes(team.slug ?? "")
? BILLING_PLANS.ENTERPRISE
: BILLING_PLANS.ORGANIZATIONS;
}
if (teamMetadata?.subscriptionId) {
return BILLING_PLANS.TEAMS;
}
// no subscriptionId or parent subscription id in this loop, so this membership hasn't got a plan.
// continue;
}
return BillingPlan.UNKNOWN;
return BILLING_PLANS.UNKNOWN;
}
}
Loading