feat: 修复 H5 底部导航覆盖 + 更新项目进度文档
## H5 底部导航修复 (Bug #10) - 精简 App.vue,移除重复 tabbar,仅保留全局样式 - uni-page 设置 height: calc(100% - 50px) + overflow-y: auto - 内容区域精确停在底部导航上方,独立滚动不再叠加 - 恢复 custom-tab-bar 组件 ## 项目进度文档 - PROGRESS.md 更新至 10 个 Bug 修复 - 新增 H5 底部导航修复记录 - 新增历史变更条目
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import hmac
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from app.models.subscription import Subscription
|
||||
from app.models.user import User
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PLANS = {
|
||||
"free": {"price": 0, "duration_days": None},
|
||||
"pro": {"price": 99, "duration_days": 30},
|
||||
"enterprise": {"price": 399, "duration_days": 30},
|
||||
}
|
||||
|
||||
|
||||
class PaymentService:
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def get_plans(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"plans": [
|
||||
{
|
||||
"id": "free",
|
||||
"name": "免费版",
|
||||
"price": 0,
|
||||
"features": [
|
||||
"1 个产品",
|
||||
"20 次翻译/天",
|
||||
"5 个客户",
|
||||
"基础回复建议",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "pro",
|
||||
"name": "Pro 版",
|
||||
"price": 99,
|
||||
"features": [
|
||||
"10 个产品",
|
||||
"无限翻译",
|
||||
"50 个客户",
|
||||
"跟进提醒",
|
||||
"报价单生成",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "enterprise",
|
||||
"name": "企业版",
|
||||
"price": 399,
|
||||
"features": [
|
||||
"无限产品",
|
||||
"多人协作",
|
||||
"品牌报价单",
|
||||
"专属语料训练",
|
||||
"API 接入",
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async def get_current_subscription(self, user_id: str) -> Dict[str, Any]:
|
||||
result = await self.db.execute(
|
||||
select(Subscription).where(
|
||||
Subscription.user_id == user_id,
|
||||
Subscription.status == "active",
|
||||
).order_by(Subscription.created_at.desc()).limit(1)
|
||||
)
|
||||
sub = result.scalar_one_or_none()
|
||||
|
||||
result = await self.db.execute(
|
||||
select(User).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
return {
|
||||
"plan": user.tier if user else "free",
|
||||
"status": sub.status if sub else "active",
|
||||
"expires_at": sub.expires_at.isoformat() if sub and sub.expires_at else None,
|
||||
"auto_renew": sub.auto_renew if sub else False,
|
||||
}
|
||||
|
||||
async def create_order(self, user_id: str, plan: str) -> Dict[str, Any]:
|
||||
if plan not in PLANS:
|
||||
raise ValueError(f"Invalid plan: {plan}")
|
||||
|
||||
plan_info = PLANS[plan]
|
||||
if plan_info["price"] == 0:
|
||||
result = await self.db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if user:
|
||||
user.tier = plan
|
||||
await self.db.flush()
|
||||
return {"status": "ok", "plan": plan, "amount": 0}
|
||||
|
||||
from app.config import settings
|
||||
order_id = f"ORD{datetime.utcnow().strftime('%Y%m%d%H%M%S')}{user_id[-6:]}"
|
||||
|
||||
sub = Subscription(
|
||||
user_id=user_id,
|
||||
plan=plan,
|
||||
status="pending",
|
||||
amount=plan_info["price"],
|
||||
payment_id=order_id,
|
||||
)
|
||||
self.db.add(sub)
|
||||
await self.db.flush()
|
||||
|
||||
pay_params = {
|
||||
"appId": settings.WECHAT_APP_ID or "",
|
||||
"timeStamp": str(int(datetime.utcnow().timestamp())),
|
||||
"nonceStr": hashlib.md5(order_id.encode()).hexdigest()[:16],
|
||||
"package": f"prepay_id={order_id}",
|
||||
"signType": "MD5",
|
||||
}
|
||||
sign_str = "&".join(f"{k}={v}" for k, v in sorted(pay_params.items()))
|
||||
sign_str += f"&key={settings.SECRET_KEY}"
|
||||
pay_params["paySign"] = hashlib.md5(sign_str.encode()).hexdigest().upper()
|
||||
|
||||
return {
|
||||
"status": "pending",
|
||||
"order_id": order_id,
|
||||
"plan": plan,
|
||||
"amount": plan_info["price"],
|
||||
"currency": "CNY",
|
||||
"pay_params": pay_params,
|
||||
}
|
||||
|
||||
async def handle_payment_callback(self, payment_id: str, success: bool) -> bool:
|
||||
result = await self.db.execute(
|
||||
select(Subscription).where(Subscription.payment_id == payment_id)
|
||||
)
|
||||
sub = result.scalar_one_or_none()
|
||||
if not sub:
|
||||
return False
|
||||
|
||||
if success:
|
||||
sub.status = "active"
|
||||
sub.started_at = datetime.utcnow()
|
||||
sub.expires_at = datetime.utcnow() + timedelta(days=PLANS[sub.plan]["duration_days"])
|
||||
|
||||
user_result = await self.db.execute(select(User).where(User.id == sub.user_id))
|
||||
user = user_result.scalar_one_or_none()
|
||||
if user:
|
||||
user.tier = sub.plan
|
||||
else:
|
||||
sub.status = "failed"
|
||||
|
||||
await self.db.flush()
|
||||
return True
|
||||
|
||||
|
||||
payment_service = PaymentService
|
||||
Reference in New Issue
Block a user