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") } }