Files
back-hokm/internal/economy/handler.go
T
2026-06-15 16:53:25 +03:30

187 lines
5.4 KiB
Go

package economy
import (
"encoding/json"
"errors"
"net/http"
"hakemsho/internal/auth"
"hakemsho/internal/httpx"
)
// Handler endpointهای فروشگاه و کیف‌پول (همه پشت JWT).
type Handler struct {
svc *Service
}
func NewHandler(svc *Service) *Handler { return &Handler{svc: svc} }
func uid(r *http.Request) (int64, bool) { return auth.UserID(r.Context()) }
// Wallet — GET /api/wallet
func (h *Handler) Wallet(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
p, err := h.svc.GetProfile(r.Context(), id)
if err != nil {
httpx.Error(w, http.StatusNotFound, "user not found")
return
}
httpx.JSON(w, http.StatusOK, p)
}
// Shop — GET /api/shop
func (h *Handler) Shop(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
owned, err := h.svc.OwnedCards(r.Context(), id)
if err != nil {
httpx.Error(w, http.StatusInternalServerError, "server error")
return
}
p, _ := h.svc.GetProfile(r.Context(), id)
selected := "simple"
if p != nil {
selected = p.SelectedCard
}
httpx.JSON(w, http.StatusOK, map[string]any{
"catalog": FullCatalog(),
"owned_cards": owned,
"selected_card": selected,
})
}
type cardReq struct {
CardID string `json:"card_id"`
}
// BuyCard — POST /api/shop/buy-card
func (h *Handler) BuyCard(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
var req cardReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
httpx.Error(w, http.StatusBadRequest, "invalid body")
return
}
switch err := h.svc.BuyCard(r.Context(), id, req.CardID); {
case err == nil:
httpx.JSON(w, http.StatusOK, map[string]string{"message": "ok"})
case errors.Is(err, ErrNotFound):
httpx.Error(w, http.StatusNotFound, "card not found")
case errors.Is(err, ErrAlreadyOwned):
httpx.Error(w, http.StatusConflict, "already owned")
case errors.Is(err, ErrInsufficient):
httpx.Error(w, http.StatusPaymentRequired, "insufficient coins")
default:
httpx.Error(w, http.StatusInternalServerError, "server error")
}
}
// SelectCard — POST /api/shop/select-card
func (h *Handler) SelectCard(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
var req cardReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
httpx.Error(w, http.StatusBadRequest, "invalid body")
return
}
switch err := h.svc.SelectCard(r.Context(), id, req.CardID); {
case err == nil:
httpx.JSON(w, http.StatusOK, map[string]string{"message": "ok"})
case errors.Is(err, ErrNotFound):
httpx.Error(w, http.StatusNotFound, "card not found")
case errors.Is(err, ErrNotOwned):
httpx.Error(w, http.StatusForbidden, "card not owned")
default:
httpx.Error(w, http.StatusInternalServerError, "server error")
}
}
// Purchase — POST /api/shop/purchase (تأیید خرید IAP)
func (h *Handler) Purchase(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
var req PurchaseRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
httpx.Error(w, http.StatusBadRequest, "invalid body")
return
}
switch err := h.svc.VerifyPurchase(r.Context(), id, req); {
case err == nil:
p, _ := h.svc.GetProfile(r.Context(), id)
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "wallet": p})
case errors.Is(err, ErrAlreadyOwned):
httpx.Error(w, http.StatusConflict, "already processed")
case errors.Is(err, ErrIAPInvalid):
httpx.Error(w, http.StatusBadRequest, "purchase not valid")
case errors.Is(err, ErrNotFound):
httpx.Error(w, http.StatusNotFound, "package not found")
default:
httpx.Error(w, http.StatusInternalServerError, "server error")
}
}
// Daily — POST /api/rewards/daily
func (h *Handler) Daily(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
switch amount, err := h.svc.ClaimDaily(r.Context(), id); {
case err == nil:
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "amount": amount})
case errors.Is(err, ErrTooSoon):
httpx.Error(w, http.StatusTooManyRequests, "already claimed today")
default:
httpx.Error(w, http.StatusInternalServerError, "server error")
}
}
type adReq struct {
Token string `json:"token"`
}
// AdReward — POST /api/rewards/ad (سکه رایگان پس از دیدن کامل تبلیغ)
func (h *Handler) AdReward(w http.ResponseWriter, r *http.Request) {
id, ok := uid(r)
if !ok {
httpx.Error(w, http.StatusUnauthorized, "unauthorized")
return
}
var req adReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
httpx.Error(w, http.StatusBadRequest, "invalid body")
return
}
switch amount, err := h.svc.ClaimAdReward(r.Context(), id, req.Token); {
case err == nil:
httpx.JSON(w, http.StatusOK, map[string]any{"message": "ok", "amount": amount})
case errors.Is(err, ErrAdNotVerified):
httpx.Error(w, http.StatusBadRequest, "ad not verified")
case errors.Is(err, ErrAdDuplicate):
httpx.Error(w, http.StatusConflict, "already claimed")
case errors.Is(err, ErrAdCap):
httpx.Error(w, http.StatusTooManyRequests, "daily ad cap reached")
default:
httpx.Error(w, http.StatusInternalServerError, "server error")
}
}