mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 10:30:00 -06:00
fix: billing checks (#7137)
This commit is contained in:
committed by
GitHub
parent
379a86cf46
commit
0da083a214
@@ -8,6 +8,7 @@ import {
|
||||
getBiggerUploadFileSizePermission,
|
||||
getIsContactsEnabled,
|
||||
getIsMultiOrgEnabled,
|
||||
getIsQuotasEnabled,
|
||||
getIsSamlSsoEnabled,
|
||||
getIsSpamProtectionEnabled,
|
||||
getIsSsoEnabled,
|
||||
@@ -48,6 +49,7 @@ const defaultFeatures: TEnterpriseLicenseFeatures = {
|
||||
auditLogs: false,
|
||||
multiLanguageSurveys: false,
|
||||
accessControl: false,
|
||||
quotas: false,
|
||||
};
|
||||
|
||||
const defaultLicense = {
|
||||
@@ -184,10 +186,10 @@ describe("License Utils", () => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should return true if license active but accessControl feature disabled because of fallback", async () => {
|
||||
test("should return false if license active but accessControl feature disabled (self-hosted)", async () => {
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue(defaultLicense);
|
||||
const result = await getAccessControlPermission(mockOrganization.billing.plan);
|
||||
expect(result).toBe(true);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("should return false if license is inactive", async () => {
|
||||
@@ -273,10 +275,10 @@ describe("License Utils", () => {
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should return true if license active but multiLanguageSurveys feature disabled because of fallback", async () => {
|
||||
test("should return false if license active but multiLanguageSurveys feature disabled (self-hosted)", async () => {
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue(defaultLicense);
|
||||
const result = await getMultiLanguagePermission(mockOrganization.billing.plan);
|
||||
expect(result).toBe(true);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("should return false if license is inactive", async () => {
|
||||
@@ -289,6 +291,54 @@ describe("License Utils", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getIsQuotasEnabled", () => {
|
||||
test("should return true if license active and quotas feature enabled (self-hosted)", async () => {
|
||||
vi.mocked(constants).IS_FORMBRICKS_CLOUD = false;
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue({
|
||||
...defaultLicense,
|
||||
features: { ...defaultFeatures, quotas: true },
|
||||
});
|
||||
const result = await getIsQuotasEnabled(mockOrganization.billing.plan);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should return true if license active, quotas enabled and plan is CUSTOM (cloud)", async () => {
|
||||
vi.mocked(constants).IS_FORMBRICKS_CLOUD = true;
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue({
|
||||
...defaultLicense,
|
||||
features: { ...defaultFeatures, quotas: true },
|
||||
});
|
||||
const result = await getIsQuotasEnabled(constants.PROJECT_FEATURE_KEYS.CUSTOM);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should return false if license active, quotas enabled but plan is not CUSTOM (cloud)", async () => {
|
||||
vi.mocked(constants).IS_FORMBRICKS_CLOUD = true;
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue({
|
||||
...defaultLicense,
|
||||
features: { ...defaultFeatures, quotas: true },
|
||||
});
|
||||
const result = await getIsQuotasEnabled(constants.PROJECT_FEATURE_KEYS.STARTUP);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("should return false if license active but quotas feature disabled (self-hosted)", async () => {
|
||||
vi.mocked(constants).IS_FORMBRICKS_CLOUD = false;
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue(defaultLicense);
|
||||
const result = await getIsQuotasEnabled(mockOrganization.billing.plan);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("should return false if license is inactive", async () => {
|
||||
vi.mocked(licenseModule.getEnterpriseLicense).mockResolvedValue({
|
||||
...defaultLicense,
|
||||
active: false,
|
||||
});
|
||||
const result = await getIsQuotasEnabled(mockOrganization.billing.plan);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getIsMultiOrgEnabled", () => {
|
||||
test("should return true if feature flag isMultiOrgEnabled is true", async () => {
|
||||
vi.mocked(licenseModule.getLicenseFeatures).mockResolvedValue({
|
||||
|
||||
@@ -10,6 +10,8 @@ import { TEnterpriseLicenseFeatures } from "@/modules/ee/license-check/types/ent
|
||||
import { getEnterpriseLicense, getLicenseFeatures } from "./license";
|
||||
|
||||
// Helper function for feature permissions (e.g., removeBranding, whitelabel)
|
||||
// On Cloud: requires active license and non-FREE plan
|
||||
// On Self-hosted: requires active license and feature enabled
|
||||
const getFeaturePermission = async (
|
||||
billingPlan: Organization["billing"]["plan"],
|
||||
featureKey: keyof Pick<TEnterpriseLicenseFeatures, "removeBranding" | "whitelabel">
|
||||
@@ -23,6 +25,41 @@ const getFeaturePermission = async (
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function for enterprise features that require CUSTOM plan on Cloud
|
||||
// On Cloud: requires active license AND feature enabled in license AND CUSTOM billing plan
|
||||
// On Self-hosted: requires active license AND feature enabled in license
|
||||
const getCustomPlanFeaturePermission = async (
|
||||
billingPlan: Organization["billing"]["plan"],
|
||||
featureKey: keyof Pick<TEnterpriseLicenseFeatures, "accessControl" | "multiLanguageSurveys" | "quotas">
|
||||
): Promise<boolean> => {
|
||||
const license = await getEnterpriseLicense();
|
||||
|
||||
if (!license.active) return false;
|
||||
|
||||
const isFeatureEnabled = license.features?.[featureKey] ?? false;
|
||||
if (!isFeatureEnabled) return false;
|
||||
|
||||
if (IS_FORMBRICKS_CLOUD) {
|
||||
return billingPlan === PROJECT_FEATURE_KEYS.CUSTOM;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Helper function for license-only feature flags (no billing plan check)
|
||||
// Returns true only if the license is active AND the specific feature is enabled in the license
|
||||
// Used for features that are controlled purely by the license key, not billing plans
|
||||
const getSpecificFeatureFlag = async (
|
||||
featureKey: keyof Pick<
|
||||
TEnterpriseLicenseFeatures,
|
||||
"isMultiOrgEnabled" | "contacts" | "twoFactorAuth" | "sso" | "auditLogs"
|
||||
>
|
||||
): Promise<boolean> => {
|
||||
const licenseFeatures = await getLicenseFeatures();
|
||||
if (!licenseFeatures) return false;
|
||||
return typeof licenseFeatures[featureKey] === "boolean" ? licenseFeatures[featureKey] : false;
|
||||
};
|
||||
|
||||
export const getRemoveBrandingPermission = async (
|
||||
billingPlan: Organization["billing"]["plan"]
|
||||
): Promise<boolean> => {
|
||||
@@ -45,24 +82,6 @@ export const getBiggerUploadFileSizePermission = async (
|
||||
return false;
|
||||
};
|
||||
|
||||
const getSpecificFeatureFlag = async (
|
||||
featureKey: keyof Pick<
|
||||
TEnterpriseLicenseFeatures,
|
||||
| "isMultiOrgEnabled"
|
||||
| "contacts"
|
||||
| "twoFactorAuth"
|
||||
| "sso"
|
||||
| "auditLogs"
|
||||
| "multiLanguageSurveys"
|
||||
| "accessControl"
|
||||
| "quotas"
|
||||
>
|
||||
): Promise<boolean> => {
|
||||
const licenseFeatures = await getLicenseFeatures();
|
||||
if (!licenseFeatures) return false;
|
||||
return typeof licenseFeatures[featureKey] === "boolean" ? licenseFeatures[featureKey] : false;
|
||||
};
|
||||
|
||||
export const getIsMultiOrgEnabled = async (): Promise<boolean> => {
|
||||
return getSpecificFeatureFlag("isMultiOrgEnabled");
|
||||
};
|
||||
@@ -80,12 +99,7 @@ export const getIsSsoEnabled = async (): Promise<boolean> => {
|
||||
};
|
||||
|
||||
export const getIsQuotasEnabled = async (billingPlan: Organization["billing"]["plan"]): Promise<boolean> => {
|
||||
const isEnabled = await getSpecificFeatureFlag("quotas");
|
||||
// If the feature is enabled in the license, return true
|
||||
if (isEnabled) return true;
|
||||
|
||||
// If the feature is not enabled in the license, check the fallback(Backwards compatibility)
|
||||
return featureFlagFallback(billingPlan);
|
||||
return getCustomPlanFeaturePermission(billingPlan, "quotas");
|
||||
};
|
||||
|
||||
export const getIsAuditLogsEnabled = async (): Promise<boolean> => {
|
||||
@@ -118,33 +132,16 @@ export const getIsSpamProtectionEnabled = async (
|
||||
return license.active && !!license.features?.spamProtection;
|
||||
};
|
||||
|
||||
const featureFlagFallback = async (billingPlan: Organization["billing"]["plan"]): Promise<boolean> => {
|
||||
const license = await getEnterpriseLicense();
|
||||
if (IS_FORMBRICKS_CLOUD) return license.active && billingPlan === PROJECT_FEATURE_KEYS.CUSTOM;
|
||||
else if (!IS_FORMBRICKS_CLOUD) return license.active;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getMultiLanguagePermission = async (
|
||||
billingPlan: Organization["billing"]["plan"]
|
||||
): Promise<boolean> => {
|
||||
const isEnabled = await getSpecificFeatureFlag("multiLanguageSurveys");
|
||||
// If the feature is enabled in the license, return true
|
||||
if (isEnabled) return true;
|
||||
|
||||
// If the feature is not enabled in the license, check the fallback(Backwards compatibility)
|
||||
return featureFlagFallback(billingPlan);
|
||||
return getCustomPlanFeaturePermission(billingPlan, "multiLanguageSurveys");
|
||||
};
|
||||
|
||||
export const getAccessControlPermission = async (
|
||||
billingPlan: Organization["billing"]["plan"]
|
||||
): Promise<boolean> => {
|
||||
const isEnabled = await getSpecificFeatureFlag("accessControl");
|
||||
// If the feature is enabled in the license, return true
|
||||
if (isEnabled) return true;
|
||||
|
||||
// If the feature is not enabled in the license, check the fallback(Backwards compatibility)
|
||||
return featureFlagFallback(billingPlan);
|
||||
return getCustomPlanFeaturePermission(billingPlan, "accessControl");
|
||||
};
|
||||
|
||||
export const getOrganizationProjectsLimit = async (
|
||||
|
||||
Reference in New Issue
Block a user