# -*- coding: utf-8 -*- """크레딧 라우터: 잔액·원장 조회 + (개발용) 충전. 실서비스 충전은 관리자 승인 워크플로 필요.""" from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from .. import credit_service from ..config import settings from ..database import get_session from ..deps import Pagination, get_current_user, get_pagination from ..models import CreditTransaction, User from ..schemas import CreditTx, PaginatedResponse router = APIRouter(prefix="/api/credits", tags=["Credit"]) @router.get("/balance") async def balance(user: User = Depends(get_current_user)): return {"credits": user.credits} @router.get("/transactions", response_model=PaginatedResponse[CreditTx]) async def transactions(pg: Pagination = Depends(get_pagination), user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session)): where = [CreditTransaction.user_uuid == user.user_uuid] total = (await session.execute(select(func.count(CreditTransaction.id)).where(*where))).scalar() or 0 rows = ( await session.execute( select(CreditTransaction).where(*where).order_by(CreditTransaction.created_at.desc()) .offset(pg.offset).limit(pg.page_size) ) ).scalars().all() items = [CreditTx(amount=r.amount, balance_after=r.balance_after, type=r.type, reason=r.reason, created_at=r.created_at) for r in rows] return PaginatedResponse.create(items, total, pg.page, pg.page_size) class ChargeReq(BaseModel): amount: int = 10 @router.post("/charge") async def charge(body: ChargeReq, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session)): """개발용 즉시 충전. 실서비스에선 결제/관리자 승인으로 대체.""" if not settings.DEBUG: raise HTTPException(403, "충전은 결제 연동 후 제공됩니다.") if body.amount <= 0 or body.amount > 1000: raise HTTPException(400, "충전 금액이 올바르지 않습니다.") await credit_service.charge(session, user.user_uuid, body.amount, type_="charge", reason="개발 충전") await session.commit() return {"credits": user.credits} # ── 크레딧 충전 상품 ───────────────────────────────────────────── # 편당 1크레딧 소모. bonus 는 덤으로 주는 크레딧. PRODUCTS = [ {"id": "p10", "label": "스타터", "credits": 10, "bonus": 0, "price": 3900, "best": False}, {"id": "p30", "label": "베이직", "credits": 30, "bonus": 5, "price": 9900, "best": True}, {"id": "p100", "label": "프로", "credits": 100, "bonus": 25, "price": 29900, "best": False}, ] _PRODUCTS_BY_ID = {p["id"]: p for p in PRODUCTS} @router.get("/products") async def products(): """충전 상품 목록. price 는 원(₩).""" return {"items": PRODUCTS} class PurchaseReq(BaseModel): product_id: str @router.post("/purchase") async def purchase(body: PurchaseReq, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session)): """상품 구매 → 크레딧 지급. 결제(PG) 미연동이라 개발 모드에서 즉시 지급.""" p = _PRODUCTS_BY_ID.get(body.product_id) if not p: raise HTTPException(404, "존재하지 않는 상품입니다.") if not settings.DEBUG: raise HTTPException(501, "결제 연동 준비 중입니다.") # 실서비스: PG 결제 검증 후 지급 total = p["credits"] + p["bonus"] await credit_service.charge(session, user.user_uuid, total, type_="purchase", reason=f"{p['label']} 상품({p['credits']}+{p['bonus']})") await session.commit() return {"credits": user.credits, "added": total, "product": p["label"]}