2025-11-30 09:12:40 +01:00
|
|
|
"""
|
|
|
|
|
Subscription Service Client
|
|
|
|
|
Client for interacting with subscription service functionality
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import structlog
|
|
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
from fastapi import Depends
|
|
|
|
|
|
|
|
|
|
from shared.database.base import create_database_manager
|
|
|
|
|
from app.repositories.subscription_repository import SubscriptionRepository
|
|
|
|
|
from app.models.tenants import Subscription, Tenant
|
|
|
|
|
from app.repositories.tenant_repository import TenantRepository
|
|
|
|
|
from shared.subscription.plans import SubscriptionTier
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SubscriptionServiceClient:
|
|
|
|
|
"""Client for subscription service operations"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, database_manager=None):
|
|
|
|
|
self.database_manager = database_manager or create_database_manager()
|
|
|
|
|
|
|
|
|
|
async def get_subscription(self, tenant_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""Get subscription details for a tenant"""
|
|
|
|
|
try:
|
|
|
|
|
async with self.database_manager.get_session() as session:
|
|
|
|
|
subscription_repo = SubscriptionRepository(Subscription, session)
|
|
|
|
|
subscription = await subscription_repo.get_active_subscription(tenant_id)
|
|
|
|
|
|
|
|
|
|
if not subscription:
|
|
|
|
|
# Return default starter subscription if none found
|
|
|
|
|
return {
|
|
|
|
|
'id': None,
|
|
|
|
|
'tenant_id': tenant_id,
|
|
|
|
|
'plan': SubscriptionTier.STARTER.value,
|
|
|
|
|
'status': 'active',
|
|
|
|
|
'monthly_price': 0,
|
|
|
|
|
'max_users': 5,
|
|
|
|
|
'max_locations': 1,
|
|
|
|
|
'max_products': 50,
|
|
|
|
|
'features': {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'id': str(subscription.id) if subscription.id else None,
|
|
|
|
|
'tenant_id': tenant_id,
|
|
|
|
|
'plan': subscription.plan,
|
|
|
|
|
'status': subscription.status,
|
|
|
|
|
'monthly_price': subscription.monthly_price,
|
|
|
|
|
'max_users': subscription.max_users,
|
|
|
|
|
'max_locations': subscription.max_locations,
|
|
|
|
|
'max_products': subscription.max_products,
|
|
|
|
|
'features': subscription.features or {}
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
2026-01-15 20:45:49 +01:00
|
|
|
logger.error(f"Failed to get subscription, tenant_id={tenant_id}, error={str(e)}")
|
2025-11-30 09:12:40 +01:00
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def update_subscription_plan(self, tenant_id: str, new_plan: str) -> Dict[str, Any]:
|
|
|
|
|
"""Update subscription plan for a tenant"""
|
|
|
|
|
try:
|
|
|
|
|
async with self.database_manager.get_session() as session:
|
|
|
|
|
subscription_repo = SubscriptionRepository(Subscription, session)
|
|
|
|
|
|
|
|
|
|
# Get existing subscription
|
|
|
|
|
existing_subscription = await subscription_repo.get_active_subscription(tenant_id)
|
|
|
|
|
|
|
|
|
|
if existing_subscription:
|
|
|
|
|
# Update the existing subscription
|
|
|
|
|
updated_subscription = await subscription_repo.update_subscription(
|
|
|
|
|
existing_subscription.id,
|
|
|
|
|
{'plan': new_plan}
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# Create a new subscription if none exists
|
|
|
|
|
updated_subscription = await subscription_repo.create_subscription({
|
|
|
|
|
'tenant_id': tenant_id,
|
|
|
|
|
'plan': new_plan,
|
|
|
|
|
'status': 'active',
|
|
|
|
|
'created_at': None # Let the database set this
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'id': str(updated_subscription.id),
|
|
|
|
|
'tenant_id': tenant_id,
|
|
|
|
|
'plan': updated_subscription.plan,
|
|
|
|
|
'status': updated_subscription.status
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
2026-01-15 20:45:49 +01:00
|
|
|
logger.error(f"Failed to update subscription plan, tenant_id={tenant_id}, new_plan={new_plan}, error={str(e)}")
|
2025-11-30 09:12:40 +01:00
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def create_child_subscription(self, child_tenant_id: str, parent_tenant_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""Create a child subscription inheriting from parent"""
|
|
|
|
|
try:
|
|
|
|
|
async with self.database_manager.get_session() as session:
|
|
|
|
|
subscription_repo = SubscriptionRepository(Subscription, session)
|
|
|
|
|
tenant_repo = TenantRepository(Tenant, session)
|
|
|
|
|
|
|
|
|
|
# Get parent subscription to inherit plan
|
|
|
|
|
parent_subscription = await subscription_repo.get_active_subscription(parent_tenant_id)
|
|
|
|
|
|
|
|
|
|
if not parent_subscription:
|
|
|
|
|
# If parent has no subscription, create child with starter plan
|
|
|
|
|
plan = SubscriptionTier.STARTER.value
|
|
|
|
|
else:
|
|
|
|
|
plan = parent_subscription.plan
|
|
|
|
|
|
|
|
|
|
# Create subscription for child tenant
|
|
|
|
|
child_subscription = await subscription_repo.create_subscription({
|
|
|
|
|
'tenant_id': child_tenant_id,
|
|
|
|
|
'plan': plan,
|
|
|
|
|
'status': 'active',
|
|
|
|
|
'created_at': None # Let the database set this
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
# Update the child tenant's subscription tier
|
|
|
|
|
await tenant_repo.update_tenant(child_tenant_id, {
|
|
|
|
|
'subscription_tier': plan
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
'id': str(child_subscription.id),
|
|
|
|
|
'tenant_id': child_tenant_id,
|
|
|
|
|
'plan': child_subscription.plan,
|
|
|
|
|
'status': child_subscription.status
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Failed to create child subscription",
|
|
|
|
|
child_tenant_id=child_tenant_id,
|
|
|
|
|
parent_tenant_id=parent_tenant_id,
|
|
|
|
|
error=str(e))
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def get_subscription_by_tenant(self, tenant_id: str) -> Optional[Dict[str, Any]]:
|
|
|
|
|
"""Get subscription by tenant ID"""
|
|
|
|
|
return await self.get_subscription(tenant_id)
|
|
|
|
|
|
|
|
|
|
async def get_tenant_subscription_tier(self, tenant_id: str) -> str:
|
|
|
|
|
"""Get the subscription tier for a tenant"""
|
|
|
|
|
subscription = await self.get_subscription(tenant_id)
|
|
|
|
|
return subscription.get('plan', SubscriptionTier.STARTER.value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Dependency function for FastAPI
|
|
|
|
|
async def get_subscription_service_client() -> SubscriptionServiceClient:
|
|
|
|
|
"""FastAPI dependency for subscription service client"""
|
|
|
|
|
return SubscriptionServiceClient()
|