package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" "gorm.io/gorm" "veza-backend-api/internal/core/marketplace" ) // AdminTransferHandler handles admin transfer dashboard endpoints (v0.701). type AdminTransferHandler struct { db *gorm.DB ts marketplace.TransferService logger *zap.Logger feeRate float64 } // NewAdminTransferHandler creates a new AdminTransferHandler. func NewAdminTransferHandler(db *gorm.DB, ts marketplace.TransferService, feeRate float64, logger *zap.Logger) *AdminTransferHandler { return &AdminTransferHandler{ db: db, ts: ts, logger: logger, feeRate: feeRate, } } // GetTransfers returns a paginated list of all platform transfers with optional filters. // Query params: status, seller_id, from, to, limit (default 50), offset (default 0). func (h *AdminTransferHandler) GetTransfers(c *gin.Context) { query := h.db.Model(&marketplace.SellerTransfer{}) if status := c.Query("status"); status != "" { query = query.Where("status = ?", status) } if sellerIDStr := c.Query("seller_id"); sellerIDStr != "" { sellerID, err := uuid.Parse(sellerIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid seller_id"}) return } query = query.Where("seller_id = ?", sellerID) } if from := c.Query("from"); from != "" { query = query.Where("created_at >= ?", from) } if to := c.Query("to"); to != "" { query = query.Where("created_at <= ?", to) } var total int64 if err := query.Count(&total).Error; err != nil { h.logger.Error("GetTransfers count failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count transfers"}) return } limit := 50 if l := c.Query("limit"); l != "" { if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 { limit = parsed } } offset := 0 if o := c.Query("offset"); o != "" { if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { offset = parsed } } var transfers []marketplace.SellerTransfer if err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&transfers).Error; err != nil { h.logger.Error("GetTransfers find failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch transfers"}) return } RespondSuccess(c, http.StatusOK, gin.H{ "transfers": transfers, "total": total, }) } // RetryTransfer manually retries a failed transfer. func (h *AdminTransferHandler) RetryTransfer(c *gin.Context) { if h.ts == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Stripe Connect is not enabled"}) return } idStr := c.Param("id") if idStr == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "transfer id required"}) return } transferID, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid transfer id"}) return } var t marketplace.SellerTransfer if err := h.db.First(&t, transferID).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(http.StatusNotFound, gin.H{"error": "transfer not found"}) return } h.logger.Error("RetryTransfer find failed", zap.Error(err), zap.String("transfer_id", idStr)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch transfer"}) return } if t.Status != "failed" { c.JSON(http.StatusBadRequest, gin.H{"error": "only failed transfers can be retried"}) return } err = h.ts.CreateTransfer(c.Request.Context(), t.SellerID, t.AmountCents, t.Currency, t.OrderID.String()) if err != nil { t.RetryCount++ t.ErrorMessage = err.Error() // Could set next_retry_at for worker to pick up later, but for manual retry we just record the failure if saveErr := h.db.Save(&t).Error; saveErr != nil { h.logger.Error("RetryTransfer save failed", zap.Error(saveErr)) } h.logger.Error("RetryTransfer CreateTransfer failed", zap.Error(err), zap.String("transfer_id", idStr)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Transfer failed: " + err.Error()}) return } t.Status = "completed" t.ErrorMessage = "" if err := h.db.Save(&t).Error; err != nil { h.logger.Error("RetryTransfer save failed", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update transfer"}) return } RespondSuccess(c, http.StatusOK, t) }