13c302c037
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 54s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 55s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m5s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m2s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 54s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 3m24s
CI/CD Pipeline / Integration Tests (push) Successful in 3m32s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 4m7s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m6s
CI/CD Pipeline / Validate - Style (push) Successful in 5m29s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m23s
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Failing after 6h5m2s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 6h2m58s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 6h5m9s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 6h2m58s
CI/CD Pipeline / PR Build API Image (push) Failing after 6h4m9s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 6h2m58s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 6h4m8s
CI/CD Pipeline / Frontend Lint (push) Failing after 6h4m10s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
210 lines
5.4 KiB
Python
210 lines
5.4 KiB
Python
"""积分 & 会员相关 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
|