86 lines
3.0 KiB
Python
86 lines
3.0 KiB
Python
|
|
"""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,
|
|||
|
|
)
|