Editions
Named subscription plans with feature bundles, overflow policies, versioning, and rollout strategies.
Editions are named plans (e.g. Basic, Pro, Enterprise) that bundle feature values together. Each tenant subscribes to an edition, which determines their feature access. Editions support versioning with controlled rollout strategies for safe deployment of changes.
Edition Entity
An Edition is a named plan that bundles feature values. System editions are created by platform admins; retail editions are created by reseller tenants for their child tenants.
| Property | Type | Description |
|---|---|---|
| Id | Guid | Primary key (auto-generated) |
| Name | string | Unique internal slug per platform (e.g. 'general-free', 'general-pro') |
| DisplayNameEn / DisplayNameAr | string? | Localized display titles shown in UI and pricing pages |
| Description | string? | Marketing description for the edition |
| Tagline | string? | Short marketing tagline (e.g. 'Best for growing teams') |
| RecommendationLabels | string? | JSON array of badge labels (e.g. ['Best Value', 'Most Popular']) |
| TierLevel | int | Hierarchical level (0 = Free, 1+ = premium) — used for upgrade/downgrade logic |
| IsSystem | bool | true = system-defined platform plan. false = reseller-defined retail plan |
| IsRetired | bool | When true, no new tenant assignments allowed (soft retirement) |
| EditionCategoryId | Guid? | Optional grouping for the plan picker UI on the signup flow |
| FallbackEditionId | Guid? | Downgrade target on subscription expiry. Null = tenant is suspended |
| OverflowPolicy | OverflowPolicy enum | Block | Warn | Allow — governs behavior when resource counts exceed new limits after downgrade |
| AllowMonthly / AllowYearly / AllowLifetime / AllowTrial | bool | Billing cycle toggles — controls which billing modes are available for this edition |
| IsSelfServiceEnabled | bool | When true, tenants can self-subscribe via Stripe Checkout. False = sales-assisted only |
| IsContactSalesOnly | bool | Enterprise plans where no Stripe Checkout is presented — sales contact required |
| IsFree | bool (computed) | Derived: true when no billing cycles AND no trial AND IsContactSalesOnly is false |
| TrialDurationDays | int | Length of the trial period in days (0 = no trial). Range: 1–730 |
| TrialIsFree | bool | true = 100% free trial. false = discounted trial (see TrialDiscountPercent) |
| TrialDiscountPercent | int | Discount during trial (0–100). Only meaningful when TrialIsFree = false |
| GracePeriodDays | int | Days after payment failure before subscription is suspended |
| MaxActiveSubscriptions | int | Global capacity cap for this edition. -1 = unlimited |
| CurrentVersion | int | Auto-incremented version number — incremented each time features are published |
| ConnectCommissionRate | decimal? | Stripe Connect platform commission rate. Null = use global default |
| StripeProductId | string? | Stripe product catalog ID — null until first sync |
| CreatedAt / UpdatedAt | DateTime | Audit timestamps |
public class Edition : AuditableEntity<Guid>
{
public string Name { get; set; } = null!; // internal slug (e.g. "basic", "pro")
public string? DisplayNameEn { get; set; } // localized display name
public string? DisplayNameAr { get; set; }
public string? Description { get; set; }
public string? Tagline { get; set; } // short marketing tagline
public string? RecommendationLabels { get; set; } // JSON badge labels (e.g. ["Best Value"])
public int TierLevel { get; set; } // 0 = Free, 1+ = premium
public bool IsSystem { get; set; }
public bool IsRetired { get; set; } // no new assignments allowed
public Guid? EditionCategoryId { get; set; } // grouping for signup plan picker
public Guid? FallbackEditionId { get; set; }
public OverflowPolicy OverflowPolicy { get; set; } = OverflowPolicy.Block;
// Billing Toggles
public bool AllowMonthly { get; set; } = true;
public bool AllowYearly { get; set; } = true;
public bool AllowLifetime { get; set; } = true;
public bool AllowTrial { get; set; } = true;
public bool IsSelfServiceEnabled { get; set; } = true;
public bool IsContactSalesOnly { get; set; }
// Computed: free when no billing types enabled
public bool IsFree => !AllowTrial && !AllowMonthly && !AllowYearly && !AllowLifetime && !IsContactSalesOnly;
// Trial Configuration
[Range(1, 730)]
public int TrialDurationDays { get; set; } = 14;
public bool TrialIsFree { get; set; } = true;
[Range(0, 100)]
public int TrialDiscountPercent { get; set; } = 100; // int, not decimal
public int GracePeriodDays { get; set; } = 0;
public int MaxActiveSubscriptions { get; set; } = -1; // -1 = unlimited
public int CurrentVersion { get; set; } = 1;
public decimal? ConnectCommissionRate { get; set; } // Stripe Connect commission (null = global default)
public string? StripeProductId { get; set; } // null until first catalog sync
public virtual ICollection<EditionFeature> Features { get; set; } = new List<EditionFeature>();
public virtual ICollection<EditionPrice> Prices { get; set; } = new List<EditionPrice>();
public virtual ICollection<EditionVersion> Versions { get; set; } = new List<EditionVersion>();
public ICollection<EditionPromotion> Promotions { get; set; } = new List<EditionPromotion>();
}System vs Retail Editions
SCRIPE supports two types of editions: System editions created by platform admins visible to all tenants, and Retail editions created by reseller tenants for their child tenants only.
| Type | Created By | Visible To | Can Subscribe | Use Case |
|---|---|---|---|---|
| System Edition | Platform SuperAdmin | All tenants | Any tenant | Standard platform plans (Basic, Pro, Enterprise) |
| Retail Edition | Reseller tenant | Creator + child tenants | Child tenants only | Reseller creating custom plans for sub-tenants |
Seeded System Editions
The platform seeds two standard system editions out of the box on startup via EditionSeeder, establishing default limits for features.
| Edition | Description | Included Features & Limits |
|---|---|---|
| Free | TierLevel = 0. Default fallback plan for all tenants upon subscription expiry. Permanent $0 plan. | 5 Admins, 3 Roles, 1 User Group, 0 child tenants, 256MB storage, 10MB upload limit. Two-Factor Authentication and External Auth are disabled. |
| Standard | TierLevel = 1. Premium plan enabling advanced administrative and organization capabilities. | 50 Admins, 10 Roles, 5 User Groups, 5 child tenants, 5GB storage, 25MB upload limit. Two-Factor Authentication and External Auth are enabled. |
Overflow Policy
When a tenant downgrades to an edition with lower limits, their existing resources may exceed the new limits. The OverflowPolicy determines what happens:
| Policy | Behavior | When to Use |
|---|---|---|
| Block | Prevents downgrade if any feature would overflow. Returns 409 Conflict with details. | Strict enforcement — tenant must clean up first |
| Warn | Allows the downgrade but logs a warning. Existing resources above the new limit continue to work, but new ones are blocked. | Soft enforcement — graceful transition period |
| Allow | Silently allows the downgrade. No restrictions on existing resources. | Lenient — existing resources are grandfathered |
public enum OverflowPolicy
{
/// <summary>Block downgrade if any resource exceeds the new limit</summary>
Block = 0,
/// <summary>Allow downgrade but warn; existing resources grandfathered</summary>
Warn = 1,
/// <summary>Silently allow downgrade; no enforcement on existing data</summary>
Allow = 2,
}Edition Features
Each edition contains a set of EditionFeature records that map features to their values within that plan. Features not explicitly set in an edition fall back to the Feature.DefaultValue.
| Property | Type | Description |
|---|---|---|
| EditionId | Guid | FK to the parent Edition |
| FeatureId | Guid | FK to the Feature being configured |
| Value | string | The feature value for this edition (interpreted by Feature.ValueType) |
public class EditionFeature
{
public Guid EditionId { get; set; }
public Edition Edition { get; set; } = null!;
public Guid FeatureId { get; set; }
public Feature Feature { get; set; } = null!;
public string Value { get; set; } = string.Empty;
// Interpreted as: bool (Boolean), int (Numeric), or string (String)
}Edition Versions
Edition Versions provide a versioning and rollout system for feature changes. Instead of modifying features directly, admins can create a new version (snapshot), choose a rollout strategy, and publish it.
| Property | Type | Description |
|---|---|---|
| Id | Guid | Version identifier |
| EditionId | Guid | FK to parent Edition |
| VersionNumber | int | Auto-incrementing version number (1, 2, 3...) |
| FeaturesSnapshot | JSON | Complete snapshot of all feature values at this version |
| ChangeNotes | string? | Description of what changed |
| RolloutStrategy | enum | How to deploy: Immediate, Gradual, Manual |
| Status | enum | Draft, Published, RolledBack |
| PublishedAt | DateTime? | When the version was published |
| PublishedBy | Guid? | Who published the version |
| CreatedAt | DateTime | When the version was created |
Rollout Strategies
When publishing an edition version, admins choose how the changes are deployed to subscribed tenants:
| Strategy | Behavior | Use Case |
|---|---|---|
| Immediate | All subscribed tenants receive the new feature values instantly upon publish | Urgent fixes, security patches, small changes |
| Gradual | Tenants are migrated in batches (configurable batch size and delay) | Large changes where you want to monitor impact |
| Manual | Tenants must be manually assigned the new version via API | Controlled rollouts, beta programs, VIP tenants first |
Apply Now vs Save as Version
SCRIPE provides two ways to update edition features, each suited for different scenarios:
| Apply Now (Direct) | Save as Version | |
|---|---|---|
| Speed | Instant — changes take effect immediately | Requires create → review → publish |
| Audit Trail | Logged as a single mutation | Full version history with change notes |
| Rollback | Must manually revert changes | One-click rollback to any previous version |
| Rollout Control | All-or-nothing | Immediate, Gradual, or Manual per-tenant |
| Best For | Urgent fixes, small tweaks | Major plan updates, feature launches |
API Endpoints
The Editions controller exposes 11 endpoints for managing editions, their features, and version lifecycle:
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/editions | List all editions (paginated, filterable) | 🔒 Required |
| GET | /api/v1/editions/{id} | Get edition details by ID | 🔒 Required |
| POST | /api/v1/editions | Create a new edition | 🔒 Required |
| PUT | /api/v1/editions/{id} | Update edition metadata | 🔒 Required |
| DELETE | /api/v1/editions/{id} | Soft-delete an edition | 🔒 Required |
| GET | /api/v1/editions/{id}/features | List features configured for this edition | 🔒 Required |
| PUT | /api/v1/editions/{id}/features | Set/update features for this edition | 🔒 Required |
| POST | /api/v1/editions/{id}/features/apply | Apply feature changes immediately (no versioning) | 🔒 Required |
| GET | /api/v1/editions/{id}/versions | List all versions for this edition | 🔒 Required |
| POST | /api/v1/editions/{id}/versions | Create a new draft version with feature snapshot | 🔒 Required |
| POST | /api/v1/editions/{id}/versions/{vId}/publish | Publish a draft version with chosen rollout strategy | 🔒 Required |