"""积分 & 会员相关 Pydantic Schema (#1895)""" from __future__ import annotations from datetime import datetime from typing import Any, Optional from pydantic import BaseModel, Field # ============ 余额 & 账户 ============ class PointsBalanceResponse(BaseModel): """积分余额 + 会员状态""" balance: int = Field(..., description="当前积分余额") total_earned: int = Field(..., description="累计获得积分") total_spent: int = Field(..., description="累计消耗积分") is_member: bool = Field(default=False, description="是否付费会员") member_type: Optional[str] = Field(None, description="会员类型: monthly/quarterly/yearly") member_expires_at: Optional[datetime] = Field(None, description="会员到期时间") # ============ 流水 ============ class PointsTransactionItem(BaseModel): """单条积分流水""" id: str type: str = Field(..., description="类型: add/deduct") source: str = Field(..., description="来源场景") amount: int balance_after: int description: str = "" ref_id: str = "" created_at: Optional[str] = None class PointsTransactionsResponse(BaseModel): """积分流水分页响应""" items: list[PointsTransactionItem] total: int page: int page_size: int # ============ 规则 & 积分包 ============ class PointRuleItem(BaseModel): """单条积分规则""" scene_key: str name: str base_points: int unit: str extra_per_30s: Optional[int] = None description: str = Field(default="", description="规则中文说明,例如 AI 配音每分钟消耗 X 积分") class PointsRulesResponse(BaseModel): """所有积分消耗规则""" rules: list[PointRuleItem] free_user_multiplier: float = Field(..., description="免费用户积分上浮系数") class PointsPackageItem(BaseModel): """积分包信息""" code: str name: str points: int price_cents: int unit_price: str = Field("", description="单价描述,如 ¥0.099/积分") class PointsPackagesResponse(BaseModel): """可购买的积分包列表""" packages: list[PointsPackageItem] user_discount: Optional[float] = Field(None, description="当前用户折扣(会员)") # ============ 消费前检查 ============ class PointsCheckRequest(BaseModel): """消费前余额检查请求""" scene_key: str duration_minutes: Optional[float] = None quantity: Optional[int] = 1 class PointsCheckResponse(BaseModel): """消费前余额检查响应""" allowed: bool required_points: int current_balance: int remaining_after: int is_free_quota: bool = False # ============ 手动扣减 / 退还(内部接口) ============ class PointsDeductRequest(BaseModel): """积分扣减请求""" scene_key: str amount: int description: Optional[str] = "" ref_id: Optional[str] = "" class PointsRefundRequest(BaseModel): """积分退还请求""" transaction_id: str reason: Optional[str] = "" class PointsRechargeRequest(BaseModel): """积分充值请求""" package_id: str = Field(..., description="积分包 code,如 starter_pack") # ============ 订单 ============ class PointsOrderResponse(BaseModel): """订单信息""" id: str order_type: str product_code: str amount_cents: int points_amount: int = Field(0, description="本次充值/购买可获得的积分(仅 points 类型订单有意义)") status: str pay_params: dict[str, Any] = Field( default_factory=dict, description="拉起支付所需参数(payment_url/prepay_id 等),支付通道接入后填充" ) expire_at: Optional[str] = Field(None, description="订单过期时间(ISO 8601),默认创建后 48 小时") created_at: Optional[str] = None # ============ 每日额度 ============ class DailyUsageResponse(BaseModel): """今日免费额度使用情况""" free_clips_used: int free_clips_limit: int free_clips_remaining: int reset_at: str # ============ 会员状态(聚合) ============ class MembershipStatusResponse(BaseModel): """当前用户会员状态(聚合信息)""" is_member: bool member_type: Optional[str] = None member_expires_at: Optional[datetime] = None points_balance: int max_resolution: str = Field( default="1080p", description="可用最高分辨率: 720p(free) / 1080p(paid)", ) # ============ 订阅档位 ============ class MembershipPlanItem(BaseModel): """单个会员档位""" plan_id: str = Field(..., description="档位标识: monthly/quarterly/yearly") name: str = Field(..., description="档位名称,例如 月卡") monthly_price_cents: int = Field(..., description="折算月价(分)") price_cents: int = Field(..., description="该档位总价(分)") duration_days: int = Field(..., description="时长(天)") points_discount: float = Field(..., description="该档位积分折扣,如 0.9 表示 9 折") features: dict[str, Any] = Field(default_factory=dict, description="档位权益(max_resolution 等)") class MembershipPlansResponse(BaseModel): """所有会员档位列表""" plans: list[MembershipPlanItem] # ============ 通用响应 ============ class SimpleMessageResponse(BaseModel): """简单消息响应""" success: bool message: str data: Optional[dict[str, Any]] = None