Files

179 lines
5.4 KiB
Python

import json
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.api.v1.deps import get_current_user_id
from app.services.credit import CreditService
from app.services.payment import PaymentService
router = APIRouter()
class PurchaseRequest(BaseModel):
package_id: str
pay_type: str = "alipay"
class StripePurchaseRequest(BaseModel):
package_id: str
gateway: str = "stripe"
success_url: str = "https://trade.yuzhiran.com/workspace/credits?pay=success"
cancel_url: str = "https://trade.yuzhiran.com/workspace/credits?pay=cancel"
class SubscribeRequest(BaseModel):
plan_id: str
pay_type: str = "alipay"
@router.get("/balance")
async def get_balance(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
return await svc.get_balance(user_id)
@router.get("/history")
async def get_history(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
return await svc.get_history(user_id, page, size)
@router.get("/packages")
async def list_packages(
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
return await svc.get_packages()
@router.get("/subscription-plans")
async def list_subscription_plans(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
return await svc.get_subscription_plans()
@router.post("/purchase")
async def purchase_package(
req: PurchaseRequest,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
packages = await svc.get_packages()
pkg = next((p for p in packages if p["id"] == req.package_id), None)
if not pkg:
raise HTTPException(status_code=404, detail="次数包不存在")
pay_svc = PaymentService(db)
order = await pay_svc.create_credit_order(
user_id=user_id,
amount=pkg["price"],
description=f"购买 {pkg['name']} ({pkg['credits']}次)",
pay_type=req.pay_type,
metadata={"credit_package_id": req.package_id, "credits": pkg["credits"]},
)
return order
@router.post("/subscribe")
async def subscribe_plan(
req: SubscribeRequest,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
plans = await svc.get_subscription_plans()
plan = next((p for p in plans if p["id"] == req.plan_id), None)
if not plan:
raise HTTPException(status_code=404, detail="订阅套餐不存在")
pay_svc = PaymentService(db)
order = await pay_svc.create_credit_order(
user_id=user_id,
amount=plan["price"],
description=f"开通 {plan['name']} (每月{plan['credits_per_month']}次)",
pay_type=req.pay_type,
metadata={"subscription_plan_id": req.plan_id, "credits_per_month": plan["credits_per_month"]},
)
return order
@router.post("/stripe-purchase")
async def stripe_purchase(
req: StripePurchaseRequest,
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
svc = CreditService(db)
packages = await svc.get_packages()
pkg = next((p for p in packages if p["id"] == req.package_id), None)
if not pkg:
raise HTTPException(status_code=404, detail="次数包不存在")
from app.services.payment import get_gateway, gen_order_no
price_usd = pkg.get("price_usd") or round(pkg["price"] / 7, 2)
amount_cents = int(price_usd * 100)
order_no = gen_order_no(user_id)
gw = get_gateway(req.gateway)
sep = '&' if '?' in req.success_url else '?'
success_url = f"{req.success_url}{sep}order_id={order_no}"
gw_result = await gw.create_order(
order_no, amount_cents, f"{pkg['name_en']} ({pkg['credits']} credits)",
pay_type=req.gateway,
success_url=success_url,
cancel_url=req.cancel_url,
)
from app.models.payment_transaction import PaymentTransaction
txn = PaymentTransaction(
user_id=user_id, order_no=order_no, plan="credit_purchase",
amount=price_usd, gateway=req.gateway, pay_type=req.gateway,
status="pending", description=json.dumps({"credits": pkg["credits"]}),
gateway_order_no=gw_result.get("session_id", ""),
)
db.add(txn)
await db.flush()
return {
"status": "pending",
"order_id": order_no,
"session_url": gw_result.get("session_url"),
"session_id": gw_result.get("session_id"),
"amount": price_usd,
"currency": "USD",
"gateway": req.gateway,
}
@router.post("/cancel-subscription")
async def cancel_subscription(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
from app.models.user_credit import UserCredit
from sqlalchemy import select
result = await db.execute(select(UserCredit).where(UserCredit.user_id == user_id))
uc = result.scalar_one_or_none()
if not uc or not uc.subscription_plan_id:
raise HTTPException(status_code=400, detail="没有有效的订阅")
uc.subscription_auto_renew = False
await db.flush()
return {"success": True, "message": "已取消自动续费,当前订阅到期后不再续费"}