package handler import ( "errors" "strconv" "accounting-app/pkg/api" "accounting-app/internal/models" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // RefundServiceInterface defines the interface for refund operations type RefundServiceInterface interface { ProcessRefund(userID uint, transactionID uint, amount float64) (*models.Transaction, error) } // RefundHandler handles HTTP requests for refund operations // Feature: accounting-feature-upgrade // Validates: Requirements 8.10-8.18 type RefundHandler struct { refundService RefundServiceInterface } // NewRefundHandler creates a new RefundHandler instance func NewRefundHandler(refundService RefundServiceInterface) *RefundHandler { return &RefundHandler{ refundService: refundService, } } // ProcessRefundInput represents the input for processing a refund type ProcessRefundInput struct { Amount float64 `json:"amount" binding:"required,gt=0"` } // ProcessRefund handles PUT /api/v1/transactions/:id/refund // Processes a refund on an expense transaction, automatically creating a refund income record // Feature: accounting-feature-upgrade // Validates: Requirements 8.10-8.18, 8.28 func (h *RefundHandler) ProcessRefund(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid transaction ID") return } var input ProcessRefundInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, "Invalid request body: "+err.Error()) return } refundIncome, err := h.refundService.ProcessRefund(userId.(uint), uint(id), input.Amount) if err != nil { if errors.Is(err, service.ErrTransactionNotFound) { api.NotFound(c, "Transaction not found") return } if errors.Is(err, service.ErrNotExpenseTransaction) { api.BadRequest(c, "Only expense transactions can be refunded") return } if errors.Is(err, service.ErrAlreadyRefunded) { api.BadRequest(c, "Transaction already refunded") return } if errors.Is(err, service.ErrInvalidRefundAmount) { api.BadRequest(c, "Refund amount must be greater than 0 and not exceed original amount") return } if errors.Is(err, service.ErrRefundCategoryNotFound) { api.InternalError(c, "Refund system category not found. Please run database migrations.") return } api.InternalError(c, "Failed to process refund: "+err.Error()) return } api.Success(c, refundIncome) } // RegisterRoutes registers all refund routes to the given router group func (h *RefundHandler) RegisterRoutes(rg *gin.RouterGroup) { transactions := rg.Group("/transactions") { transactions.PUT("/:id/refund", h.ProcessRefund) } }