# 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 ```python 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`: ```python 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`: ```python 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 ```python 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 ```python 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: ```python 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): ```json { "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`: ```javascript // 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`: ```javascript // 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 ```jinja2 {% from "shared/macros/feature_gate.html" import feature_gate %} {% call feature_gate("analytics_dashboard") %}
Analytics content here - only visible if feature available
{% endcall %} ``` #### Feature Locked Card ```jinja2 {% from "shared/macros/feature_gate.html" import feature_locked %} {{ feature_locked("advanced_analytics", "Advanced Analytics", "Get deeper insights") }} ``` #### Upgrade Banner ```jinja2 {% from "shared/macros/feature_gate.html" import upgrade_banner %} {{ upgrade_banner("custom_domain") }} ``` #### Usage Limit Warning ```jinja2 {% from "shared/macros/feature_gate.html" import limit_warning %} {{ limit_warning("orders") }} {# Shows warning when approaching limit #} ``` #### Usage Progress Bar ```jinja2 {% from "shared/macros/feature_gate.html" import usage_bar %} {{ usage_bar("products", "Products") }} ``` #### Tier Badge ```jinja2 {% 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: ```bash 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()` ## Related Documentation - [Subscription Billing](../features/subscription-billing.md) - Core subscription system - [Subscription Workflow Plan](./subscription-workflow-plan.md) - Implementation roadmap