Files
orion/docs/implementation/feature-gating-system.md
Samir Boulahtit e9253fbd84 refactor: rename Wizamart to Orion across entire codebase
Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:46:56 +01:00

13 KiB

Feature Gating System

Overview

The feature gating system provides tier-based access control for platform features. It allows restricting functionality based on store subscription tiers (Essential, Professional, Business, Enterprise) with contextual upgrade prompts when features are locked.

Implemented: December 31, 2025

Architecture

Database Models

Located in models/database/feature.py:

Model Purpose
Feature Feature definitions with tier requirements
StoreFeatureOverride Per-store feature overrides (enable/disable)

Feature Model Structure

class Feature(Base):
    __tablename__ = "features"

    id: int                    # Primary key
    code: str                  # Unique feature code (e.g., "analytics_dashboard")
    name: str                  # Display name
    description: str           # User-facing description
    category: str              # Feature category
    minimum_tier_code: str     # Minimum tier required (essential/professional/business/enterprise)
    minimum_tier_order: int    # Tier order for comparison (1-4)
    is_active: bool           # Whether feature is available
    created_at: datetime
    updated_at: datetime

Tier Ordering

Tier Order Code
Essential 1 essential
Professional 2 professional
Business 3 business
Enterprise 4 enterprise

Feature Categories

30 features organized into 8 categories:

1. Analytics

Feature Code Name Min Tier
basic_analytics Basic Analytics Essential
analytics_dashboard Analytics Dashboard Professional
advanced_analytics Advanced Analytics Business
custom_reports Custom Reports Enterprise

2. Product Management

Feature Code Name Min Tier
basic_products Product Management Essential
bulk_product_edit Bulk Product Edit Professional
product_variants Product Variants Professional
product_bundles Product Bundles Business
inventory_alerts Inventory Alerts Professional

3. Order Management

Feature Code Name Min Tier
basic_orders Order Management Essential
order_automation Order Automation Professional
advanced_fulfillment Advanced Fulfillment Business
multi_warehouse Multi-Warehouse Enterprise

4. Marketing

Feature Code Name Min Tier
discount_codes Discount Codes Professional
abandoned_cart Abandoned Cart Recovery Business
email_marketing Email Marketing Business
loyalty_program Loyalty Program Enterprise

5. Support

Feature Code Name Min Tier
basic_support Email Support Essential
priority_support Priority Support Professional
phone_support Phone Support Business
dedicated_manager Dedicated Account Manager Enterprise

6. Integration

Feature Code Name Min Tier
basic_api Basic API Access Professional
advanced_api Advanced API Access Business
webhooks Webhooks Business
custom_integrations Custom Integrations Enterprise

7. Branding

Feature Code Name Min Tier
basic_theme Theme Customization Essential
custom_domain Custom Domain Professional
white_label White Label Enterprise
custom_checkout Custom Checkout Enterprise

8. Team

Feature Code Name Min Tier
team_management Team Management Professional
role_permissions Role Permissions Business
audit_logs Audit Logs Business

Services

FeatureService

Located in app/services/feature_service.py:

class FeatureService:
    """Service for managing tier-based feature access."""

    # In-memory caching (refreshed every 5 minutes)
    _feature_cache: dict[str, Feature] = {}
    _cache_timestamp: datetime | None = None
    CACHE_TTL_SECONDS = 300

    def has_feature(self, db: Session, store_id: int, feature_code: str) -> bool:
        """Check if store has access to a feature."""

    def get_available_features(self, db: Session, store_id: int) -> list[str]:
        """Get list of feature codes available to store."""

    def get_all_features_with_status(self, db: Session, store_id: int) -> list[dict]:
        """Get all features with availability status for store."""

    def get_feature_info(self, db: Session, feature_code: str) -> dict | None:
        """Get full feature information including tier requirements."""

UsageService

Located in app/services/usage_service.py:

class UsageService:
    """Service for tracking and managing store usage against tier limits."""

    def get_usage_summary(self, db: Session, store_id: int) -> dict:
        """Get comprehensive usage summary with limits and upgrade info."""

    def check_limit(self, db: Session, store_id: int, limit_type: str) -> dict:
        """Check specific limit with detailed info."""

    def get_upgrade_info(self, db: Session, store_id: int) -> dict:
        """Get upgrade recommendations based on current usage."""

Backend Enforcement

Decorator Pattern

from app.core.feature_gate import require_feature

@router.get("/analytics/advanced")
@require_feature("advanced_analytics")
async def get_advanced_analytics(
    db: Session = Depends(get_db),
    store_id: int = Depends(get_current_store_id)
):
    # Only accessible if store has advanced_analytics feature
    pass

Dependency Pattern

from app.core.feature_gate import RequireFeature

@router.get("/marketing/loyalty")
async def get_loyalty_program(
    db: Session = Depends(get_db),
    _: None = Depends(RequireFeature("loyalty_program"))
):
    # Only accessible if store has loyalty_program feature
    pass

Exception Handling

When a feature is not available, FeatureNotAvailableException is raised:

class FeatureNotAvailableException(Exception):
    def __init__(self, feature_code: str, required_tier: str):
        self.feature_code = feature_code
        self.required_tier = required_tier
        super().__init__(f"Feature '{feature_code}' requires {required_tier} tier")

HTTP Response (403):

{
    "detail": "Feature 'advanced_analytics' requires Professional tier or higher",
    "feature_code": "advanced_analytics",
    "required_tier": "Professional",
    "upgrade_url": "/store/orion/billing"
}

API Endpoints

Store Features API

Base: /api/v1/store/features

Endpoint Method Description
/features/available GET List available feature codes
/features GET All features with availability status
/features/{code} GET Single feature info
/features/{code}/check GET Quick availability check

Store Usage API

Base: /api/v1/store/usage

Endpoint Method Description
/usage GET Full usage summary with limits
/usage/check/{limit_type} GET Check specific limit (orders/products/team_members)
/usage/upgrade-info GET Upgrade recommendations

Admin Features API

Base: /api/v1/admin/features

Endpoint Method Description
/features GET List all features
/features/{id} GET Get feature details
/features/{id} PUT Update feature
/features/{id}/toggle POST Toggle feature active status
/features/stores/{store_id}/overrides GET Get store overrides
/features/stores/{store_id}/overrides POST Create override

Frontend Integration

Alpine.js Feature Store

Located in static/shared/js/feature-store.js:

// Usage in templates
$store.features.has('analytics_dashboard')  // Check feature
$store.features.loaded                       // Loading state
$store.features.getFeature('advanced_api')   // Get feature details

Alpine.js Upgrade Store

Located in static/shared/js/upgrade-prompts.js:

// Usage in templates
$store.upgrade.shouldShowLimitWarning('orders')
$store.upgrade.getUsageString('products')
$store.upgrade.hasUpgradeRecommendation

Jinja2 Macros

Located in app/templates/shared/macros/feature_gate.html:

Feature Gate Container

{% from "shared/macros/feature_gate.html" import feature_gate %}

{% call feature_gate("analytics_dashboard") %}
    <div>Analytics content here - only visible if feature available</div>
{% endcall %}

Feature Locked Card

{% from "shared/macros/feature_gate.html" import feature_locked %}

{{ feature_locked("advanced_analytics", "Advanced Analytics", "Get deeper insights") }}

Upgrade Banner

{% from "shared/macros/feature_gate.html" import upgrade_banner %}

{{ upgrade_banner("custom_domain") }}

Usage Limit Warning

{% from "shared/macros/feature_gate.html" import limit_warning %}

{{ limit_warning("orders") }}  {# Shows warning when approaching limit #}

Usage Progress Bar

{% from "shared/macros/feature_gate.html" import usage_bar %}

{{ usage_bar("products", "Products") }}

Tier Badge

{% from "shared/macros/feature_gate.html" import tier_badge %}

{{ tier_badge() }}  {# Shows current tier as colored badge #}

Store Dashboard Integration

The store dashboard (/store/{code}/dashboard) now includes:

  1. Tier Badge: Shows current subscription tier in header
  2. Usage Bars: Visual progress bars for orders, products, team members
  3. Upgrade Prompts: Contextual upgrade recommendations when approaching limits
  4. Feature Gates: Locked sections for premium features

Admin Features Page

Located at /admin/features:

  • View all 30 features in categorized table
  • Toggle features on/off globally
  • Filter by category
  • Search by name/code
  • View tier requirements

Admin Tier Management UI

Located at /admin/subscription-tiers:

Overview

The subscription tiers admin page provides full CRUD functionality for managing subscription tiers and their feature assignments.

Features

  1. Stats Cards: Display total tiers, active tiers, public tiers, and estimated MRR

  2. Tier Table: Sortable list of all tiers with:

    • Display order
    • Code (colored badge by tier)
    • Name
    • Monthly/Annual pricing
    • Feature count
    • Status (Active/Private/Inactive)
    • Actions (Edit Features, Edit, Activate/Deactivate)
  3. Create/Edit Modal: Form with all tier fields:

    • Code and Name
    • Monthly and Annual pricing (in cents)
    • Display order
    • Stripe IDs (optional)
    • Description
    • Active/Public toggles
  4. Feature Assignment Slide-over Panel:

    • Opens when clicking the puzzle-piece icon
    • Shows all features grouped by category
    • Binary features: checkbox selection with Select all/Deselect all per category
    • Quantitative features: checkbox + numeric limit input for limit_value
    • Feature count in footer
    • Save to update tier's feature assignments via TierFeatureLimitEntry[]

Files

File Purpose
app/templates/admin/subscription-tiers.html Page template
static/admin/js/subscription-tiers.js Alpine.js component
app/routes/admin_pages.py Route registration

API Endpoints Used

Action Method Endpoint
Load tiers GET /api/v1/admin/subscriptions/tiers
Load stats GET /api/v1/admin/subscriptions/stats
Create tier POST /api/v1/admin/subscriptions/tiers
Update tier PATCH /api/v1/admin/subscriptions/tiers/{code}
Delete tier DELETE /api/v1/admin/subscriptions/tiers/{code}
Load feature catalog GET /api/v1/admin/subscriptions/features/catalog
Get tier feature limits GET /api/v1/admin/subscriptions/features/tiers/{code}/limits
Update tier feature limits PUT /api/v1/admin/subscriptions/features/tiers/{code}/limits

Migration

The features are seeded via Alembic migration:

alembic/versions/n2c3d4e5f6a7_add_features_table.py

This creates:

  • features table with 30 default features
  • store_feature_overrides table for per-store exceptions

Testing

Unit tests located in:

  • tests/unit/services/test_feature_service.py
  • tests/unit/services/test_usage_service.py

Run tests:

pytest tests/unit/services/test_feature_service.py -v
pytest tests/unit/services/test_usage_service.py -v

Architecture Compliance

All JavaScript files follow architecture rules:

  • JS-003: Alpine components use store* naming convention
  • JS-005: Init guards prevent duplicate initialization
  • JS-006: Async operations have try/catch error handling
  • JS-008: API calls use apiClient (not raw fetch())
  • JS-009: Notifications use Utils.showToast()