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
- Mixed Concerns: Subscription endpoints are scattered across different API files
- Inconsistent URL Patterns: Mixed use of path vs query parameters for tenant IDs
- Poor Organization: Subscription operations mixed with tenant management operations
- Middleware Dependencies: Hardcoded URLs that need updating
- 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.pytosubscription.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 endpointsservices/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 patternsgateway/app/middleware/auth.py- Update subscription tier lookup URLgateway/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
- Clear Separation of Concerns: Subscription operations are properly separated from tenant management operations
- Consistent URL Patterns: All subscription endpoints follow a logical, standardized structure
- Better Organization: Easier to find and maintain subscription-related code
- Service-Based Architecture: Maintains the constraint of using tenant client only
- Improved Maintainability: Changes to subscription logic are localized to one API file
- Better Performance: Clear caching strategies for subscription data
- Easier Scaling: Subscription endpoints can be scaled independently if needed
- 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 endpointsservices/tenant/app/api/tenant_operations.py- Remove subscription endpointsservices/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 patternsgateway/app/middleware/subscription.py- Update URL patternsgateway/app/middleware/auth.py- Update subscription tier lookup URLgateway/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
- Backend API Reorganization (Phase 1)
- Update Tenant Client (Phase 2)
- Gateway Updates (Phase 3)
- 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.