Files
bakery-ia/docs/SUBSCRIPTION_ARCHITECTURE_REDESIGN.md
2026-01-16 15:19:34 +01:00

16 KiB

Subscription System Architecture Redesign

Overview

This document outlines a comprehensive redesign of the subscription system architecture to improve organization, maintainability, and separation of concerns while respecting the existing service-based client architecture.

Current Issues

  1. Mixed Concerns: Subscription endpoints are scattered across different API files
  2. Inconsistent URL Patterns: Mixed use of path vs query parameters for tenant IDs
  3. Poor Organization: Subscription operations mixed with tenant management operations
  4. Middleware Dependencies: Hardcoded URLs that need updating
  5. Client Layer Confusion: Tenant client contains many subscription-specific methods

New Architecture Design

API Structure & URL Patterns

Public Endpoints (No Authentication)

GET    /api/v1/plans                          - Get all available plans
GET    /api/v1/plans/{tier}                  - Get specific plan details
GET    /api/v1/plans/{tier}/features         - Get plan features
GET    /api/v1/plans/{tier}/limits           - Get plan limits
GET    /api/v1/plans/compare                 - Compare all plans

Registration Flow (No Tenant Context)

POST   /api/v1/registration/payment-setup    - Start registration with payment
POST   /api/v1/registration/complete         - Complete registration after 3DS
GET    /api/v1/registration/state/{state_id} - Check registration state

Tenant-Dependent Subscription Endpoints

GET    /api/v1/tenants/{tenant_id}/subscription/status         - Get subscription status
GET    /api/v1/tenants/{tenant_id}/subscription/details        - Get full subscription details
GET    /api/v1/tenants/{tenant_id}/subscription/tier           - Get subscription tier (cached)
GET    /api/v1/tenants/{tenant_id}/subscription/limits         - Get subscription limits
GET    /api/v1/tenants/{tenant_id}/subscription/usage          - Get usage summary
GET    /api/v1/tenants/{tenant_id}/subscription/features/{feature} - Check feature access

# Subscription Management
POST   /api/v1/tenants/{tenant_id}/subscription/cancel         - Cancel subscription
POST   /api/v1/tenants/{tenant_id}/subscription/reactivate     - Reactivate subscription
GET    /api/v1/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan} - Validate upgrade
POST   /api/v1/tenants/{tenant_id}/subscription/upgrade        - Upgrade subscription

# Quota & Limit Checks
GET    /api/v1/tenants/{tenant_id}/subscription/limits/locations - Check location limits
GET    /api/v1/tenants/{tenant_id}/subscription/limits/products  - Check product limits
GET    /api/v1/tenants/{tenant_id}/subscription/limits/users     - Check user limits
GET    /api/v1/tenants/{tenant_id}/subscription/limits/recipes   - Check recipe limits
GET    /api/v1/tenants/{tenant_id}/subscription/limits/suppliers - Check supplier limits

# Payment Management
GET    /api/v1/tenants/{tenant_id}/subscription/payment-method  - Get payment method
POST   /api/v1/tenants/{tenant_id}/subscription/payment-method - Update payment method
GET    /api/v1/tenants/{tenant_id}/subscription/invoices        - Get invoices

Service Layer Organization

subscription.py - All subscription-related endpoints

  • Registration flow endpoints
  • Tenant-dependent subscription endpoints
  • Subscription management endpoints
  • Quota and limit checks
  • Payment management endpoints

tenant_operations.py - Only tenant-centric operations

  • Tenant creation/management
  • Tenant hierarchy operations
  • Tenant settings management
  • Tenant location management

plans.py - Public plan information (unchanged)

Implementation Plan

Phase 1: Backend API Reorganization

Step 1: Move Subscription Endpoints

  • Move all subscription endpoints from tenant_operations.py to subscription.py
  • Implement new URL patterns with consistent structure
  • Update all endpoint implementations to use new paths

Files to modify:

  • services/tenant/app/api/subscription.py - Add all subscription endpoints
  • services/tenant/app/api/tenant_operations.py - Remove subscription endpoints

Step 2: Update Tenant Service Main.py

  • Ensure proper router inclusion for new subscription endpoints
  • Remove old subscription endpoints from tenant operations router

Files to modify:

  • services/tenant/app/main.py - Update router inclusion

Phase 2: Update Tenant Client

Step 3: Update Tenant Client URL Patterns

Update all subscription-related methods in shared/clients/tenant_client.py:

async def get_subscription_status(self, tenant_id: str) -> Optional[Dict[str, Any]]:
    # Updated URL pattern
    result = await self.get(f"tenants/{tenant_id}/subscription/status")
    return result

async def get_subscription_details(self, tenant_id: str) -> Optional[Dict[str, Any]]:
    # Updated URL pattern
    result = await self.get(f"tenants/{tenant_id}/subscription/details")
    return result

# Update all other subscription methods similarly
async def get_subscription_tier(self, tenant_id: str) -> Optional[str]:
    result = await self.get(f"tenants/{tenant_id}/subscription/tier")
    return result.get('tier') if result else None

async def get_subscription_limits(self, tenant_id: str) -> Optional[Dict[str, Any]]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits")

async def get_usage_summary(self, tenant_id: str) -> Optional[Dict[str, Any]]:
    return await self.get(f"tenants/{tenant_id}/subscription/usage")

async def has_feature(self, tenant_id: str, feature: str) -> bool:
    result = await self.get(f"tenants/{tenant_id}/subscription/features/{feature}")
    return result.get('has_feature', False) if result else False

# Registration flow methods
async def start_registration_payment_setup(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
    result = await self.post("registration/payment-setup", user_data)
    return result

async def complete_registration(self, setup_intent_id: str, user_data: Dict[str, Any]) -> Dict[str, Any]:
    result = await self.post("registration/complete", {
        "setup_intent_id": setup_intent_id,
        "user_data": user_data
    })
    return result

# Update quota check methods
async def can_add_location(self, tenant_id: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits/locations")

async def can_add_product(self, tenant_id: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits/products")

async def can_add_user(self, tenant_id: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits/users")

async def can_add_recipe(self, tenant_id: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits/recipes")

async def can_add_supplier(self, tenant_id: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/limits/suppliers")

# Update subscription management methods
async def cancel_subscription(self, tenant_id: str, reason: str = "") -> Dict[str, Any]:
    return await self.post(f"tenants/{tenant_id}/subscription/cancel", {"reason": reason})

async def reactivate_subscription(self, tenant_id: str, plan: str = "starter") -> Dict[str, Any]:
    return await self.post(f"tenants/{tenant_id}/subscription/reactivate", {"plan": plan})

async def validate_plan_upgrade(self, tenant_id: str, new_plan: str) -> Dict[str, Any]:
    return await self.get(f"tenants/{tenant_id}/subscription/validate-upgrade/{new_plan}")

async def upgrade_subscription_plan(self, tenant_id: str, new_plan: str) -> Dict[str, Any]:
    return await self.post(f"tenants/{tenant_id}/subscription/upgrade", {"new_plan": new_plan})

# Update payment management methods
async def get_payment_method(self, tenant_id: str) -> Optional[Dict[str, Any]]:
    return await self.get(f"tenants/{tenant_id}/subscription/payment-method")

async def update_payment_method(self, tenant_id: str, payment_method_id: str) -> Dict[str, Any]:
    return await self.post(f"tenants/{tenant_id}/subscription/payment-method", {
        "payment_method_id": payment_method_id
    })

async def get_invoices(self, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
    return await self.get(f"tenants/{tenant_id}/subscription/invoices")

Files to modify:

  • shared/clients/tenant_client.py - Update all subscription methods with new URL patterns

Phase 3: Gateway Updates

Step 4: Update Gateway Routes

# In gateway/app/routes/subscription.py

# Public endpoints
@router.get("/plans")
@router.get("/plans/{tier}")
@router.get("/plans/{tier}/features")
@router.get("/plans/{tier}/limits")
@router.get("/plans/compare")

# Registration flow
@router.post("/registration/payment-setup")
@router.post("/registration/complete")
@router.get("/registration/state/{state_id}")

# Tenant subscription endpoints
@router.get("/tenants/{tenant_id}/subscription/status")
@router.get("/tenants/{tenant_id}/subscription/details")
@router.get("/tenants/{tenant_id}/subscription/tier")
@router.get("/tenants/{tenant_id}/subscription/limits")
@router.get("/tenants/{tenant_id}/subscription/usage")
@router.get("/tenants/{tenant_id}/subscription/features/{feature}")
@router.post("/tenants/{tenant_id}/subscription/cancel")
@router.post("/tenants/{tenant_id}/subscription/reactivate")
@router.get("/tenants/{tenant_id}/subscription/validate-upgrade/{new_plan}")
@router.post("/tenants/{tenant_id}/subscription/upgrade")
@router.get("/tenants/{tenant_id}/subscription/limits/locations")
@router.get("/tenants/{tenant_id}/subscription/limits/products")
@router.get("/tenants/{tenant_id}/subscription/limits/users")
@router.get("/tenants/{tenant_id}/subscription/limits/recipes")
@router.get("/tenants/{tenant_id}/subscription/limits/suppliers")
@router.get("/tenants/{tenant_id}/subscription/payment-method")
@router.post("/tenants/{tenant_id}/subscription/payment-method")
@router.get("/tenants/{tenant_id}/subscription/invoices")

Files to modify:

  • gateway/app/routes/subscription.py - Update all gateway routes with new patterns

Step 5: Update Middleware URL Patterns

# In gateway/app/middleware/auth.py
async def _get_tenant_subscription_tier(self, tenant_id: str, request: Request) -> Optional[str]:
    try:
        # Updated URL pattern
        response = await self._make_request(
            "GET",
            f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/tier"
        )
        # ... rest of method remains the same
# In gateway/app/middleware/read_only_mode.py
async def check_subscription_status(self, tenant_id: str, authorization: str) -> dict:
    try:
        # Updated URL pattern
        response = await self._make_request(
            "GET",
            f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/status"
        )
        # ... rest of method remains the same

Files to modify:

  • gateway/app/middleware/subscription.py - Update URL patterns
  • gateway/app/middleware/auth.py - Update subscription tier lookup URL
  • gateway/app/middleware/read_only_mode.py - Update subscription status check URL

Phase 4: Frontend Updates

Step 6: Update Frontend Subscription Service

// In frontend/src/api/services/subscription.ts
export class SubscriptionService {
  // Update all methods to use new URL patterns

  async getSubscriptionStatus(tenantId: string): Promise<UsageSummary> {
    return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/status`);
  }

  async getSubscriptionDetails(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/details`);
  }

  async getSubscriptionTier(tenantId: string): Promise<string> {
    return apiClient.get(`/tenants/${tenantId}/subscription/tier`);
  }

  async getUsageSummary(tenantId: string): Promise<UsageSummary> {
    return apiClient.get<UsageSummary>(`/tenants/${tenantId}/subscription/usage`);
  }

  async checkFeatureAccess(tenantId: string, featureName: string): Promise<FeatureCheckResponse> {
    return apiClient.get<FeatureCheckResponse>(`/tenants/${tenantId}/subscription/features/${featureName}`);
  }

  async cancelSubscription(tenantId: string, reason?: string): Promise<any> {
    return apiClient.post(`/tenants/${tenantId}/subscription/cancel`, { reason });
  }

  async reactivateSubscription(tenantId: string, plan: string = 'starter'): Promise<any> {
    return apiClient.post(`/tenants/${tenantId}/subscription/reactivate`, { plan });
  }

  async validatePlanUpgrade(tenantId: string, newPlan: string): Promise<PlanUpgradeValidation> {
    return apiClient.get<PlanUpgradeValidation>(`/tenants/${tenantId}/subscription/validate-upgrade/${newPlan}`);
  }

  async upgradePlan(tenantId: string, newPlan: string): Promise<PlanUpgradeResult> {
    return apiClient.post<PlanUpgradeResult>(`/tenants/${tenantId}/subscription/upgrade`, { new_plan: newPlan });
  }

  async canAddLocation(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/limits/locations`);
  }

  async canAddProduct(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/limits/products`);
  }

  async canAddUser(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/limits/users`);
  }

  async canAddRecipe(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/limits/recipes`);
  }

  async canAddSupplier(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/limits/suppliers`);
  }

  async getPaymentMethod(tenantId: string): Promise<any> {
    return apiClient.get(`/tenants/${tenantId}/subscription/payment-method`);
  }

  async updatePaymentMethod(tenantId: string, paymentMethodId: string): Promise<any> {
    return apiClient.post(`/tenants/${tenantId}/subscription/payment-method`, { payment_method_id: paymentMethodId });
  }

  async getInvoices(tenantId: string): Promise<any[]> {
    return apiClient.get(`/tenants/${tenantId}/subscription/invoices`);
  }

  // Registration flow methods
  async startRegistrationPaymentSetup(userData: any): Promise<any> {
    return apiClient.post('/registration/payment-setup', userData);
  }

  async completeRegistration(setupIntentId: string, userData: any): Promise<any> {
    return apiClient.post('/registration/complete', {
      setup_intent_id: setupIntentId,
      user_data: userData
    });
  }
}

Files to modify:

  • frontend/src/api/services/subscription.ts - Update all URL patterns

Benefits of New Architecture

  1. Clear Separation of Concerns: Subscription operations are properly separated from tenant management operations
  2. Consistent URL Patterns: All subscription endpoints follow a logical, standardized structure
  3. Better Organization: Easier to find and maintain subscription-related code
  4. Service-Based Architecture: Maintains the constraint of using tenant client only
  5. Improved Maintainability: Changes to subscription logic are localized to one API file
  6. Better Performance: Clear caching strategies for subscription data
  7. Easier Scaling: Subscription endpoints can be scaled independently if needed
  8. Cleaner Codebase: No mixed concerns between tenant and subscription operations

Implementation Summary

Files to Modify

Backend Services:

  • services/tenant/app/api/subscription.py - Add all subscription endpoints
  • services/tenant/app/api/tenant_operations.py - Remove subscription endpoints
  • services/tenant/app/main.py - Update router inclusion

Shared Client Layer:

  • shared/clients/tenant_client.py - Update all subscription methods with new URL patterns

Gateway Layer:

  • gateway/app/routes/subscription.py - Update all gateway routes with new patterns
  • gateway/app/middleware/subscription.py - Update URL patterns
  • gateway/app/middleware/auth.py - Update subscription tier lookup URL
  • gateway/app/middleware/read_only_mode.py - Update subscription status check URL

Frontend Layer:

  • frontend/src/api/services/subscription.ts - Update all URL patterns

Implementation Order

  1. Backend API Reorganization (Phase 1)
  2. Update Tenant Client (Phase 2)
  3. Gateway Updates (Phase 3)
  4. Frontend Updates (Phase 4)

This comprehensive redesign creates a clean, modern subscription system with proper separation of concerns while respecting the existing service-based client architecture constraint.