90 lines
3.9 KiB
Python
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"]}
|