qwen_agent/routes/payment.py
2026-02-26 23:56:45 +08:00

391 lines
10 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.

"""
支付代理 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