""" Coupon Service - Coupon Operations This service handles ONLY coupon validation and redemption NO payment provider interactions, NO subscription logic """ import structlog from typing import Dict, Any, Optional, Tuple from sqlalchemy.ext.asyncio import AsyncSession from app.repositories.coupon_repository import CouponRepository logger = structlog.get_logger() class CouponService: """Service for handling coupon validation and redemption""" def __init__(self, db_session: AsyncSession): self.db_session = db_session self.coupon_repo = CouponRepository(db_session) async def validate_coupon_code( self, coupon_code: str, tenant_id: str ) -> Dict[str, Any]: """ Validate a coupon code for a tenant Args: coupon_code: Coupon code to validate tenant_id: Tenant ID Returns: Dictionary with validation results """ try: validation = await self.coupon_repo.validate_coupon(coupon_code, tenant_id) return { "valid": validation.valid, "error_message": validation.error_message, "discount_preview": validation.discount_preview, "coupon": { "code": validation.coupon.code, "discount_type": validation.coupon.discount_type.value, "discount_value": validation.coupon.discount_value } if validation.coupon else None } except Exception as e: logger.error("Failed to validate coupon", error=str(e), coupon_code=coupon_code) return { "valid": False, "error_message": "Error al validar el cupón", "discount_preview": None, "coupon": None } async def redeem_coupon( self, coupon_code: str, tenant_id: str, base_trial_days: int = 0 ) -> Tuple[bool, Optional[Dict[str, Any]], Optional[str]]: """ Redeem a coupon for a tenant Args: coupon_code: Coupon code to redeem tenant_id: Tenant ID base_trial_days: Base trial days without coupon Returns: Tuple of (success, discount_applied, error_message) """ try: success, redemption, error = await self.coupon_repo.redeem_coupon( coupon_code, tenant_id, base_trial_days ) if success and redemption: return True, redemption.discount_applied, None else: return False, None, error except Exception as e: logger.error("Failed to redeem coupon", error=str(e), coupon_code=coupon_code) return False, None, f"Error al aplicar el cupón: {str(e)}" async def get_coupon_by_code(self, coupon_code: str) -> Optional[Any]: """ Get coupon details by code Args: coupon_code: Coupon code to retrieve Returns: Coupon object or None """ try: return await self.coupon_repo.get_coupon_by_code(coupon_code) except Exception as e: logger.error("Failed to get coupon by code", error=str(e), coupon_code=coupon_code) return None