package handler import ( "errors" "strconv" "accounting-app/pkg/api" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // CreditAccountHandler handles HTTP requests for credit account operations type CreditAccountHandler struct { billingService *service.BillingService repaymentService *service.RepaymentService } // NewCreditAccountHandler creates a new CreditAccountHandler instance func NewCreditAccountHandler( billingService *service.BillingService, repaymentService *service.RepaymentService, ) *CreditAccountHandler { return &CreditAccountHandler{ billingService: billingService, repaymentService: repaymentService, } } // GetBills handles GET /api/v1/accounts/:id/bills // Returns all bills for a specific credit account func (h *CreditAccountHandler) GetBills(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } accountID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid account ID") return } bills, err := h.billingService.GetBillsByAccountID(userId.(uint), uint(accountID)) if err != nil { api.InternalError(c, "Failed to get bills: "+err.Error()) return } api.Success(c, bills) } // RepayInput represents the input for repayment type RepayInput struct { BillID uint `json:"bill_id" binding:"required"` Amount float64 `json:"amount" binding:"required,gt=0"` FromAccountID uint `json:"from_account_id" binding:"required"` } // Repay handles POST /api/v1/accounts/:id/repay // Processes a repayment for a credit account bill func (h *CreditAccountHandler) Repay(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } accountID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid account ID") return } var input RepayInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, "Invalid request body: "+err.Error()) return } // Get the bill to verify it belongs to this account bill, err := h.billingService.GetBillByID(userId.(uint), input.BillID) if err != nil { if errors.Is(err, service.ErrBillNotFound) { api.NotFound(c, "Bill not found") return } api.InternalError(c, "Failed to get bill: "+err.Error()) return } // Verify bill belongs to the specified account if bill.AccountID != uint(accountID) { api.BadRequest(c, "Bill does not belong to the specified account") return } // Check if bill is already paid if bill.Status == "paid" { api.BadRequest(c, "Bill is already paid") return } // Check if payment amount exceeds bill balance if input.Amount > bill.CurrentBalance { api.BadRequest(c, "Payment amount exceeds bill balance") return } // Check if bill has a repayment plan plan, err := h.repaymentService.GetRepaymentPlanByBillID(userId.(uint), input.BillID) if err != nil && !errors.Is(err, service.ErrRepaymentPlanNotFound) { api.InternalError(c, "Failed to check repayment plan: "+err.Error()) return } // If bill has a repayment plan, use installment payment if plan != nil { // Find the next unpaid installment var nextInstallment *uint for _, installment := range plan.Installments { if installment.Status != "paid" { nextInstallment = &installment.ID break } } if nextInstallment == nil { api.BadRequest(c, "All installments are already paid") return } // Pay the installment payInput := service.PayInstallmentInput{ InstallmentID: *nextInstallment, Amount: input.Amount, FromAccountID: input.FromAccountID, } if err := h.repaymentService.PayInstallment(userId.(uint), payInput); err != nil { if errors.Is(err, service.ErrInvalidRepaymentAmount) { api.BadRequest(c, "Invalid payment amount") return } if errors.Is(err, service.ErrPaymentExceedsInstallment) { api.BadRequest(c, "Payment amount exceeds installment amount") return } if errors.Is(err, service.ErrInstallmentAlreadyPaid) { api.BadRequest(c, "Installment is already paid") return } api.InternalError(c, "Failed to process payment: "+err.Error()) return } api.Success(c, gin.H{ "message": "Payment processed successfully via repayment plan", "plan_id": plan.ID, }) return } // If no repayment plan, process as direct payment // This would require a direct payment method in the billing service // For now, we'll suggest creating a repayment plan for structured payments api.Success(c, gin.H{ "message": "Direct payment not yet implemented. Please create a repayment plan first.", "suggestion": "Create a repayment plan with POST /api/v1/repayment-plans", }) } // RegisterRoutes registers credit account routes to the given router group func (h *CreditAccountHandler) RegisterRoutes(rg *gin.RouterGroup) { // Credit account specific routes are nested under accounts accounts := rg.Group("/accounts") { accounts.GET("/:id/bills", h.GetBills) accounts.POST("/:id/repay", h.Repay) } }