"""Plan service — 套餐定义与查询。""" from typing import List, Optional from app.core.config import settings from app.features.plan.schemas import CurrentPlanResponse, PlanResponse from app.features.quota.service import QuotaService from app.features.user.models import User ENABLE_TEST_PLAN = (settings.enable_test_plan or "").lower() in ("1", "true", "yes") AVAILABLE_PLANS = [ PlanResponse( id="free", name="free", display_name="免费体验版", price=0.0, currency="CNY", features=["500 轮对话", "无章节限制", "完整回忆录生成流程"], max_conversations=500, is_popular=False, ), PlanResponse( id="pro", name="pro", display_name="Pro 版", price=88.0, currency="CNY", features=["2000 轮对话", "无章节限制", "完整回忆录生成"], max_conversations=2000, is_popular=True, ), PlanResponse( id="pro_plus", name="pro_plus", display_name="Pro+ 版", price=288.0, currency="CNY", features=["10000 轮对话", "无章节限制", "完整回忆录生成", "长期创作无忧"], max_conversations=10000, is_popular=False, ), ] TEST_PLAN = PlanResponse( id="test", name="test", display_name="一分钱测试版", price=0.01, currency="CNY", features=["无限对话", "无限章节整理", "仅用于开发环境测试支付"], is_popular=False, ) def get_plans_for_api() -> list[PlanResponse]: if ENABLE_TEST_PLAN: return AVAILABLE_PLANS + [TEST_PLAN] return list(AVAILABLE_PLANS) def get_plan_by_type(subscription_type: str) -> Optional[PlanResponse]: if subscription_type == "premium": subscription_type = "pro" if subscription_type == "test": return TEST_PLAN if ENABLE_TEST_PLAN else AVAILABLE_PLANS[0] for plan in AVAILABLE_PLANS: if plan.id == subscription_type: return plan return AVAILABLE_PLANS[0] class PlanService: def __init__(self, quota_service: QuotaService): self._quota = quota_service def get_plans_for_api(self) -> list[PlanResponse]: """对外套餐列表(供 payment 等 feature 通过注入使用,不直接 import plan.service)。""" return get_plans_for_api() async def get_current_plan_response(self, user: User) -> CurrentPlanResponse: plan = get_plan_by_type(user.subscription_type) segment_count, chapter_count = await self._quota.get_usage(user.id) usage = { "conversations": segment_count, "chapters": chapter_count, "max_conversations": plan.max_conversations, "max_chapters": plan.max_chapters, } expires_at = None if user.subscription_expires_at: expires_at = user.subscription_expires_at.isoformat() return CurrentPlanResponse( plan_id=plan.id, plan_name=plan.display_name, subscription_type=user.subscription_type, expires_at=expires_at, features=plan.features, usage=usage, )