Some checks failed
- Add redis-exporter container to docker-compose (oliver006/redis_exporter, 32MB) - Add Redis scrape target to Prometheus config - Add 4 Redis alert rules: RedisDown, HighMemory, HighConnections, RejectedConnections - Document Step 19b (Sentry Error Tracking) in Hetzner deployment guide - Document Step 19c (Redis Monitoring) in Hetzner deployment guide - Update resource budget and port reference tables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
220 lines
5.7 KiB
Python
220 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
|