package handlers import ( "log/slog" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "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"` BranchName string `json:"branch_name" example:"Branch Name"` BranchLocation string `json:"branch_location" example:"Branch Location"` CompanyID int64 `json:"company_id" example:"1"` CashierID int64 `json:"cashier_id" example:"1"` CashierName string `json:"cashier_name" example:"John Smith"` BetID int64 `json:"bet_id" example:"1"` NumberOfOutcomes int64 `json:"number_of_outcomes" 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"` ApprovedBy *int64 `json:"approved_by" example:"1"` ApproverName *string `json:"approver_name" example:"John Smith"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` } 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"` BranchID *int64 `json:"branch_id,omitempty" example:"1"` } func convertTransaction(transaction domain.Transaction) TransactionRes { newTransaction := TransactionRes{ ID: transaction.ID, Amount: transaction.Amount.Float32(), BranchID: transaction.BranchID, BranchName: transaction.BranchName, BranchLocation: transaction.BranchLocation, CompanyID: transaction.CompanyID, CashierID: transaction.CashierID, CashierName: transaction.CashierName, 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, NumberOfOutcomes: transaction.NumberOfOutcomes, CreatedAt: transaction.CreatedAt, UpdatedAt: transaction.UpdatedAt, } if transaction.ApprovedBy.Valid { newTransaction.ApprovedBy = &transaction.ApprovedBy.Value newTransaction.ApproverName = &transaction.ApproverName.Value } return newTransaction } // 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 (h *Handler) CreateTransaction(c *fiber.Ctx) error { userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) // user, err := h.userSvc.GetUserByID(c.Context(), userID) // TODO: Make a "Only Company" middleware auth and move this into that if role == domain.RoleCustomer { h.logger.Error("CreateTransactionReq failed due to unauthorized access") return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "unauthorized access", }) } var req CreateTransactionReq if err := c.BodyParser(&req); err != nil { h.logger.Error("CreateTransaction failed to parse request", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } valErrs, ok := h.validator.Validate(c, req) if !ok { h.logger.Error("CreateTransactionReq failed v", "error", valErrs) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } var branchID int64 var branchName string var branchLocation string var companyID int64 if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { if req.BranchID == nil { h.logger.Error("CreateTransactionReq Branch ID is required for this user role") return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID is required for this user role", nil, nil) } branch, err := h.branchSvc.GetBranchByID(c.Context(), *req.BranchID) if err != nil { h.logger.Error("CreateTransactionReq no branches") return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil) } branchID = branch.ID branchName = branch.Name branchLocation = branch.Location companyID = branch.CompanyID } else { branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) if err != nil { h.logger.Error("CreateTransactionReq failed, branch id invalid") return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil) } branchID = branch.ID branchName = branch.Name branchLocation = branch.Location companyID = branch.CompanyID } bet, err := h.betSvc.GetBetByID(c.Context(), req.BetID) if err != nil { h.logger.Error("CreateTransactionReq failed", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Bet ID invalid", err, nil) } // if bet.Status != domain.OUTCOME_STATUS_WIN { // h.logger.Error("CreateTransactionReq failed, bet has not won") // return response.WriteJSON(c, fiber.StatusBadRequest, "User has not won bet", err, nil) // } if bet.CashedOut { h.logger.Error(("Bet has already been cashed out")) return response.WriteJSON(c, fiber.StatusBadRequest, "This bet has already been cashed out", err, nil) } user, err := h.userSvc.GetUserByID(c.Context(), userID) transaction, err := h.transactionSvc.CreateTransaction(c.Context(), domain.CreateTransaction{ BranchID: branchID, CashierID: userID, Amount: domain.ToCurrency(req.Amount), BetID: bet.ID, NumberOfOutcomes: int64(len(bet.Outcomes)), 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, CashierName: user.FirstName + " " + user.LastName, BranchName: branchName, BranchLocation: branchLocation, CompanyID: companyID, }) if err != nil { h.logger.Error("CreateTransactionReq failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } err = h.betSvc.UpdateCashOut(c.Context(), req.BetID, true) if err != nil { h.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 successfully", 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 (h *Handler) GetAllTransactions(c *fiber.Ctx) error { // Get user_id from middleware userID := c.Locals("user_id").(int64) // Fetch user details user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { h.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 // TODO: filtering by the user role switch user.Role { case domain.RoleSuperAdmin: // Admin can fetch all transactions transactions, err = h.transactionSvc.GetAllTransactions(c.Context()) case domain.RoleAdmin: // Admins can fetch transaction for company branches transactions, err = h.transactionSvc.GetAllTransactions(c.Context()) case domain.RoleBranchManager, domain.RoleCashier: // Branch Manager or Cashier can fetch transactions for their branches // transactions, err = transactionSvc.GetTransactionByBranch(c.Context(), user.BranchID) transactions, err = h.transactionSvc.GetAllTransactions(c.Context()) default: // Unauthorized role return response.WriteJSON(c, fiber.StatusForbidden, "Unauthorized", nil, nil) } if err != nil { h.logger.Error("Failed to get transactions", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) } res := make([]TransactionRes, len(transactions)) for i, transaction := range transactions { res[i] = 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 (h *Handler) GetTransactionByID(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), id) if err != nil { h.logger.Error("Failed to get transaction by ID", "transactionID", id, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") } res := convertTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) } type UpdateTransactionVerifiedReq struct { Verified bool `json:"verified" example:"true"` } // UpdateTransactionVerified godoc // @Summary Updates the verified field of a transaction // @Description Updates the verified status of a transaction // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Param updateVerified body UpdateTransactionVerifiedReq true "Updates Transaction Verification" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /transaction/{id} [put] func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { transactionID := c.Params("id") userID := c.Locals("user_id").(int64) companyID := c.Locals("company_id").(domain.ValidInt64) role := c.Locals("role").(domain.Role) id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { h.logger.Error("Invalid transaction ID", "transactionID", transactionID, "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } var req UpdateTransactionVerifiedReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } h.logger.Info("Update Transaction Verified", slog.Bool("verified", req.Verified)) if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), id) if role != domain.RoleSuperAdmin { if !companyID.Valid || companyID.Value != transaction.CompanyID { h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil) } } user, err := h.userSvc.GetUserById(c.Context(), userID) if err != nil { h.logger.Error("Invalid user ID", "userID", userID, "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID") } err = h.transactionSvc.UpdateTransactionVerified(c.Context(), id, req.Verified, userID, user.FirstName+" "+user.LastName) if err != nil { h.logger.Error("Failed to update transaction verification", "transactionID", id, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") } return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) }