Files
life-echo/api/app/features/plan/service.py
2026-03-19 14:36:40 +08:00

101 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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,
)