15 KiB
Subscription System Architecture Redesign - IMPLEMENTED
Overview
This document outlines the completed implementation of the comprehensive subscription system architecture redesign. The implementation improves organization, maintainability, and separation of concerns while respecting the existing service-based client architecture.
Current State - FULLY IMPLEMENTED ✅
Issues Resolved
- ✅ Mixed Concerns: Subscription endpoints are now properly separated from tenant management
- ✅ Inconsistent URL Patterns: All endpoints now use consistent
/tenants/{tenant_id}/subscription/*structure - ✅ Poor Organization: Subscription operations are now centralized in one API file
- ✅ Middleware Dependencies: All middleware now uses updated URL patterns
- ✅ Client Layer Confusion: Tenant client contains only tenant-specific methods, subscription methods use proper patterns
New Architecture Design - FULLY IMPLEMENTED ✅
API Structure & URL Patterns
Public Endpoints (No Authentication) - ✅ IMPLEMENTED
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) - ✅ IMPLEMENTED
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 - ✅ IMPLEMENTED
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 - ✅ IMPLEMENTED
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 Status - COMPLETE ✅
Phase 1: Backend API Reorganization - ✅ COMPLETED
Step 1: Move Subscription Endpoints - ✅ DONE
- ✅ Moved all subscription endpoints from
tenant_operations.pytosubscription.py - ✅ Implemented new URL patterns with consistent structure
- ✅ Updated all endpoint implementations to use new paths
Files Modified:
services/tenant/app/api/subscription.py- ✅ All subscription endpoints addedservices/tenant/app/api/tenant_operations.py- ✅ Subscription endpoints removed
Step 2: Update Tenant Service Main.py - ✅ DONE
- ✅ Ensured proper router inclusion for new subscription endpoints
- ✅ Removed old subscription endpoints from tenant operations router
Files Modified:
services/tenant/app/main.py- ✅ Router inclusion updated
Phase 2: Update Tenant Client - ✅ COMPLETED
Step 3: Update Tenant Client URL Patterns - ✅ DONE
All subscription-related methods in shared/clients/tenant_client.py updated:
# ✅ All methods already updated with new URL patterns
async def get_subscription_status(self, tenant_id: str) -> Optional[Dict[str, Any]]:
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]]:
result = await self.get(f"tenants/{tenant_id}/subscription/details")
return result
# ✅ All other subscription methods similarly updated
Files Modified:
shared/clients/tenant_client.py- ✅ All subscription methods updated
Phase 3: Gateway Updates - ✅ COMPLETED
Step 4: Update Gateway Routes - ✅ DONE
# ✅ All gateway routes updated with new patterns
@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 Modified:
gateway/app/routes/subscription.py- ✅ All gateway routes updated
Step 5: Update Middleware URL Patterns - ✅ DONE
# ✅ In gateway/app/middleware/auth.py
async def _get_tenant_subscription_tier(self, tenant_id: str, request: Request) -> Optional[str]:
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/tier"
)
# ✅ In gateway/app/middleware/read_only_mode.py
async def check_subscription_status(self, tenant_id: str, authorization: str) -> dict:
response = await self._make_request(
"GET",
f"{settings.TENANT_SERVICE_URL}/api/v1/tenants/{tenant_id}/subscription/status"
)
Files Modified:
gateway/app/middleware/subscription.py- ✅ URL patterns updatedgateway/app/middleware/auth.py- ✅ Subscription tier lookup URL updatedgateway/app/middleware/read_only_mode.py- ✅ Subscription status check URL updatedgateway/app/middleware/rate_limiting.py- ✅ Subscription tier URL fixedgateway/app/routes/tenant.py- ✅ Removed conflicting wildcard route
Phase 4: Frontend Updates - ✅ COMPLETED
Step 6: Update Frontend Subscription Service - ✅ DONE
// ✅ In frontend/src/api/services/subscription.ts
export class SubscriptionService {
// ✅ All methods updated 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 Modified:
frontend/src/api/services/subscription.ts- ✅ All URL patterns updatedfrontend/src/api/services/tenant.ts- ✅ Fixed subscription linking endpoint
Additional Fixes - ✅ COMPLETED
Service Integration Updates
Files Modified:
services/auth/app/utils/subscription_fetcher.py- ✅ Fixed subscription details URLsservices/auth/app/api/account_deletion.py- ✅ Fixed subscription status URLservices/inventory/app/api/ingredients.py- ✅ Fixed quota check URLsservices/tenant/tests/integration/test_subscription_creation_flow.py- ✅ Fixed test URLs
Benefits Achieved ✅
- 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 - COMPLETE ✅
Files Modified
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/routes/tenant.py- ✅ Remove conflicting wildcard routegateway/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 URLgateway/app/middleware/rate_limiting.py- ✅ Fix subscription tier URL
Frontend Layer:
frontend/src/api/services/subscription.ts- ✅ Update all URL patternsfrontend/src/api/services/tenant.ts- ✅ Fix subscription linking endpoint
Service Integration:
services/auth/app/utils/subscription_fetcher.py- ✅ Fix subscription details URLsservices/auth/app/api/account_deletion.py- ✅ Fix subscription status URLservices/inventory/app/api/ingredients.py- ✅ Fix quota check URLsservices/tenant/tests/integration/test_subscription_creation_flow.py- ✅ Fix test URLs
Implementation Order
- Backend API Reorganization (Phase 1) - ✅ COMPLETED
- Update Tenant Client (Phase 2) - ✅ COMPLETED
- Gateway Updates (Phase 3) - ✅ COMPLETED
- Frontend Updates (Phase 4) - ✅ COMPLETED
- Service Integration Fixes - ✅ COMPLETED
Verification Status ✅
- ✅ All backend endpoints implemented and tested
- ✅ All client methods updated and verified
- ✅ All gateway routes updated and verified
- ✅ All frontend services updated and verified
- ✅ All middleware updated and verified
- ✅ All service integrations updated and verified
- ✅ No remaining old URL patterns in production code
- ✅ All tests updated to use new patterns
Migration Complete ✅
The subscription system architecture redesign has been fully implemented and is ready for production use. All components are using the new, consistent URL patterns as specified in the architecture redesign. The system maintains proper separation of concerns while respecting the existing service-based client architecture constraint.
Status: PRODUCTION READY 🚀