391 lines
10 KiB
Python
391 lines
10 KiB
Python
"""
|
||
支付代理 API 路由
|
||
代理前端请求到 New API 支付后端
|
||
"""
|
||
import logging
|
||
from typing import Optional, List
|
||
from fastapi import APIRouter, HTTPException, Header, Cookie
|
||
from pydantic import BaseModel
|
||
|
||
from utils.new_api_proxy import get_new_api_proxy
|
||
from agent.db_pool_manager import get_db_pool_manager
|
||
from utils.fastapi_utils import extract_api_key_from_auth
|
||
|
||
logger = logging.getLogger('app')
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
# ============== Pydantic Models ==============
|
||
|
||
class TopupRequest(BaseModel):
|
||
"""充值请求"""
|
||
amount: int # 充值金额
|
||
payment_method: str = "alipay" # 支付方式: alipay, wxpay
|
||
|
||
|
||
class StripeTopupRequest(BaseModel):
|
||
"""Stripe 充值请求"""
|
||
amount: int
|
||
|
||
|
||
class CreemTopupRequest(BaseModel):
|
||
"""Creem 充值请求"""
|
||
product_id: str
|
||
|
||
|
||
class SubscribeRequest(BaseModel):
|
||
"""订阅请求"""
|
||
plan_id: int
|
||
payment_method: str = "alipay" # 支付方式: alipay, wxpay
|
||
|
||
|
||
class PaymentInfoResponse(BaseModel):
|
||
"""支付信息响应"""
|
||
success: bool
|
||
message: str = ""
|
||
data: Optional[dict] = None
|
||
|
||
|
||
class OrderListResponse(BaseModel):
|
||
"""订单列表响应"""
|
||
success: bool
|
||
message: str = ""
|
||
data: Optional[dict] = None
|
||
|
||
|
||
# ============== 辅助函数 ==============
|
||
|
||
async def verify_user_and_get_session(authorization: Optional[str]) -> tuple[str, Optional[dict], Optional[int]]:
|
||
"""
|
||
验证用户并获取 New API session cookies 和 user_id
|
||
|
||
子账号无法访问支付功能
|
||
|
||
Args:
|
||
authorization: Authorization header
|
||
|
||
Returns:
|
||
tuple[str, Optional[dict], Optional[int]]: (user_id, cookies_dict, new_api_user_id)
|
||
|
||
Raises:
|
||
HTTPException: 认证失败或子账号访问
|
||
"""
|
||
token = extract_api_key_from_auth(authorization)
|
||
if not token:
|
||
raise HTTPException(status_code=401, detail="Authorization required")
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
# 验证 token 并获取用户信息
|
||
await cursor.execute("""
|
||
SELECT u.id, u.new_api_session, u.new_api_user_id, u.is_subaccount
|
||
FROM agent_user_tokens t
|
||
JOIN agent_user u ON t.user_id = u.id
|
||
WHERE t.token = %s
|
||
AND t.expires_at > NOW()
|
||
AND u.is_active = TRUE
|
||
""", (token,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
||
|
||
user_id, new_api_session, new_api_user_id, is_subaccount = row
|
||
|
||
# 子账号不能访问支付功能
|
||
if is_subaccount:
|
||
raise HTTPException(
|
||
status_code=403,
|
||
detail="Subaccounts cannot access payment features"
|
||
)
|
||
|
||
# 如果有 session,构建 cookies
|
||
cookies = None
|
||
if new_api_session:
|
||
cookies = {"session": new_api_session}
|
||
|
||
return str(user_id), cookies, new_api_user_id
|
||
|
||
|
||
# ============== 套餐/产品相关 API ==============
|
||
|
||
@router.get("/api/v1/payment/packages")
|
||
async def get_payment_packages(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取可购买的套餐列表
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
# 如果没有 New API session 或 user_id,返回需要绑定的提示
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_topup_info(cookies, new_api_user_id)
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/api/v1/payment/subscription-plans")
|
||
async def get_subscription_plans(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取订阅计划列表
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_subscription_plans(cookies, new_api_user_id)
|
||
|
||
return result
|
||
|
||
|
||
# ============== 支付相关 API ==============
|
||
|
||
@router.post("/api/v1/payment/topup")
|
||
async def create_topup_order(
|
||
request: TopupRequest,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
创建充值订单(易支付)
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.create_topup_order(
|
||
cookies=cookies,
|
||
amount=request.amount,
|
||
payment_method=request.payment_method,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
@router.post("/api/v1/payment/stripe")
|
||
async def create_stripe_order(
|
||
request: StripeTopupRequest,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
创建 Stripe 支付订单
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.create_stripe_order(
|
||
cookies=cookies,
|
||
amount=request.amount,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
@router.post("/api/v1/payment/creem")
|
||
async def create_creem_order(
|
||
request: CreemTopupRequest,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
创建 Creem 支付订单
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.create_creem_order(
|
||
cookies=cookies,
|
||
product_id=request.product_id,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
# ============== 订单相关 API ==============
|
||
|
||
@router.get("/api/v1/payment/orders")
|
||
async def get_order_list(
|
||
page: int = 1,
|
||
page_size: int = 20,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
获取订单列表
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_order_list(
|
||
cookies=cookies,
|
||
page=page,
|
||
page_size=page_size,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/api/v1/payment/orders/{order_id}")
|
||
async def get_order_status(
|
||
order_id: str,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
查询订单状态
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_order_status(
|
||
cookies=cookies,
|
||
order_id=order_id,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
# ============== 订阅相关 API ==============
|
||
|
||
@router.post("/api/v1/payment/subscribe")
|
||
async def subscribe_plan(
|
||
request: SubscribeRequest,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
订阅计划(易支付)
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.subscribe_plan(
|
||
cookies=cookies,
|
||
plan_id=request.plan_id,
|
||
payment_method=request.payment_method,
|
||
new_api_user_id=new_api_user_id
|
||
)
|
||
|
||
return result
|
||
|
||
|
||
@router.get("/api/v1/payment/subscription/status")
|
||
async def get_subscription_status(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取订阅状态
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_subscription_status(cookies, new_api_user_id)
|
||
|
||
return result
|
||
|
||
|
||
@router.post("/api/v1/payment/subscription/cancel")
|
||
async def cancel_subscription(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
取消订阅
|
||
|
||
注意:New API 不支持用户自行取消订阅,需要联系管理员
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
# New API 不支持用户自行取消订阅,需要管理员操作
|
||
return {
|
||
"success": False,
|
||
"message": "如需取消订阅,请联系管理员处理"
|
||
}
|
||
|
||
|
||
# ============== 额度相关 API ==============
|
||
|
||
@router.get("/api/v1/payment/quota")
|
||
async def get_quota(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取用户额度信息
|
||
"""
|
||
user_id, cookies, new_api_user_id = await verify_user_and_get_session(authorization)
|
||
|
||
if not cookies or not new_api_user_id:
|
||
return {
|
||
"success": False,
|
||
"need_bind": True,
|
||
"message": "请先绑定支付账户"
|
||
}
|
||
|
||
proxy = get_new_api_proxy()
|
||
result = await proxy.get_quota(cookies, new_api_user_id)
|
||
|
||
return result
|