Files
video-gen/video-gen-api/app/api/v1/payments.py
T

157 lines
5.3 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.
import logging
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
logger = logging.getLogger("videogen")
from app.dependencies import get_db, get_current_user
from app.models.user import User
from app.models.payment_order import PaymentOrder
from app.models.recharge_package import RechargePackage
from app.schemas.payment import RechargeRequest, PaymentOrderOut
from app.services.payment import (
create_recharge_order,
verify_wechat_callback,
verify_alipay_callback,
process_payment_success_by_order_no,
)
router = APIRouter(prefix="/payments", tags=["payments"])
@router.get("/methods")
async def get_payment_methods(db: AsyncSession = Depends(get_db)):
"""Return which payment methods are enabled (from admin config)."""
from app.services.payment import _get_payment_configs
configs = await _get_payment_configs(db)
return {
"alipay": configs.get("payment_alipay_enabled", "").lower() == "true",
"wechat": configs.get("payment_wechat_enabled", "").lower() == "true",
}
@router.post("/recharge", response_model=PaymentOrderOut)
async def recharge(
req: RechargeRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
if req.method not in ("wechat", "alipay"):
raise HTTPException(status_code=400, detail="不支持的支付方式")
# Check if the selected payment method is enabled in admin config
from app.services.payment import _get_payment_configs, _is_mock_mode
configs = await _get_payment_configs(db)
if not _is_mock_mode(configs):
enabled_key = f"payment_{req.method}_enabled"
if configs.get(enabled_key, "").lower() != "true":
raise HTTPException(status_code=400, detail="该支付方式未启用")
result = await db.execute(
select(RechargePackage).where(
RechargePackage.id == req.plan,
RechargePackage.is_active == True,
)
.limit(1)
)
pkg = result.scalar_one_or_none()
if not pkg:
raise HTTPException(status_code=400, detail="无效的套餐")
try:
order = await create_recharge_order(
db,
current_user.id,
credits=pkg.credits,
price=pkg.price,
label=pkg.name,
bonus_credits=pkg.bonus_credits,
method=req.method,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
return order
@router.post("/wechat/callback")
async def wechat_callback(request: Request, db: AsyncSession = Depends(get_db)):
data = await request.json()
if not await verify_wechat_callback(data, db):
raise HTTPException(status_code=400, detail="签名验证失败")
order_no = data.get("out_trade_no")
if order_no:
await process_payment_success_by_order_no(db, order_no)
return {"code": "SUCCESS", "message": "OK"}
@router.post("/alipay/callback")
async def alipay_callback(request: Request, db: AsyncSession = Depends(get_db)):
form_data = await request.form()
data = dict(form_data)
logger.info(
f"ALIPAY_CALLBACK order_no={data.get('out_trade_no')} "
f"data={data}"
)
# Verify signature first
if not await verify_alipay_callback(data, db):
raise HTTPException(status_code=400, detail="签名验证失败")
# Check trade_status only "TRADE_SUCCESS" and "TRADE_FINISHED" mean paid
trade_status = data.get("trade_status", "")
if trade_status not in ("TRADE_SUCCESS", "TRADE_FINISHED"):
logger.info(f"Alipay callback trade_status={trade_status}, ignoring")
return "success"
order_no = data.get("out_trade_no")
trade_no = data.get("trade_no", "")
if order_no:
await process_payment_success_by_order_no(db, order_no, trade_no)
return "success"
@router.get("/orders", response_model=list[PaymentOrderOut])
async def list_orders(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
# Auto-expire stale pending orders before returning
from app.services.payment import _check_and_expire_order
result = await db.execute(
select(PaymentOrder)
.where(PaymentOrder.user_id == current_user.id)
.order_by(PaymentOrder.created_at.desc())
)
orders = result.scalars().all()
for o in orders:
await _check_and_expire_order(db, o)
return orders
@router.post("/orders/{order_no}/cancel")
async def cancel_order(
order_no: str,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Cancel a pending order. Only the order owner can cancel, only if still pending."""
result = await db.execute(
select(PaymentOrder).where(
PaymentOrder.order_no == order_no,
PaymentOrder.user_id == current_user.id,
).limit(1)
)
order = result.scalar_one_or_none()
if not order:
raise HTTPException(status_code=404, detail="订单不存在")
if order.status != "pending":
raise HTTPException(status_code=400, detail=f"订单状态为{order.status},无法取消")
order.status = "cancelled"
await db.flush()
logger.info(
f"ORDER_CANCELLED order_no={order_no} user={current_user.id} amount={order.amount}"
)
return {"ok": True}