Spaces:
Sleeping
Sleeping
File size: 6,530 Bytes
7482820 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | """
支付相关 API 路由
"""
import logging
from typing import Optional, List
from datetime import datetime
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from ...database.session import get_db
from ...database.models import Account
from ...database import crud
from ...config.settings import get_settings
from .accounts import resolve_account_ids
from ...core.openai.payment import (
generate_plus_link,
generate_team_link,
open_url_incognito,
check_subscription_status,
)
logger = logging.getLogger(__name__)
router = APIRouter()
# ============== Pydantic Models ==============
class GenerateLinkRequest(BaseModel):
account_id: int
plan_type: str # 'plus' or 'team'
workspace_name: str = "MyTeam"
price_interval: str = "month"
seat_quantity: int = 5
proxy: Optional[str] = None
auto_open: bool = False # 生成后是否自动无痕打开
country: str = "SG" # 计费国家,决定货币 # 生成后是否自动无痕打开
class OpenIncognitoRequest(BaseModel):
url: str
account_id: Optional[int] = None # 可选,用于注入账号 cookie
class MarkSubscriptionRequest(BaseModel):
subscription_type: str # 'free' / 'plus' / 'team'
class BatchCheckSubscriptionRequest(BaseModel):
ids: List[int] = []
proxy: Optional[str] = None
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
# ============== 支付链接生成 ==============
@router.post("/generate-link")
def generate_payment_link(request: GenerateLinkRequest):
"""生成 Plus 或 Team 支付链接,可选自动无痕打开"""
with get_db() as db:
account = db.query(Account).filter(Account.id == request.account_id).first()
if not account:
raise HTTPException(status_code=404, detail="账号不存在")
proxy = request.proxy or get_settings().proxy_url
try:
if request.plan_type == "plus":
link = generate_plus_link(account, proxy, country=request.country)
elif request.plan_type == "team":
link = generate_team_link(
account,
workspace_name=request.workspace_name,
price_interval=request.price_interval,
seat_quantity=request.seat_quantity,
proxy=proxy,
country=request.country,
)
else:
raise HTTPException(status_code=400, detail="plan_type 必须为 plus 或 team")
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"生成支付链接失败: {e}")
raise HTTPException(status_code=500, detail=f"生成链接失败: {str(e)}")
opened = False
if request.auto_open and link:
cookies_str = account.cookies if account else None
opened = open_url_incognito(link, cookies_str)
return {
"success": True,
"link": link,
"plan_type": request.plan_type,
"auto_opened": opened,
}
@router.post("/open-incognito")
def open_browser_incognito(request: OpenIncognitoRequest):
"""后端以无痕模式打开指定 URL,可注入账号 cookie"""
if not request.url:
raise HTTPException(status_code=400, detail="URL 不能为空")
cookies_str = None
if request.account_id:
with get_db() as db:
account = db.query(Account).filter(Account.id == request.account_id).first()
if account:
cookies_str = account.cookies
success = open_url_incognito(request.url, cookies_str)
if success:
return {"success": True, "message": "已在无痕模式打开浏览器"}
return {"success": False, "message": "未找到可用的浏览器,请手动复制链接"}
# ============== 订阅状态 ==============
@router.post("/accounts/batch-check-subscription")
def batch_check_subscription(request: BatchCheckSubscriptionRequest):
"""批量检测账号订阅状态"""
proxy = request.proxy or get_settings().proxy_url
results = {"success_count": 0, "failed_count": 0, "details": []}
with get_db() as db:
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
for account_id in ids:
account = db.query(Account).filter(Account.id == account_id).first()
if not account:
results["failed_count"] += 1
results["details"].append(
{"id": account_id, "email": None, "success": False, "error": "账号不存在"}
)
continue
try:
status = check_subscription_status(account, proxy)
account.subscription_type = None if status == "free" else status
account.subscription_at = datetime.utcnow() if status != "free" else account.subscription_at
db.commit()
results["success_count"] += 1
results["details"].append(
{"id": account_id, "email": account.email, "success": True, "subscription_type": status}
)
except Exception as e:
results["failed_count"] += 1
results["details"].append(
{"id": account_id, "email": account.email, "success": False, "error": str(e)}
)
return results
@router.post("/accounts/{account_id}/mark-subscription")
def mark_subscription(account_id: int, request: MarkSubscriptionRequest):
"""手动标记账号订阅类型"""
allowed = ("free", "plus", "team")
if request.subscription_type not in allowed:
raise HTTPException(status_code=400, detail=f"subscription_type 必须为 {allowed}")
with get_db() as db:
account = db.query(Account).filter(Account.id == account_id).first()
if not account:
raise HTTPException(status_code=404, detail="账号不存在")
account.subscription_type = None if request.subscription_type == "free" else request.subscription_type
account.subscription_at = datetime.utcnow() if request.subscription_type != "free" else None
db.commit()
return {"success": True, "subscription_type": request.subscription_type}
|