package handlers import ( // "fmt" "log/slog" "strconv" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" "github.com/gofiber/fiber/v2" ) type TransactionRes struct { ID int64 `json:"id" example:"1"` Amount float32 `json:"amount" example:"100.0"` BranchID int64 `json:"branch_id" example:"1"` CashierID int64 `json:"cashier_id" example:"1"` BetID int64 `json:"bet_id" example:"1"` Type int64 `json:"type" example:"1"` PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` FullName string `json:"full_name" example:"John Smith"` PhoneNumber string `json:"phone_number" example:"0911111111"` BankCode string `json:"bank_code"` BeneficiaryName string `json:"beneficiary_name"` AccountName string `json:"account_name"` AccountNumber string `json:"account_number"` ReferenceNumber string `json:"reference_number"` Verified bool `json:"verified" example:"true"` } type CreateTransactionReq struct { CashoutID string `json:"cashout_id" example:"191212"` Amount float32 `json:"amount" example:"100.0"` BetID int64 `json:"bet_id" example:"1"` Type int64 `json:"type" example:"1"` PaymentOption domain.PaymentOption `json:"payment_option" example:"1"` FullName string `json:"full_name" example:"John Smith"` PhoneNumber string `json:"phone_number" example:"0911111111"` BankCode string `json:"bank_code"` BeneficiaryName string `json:"beneficiary_name"` AccountName string `json:"account_name"` AccountNumber string `json:"account_number"` ReferenceNumber string `json:"reference_number"` } func convertTransaction(transaction domain.Transaction) TransactionRes { return TransactionRes{ ID: transaction.ID, Amount: transaction.Amount.Float64(), BranchID: transaction.BranchID, CashierID: transaction.CashierID, BetID: transaction.BetID, Type: int64(transaction.Type), PaymentOption: transaction.PaymentOption, FullName: transaction.FullName, PhoneNumber: transaction.PhoneNumber, BankCode: transaction.BankCode, BeneficiaryName: transaction.BeneficiaryName, AccountName: transaction.AccountName, AccountNumber: transaction.AccountNumber, ReferenceNumber: transaction.ReferenceNumber, Verified: transaction.Verified, } } // CreateTransaction godoc // @Summary Create a transaction // @Description Creates a transaction // @Tags transaction // @Accept json // @Produce json // @Param createBet body CreateTransactionReq true "Creates transaction" // @Success 200 {object} TransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction [post] func CreateTransaction(logger *slog.Logger, transactionSvc *transaction.Service, userSvc *user.Service, branchSvc *branch.Service, betSvc *bet.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { userID := c.Locals("user_id").(int64) user, err := userSvc.GetUserByID(c.Context(), userID) if user.Role == domain.RoleCustomer { logger.Error("CreateTransactionReq failed") return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "unauthorized access", }) } // TODO: Add validation to make sure that the bet hasn't already been cashed out by someone else var branchID int64 if user.Role == domain.RoleAdmin || user.Role == domain.RoleBranchManager || user.Role == domain.RoleSuperAdmin { branch, err := branchSvc.GetBranchByID(c.Context(), 1) if err != nil { logger.Error("CreateTransactionReq no branches") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "This user type doesn't have branches", }) } branchID = branch.ID } else { branch, err := branchSvc.GetBranchByCashier(c.Context(), user.ID) if err != nil { logger.Error("CreateTransactionReq failed, branch id invalid") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Branch ID invalid", }) } branchID = branch.ID } var req CreateTransactionReq if err := c.BodyParser(&req); err != nil { logger.Error("CreateTransactionReq failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { logger.Error("CreateTransactionReq failed v", "error", valErrs) response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } transaction, err := transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ BranchID: branchID, CashierID: userID, Amount: domain.ToCurrency(req.Amount), BetID: req.BetID, Type: domain.TransactionType(req.Type), PaymentOption: domain.PaymentOption(req.PaymentOption), FullName: req.FullName, PhoneNumber: req.PhoneNumber, BankCode: req.BankCode, BeneficiaryName: req.BeneficiaryName, AccountName: req.AccountName, AccountNumber: req.AccountNumber, ReferenceNumber: req.ReferenceNumber, }) if err != nil { logger.Error("CreateTransactionReq failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } err = betSvc.UpdateCashOut(c.Context(), req.BetID, true) if err != nil { logger.Error("CreateTransactionReq failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } res := convertTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction Created", res, nil) } } // GetAllTransactions godoc // @Summary Gets all transactions // @Description Gets all the transactions // @Tags transaction // @Accept json // @Produce json // @Success 200 {array} TransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction [get] func GetAllTransactions( logger *slog.Logger, transactionSvc *transaction.Service, userSvc *user.Service, validator *customvalidator.CustomValidator, ) fiber.Handler { return func(c *fiber.Ctx) error { // Get user_id from middleware userID := c.Locals("user_id").(int64) // Fetch user details user, err := userSvc.GetUserByID(c.Context(), userID) if err != nil { logger.Error("Failed to fetch user details", "user_id", userID, "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve user details", err, nil) } var transactions []domain.Transaction // Check user role and fetch transactions accordingly switch user.Role { case domain.RoleSuperAdmin: // Admin can fetch all transactions transactions, err = transactionSvc.GetAllTransactions(c.Context()) case domain.RoleAdmin: // Admin can fetch all transactions transactions, err = transactionSvc.GetAllTransactions(c.Context()) case domain.RoleBranchManager, domain.RoleCashier: // Branch Manager or Cashier can fetch transactions for their branch // transactions, err = transactionSvc.GetTransactionByBranch(c.Context(), user.BranchID) transactions, err = transactionSvc.GetAllTransactions(c.Context()) default: // Unauthorized role return response.WriteJSON(c, fiber.StatusForbidden, "Unauthorized", nil, nil) } if err != nil { logger.Error("Failed to get transactions", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) } // Convert transactions to response format var res []TransactionRes = make([]TransactionRes, 0, len(transactions)) for _, transaction := range transactions { res = append(res, convertTransaction(transaction)) } return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) } } // GetTransactionByID godoc // @Summary Gets transaction by id // @Description Gets a single transaction by id // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Success 200 {object} TransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction/{id} [get] func GetTransactionByID(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil) } transaction, err := transactionSvc.GetTransactionByID(c.Context(), id) if err != nil { logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transaction", err, nil) } res := convertTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) } } type UpdateTransactionVerifiedReq struct { Verified bool } // UpdateTransactionVerified godoc // @Summary Updates the cashed out field // @Description Updates the cashed out field // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Param updateCashOut body UpdateTransactionVerifiedReq true "Updates Transaction Verification" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction/{id} [patch] func UpdateTransactionVerified(logger *slog.Logger, transactionSvc *transaction.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil) } var req UpdateTransactionVerifiedReq if err := c.BodyParser(&req); err != nil { logger.Error("UpdateTransactionVerifiedReq failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } err = transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified) if err != nil { logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update transaction verification", err, nil) } return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) } }