Add subcription feature
This commit is contained in:
@@ -21,6 +21,7 @@ from shared.database.unit_of_work import UnitOfWork
|
||||
from shared.database.transactions import transactional
|
||||
from shared.database.exceptions import DatabaseError, ValidationError, DuplicateRecordError
|
||||
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -169,9 +170,62 @@ class EnhancedAuthService:
|
||||
# Re-raise to ensure registration fails if consent can't be recorded
|
||||
raise
|
||||
|
||||
# Payment customer creation via tenant service
|
||||
# The auth service calls the tenant service to create payment customer
|
||||
# This maintains proper separation of concerns while providing seamless user experience
|
||||
|
||||
try:
|
||||
# Call tenant service to create payment customer
|
||||
from shared.clients.tenant_client import TenantServiceClient
|
||||
from app.core.config import settings
|
||||
|
||||
tenant_client = TenantServiceClient(settings)
|
||||
|
||||
# Prepare user data for tenant service
|
||||
user_data_for_tenant = {
|
||||
"user_id": str(new_user.id),
|
||||
"email": user_data.email,
|
||||
"full_name": user_data.full_name,
|
||||
"name": user_data.full_name
|
||||
}
|
||||
|
||||
# Call tenant service to create payment customer
|
||||
payment_result = await tenant_client.create_payment_customer(
|
||||
user_data_for_tenant,
|
||||
user_data.payment_method_id
|
||||
)
|
||||
|
||||
if payment_result and payment_result.get("success"):
|
||||
# Store payment customer ID from tenant service response
|
||||
new_user.payment_customer_id = payment_result.get("payment_customer_id")
|
||||
|
||||
logger.info("Payment customer created successfully via tenant service",
|
||||
user_id=new_user.id,
|
||||
payment_customer_id=new_user.payment_customer_id,
|
||||
payment_method_id=user_data.payment_method_id)
|
||||
else:
|
||||
logger.warning("Payment customer creation via tenant service returned no success",
|
||||
user_id=new_user.id,
|
||||
result=payment_result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Payment customer creation via tenant service failed",
|
||||
user_id=new_user.id,
|
||||
error=str(e))
|
||||
# Don't fail registration if payment customer creation fails
|
||||
# This allows users to register even if payment system is temporarily unavailable
|
||||
new_user.payment_customer_id = None
|
||||
|
||||
# Store payment method ID if provided (will be used by tenant service)
|
||||
if user_data.payment_method_id:
|
||||
new_user.default_payment_method_id = user_data.payment_method_id
|
||||
logger.info("Payment method ID stored for later use by tenant service",
|
||||
user_id=new_user.id,
|
||||
payment_method_id=user_data.payment_method_id)
|
||||
|
||||
# Store subscription plan selection in onboarding progress BEFORE committing
|
||||
# This ensures it's part of the same transaction
|
||||
if user_data.subscription_plan or user_data.use_trial or user_data.payment_method_id:
|
||||
if user_data.subscription_plan or user_data.payment_method_id or user_data.billing_cycle or user_data.coupon_code:
|
||||
try:
|
||||
from app.repositories.onboarding_repository import OnboardingRepository
|
||||
from app.models.onboarding import UserOnboardingProgress
|
||||
@@ -181,8 +235,10 @@ class EnhancedAuthService:
|
||||
plan_data = {
|
||||
"subscription_plan": user_data.subscription_plan or "starter",
|
||||
"subscription_tier": user_data.subscription_plan or "starter", # Store tier for enterprise onboarding logic
|
||||
"use_trial": user_data.use_trial or False,
|
||||
"billing_cycle": user_data.billing_cycle or "monthly",
|
||||
"coupon_code": user_data.coupon_code,
|
||||
"payment_method_id": user_data.payment_method_id,
|
||||
"payment_customer_id": new_user.payment_customer_id, # Now created via tenant service
|
||||
"saved_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
@@ -197,11 +253,15 @@ class EnhancedAuthService:
|
||||
auto_commit=False
|
||||
)
|
||||
|
||||
logger.info("Subscription plan saved to onboarding progress",
|
||||
logger.info("Subscription plan and parameters saved to onboarding progress",
|
||||
user_id=new_user.id,
|
||||
plan=user_data.subscription_plan)
|
||||
plan=user_data.subscription_plan,
|
||||
billing_cycle=user_data.billing_cycle,
|
||||
coupon_code=user_data.coupon_code,
|
||||
payment_method_id=user_data.payment_method_id,
|
||||
payment_customer_id=new_user.payment_customer_id)
|
||||
except Exception as e:
|
||||
logger.error("Failed to save subscription plan to onboarding progress",
|
||||
logger.error("Failed to save subscription plan and parameters to onboarding progress",
|
||||
user_id=new_user.id,
|
||||
error=str(e))
|
||||
# Re-raise to ensure registration fails if onboarding data can't be saved
|
||||
@@ -730,6 +790,177 @@ class EnhancedAuthService:
|
||||
)
|
||||
|
||||
|
||||
async def create_subscription_via_tenant_service(
|
||||
self,
|
||||
user_id: str,
|
||||
plan_id: str,
|
||||
payment_method_id: str,
|
||||
billing_cycle: str,
|
||||
coupon_code: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Create a tenant-independent subscription via tenant service
|
||||
|
||||
This method calls the tenant service to create a subscription during user registration
|
||||
that is not linked to any tenant. The subscription will be linked to a tenant
|
||||
during the onboarding flow.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
plan_id: Subscription plan ID
|
||||
payment_method_id: Payment method ID
|
||||
billing_cycle: Billing cycle (monthly/yearly)
|
||||
coupon_code: Optional coupon code
|
||||
|
||||
Returns:
|
||||
Dict with subscription creation results including:
|
||||
- success: boolean
|
||||
- subscription_id: string
|
||||
- customer_id: string
|
||||
- status: string
|
||||
- plan: string
|
||||
- billing_cycle: string
|
||||
Returns None if creation fails
|
||||
"""
|
||||
try:
|
||||
from shared.clients.tenant_client import TenantServiceClient
|
||||
from shared.config.base import BaseServiceSettings
|
||||
|
||||
# Get the base settings to create tenant client
|
||||
tenant_client = TenantServiceClient(BaseServiceSettings())
|
||||
|
||||
# Get user data for tenant service
|
||||
user_data = await self.get_user_data_for_tenant_service(user_id)
|
||||
|
||||
logger.info("Creating tenant-independent subscription via tenant service",
|
||||
user_id=user_id,
|
||||
plan_id=plan_id)
|
||||
|
||||
# Call tenant service using the new dedicated method
|
||||
result = await tenant_client.create_subscription_for_registration(
|
||||
user_data=user_data,
|
||||
plan_id=plan_id,
|
||||
payment_method_id=payment_method_id,
|
||||
billing_cycle=billing_cycle,
|
||||
coupon_code=coupon_code
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info("Tenant-independent subscription created successfully via tenant service",
|
||||
user_id=user_id,
|
||||
subscription_id=result.get('subscription_id'))
|
||||
return result
|
||||
else:
|
||||
logger.error("Tenant-independent subscription creation failed via tenant service",
|
||||
user_id=user_id)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to create tenant-independent subscription via tenant service",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
return None
|
||||
|
||||
async def get_user_data_for_tenant_service(self, user_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get user data formatted for tenant service calls
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
|
||||
Returns:
|
||||
Dict with user data including email, name, etc.
|
||||
"""
|
||||
try:
|
||||
# Get user from database
|
||||
async with self.database_manager.get_session() as db_session:
|
||||
async with UnitOfWork(db_session) as uow:
|
||||
user_repo = uow.register_repository("users", UserRepository, User)
|
||||
|
||||
user = await user_repo.get_by_id(user_id)
|
||||
|
||||
if not user:
|
||||
raise ValueError(f"User {user_id} not found")
|
||||
|
||||
return {
|
||||
"user_id": str(user.id),
|
||||
"email": user.email,
|
||||
"full_name": user.full_name,
|
||||
"name": user.full_name
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error("Failed to get user data for tenant service",
|
||||
user_id=user_id,
|
||||
error=str(e))
|
||||
raise
|
||||
|
||||
async def save_subscription_to_onboarding_progress(
|
||||
self,
|
||||
user_id: str,
|
||||
subscription_id: str,
|
||||
registration_data: UserRegistration
|
||||
) -> None:
|
||||
"""
|
||||
Save subscription data to the user's onboarding progress
|
||||
|
||||
This method stores subscription information in the onboarding progress
|
||||
so it can be retrieved later during the tenant creation step.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
subscription_id: Subscription ID created by tenant service
|
||||
registration_data: Original registration data including plan, payment method, etc.
|
||||
"""
|
||||
try:
|
||||
from app.repositories.onboarding_repository import OnboardingRepository
|
||||
from app.models.onboarding import UserOnboardingProgress
|
||||
|
||||
# Prepare subscription data to store
|
||||
subscription_data = {
|
||||
"subscription_id": subscription_id,
|
||||
"plan_id": registration_data.subscription_plan,
|
||||
"payment_method_id": registration_data.payment_method_id,
|
||||
"billing_cycle": registration_data.billing_cycle or "monthly",
|
||||
"coupon_code": registration_data.coupon_code,
|
||||
"created_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
logger.info("Saving subscription data to onboarding progress",
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id)
|
||||
|
||||
# Save to onboarding progress
|
||||
async with self.database_manager.get_session() as db_session:
|
||||
async with UnitOfWork(db_session) as uow:
|
||||
onboarding_repo = uow.register_repository(
|
||||
"onboarding",
|
||||
OnboardingRepository,
|
||||
UserOnboardingProgress
|
||||
)
|
||||
|
||||
# Save or update the subscription step data
|
||||
await onboarding_repo.save_step_data(
|
||||
user_id=user_id,
|
||||
step_name="subscription",
|
||||
step_data=subscription_data,
|
||||
auto_commit=False
|
||||
)
|
||||
|
||||
# Commit the transaction
|
||||
await uow.commit()
|
||||
|
||||
logger.info("Subscription data saved successfully to onboarding progress",
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to save subscription data to onboarding progress",
|
||||
user_id=user_id,
|
||||
subscription_id=subscription_id,
|
||||
error=str(e))
|
||||
# Don't raise - we don't want to fail the registration if this fails
|
||||
# The subscription was already created, so the user can still proceed
|
||||
|
||||
# Legacy compatibility - alias EnhancedAuthService as AuthService
|
||||
AuthService = EnhancedAuthService
|
||||
|
||||
|
||||
Reference in New Issue
Block a user