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": "已取消自动续费,当前订阅到期后不再续费"}