499 lines
19 KiB
Go
499 lines
19 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"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"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type ShopTransactionRes 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 CashoutReq 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 convertShopTransaction(transaction domain.ShopTransaction) ShopTransactionRes {
|
|
newTransaction := ShopTransactionRes{
|
|
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
|
|
}
|
|
|
|
// CashoutBet godoc
|
|
// @Summary Cashout bet at branch
|
|
// @Description Cashout bet at branch
|
|
// @Tags transaction
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param createBet body CashoutReq true "cashout bet"
|
|
// @Success 200 {object} TransactionRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /shop/cashout [post]
|
|
func (h *Handler) CashoutBet(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("CashoutReq failed due to unauthorized access")
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
|
"error": "unauthorized access",
|
|
})
|
|
}
|
|
|
|
var req CashoutReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("CashoutReq 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("CashoutReq 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("CashoutReq 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("CashoutReq 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("CashoutReq 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("CashoutReq failed", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Bet ID invalid", err, nil)
|
|
}
|
|
|
|
// if bet.Status != domain.OUTCOME_STATUS_WIN {
|
|
// h.logger.Error("CashoutReq 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("CashoutReq failed, user id invalid", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "User ID invalid", err, nil)
|
|
}
|
|
transaction, err := h.transactionSvc.CreateShopTransaction(c.Context(), domain.CreateShopTransaction{
|
|
BranchID: branchID,
|
|
CashierID: userID,
|
|
Amount: domain.ToCurrency(req.Amount),
|
|
BetID: bet.ID,
|
|
NumberOfOutcomes: int64(len(bet.Outcomes)),
|
|
Type: domain.ShopTransactionType(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("CashoutReq 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("CashoutReq failed", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil)
|
|
}
|
|
|
|
res := convertShopTransaction(transaction)
|
|
return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil)
|
|
}
|
|
|
|
type DepositForCustomerReq struct {
|
|
Amount float32 `json:"amount" example:"100.0"`
|
|
PaymentMethod string `json:"payment_method" example:"cash"`
|
|
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"`
|
|
}
|
|
|
|
// DepositForCustomer godoc
|
|
// @Summary Shop deposit into customer wallet
|
|
// @Description Transfers money from branch wallet to customer wallet
|
|
// @Tags transaction
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param transferToWallet body DepositForCustomerReq true "DepositForCustomer"
|
|
// @Success 200 {object} TransferWalletRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /shop/deposit/:id [post]
|
|
|
|
func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
|
|
customerIDString := c.Params("id")
|
|
customerID, err := strconv.ParseInt(customerIDString, 10, 64)
|
|
|
|
if err != nil {
|
|
h.logger.Error("Invalid customer ID", "customerID", customerID, "error", err)
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid wallet ID", err, nil)
|
|
}
|
|
|
|
// Get sender ID from the cashier
|
|
userID := c.Locals("user_id").(int64)
|
|
role := c.Locals("role").(domain.Role)
|
|
|
|
var req DepositForCustomerReq
|
|
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.logger.Error("CreateTransferReq failed", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
|
|
}
|
|
|
|
valErrs, ok := h.validator.Validate(c, req)
|
|
if !ok {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
|
}
|
|
var senderID int64
|
|
switch role {
|
|
case domain.RoleCustomer:
|
|
h.logger.Error("Unauthorized access", "userID", userID, "role", role)
|
|
return response.WriteJSON(c, fiber.StatusUnauthorized, "Unauthorized access", nil, nil)
|
|
case domain.RoleAdmin, domain.RoleSuperAdmin, domain.RoleBranchManager:
|
|
if req.BranchID == nil {
|
|
h.logger.Error("CashoutReq 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("CashoutReq no branches")
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil)
|
|
}
|
|
|
|
senderID = branch.WalletID
|
|
case domain.RoleCashier:
|
|
cashierBranch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to get branch", "user ID", userID, "error", err)
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve cashier branch", err, nil)
|
|
}
|
|
senderID = cashierBranch.WalletID
|
|
default:
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Unknown Role", err, nil)
|
|
}
|
|
|
|
customerWallet, err := h.walletSvc.GetCustomerWallet(c.Context(), customerID)
|
|
|
|
if err != nil {
|
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid customer id", err, nil)
|
|
}
|
|
|
|
transfer, err := h.walletSvc.TransferToWallet(c.Context(),
|
|
senderID, customerWallet.RegularID, domain.ToCurrency(req.Amount), domain.PaymentMethod(req.PaymentMethod),
|
|
domain.ValidInt64{Value: userID, Valid: true},
|
|
fmt.Sprintf("Transferred %v from wallet to customer wallet", req.Amount),
|
|
)
|
|
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to transfer money to wallet", zap.Error(err))
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Transfer Failed", err, nil)
|
|
}
|
|
|
|
transaction, err := h.transactionSvc.CreateShopTransaction(c.Context(), domain.CreateShopTransaction{
|
|
BranchID: branchID,
|
|
CashierID: userID,
|
|
Amount: domain.ToCurrency(req.Amount),
|
|
Type: domain.TRANSACTION_DEPOSIT,
|
|
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,
|
|
})
|
|
|
|
res := convertTransfer(transfer)
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", 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 /shop/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.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{
|
|
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([]ShopTransactionRes, len(transactions))
|
|
for i, transaction := range transactions {
|
|
res[i] = convertShopTransaction(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 /shop/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.GetShopTransactionByID(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 := convertShopTransaction(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 /shop/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.GetShopTransactionByID(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.UpdateShopTransactionVerified(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)
|
|
}
|