ssulbox/backend/app/routers/credit.py

90 lines
3.9 KiB
Python

# -*- 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"]}