Feature #11983
openSupport #11764: E-commerce pending tasks for nov 15 release
Feature #11610: Free Trial Integration in Company on boarding
Cancellation for tenants and control from platform settings
0%
Description
getTenantBillingSummary properly falls back to the platform setting and returns allowTenantSelfCancellation to the frontend?
cancelTenantSubscription honors both the per-tenant override and the platform fallback?
Any relevant unit tests are updated/added
when creating a new tenant, allowTenantSelfCancellation should defaults to the company setting allowTenantSelfCancellation value... is this correct way of configuration ?
The short answer is no, copying/freezing the value at creation time is generally an anti-pattern. Instead, the best practice for multi-tenant SaaS architecture is Hierarchical Inheritance (Cascading 3-State Configuration).
Why copying at tenant creation causes issues
If you copy the platform/company default into newTenant.saasBilling.allowTenantSelfCancellation at creation time:
Loss of Global Policy Control:
If the platform superadmin later changes the platform setting in PlatformBillingSettingsPage.tsx (which advertises: "Platform billing settings updated successfully across all tenants"), none of the existing tenants will reflect the change because their values were hardcoded on creation.
Database Migration Overhead:
Any time policy changes globally, you would need to run a batch database migration across all tenant documents instead of simply changing a single platform-level setting.
The Recommended Pattern: Hierarchical Inheritance (3-State)
Instead of hardcoding a boolean at creation, leave the tenant setting as null (unset), and dynamically resolve the effective value using a fallback chain:
Effective Value
=
Tenant Override
(
if not null
)
⟶
Platform/Company Setting
⟶
Hardcoded Fallback (false)
Effective Value=Tenant Override(if not null)⟶Platform/Company Setting⟶Hardcoded Fallback (false)
In this model:
null (Default for every new tenant)
→
→ "Inherit Global Platform Setting".
true
→
→ Explicit Override: Force-enabled for this specific tenant (even if platform default is disabled).
false
→
→ Explicit Override: Force-disabled for this specific tenant (even if platform default is enabled).
Evidence Already in Your Codebase
Your codebase was actually designed for this hierarchical model:
In company-model.js:
javascript
// Line 1059: Tenant-level override defaults to null
saasBilling: {
allowTenantSelfCancellation: { type: Boolean, default: null },
},
// Line 1079: Platform-level default
billingSettings: {
allowTenantSelfCancellation: { type: Boolean, default: false },
}
In billing.controller.js:
toggleTenantSelfCancellation specifically allows superadmins to override this per company.
Current Inconsistencies to Fix in Your Logic
While the schema was designed for hierarchical inheritance, a few places in tenantBilling.service.js have conflicting or incomplete resolution logic:
1. In resolveBillingContext (tenantBilling.service.js#L586):
It currently does:
javascript
// Boolean(null) becomes false, ignoring the global setting!
allowTenantSelfCancellation: billing.status === "trialing" || Boolean(billing.allowTenantSelfCancellation),
2. In cancelTenantSubscription (tenantBilling.service.js#L3440-L3446):
It currently checks only globalSettings.allowTenantSelfCancellation and ignores company.saasBilling?.allowTenantSelfCancellation. If a superadmin overrides a specific tenant to allow self-cancellation, the backend still blocks them.
3. In getTenantBillingSummary (tenantBilling.service.js#L1780-L1903):
PlanUsagePage.tsx#L638 expects data.allowTenantSelfCancellation at the top level of the API response, but getTenantBillingSummary defines allowTenantSelfCancellation on line 1781 and never attaches it to the return object.
Suggested Helper for Resolution
Create a unified helper in tenantBilling.service.js:
javascript
const resolveAllowTenantSelfCancellation = async (company) => {
// Always allow cancellation during trialing period
if (company.saasBilling?.status === "trialing") return true;
// 1. Check tenant explicit override (true / false)
if (company.saasBilling?.allowTenantSelfCancellation != null) {
return Boolean(company.saasBilling.allowTenantSelfCancellation);
}
// 2. Fall back to platform global default
const globalSettings = await getPlatformBillingSettings();
return Boolean(globalSettings.allowTenantSelfCancellation);
};
Summary Recommendation
Do not copy the setting to the tenant record on creation; leave it as null.
Use the fallback/inheritance pattern at runtime so platform-wide changes take effect automatically, while preserving the ability for superadmins to set specific per-tenant overrides.
Subtasks
Related issues