Yimaru-BackEnd/internal/web_server/handlers/transaction_handler.go

380 lines
15 KiB
Go

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)
if err != nil {
h.logger.Error("CreateTransactionReq failed, user id invalid", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "User ID invalid", err, nil)
}
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)
// role := c.Locals("role").(domain.Role)
companyID := c.Locals("company_id").(domain.ValidInt64)
branchID := c.Locals("branch_id").(domain.ValidInt64)
searchQuery := c.Query("query")
searchString := domain.ValidString{
Value: searchQuery,
Valid: searchQuery != "",
}
createdBeforeQuery := c.Query("created_before")
var createdBefore domain.ValidTime
if createdBeforeQuery != "" {
createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdBefore = domain.ValidTime{
Value: createdBeforeParsed,
Valid: true,
}
}
createdAfterQuery := c.Query("created_after")
var createdAfter domain.ValidTime
if createdAfterQuery != "" {
createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery)
if err != nil {
h.logger.Error("invalid start_time format", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid start_time format", nil, nil)
}
createdAfter = domain.ValidTime{
Value: createdAfterParsed,
Valid: true,
}
}
// Check user role and fetch transactions accordingly
transactions, err := h.transactionSvc.GetAllTransactions(c.Context(), domain.TransactionFilter{
CompanyID: companyID,
BranchID: branchID,
Query: searchString,
CreatedBefore: createdBefore,
CreatedAfter: createdAfter,
})
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)
}