2025-09-21 13:27:50 +02:00
"""
Subscription routes for API Gateway - Direct subscription endpoints
2026-01-16 15:19:34 +01:00
New URL Pattern Architecture :
- Registration : / registration / payment - setup , / registration / complete , / registration / state / { state_id }
- Tenant Subscription : / tenants / { tenant_id } / subscription / *
- Setup Intents : / setup - intents / { setup_intent_id } / verify
- Payment Customers : / payment - customers / create
- Plans : / plans ( public )
2025-09-21 13:27:50 +02:00
"""
from fastapi import APIRouter , Request , Response , HTTPException , Path
from fastapi . responses import JSONResponse
import httpx
import logging
from typing import Optional
from app . core . config import settings
2026-01-12 22:15:11 +01:00
from app . core . header_manager import header_manager
2025-09-21 13:27:50 +02:00
logger = logging . getLogger ( __name__ )
router = APIRouter ( )
# ================================================================
2026-01-16 15:19:34 +01:00
# PUBLIC ENDPOINTS (No Authentication)
2025-09-21 13:27:50 +02:00
# ================================================================
2025-09-25 14:30:47 +02:00
@router.api_route ( " /plans " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plans ( request : Request ) :
""" Proxy plans request to tenant service """
2025-10-15 16:12:49 +02:00
target_path = " /plans "
2025-09-21 13:27:50 +02:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /plans/ {tier} " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plan_details ( request : Request , tier : str = Path ( . . . ) ) :
""" Proxy specific plan details request to tenant service """
target_path = f " /plans/ { tier } "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /plans/ {tier} /features " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plan_features ( request : Request , tier : str = Path ( . . . ) ) :
""" Proxy plan features request to tenant service """
target_path = f " /plans/ { tier } /features "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /plans/ {tier} /limits " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plan_limits ( request : Request , tier : str = Path ( . . . ) ) :
""" Proxy plan limits request to tenant service """
target_path = f " /plans/ { tier } /limits "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /plans/compare " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_plan_compare ( request : Request ) :
""" Proxy plan comparison request to tenant service """
target_path = " /plans/compare "
2025-10-31 11:54:19 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
# ================================================================
# REGISTRATION FLOW ENDPOINTS (No Tenant Context)
# ================================================================
@router.api_route ( " /registration/payment-setup " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_registration_payment_setup ( request : Request ) :
""" Proxy registration payment setup request to tenant service """
target_path = " /api/v1/registration/payment-setup "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /registration/complete " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_registration_complete ( request : Request ) :
""" Proxy registration completion request to tenant service """
target_path = " /api/v1/registration/complete "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /registration/state/ {state_id} " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_registration_state ( request : Request , state_id : str = Path ( . . . ) ) :
""" Proxy registration state request to tenant service """
target_path = f " /api/v1/registration/state/ { state_id } "
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# TENANT SUBSCRIPTION STATUS ENDPOINTS
# ================================================================
@router.api_route ( " /tenants/ {tenant_id} /subscription/status " , methods = [ " GET " , " OPTIONS " ] )
2025-10-31 11:54:19 +01:00
async def proxy_subscription_status ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription status request to tenant service """
2026-01-16 15:19:34 +01:00
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/status "
2025-10-31 11:54:19 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /tenants/ {tenant_id} /subscription/details " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_details ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription details request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/details "
2026-01-14 13:15:48 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /tenants/ {tenant_id} /subscription/tier " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_tier ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription tier request to tenant service (cached) """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/tier "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription limits request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/usage " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_usage ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription usage request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/usage "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/features/ {feature} " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_subscription_feature ( request : Request , tenant_id : str = Path ( . . . ) , feature : str = Path ( . . . ) ) :
""" Proxy subscription feature check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/features/ { feature } "
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# SUBSCRIPTION MANAGEMENT ENDPOINTS
# ================================================================
@router.api_route ( " /tenants/ {tenant_id} /subscription/cancel " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_subscription_cancel ( request : Request , tenant_id : str = Path ( . . . ) ) :
2025-10-31 11:54:19 +01:00
""" Proxy subscription cancellation request to tenant service """
2026-01-16 15:19:34 +01:00
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/cancel "
2025-10-31 11:54:19 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /tenants/ {tenant_id} /subscription/reactivate " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_subscription_reactivate ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription reactivation request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/reactivate "
2026-01-13 22:22:38 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /tenants/ {tenant_id} /subscription/validate-upgrade/ {new_plan} " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_validate_upgrade ( request : Request , tenant_id : str = Path ( . . . ) , new_plan : str = Path ( . . . ) ) :
""" Proxy plan upgrade validation request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/validate-upgrade/ { new_plan } "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/upgrade " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_subscription_upgrade ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy subscription upgrade request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/upgrade "
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# QUOTA & LIMIT CHECK ENDPOINTS
# ================================================================
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits/locations " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_location_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy location limits check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits/locations "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits/products " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_product_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy product limits check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits/products "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits/users " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_user_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy user limits check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits/users "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits/recipes " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_recipe_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy recipe limits check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits/recipes "
2026-01-13 22:22:38 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
@router.api_route ( " /tenants/ {tenant_id} /subscription/limits/suppliers " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_supplier_limits ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy supplier limits check request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/limits/suppliers "
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# PAYMENT MANAGEMENT ENDPOINTS
# ================================================================
@router.api_route ( " /tenants/ {tenant_id} /subscription/payment-method " , methods = [ " GET " , " POST " , " OPTIONS " ] )
async def proxy_payment_method ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy payment method request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/payment-method "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /tenants/ {tenant_id} /subscription/invoices " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_invoices ( request : Request , tenant_id : str = Path ( . . . ) ) :
""" Proxy invoices request to tenant service """
target_path = f " /api/v1/tenants/ { tenant_id } /subscription/invoices "
return await _proxy_to_tenant_service ( request , target_path )
# ================================================================
# SETUP INTENT VERIFICATION
# ================================================================
2026-01-14 13:15:48 +01:00
@router.api_route ( " /setup-intents/ {setup_intent_id} /verify " , methods = [ " GET " , " OPTIONS " ] )
2026-01-16 15:19:34 +01:00
async def proxy_setup_intent_verify ( request : Request , setup_intent_id : str = Path ( . . . ) ) :
2026-01-14 13:15:48 +01:00
""" Proxy SetupIntent verification request to tenant service """
target_path = f " /api/v1/setup-intents/ { setup_intent_id } /verify "
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
# ================================================================
# PAYMENT CUSTOMER MANAGEMENT
# ================================================================
@router.api_route ( " /payment-customers/create " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_payment_customer_create ( request : Request ) :
""" Proxy payment customer creation request to tenant service """
target_path = " /api/v1/payment-customers/create "
2025-10-31 11:54:19 +01:00
return await _proxy_to_tenant_service ( request , target_path )
2026-01-16 15:19:34 +01:00
# ================================================================
# USAGE FORECAST ENDPOINTS
# ================================================================
2026-01-11 19:38:54 +01:00
@router.api_route ( " /usage-forecast " , methods = [ " GET " , " OPTIONS " ] )
async def proxy_usage_forecast ( request : Request ) :
""" Proxy usage forecast request to tenant service """
target_path = " /api/v1/usage-forecast "
return await _proxy_to_tenant_service ( request , target_path )
@router.api_route ( " /usage-forecast/track-usage " , methods = [ " POST " , " OPTIONS " ] )
async def proxy_track_usage ( request : Request ) :
""" Proxy track usage request to tenant service """
target_path = " /api/v1/usage-forecast/track-usage "
return await _proxy_to_tenant_service ( request , target_path )
2025-09-21 13:27:50 +02:00
# ================================================================
# PROXY HELPER FUNCTIONS
# ================================================================
async def _proxy_to_tenant_service ( request : Request , target_path : str ) :
""" Proxy request to tenant service """
return await _proxy_request ( request , target_path , settings . TENANT_SERVICE_URL )
async def _proxy_request ( request : Request , target_path : str , service_url : str ) :
""" Generic proxy function with enhanced error handling """
# Handle OPTIONS requests directly for CORS
if request . method == " OPTIONS " :
return Response (
status_code = 200 ,
headers = {
" Access-Control-Allow-Origin " : settings . CORS_ORIGINS_LIST ,
" Access-Control-Allow-Methods " : " GET, POST, PUT, DELETE, OPTIONS " ,
" Access-Control-Allow-Headers " : " Content-Type, Authorization, X-Tenant-ID " ,
" Access-Control-Allow-Credentials " : " true " ,
" Access-Control-Max-Age " : " 86400 "
}
)
try :
url = f " { service_url } { target_path } "
2026-01-12 22:15:11 +01:00
# Use unified HeaderManager for consistent header forwarding
headers = header_manager . get_all_headers_for_proxy ( request )
2026-01-16 15:19:34 +01:00
2026-01-12 22:15:11 +01:00
# Debug logging
user_context = getattr ( request . state , ' user ' , None )
2026-01-14 13:15:48 +01:00
service_context = getattr ( request . state , ' service ' , None )
2026-01-12 22:15:11 +01:00
if user_context :
logger . info ( f " Forwarding subscription request to { url } with user context: user_id= { user_context . get ( ' user_id ' ) } , email= { user_context . get ( ' email ' ) } , subscription_tier= { user_context . get ( ' subscription_tier ' , ' not_set ' ) } " )
2026-01-14 13:15:48 +01:00
elif service_context :
logger . debug ( f " Forwarding subscription request to { url } with service context: service_name= { service_context . get ( ' service_name ' ) } , user_type=service " )
2025-09-21 13:27:50 +02:00
else :
2026-01-14 13:15:48 +01:00
logger . warning ( f " No user or service context available when forwarding subscription request to { url } " )
2025-09-21 13:27:50 +02:00
# Get request body if present
body = None
if request . method in [ " POST " , " PUT " , " PATCH " ] :
body = await request . body ( )
# Add query parameters
params = dict ( request . query_params )
timeout_config = httpx . Timeout (
connect = 30.0 ,
read = 60.0 ,
write = 30.0 ,
pool = 30.0
)
async with httpx . AsyncClient ( timeout = timeout_config ) as client :
response = await client . request (
method = request . method ,
url = url ,
headers = headers ,
content = body ,
params = params
)
# Handle different response types
if response . headers . get ( " content-type " , " " ) . startswith ( " application/json " ) :
try :
content = response . json ( )
except :
content = { " message " : " Invalid JSON response from service " }
else :
content = response . text
return JSONResponse (
status_code = response . status_code ,
content = content
)
except Exception as e :
logger . error ( f " Unexpected error proxying subscription request to { service_url } { target_path } : { e } " )
raise HTTPException (
status_code = 500 ,
detail = " Internal gateway error "
2026-01-16 15:19:34 +01:00
)