Files
orion/app/modules/billing/schemas/subscription.py
Samir Boulahtit f20266167d
Some checks failed
CI / ruff (push) Failing after 7s
CI / pytest (push) Failing after 1s
CI / architecture (push) Failing after 9s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 8s
CI / docs (push) Has been skipped
fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.)
- Added ignore rules for patterns intentional in this codebase:
  E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from),
  SIM108/SIM105/SIM117 (readability preferences)
- Added per-file ignores for tests and scripts
- Excluded broken scripts/rename_terminology.py (has curly quotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:10:42 +01:00

222 lines
5.7 KiB
Python

# 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