package v1 import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/response" ) // EnterpriseAccountHandler implements Chatwoot enterprise account billing and // limit endpoints consumed by the reused dashboard EnterpriseAccountAPI client. type EnterpriseAccountHandler struct { svc *service.AccountService } func NewEnterpriseAccountHandler(svc *service.AccountService) *EnterpriseAccountHandler { return &EnterpriseAccountHandler{svc: svc} } func enterpriseAccountID(c *gin.Context) uint { if id := parseAccountIDParam(c); id != 0 { return id } return getAccountID(c) } // Limits returns account usage limits in Chatwoot's enterprise payload shape. // GET /enterprise/api/v1/accounts/:account_id/limits func (h *EnterpriseAccountHandler) Limits(c *gin.Context) { accountID := enterpriseAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } payload, err := h.svc.EnterpriseLimits(c.Request.Context(), accountID, getUserID(c)) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found") return } c.JSON(http.StatusOK, payload) } // ToggleDeletion marks or unmarks an account for scheduled deletion. // POST /enterprise/api/v1/accounts/:account_id/toggle_deletion func (h *EnterpriseAccountHandler) ToggleDeletion(c *gin.Context) { accountID := enterpriseAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } var req struct { ActionType string `json:"action_type" form:"action_type"` } _ = c.ShouldBind(&req) switch req.ActionType { case "delete": if _, err := h.svc.MarkForDeletion(c.Request.Context(), accountID, getUserID(c), "manual_deletion"); err != nil { response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrValidation, err.Error()) return } c.JSON(http.StatusOK, gin.H{"message": "Account marked for deletion"}) case "undelete": if _, err := h.svc.UnmarkForDeletion(c.Request.Context(), accountID, getUserID(c)); err != nil { response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrValidation, err.Error()) return } c.JSON(http.StatusOK, gin.H{"message": "Account unmarked for deletion"}) default: c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Invalid action_type. Must be either \"delete\" or \"undelete\""}) } } // Subscription mirrors the Cloud customer-creation guard and returns no content. // POST /enterprise/api/v1/accounts/:account_id/subscription func (h *EnterpriseAccountHandler) Subscription(c *gin.Context) { accountID := enterpriseAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } if err := h.svc.EnsureEnterpriseAccountCustomerCreationFlag(c.Request.Context(), accountID, getUserID(c)); err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found") return } c.Status(http.StatusNoContent) } // Checkout returns Chatwoot's billing-details error when no Stripe session can be created locally. // POST /enterprise/api/v1/accounts/:account_id/checkout func (h *EnterpriseAccountHandler) Checkout(c *gin.Context) { accountID := enterpriseAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } if _, err := h.svc.GetByUserAndID(c.Request.Context(), getUserID(c), accountID); err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found") return } c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Please subscribe to a plan before viewing the billing details"}) } // TopupCheckout validates credits and exposes a provider-unavailable boundary for local installs. // POST /enterprise/api/v1/accounts/:account_id/topup_checkout func (h *EnterpriseAccountHandler) TopupCheckout(c *gin.Context) { accountID := enterpriseAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } if _, err := h.svc.GetByUserAndID(c.Request.Context(), getUserID(c), accountID); err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found") return } var req struct { Credits int `json:"credits" form:"credits"` } _ = c.ShouldBind(&req) if req.Credits <= 0 { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Credits are required"}) return } c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "Top-up checkout provider is not configured"}) }