158 lines
5.3 KiB
Python
158 lines
5.3 KiB
Python
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,
|
||
_get_payment_configs,
|
||
_is_mock_mode,
|
||
_check_and_expire_order,
|
||
)
|
||
|
||
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
|
||
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
|
||
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}
|