# app/modules/billing/schemas/subscription.py """ Pydantic schemas for merchant-level subscription operations. Supports subscription management, tier information, and feature summaries. """ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field # ============================================================================ # Tier Information Schemas # ============================================================================ class TierFeatureLimitResponse(BaseModel): """Feature limit entry for a tier.""" feature_code: str limit_value: int | None = Field(None, description="None = unlimited") class TierInfo(BaseModel): """Full tier information with feature limits.""" code: str name: str description: str | None = None price_monthly_cents: int price_annual_cents: int | None feature_codes: list[str] = Field(default_factory=list) feature_limits: list[TierFeatureLimitResponse] = Field(default_factory=list) # ============================================================================ # Subscription Schemas # ============================================================================ class MerchantSubscriptionCreate(BaseModel): """Schema for creating a merchant subscription.""" tier_code: str = Field(default="essential") is_annual: bool = False trial_days: int = Field(default=14, ge=0, le=30) class MerchantSubscriptionUpdate(BaseModel): """Schema for updating a merchant subscription.""" tier_code: str | None = None status: str | None = Field(None, pattern="^(trial|active|past_due|cancelled|expired)$") is_annual: bool | None = None class MerchantSubscriptionResponse(BaseModel): """Schema for merchant subscription response.""" model_config = ConfigDict(from_attributes=True) id: int merchant_id: int platform_id: int tier_id: int | None status: str is_annual: bool period_start: datetime period_end: datetime trial_ends_at: datetime | None # Stripe info (optional, may be hidden from client) stripe_customer_id: str | None = None # Cancellation cancelled_at: datetime | None = None # Computed properties is_active: bool is_trial: bool trial_days_remaining: int | None created_at: datetime updated_at: datetime # ============================================================================ # Feature Summary Schemas # ============================================================================ class FeatureSummaryResponse(BaseModel): """Feature summary for merchant portal display.""" code: str name_key: str description_key: str category: str feature_type: str scope: str enabled: bool limit: int | None = None current: int | None = None remaining: int | None = None percent_used: float | None = None is_override: bool = False unit_key: str | None = None ui_icon: str | None = None class MerchantSubscriptionStatusResponse(BaseModel): """Full subscription status with tier info and feature summary.""" subscription: MerchantSubscriptionResponse tier: TierInfo | None = None features: list[FeatureSummaryResponse] = Field(default_factory=list) # ============================================================================ # Limit Check Schemas # ============================================================================ class LimitCheckResult(BaseModel): """Result of a limit check.""" allowed: bool limit: int | None current: int remaining: int | None message: str | None = None class FeatureCheckResponse(BaseModel): """Response for feature check.""" feature_code: str enabled: bool message: str | None = None # ============================================================================ # Merchant Portal Schemas (for merchant-facing routes) # ============================================================================ class MerchantPortalSubscriptionItem(BaseModel): """Subscription item with tier and platform names for merchant portal list.""" model_config = ConfigDict(from_attributes=True) # Base subscription fields (mirror MerchantSubscriptionResponse) id: int merchant_id: int platform_id: int tier_id: int | None status: str is_annual: bool period_start: datetime period_end: datetime trial_ends_at: datetime | None stripe_customer_id: str | None = None cancelled_at: datetime | None = None is_active: bool is_trial: bool trial_days_remaining: int | None created_at: datetime updated_at: datetime # Enrichment fields tier: str | None = None tier_name: str | None = None platform_name: str = "" class MerchantPortalSubscriptionListResponse(BaseModel): """Paginated subscription list for merchant portal.""" subscriptions: list[MerchantPortalSubscriptionItem] total: int class MerchantPortalSubscriptionDetailResponse(BaseModel): """Subscription detail with tier info for merchant portal.""" subscription: MerchantPortalSubscriptionItem tier: TierInfo | None = None class MerchantPortalAvailableTiersResponse(BaseModel): """Available tiers for a platform.""" tiers: list[dict] current_tier: str | None = None class ChangeTierRequest(BaseModel): """Request for changing subscription tier.""" tier_code: str is_annual: bool = False class ChangeTierResponse(BaseModel): """Response after tier change.""" message: str new_tier: str | None = None effective_immediately: bool = False class MerchantPortalInvoiceListResponse(BaseModel): """Paginated invoice list for merchant portal.""" invoices: list[dict] total: int skip: int limit: int