""" Coupon system for subscription discounts and promotions. Supports trial extensions, percentage discounts, and fixed amount discounts. """ from dataclasses import dataclass from datetime import datetime from enum import Enum from typing import Optional class DiscountType(str, Enum): """Type of discount offered by a coupon""" TRIAL_EXTENSION = "trial_extension" # Extends trial period by X days PERCENTAGE = "percentage" # X% off subscription price FIXED_AMOUNT = "fixed_amount" # Fixed amount off subscription price @dataclass class Coupon: """Coupon configuration""" id: str code: str discount_type: DiscountType discount_value: int # Days for trial_extension, percentage for percentage, cents for fixed_amount max_redemptions: Optional[int] # None = unlimited current_redemptions: int valid_from: datetime valid_until: Optional[datetime] # None = no expiry active: bool created_at: datetime extra_data: Optional[dict] = None def is_valid(self) -> bool: """Check if coupon is currently valid""" now = datetime.utcnow() # Check if active if not self.active: return False # Check if started if now < self.valid_from: return False # Check if expired if self.valid_until and now > self.valid_until: return False # Check if max redemptions reached if self.max_redemptions and self.current_redemptions >= self.max_redemptions: return False return True def can_be_redeemed(self) -> tuple[bool, str]: """ Check if coupon can be redeemed. Returns (can_redeem, reason_if_not) """ if not self.active: return False, "Coupon is inactive" now = datetime.utcnow() if now < self.valid_from: return False, "Coupon is not yet valid" if self.valid_until and now > self.valid_until: return False, "Coupon has expired" if self.max_redemptions and self.current_redemptions >= self.max_redemptions: return False, "Coupon has reached maximum redemptions" return True, "" @dataclass class CouponRedemption: """Record of a coupon redemption""" id: str tenant_id: str coupon_code: str redeemed_at: datetime discount_applied: dict # Details of the discount applied extra_data: Optional[dict] = None @dataclass class CouponValidationResult: """Result of coupon validation""" valid: bool coupon: Optional[Coupon] error_message: Optional[str] discount_preview: Optional[dict] # Preview of discount to be applied def calculate_trial_end_date(base_trial_days: int, extension_days: int) -> datetime: """Calculate trial end date with coupon extension""" from datetime import timedelta total_days = base_trial_days + extension_days return datetime.utcnow() + timedelta(days=total_days) def format_discount_description(coupon: Coupon) -> str: """Format human-readable discount description""" if coupon.discount_type == DiscountType.TRIAL_EXTENSION: months = coupon.discount_value // 30 days = coupon.discount_value % 30 if months > 0 and days == 0: return f"{months} {'mes' if months == 1 else 'meses'} gratis" elif months > 0: return f"{months} {'mes' if months == 1 else 'meses'} y {days} días gratis" else: return f"{coupon.discount_value} días gratis" elif coupon.discount_type == DiscountType.PERCENTAGE: return f"{coupon.discount_value}% de descuento" elif coupon.discount_type == DiscountType.FIXED_AMOUNT: amount = coupon.discount_value / 100 # Convert cents to euros return f"€{amount:.2f} de descuento" return "Descuento aplicado"