package handlers import ( "fmt" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) type TransferWalletRes struct { ID int64 `json:"id"` Amount float32 `json:"amount"` Verified bool `json:"verified"` Message string `json:"message"` Type string `json:"type"` PaymentMethod string `json:"payment_method"` ReceiverWalletID *int64 `json:"receiver_wallet_id,omitempty"` SenderWalletID *int64 `json:"sender_wallet_id,omitempty"` DepositorID *int64 `json:"depositor_id,omitempty"` DepositorFirstName string `json:"depositor_first_name"` DepositorLastName string `json:"depositor_last_name"` DepositorPhoneNumber string `json:"depositor_phone_number"` ReferenceNumber string `json:"reference_number"` // ← Add this CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type RefillRes struct { ID int64 `json:"id" example:"1"` Amount float32 `json:"amount" example:"100.0"` Verified bool `json:"verified" example:"true"` Type string `json:"type" example:"transfer"` PaymentMethod string `json:"payment_method" example:"bank"` ReceiverWalletID *int64 `json:"receiver_wallet_id" example:"1"` SenderWalletID *int64 `json:"sender_wallet_id" example:"1"` CashierID *int64 `json:"cashier_id" example:"789"` CreatedAt time.Time `json:"created_at" example:"2025-04-08T12:00:00Z"` UpdatedAt time.Time `json:"updated_at" example:"2025-04-08T12:30:00Z"` } func convertTransfer(t domain.Transfer) TransferWalletRes { var receiverID *int64 if t.ReceiverWalletID.Valid { receiverID = &t.ReceiverWalletID.Value } var senderID *int64 if t.SenderWalletID.Valid { senderID = &t.SenderWalletID.Value } var depositorID *int64 if t.DepositorID.Valid { depositorID = &t.DepositorID.Value } return TransferWalletRes{ ID: t.ID, Amount: t.Amount.Float32(), Verified: t.Verified, Message: t.Message, Type: string(t.Type), PaymentMethod: string(t.PaymentMethod), ReceiverWalletID: receiverID, SenderWalletID: senderID, DepositorID: depositorID, ReferenceNumber: t.ReferenceNumber, CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, } } func convertTransferDetail(t domain.TransferDetail) TransferWalletRes { var receiverID *int64 if t.ReceiverWalletID.Valid { receiverID = &t.ReceiverWalletID.Value } var senderID *int64 if t.SenderWalletID.Valid { senderID = &t.SenderWalletID.Value } var depositorID *int64 if t.DepositorID.Valid { depositorID = &t.DepositorID.Value } return TransferWalletRes{ ID: t.ID, Amount: t.Amount.Float32(), Verified: t.Verified, Message: t.Message, Type: string(t.Type), PaymentMethod: string(t.PaymentMethod), ReceiverWalletID: receiverID, SenderWalletID: senderID, DepositorID: depositorID, DepositorFirstName: t.DepositorFirstName, DepositorLastName: t.DepositorLastName, DepositorPhoneNumber: t.DepositorPhoneNumber, ReferenceNumber: t.ReferenceNumber, CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, } } type CreateTransferReq struct { Amount float32 `json:"amount" example:"100.0"` PaymentMethod string `json:"payment_method" example:"cash"` } type CreateRefillReq struct { Amount float32 `json:"amount" example:"100.0"` } // GetTransfersByWallet godoc // @Summary Get transfer by wallet // @Description Get transfer by wallet // @Tags transfer // @Accept json // @Produce json // @Param transferToWallet body CreateTransferReq true "Create Transfer" // @Success 200 {object} TransferWalletRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/transfer/wallet/{id} [get] func (h *Handler) GetTransfersByWallet(c *fiber.Ctx) error { walletID := c.Params("id") id, err := strconv.ParseInt(walletID, 10, 64) if err != nil { h.mongoLoggerSvc.Error("Invalid wallet ID", zap.String("walletID", walletID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID") } transfers, err := h.walletSvc.GetTransfersByWallet(c.Context(), int64(id)) if err != nil { h.mongoLoggerSvc.Error("Failed to get transfers by wallet", zap.String("walletID", walletID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } var transferResponses []TransferWalletRes for _, transfer := range transfers { transferResponses = append(transferResponses, convertTransferDetail(transfer)) } return response.WriteJSON(c, fiber.StatusOK, "Transfers retrieved successfully", transferResponses, nil) } // TransferToWallet godoc // @Summary Create a transfer to wallet // @Description Create a transfer to wallet // @Tags transfer // @Accept json // @Produce json // @Param transferToWallet body CreateTransferReq true "Create Transfer" // @Success 200 {object} TransferWalletRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/transfer/wallet/:id [post] func (h *Handler) TransferToWallet(c *fiber.Ctx) error { receiverIDString := c.Params("id") receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) if err != nil { h.mongoLoggerSvc.Info("Invalid wallet ID", zap.Int64("walletID", receiverID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID") } // Get sender ID from the cashier userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) // fmt.Printf("\n\nCompany ID: %v\n\n", companyID.Value) var senderID int64 //TODO: check to make sure that the cashiers aren't transferring TO branch wallet switch role { case domain.RoleCustomer: h.mongoLoggerSvc.Error("Unauthorized access", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusForbidden), zap.String("role", string(role)), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusForbidden, "Unauthorized access") case domain.RoleAdmin: company, err := h.companySvc.GetCompanyByID(c.Context(), companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to fetch company", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } senderID = company.WalletID // h.logger.Error("Will", "userID", userID, "role", role) case domain.RoleSuperAdmin: return fiber.NewError(fiber.StatusBadRequest, "Super Admin does not have a wallet") case domain.RoleBranchManager: return fiber.NewError(fiber.StatusBadRequest, "Branch Manager does not have a wallet") case domain.RoleCashier: cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) if err != nil { h.mongoLoggerSvc.Error("Failed to get branch by cashier", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } senderID = cashierBranch.WalletID default: h.mongoLoggerSvc.Error("Unknown Role", zap.Int64("userID", userID), zap.String("role", string(role)), zap.Int("status_code", fiber.StatusInternalServerError), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Unknown Role") } var req CreateTransferReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("CreateTransferReq failed to parse body", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request") } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Error("Failed to validate CreateTransferReq", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } transfer, err := h.walletSvc.TransferToWallet(c.Context(), senderID, receiverID, domain.ToCurrency(req.Amount), domain.PaymentMethod(req.PaymentMethod), domain.ValidInt64{Value: userID, Valid: true}, fmt.Sprintf("Transferred %v from wallet to another", req.Amount), ) if err != nil { h.mongoLoggerSvc.Error("Failed to transfer money to wallet", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := convertTransfer(transfer) return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) } // RefillWallet godoc // @Summary Refill wallet // @Description Super Admin route to refill a wallet // @Tags transfer // @Accept json // @Produce json // @Param refillWallet body CreateTransferReq true "Create Transfer" // @Success 200 {object} TransferWalletRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/transfer/refill/:id [post] func (h *Handler) RefillWallet(c *fiber.Ctx) error { receiverIDString := c.Params("id") userID := c.Locals("user_id").(int64) receiverID, err := strconv.ParseInt(receiverIDString, 10, 64) if err != nil { h.mongoLoggerSvc.Error("Invalid wallet ID", zap.Int64("walletID", receiverID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid wallet ID") } // Get sender ID from the cashier var req CreateRefillReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CreateRefillReq failed to parse CreateRefillReq", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request") } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Info("Failed to validate CreateRefillReq", zap.Int64("userID", userID), zap.String("errMsg", errMsg), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } transfer, err := h.walletSvc.AddToWallet( c.Context(), receiverID, domain.ToCurrency(req.Amount), domain.ValidInt64{ Value: userID, Valid: true, }, domain.TRANSFER_BANK, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet directly by super-admin", req.Amount)) if !ok { h.mongoLoggerSvc.Error("Creating Transfer Failed", zap.Int64("userID", userID), zap.Float32("Amount", req.Amount), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := convertTransfer(transfer) return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) }