fix: fixing issues on transaction

This commit is contained in:
Samuel Tariku 2025-07-04 00:09:03 +03:00
parent 1a5b545f37
commit a06d77da01
20 changed files with 467 additions and 127 deletions

View File

@ -152,7 +152,7 @@ func main() {
cfg.FIXER_API_KEY,
cfg.FIXER_BASE_URL,
)
transactionSvc := transaction.NewService(store, *branchSvc, *betSvc, *walletSvc)
transactionSvc := transaction.NewService(store, *branchSvc, *betSvc, *walletSvc, *userSvc)
reportSvc := report.NewService(
bet.BetStore(store),
@ -167,8 +167,6 @@ func main() {
logger,
)
go httpserver.SetupReportCronJobs(context.Background(), reportSvc)
bankRepository := repository.NewBankRepository(store)

View File

@ -207,7 +207,8 @@ CREATE TABLE IF NOT EXISTS shop_deposits (
id BIGSERIAL PRIMARY KEY,
shop_transaction_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
wallet_transfer_id BIGINT NOT NULL,
wallet_transfer_id BIGINT,
branch_wallet_id BIGINT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(shop_transaction_id)
@ -406,6 +407,7 @@ SELECT sb.*,
st.verified AS transaction_verified,
bets.status,
bets.total_odds,
bets.expires,
JSON_AGG(bet_outcomes.*) AS outcomes
FROM shop_bets AS sb
JOIN shop_transactions st ON st.id = sb.shop_transaction_id
@ -502,6 +504,52 @@ VALUES (
NULL,
FALSE
);
INSERT INTO wallets (
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
10000,
TRUE,
TRUE,
TRUE,
1,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO wallets (
balance,
is_withdraw,
is_bettable,
is_transferable,
user_id,
is_active,
created_at,
updated_at
)
VALUES (
10000,
FALSE,
TRUE,
TRUE,
1,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
);
INSERT INTO customer_wallets (
customer_id,
regular_wallet_id,
static_wallet_id
)
VALUES (1, 1, 2);
INSERT INTO users (
first_name,
last_name,
@ -606,7 +654,7 @@ VALUES (
TRUE,
TRUE,
TRUE,
1,
2,
TRUE,
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
@ -619,7 +667,7 @@ INSERT INTO companies (
values (
'Test Company',
2,
1
3
);
INSERT INTO wallets (
balance,
@ -654,7 +702,7 @@ INSERT INTO branches (
values (
'Test Branch',
'Addis Ababa',
2,
4,
2,
1,
TRUE,

View File

@ -135,7 +135,7 @@ WHERE id = $1;
INSERT INTO shop_deposits (
shop_transaction_id,
customer_id,
wallet_transfer_id
branch_wallet_id
)
VALUES ($1, $2, $3)
RETURNING *;
@ -171,3 +171,8 @@ WHERE id = $1;
SELECT *
FROM shop_deposit_detail
WHERE shop_transaction_id = $1;
-- name: UpdateShopDepositTransferID :exec
UPDATE shop_deposits
SET wallet_transfer_id = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1;

View File

@ -435,7 +435,8 @@ type ShopDeposit struct {
ID int64 `json:"id"`
ShopTransactionID int64 `json:"shop_transaction_id"`
CustomerID int64 `json:"customer_id"`
WalletTransferID int64 `json:"wallet_transfer_id"`
WalletTransferID pgtype.Int8 `json:"wallet_transfer_id"`
BranchWalletID int64 `json:"branch_wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
@ -444,7 +445,8 @@ type ShopDepositDetail struct {
ID int64 `json:"id"`
ShopTransactionID int64 `json:"shop_transaction_id"`
CustomerID int64 `json:"customer_id"`
WalletTransferID int64 `json:"wallet_transfer_id"`
WalletTransferID pgtype.Int8 `json:"wallet_transfer_id"`
BranchWalletID int64 `json:"branch_wallet_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
FullName string `json:"full_name"`

View File

@ -55,26 +55,27 @@ const CreateShopDeposit = `-- name: CreateShopDeposit :one
INSERT INTO shop_deposits (
shop_transaction_id,
customer_id,
wallet_transfer_id
branch_wallet_id
)
VALUES ($1, $2, $3)
RETURNING id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at
RETURNING id, shop_transaction_id, customer_id, wallet_transfer_id, branch_wallet_id, created_at, updated_at
`
type CreateShopDepositParams struct {
ShopTransactionID int64 `json:"shop_transaction_id"`
CustomerID int64 `json:"customer_id"`
WalletTransferID int64 `json:"wallet_transfer_id"`
BranchWalletID int64 `json:"branch_wallet_id"`
}
func (q *Queries) CreateShopDeposit(ctx context.Context, arg CreateShopDepositParams) (ShopDeposit, error) {
row := q.db.QueryRow(ctx, CreateShopDeposit, arg.ShopTransactionID, arg.CustomerID, arg.WalletTransferID)
row := q.db.QueryRow(ctx, CreateShopDeposit, arg.ShopTransactionID, arg.CustomerID, arg.BranchWalletID)
var i ShopDeposit
err := row.Scan(
&i.ID,
&i.ShopTransactionID,
&i.CustomerID,
&i.WalletTransferID,
&i.BranchWalletID,
&i.CreatedAt,
&i.UpdatedAt,
)
@ -251,7 +252,7 @@ func (q *Queries) GetAllShopBets(ctx context.Context, arg GetAllShopBetsParams)
}
const GetAllShopDeposit = `-- name: GetAllShopDeposit :many
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, branch_wallet_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
FROM shop_deposit_detail
WHERE (
full_name ILIKE '%' || $1 || '%'
@ -304,6 +305,7 @@ func (q *Queries) GetAllShopDeposit(ctx context.Context, arg GetAllShopDepositPa
&i.ShopTransactionID,
&i.CustomerID,
&i.WalletTransferID,
&i.BranchWalletID,
&i.CreatedAt,
&i.UpdatedAt,
&i.FullName,
@ -545,7 +547,7 @@ func (q *Queries) GetShopBetByShopTransactionID(ctx context.Context, shopTransac
}
const GetShopDepositByID = `-- name: GetShopDepositByID :one
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, branch_wallet_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
FROM shop_deposit_detail
WHERE id = $1
`
@ -558,6 +560,7 @@ func (q *Queries) GetShopDepositByID(ctx context.Context, id int64) (ShopDeposit
&i.ShopTransactionID,
&i.CustomerID,
&i.WalletTransferID,
&i.BranchWalletID,
&i.CreatedAt,
&i.UpdatedAt,
&i.FullName,
@ -571,7 +574,7 @@ func (q *Queries) GetShopDepositByID(ctx context.Context, id int64) (ShopDeposit
}
const GetShopDepositByShopTransactionID = `-- name: GetShopDepositByShopTransactionID :one
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
SELECT id, shop_transaction_id, customer_id, wallet_transfer_id, branch_wallet_id, created_at, updated_at, full_name, phone_number, branch_id, company_id, amount, transaction_verified
FROM shop_deposit_detail
WHERE shop_transaction_id = $1
`
@ -584,6 +587,7 @@ func (q *Queries) GetShopDepositByShopTransactionID(ctx context.Context, shopTra
&i.ShopTransactionID,
&i.CustomerID,
&i.WalletTransferID,
&i.BranchWalletID,
&i.CreatedAt,
&i.UpdatedAt,
&i.FullName,
@ -723,6 +727,23 @@ func (q *Queries) UpdateShopBetCashoutID(ctx context.Context, arg UpdateShopBetC
return err
}
const UpdateShopDepositTransferID = `-- name: UpdateShopDepositTransferID :exec
UPDATE shop_deposits
SET wallet_transfer_id = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
`
type UpdateShopDepositTransferIDParams struct {
ID int64 `json:"id"`
WalletTransferID pgtype.Int8 `json:"wallet_transfer_id"`
}
func (q *Queries) UpdateShopDepositTransferID(ctx context.Context, arg UpdateShopDepositTransferIDParams) error {
_, err := q.db.Exec(ctx, UpdateShopDepositTransferID, arg.ID, arg.WalletTransferID)
return err
}
const UpdateShopTransactionVerified = `-- name: UpdateShopTransactionVerified :exec
UPDATE shop_transactions
SET verified = $2,

View File

@ -116,6 +116,100 @@ type DashboardSummary struct {
ReadNotifications int64 `json:"read_notifications"`
UnreadNotifications int64 `json:"unread_notifications"`
}
type DashboardSummaryRes struct {
TotalStakes float32 `json:"total_stakes"`
TotalBets int64 `json:"total_bets"`
ActiveBets int64 `json:"active_bets"`
WinBalance float32 `json:"win_balance"`
TotalWins int64 `json:"total_wins"`
TotalLosses int64 `json:"total_losses"`
CustomerCount int64 `json:"customer_count"`
Profit float32 `json:"profit"`
WinRate float64 `json:"win_rate"`
AverageStake float32 `json:"average_stake"`
TotalDeposits float32 `json:"total_deposits"`
TotalWithdrawals float32 `json:"total_withdrawals"`
ActiveCustomers int64 `json:"active_customers"`
BranchesCount int64 `json:"branches_count"`
ActiveBranches int64 `json:"active_branches"`
TotalCashiers int64 `json:"total_cashiers"`
ActiveCashiers int64 `json:"active_cashiers"`
InactiveCashiers int64 `json:"inactive_cashiers"`
TotalWallets int64 `json:"total_wallets"`
TotalGames int64 `json:"total_games"`
ActiveGames int64 `json:"active_games"`
InactiveGames int64 `json:"inactive_games"`
TotalManagers int64 `json:"total_managers"`
ActiveManagers int64 `json:"active_managers"`
InactiveManagers int64 `json:"inactive_managers"`
InactiveBranches int64 `json:"inactive_branches"`
TotalAdmins int64 `json:"total_admins"`
ActiveAdmins int64 `json:"active_admins"`
InactiveAdmins int64 `json:"inactive_admins"`
TotalCompanies int64 `json:"total_companies"`
ActiveCompanies int64 `json:"active_companies"`
InactiveCompanies int64 `json:"inactive_companies"`
InactiveCustomers int64 `json:"inactive_customers"`
TotalNotifications int64 `json:"total_notifications"`
ReadNotifications int64 `json:"read_notifications"`
UnreadNotifications int64 `json:"unread_notifications"`
}
func ConvertDashboardSummaryToRes(summary DashboardSummary) DashboardSummaryRes {
return DashboardSummaryRes{
TotalStakes: summary.TotalStakes.Float32(),
TotalBets: summary.TotalBets,
ActiveBets: summary.ActiveBets,
WinBalance: summary.WinBalance.Float32(),
TotalWins: summary.TotalWins,
TotalLosses: summary.TotalLosses,
CustomerCount: summary.CustomerCount,
Profit: summary.Profit.Float32(),
WinRate: summary.WinRate,
AverageStake: summary.AverageStake.Float32(),
TotalDeposits: summary.TotalDeposits.Float32(),
TotalWithdrawals: summary.TotalWithdrawals.Float32(),
ActiveCustomers: summary.ActiveCustomers,
BranchesCount: summary.BranchesCount,
ActiveBranches: summary.ActiveBranches,
TotalCashiers: summary.TotalCashiers,
ActiveCashiers: summary.ActiveCashiers,
InactiveCashiers: summary.InactiveCashiers,
TotalWallets: summary.TotalWallets,
TotalGames: summary.TotalGames,
ActiveGames: summary.ActiveGames,
InactiveGames: summary.InactiveGames,
TotalManagers: summary.TotalManagers,
ActiveManagers: summary.ActiveManagers,
InactiveManagers: summary.InactiveManagers,
InactiveBranches: summary.InactiveBranches,
TotalAdmins: summary.TotalAdmins,
ActiveAdmins: summary.ActiveAdmins,
InactiveAdmins: summary.InactiveAdmins,
TotalCompanies: summary.TotalCompanies,
ActiveCompanies: summary.ActiveCompanies,
InactiveCompanies: summary.InactiveCompanies,
InactiveCustomers: summary.InactiveCustomers,
TotalNotifications: summary.TotalNotifications,
ReadNotifications: summary.ReadNotifications,
UnreadNotifications: summary.UnreadNotifications,
}
}
type CustomerActivity struct {
CustomerID int64 `json:"customer_id"`

View File

@ -1,17 +1,20 @@
package domain
import "time"
import (
"time"
)
type ShopDeposit struct {
ID int64
ShopTransactionID int64
CustomerID int64
WalletTransferID int64
WalletTransferID ValidInt64
BranchWalletID int64
}
type CreateShopDeposit struct {
ShopTransactionID int64
CustomerID int64
WalletTransferID int64
BranchWalletID int64
}
type ShopDepositFilter struct {
@ -26,7 +29,8 @@ type ShopDepositDetail struct {
ID int64
ShopTransactionID int64
CustomerID int64
WalletTransferID int64
BranchWalletID int64
WalletTransferID ValidInt64
FullName string
PhoneNumber string
Amount Currency
@ -41,8 +45,8 @@ type ShopDepositReq struct {
CustomerID int64 `json:"customer_id" example:"1"`
Amount float32 `json:"amount" example:"100.0"`
PaymentOption PaymentOption `json:"payment_option" example:"1"`
FullName string `json:"full_name" example:"John Smith"`
PhoneNumber string `json:"phone_number" example:"0911111111"`
// 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"`
@ -55,14 +59,20 @@ type ShopDepositRes struct {
ID int64 `json:"id"`
ShopTransactionID int64 `json:"shop_transaction_id"`
CustomerID int64 `json:"customer_id"`
WalletTransferID int64 `json:"wallet_transfer_id"`
WalletTransferID *int64 `json:"wallet_transfer_id,omitempty"`
}
func ConvertShopDeposit(shopDeposit ShopDeposit) ShopDepositRes {
return ShopDepositRes{
res := ShopDepositRes{
ID: shopDeposit.ID,
ShopTransactionID: shopDeposit.ShopTransactionID,
CustomerID: shopDeposit.CustomerID,
WalletTransferID: shopDeposit.WalletTransferID,
}
if shopDeposit.WalletTransferID.Valid {
res.WalletTransferID = &shopDeposit.WalletTransferID.Value
}
return res
}

View File

@ -13,7 +13,11 @@ func convertShopDeposit(deposit dbgen.ShopDeposit) domain.ShopDeposit {
ID: deposit.ID,
ShopTransactionID: deposit.ShopTransactionID,
CustomerID: deposit.CustomerID,
WalletTransferID: deposit.WalletTransferID,
BranchWalletID: deposit.BranchWalletID,
WalletTransferID: domain.ValidInt64{
Value: deposit.WalletTransferID.Int64,
Valid: deposit.WalletTransferID.Valid,
},
}
}
@ -22,7 +26,11 @@ func convertShopDepositDetail(deposit dbgen.ShopDepositDetail) domain.ShopDeposi
ID: deposit.ID,
ShopTransactionID: deposit.ShopTransactionID,
CustomerID: deposit.CustomerID,
WalletTransferID: deposit.WalletTransferID,
BranchWalletID: deposit.BranchWalletID,
WalletTransferID: domain.ValidInt64{
Value: deposit.WalletTransferID.Int64,
Valid: deposit.WalletTransferID.Valid,
},
FullName: deposit.FullName,
PhoneNumber: deposit.PhoneNumber,
Amount: domain.Currency(deposit.Amount),
@ -37,7 +45,7 @@ func convertCreateShopDeposit(deposit domain.CreateShopDeposit) dbgen.CreateShop
return dbgen.CreateShopDepositParams{
ShopTransactionID: deposit.ShopTransactionID,
CustomerID: deposit.CustomerID,
WalletTransferID: deposit.WalletTransferID,
BranchWalletID: deposit.BranchWalletID,
}
}
@ -102,3 +110,13 @@ func (s *Store) GetShopDepositByShopTransactionID(ctx context.Context, shopTrans
return convertShopDepositDetail(deposit), err
}
func (s *Store) UpdateShopDepositTransferID(ctx context.Context, id int64, transferID domain.ValidInt64) error {
return s.queries.UpdateShopDepositTransferID(ctx, dbgen.UpdateShopDepositTransferIDParams{
WalletTransferID: pgtype.Int8{
Int64: transferID.Value,
Valid: transferID.Valid,
},
ID: id,
})
}

View File

@ -196,7 +196,7 @@ func (s *Store) GetShopTransactionByBranch(ctx context.Context, id int64) ([]dom
return result, nil
}
func (s *Store) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error {
func (s *Store) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64) error {
err := s.queries.UpdateShopTransactionVerified(ctx, dbgen.UpdateShopTransactionVerifiedParams{
ID: id,
ApprovedBy: pgtype.Int8{

View File

@ -215,7 +215,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
for sportIndex, sportID := range sportIDs {
var totalPages int = 1
var page int = 0
var limit int = 200
var limit int = 1
var count int = 0
for page <= totalPages {
page = page + 1

View File

@ -11,7 +11,7 @@ type TransactionStore interface {
GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransactionDetail, error)
GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransactionDetail, error)
GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransactionDetail, error)
UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error
UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64) error
GetTransactionTotals(ctx context.Context, filter domain.ReportFilter) (deposits, withdrawals domain.Currency, err error)
GetBranchTransactionTotals(ctx context.Context, filter domain.ReportFilter) (map[int64]domain.BranchTransactions, error)
@ -28,4 +28,5 @@ type TransactionStore interface {
GetAllShopDeposit(ctx context.Context, filter domain.ShopDepositFilter) ([]domain.ShopDepositDetail, error)
GetShopDepositByID(ctx context.Context, id int64) (domain.ShopDepositDetail, error)
GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopDepositDetail, error)
UpdateShopDepositTransferID(ctx context.Context, id int64, transferID domain.ValidInt64) error
}

View File

@ -3,10 +3,13 @@ package transaction
import (
"context"
"errors"
"fmt"
"time"
"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/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)
@ -16,6 +19,8 @@ var (
ErrUnauthorizedCompanyID = errors.New("unauthorized company id")
ErrUnauthorizedBranchManager = errors.New("unauthorized branch manager")
ErrCustomerRoleNotAuthorized = errors.New("customer role not authorized")
ErrDepositCannotBeUnverified = errors.New("deposit cannot be unverified")
ErrShopBetHasExpired = errors.New("shop bet has already been expired")
)
type Service struct {
@ -23,14 +28,16 @@ type Service struct {
branchSvc branch.Service
betSvc bet.Service
walletSvc wallet.Service
userSvc user.Service
}
func NewService(transactionStore TransactionStore, branchSvc branch.Service, betSvc bet.Service, walletSvc wallet.Service) *Service {
func NewService(transactionStore TransactionStore, branchSvc branch.Service, betSvc bet.Service, walletSvc wallet.Service, userSvc user.Service) *Service {
return &Service{
transactionStore: transactionStore,
branchSvc: branchSvc,
betSvc: betSvc,
walletSvc: walletSvc,
userSvc: userSvc,
}
}
@ -46,8 +53,88 @@ func (s *Service) GetAllShopTransactions(ctx context.Context, filter domain.Shop
func (s *Service) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransactionDetail, error) {
return s.transactionStore.GetShopTransactionByBranch(ctx, id)
}
func (s *Service) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error {
return s.transactionStore.UpdateShopTransactionVerified(ctx, id, verified, approvedBy, approverName)
func (s *Service) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, role domain.Role, companyID domain.ValidInt64, branchID domain.ValidInt64) error {
// TODO: Move this into a service role verification service
// Checks to make sure only the same company and branch can modify this
transaction, err := s.GetShopTransactionByID(ctx, id)
if role != domain.RoleSuperAdmin {
if !companyID.Valid || companyID.Value != transaction.CompanyID {
// s.logger.Error("Failed to parse UpdateTransactionVerified request", "error", err)
return fmt.Errorf("user cannot modify another companies data")
}
if role == domain.RoleCashier {
if !branchID.Valid || branchID.Value != transaction.BranchID {
// h.logger.Error("Failed to parse UpdateTransactionVerified request", "error", "Unauthorized")
return fmt.Errorf("user cannot modify another companies data")
}
}
}
if err != nil {
return err
}
switch transaction.Type {
case domain.TRANSACTION_BET:
if verified {
bet, err := s.GetShopBetByShopTransactionID(ctx, transaction.ID)
if err != nil {
return err
}
var firstEvent time.Time = bet.Outcomes[0].Expires
for _, outcome := range bet.Outcomes {
if outcome.Expires.Before(firstEvent) {
firstEvent = outcome.Expires
}
}
fmt.Printf("\n\n Shop bet expire %v - now %v \n\n", firstEvent, time.Now().UTC())
if firstEvent.Before(time.Now()) {
return ErrShopBetHasExpired
}
}
case domain.TRANSACTION_DEPOSIT:
if !verified {
// TODO: Figure out what to do here? Should i return the money or verify the faulty verify
return ErrDepositCannotBeUnverified
}
deposit, err := s.GetShopDepositByShopTransactionID(ctx, transaction.ID)
if err != nil {
return err
}
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, deposit.CustomerID)
if err != nil {
return ErrUserHasNoCustomerWallet
}
transfer, err := s.walletSvc.TransferToWallet(ctx,
deposit.BranchWalletID, customerWallet.RegularID, transaction.Amount, domain.TRANSFER_DIRECT, domain.ValidInt64{
Value: transaction.UserID,
Valid: true,
},
fmt.Sprintf("Transferred %v to customer wallet due to shop deposit", transaction.Amount),
)
if err != nil {
return err
}
err = s.UpdateShopDepositTransferID(ctx, deposit.ID, domain.ValidInt64{
Value: transfer.ID,
Valid: true,
})
if err != nil {
return err
}
}
return s.transactionStore.UpdateShopTransactionVerified(ctx, id, verified, approvedBy)
}
func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role domain.Role, userID int64, userCompanyID domain.ValidInt64) (*int64, *int64, error) {

View File

@ -44,9 +44,7 @@ func (s *Service) CreateShopBet(ctx context.Context, userID int64, role domain.R
if err != nil {
return domain.ShopBet{}, err
}
cashoutID, err := s.GenerateCashoutID()
if err != nil {
return domain.ShopBet{}, err
}

View File

@ -2,16 +2,21 @@ package transaction
import (
"context"
"fmt"
"errors"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
var (
ErrUserHasNoCustomerWallet = errors.New("user has no customer wallet")
)
func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role domain.Role, req domain.ShopDepositReq) (domain.ShopDeposit, error) {
var branchID int64
var companyID int64
var senderID int64
if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin {
switch role {
case domain.RoleAdmin, domain.RoleBranchManager, domain.RoleSuperAdmin:
if req.BranchID == nil {
// h.logger.Error("CashoutReq Branch ID is required for this user role")
return domain.ShopDeposit{}, ErrBranchRequiredForRole
@ -25,7 +30,7 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma
branchID = branch.ID
companyID = branch.CompanyID
senderID = branch.WalletID
} else if role == domain.RoleCashier {
case domain.RoleCashier:
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil {
// h.logger.Error("CashoutReq failed, branch id invalid")
@ -34,21 +39,19 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma
branchID = branch.ID
companyID = branch.CompanyID
senderID = branch.WalletID
} else {
default:
return domain.ShopDeposit{}, ErrCustomerRoleNotAuthorized
}
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, req.CustomerID)
user, err := s.userSvc.GetUserByID(ctx, req.CustomerID)
if err != nil {
return domain.ShopDeposit{}, err
}
transfer, err := s.walletSvc.TransferToWallet(ctx,
senderID, customerWallet.RegularID, domain.ToCurrency(req.Amount), domain.TRANSFER_DIRECT,
domain.ValidInt64{Value: userID, Valid: true},
fmt.Sprintf("Transferred %v from customer wallet deposit", req.Amount),
)
if err != nil {
return domain.ShopDeposit{}, err
}
newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{
Amount: domain.Currency(req.Amount),
@ -56,8 +59,8 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma
CompanyID: companyID,
UserID: userID,
Type: domain.TRANSACTION_DEPOSIT,
FullName: req.FullName,
PhoneNumber: req.PhoneNumber,
FullName: user.FirstName + " " + user.LastName,
PhoneNumber: user.PhoneNumber,
PaymentOption: req.PaymentOption,
BankCode: domain.ValidString{
Value: req.BankCode,
@ -93,7 +96,7 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma
return s.transactionStore.CreateShopDeposit(ctx, domain.CreateShopDeposit{
ShopTransactionID: newTransaction.ID,
CustomerID: req.CustomerID,
WalletTransferID: transfer.ID,
BranchWalletID: senderID,
})
}
@ -112,3 +115,7 @@ func (s *Service) GetShopDepositByID(ctx context.Context, id int64) (domain.Shop
func (s *Service) GetShopDepositByShopTransactionID(ctx context.Context, shopTransactionID int64) (domain.ShopDepositDetail, error) {
return s.transactionStore.GetShopDepositByShopTransactionID(ctx, shopTransactionID)
}
func (s *Service) UpdateShopDepositTransferID(ctx context.Context, id int64, transferID domain.ValidInt64) error {
return s.transactionStore.UpdateShopDepositTransferID(ctx, id, transferID)
}

View File

@ -21,6 +21,7 @@ func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64) (d
regularWallet, err := s.CreateWallet(ctx, domain.CreateWallet{
IsWithdraw: true,
IsBettable: true,
IsTransferable: true,
UserID: customerID,
})
@ -31,6 +32,7 @@ func (s *Service) CreateCustomerWallet(ctx context.Context, customerID int64) (d
staticWallet, err := s.CreateWallet(ctx, domain.CreateWallet{
IsWithdraw: false,
IsBettable: true,
IsTransferable: true,
UserID: customerID,
})

View File

@ -123,20 +123,20 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "falied to get user information")
}
branch, err := h.branchSvc.GetBranchByID(c.Context(), user.CompanyID.Value)
if err != nil {
h.mongoLoggerSvc.Error("falied to get branch of user",
zap.Int("status_code", fiber.StatusInternalServerError),
zap.Error(err),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "falied to get branch of user")
}
// branch, err := h.branchSvc.GetBranchByID(c.Context(), user)
// if err != nil {
// h.mongoLoggerSvc.Error("falied to get branch of user",
// zap.Int("status_code", fiber.StatusInternalServerError),
// zap.Error(err),
// zap.Time("timestamp", time.Now()),
// )
// return fiber.NewError(fiber.StatusBadRequest, "falied to get branch of user")
// }
newReq := domain.CreateBetReq{
Amount: float32(bet.Amount),
Outcomes: bet_outcomes,
BranchID: &branch.ID,
BranchID: nil,
FullName: user.FirstName,
PhoneNumber: user.PhoneNumber,
}
@ -151,31 +151,12 @@ func (h *Handler) CreateBetWithFastCode(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Failed to create bet")
}
wallets, err := h.walletSvc.GetWalletsByUser(c.Context(), bet.UserID.Value)
var staticWallet domain.Wallet
var staticFound bool
for _, wallet := range wallets {
if !wallet.IsWithdraw {
staticWallet = wallet
staticFound = true
break
}
}
if err != nil || staticFound == false {
fmt.Println("wallet error: ", err)
h.mongoLoggerSvc.Error("Failed to get static wallet of user",
zap.Int("status_code", fiber.StatusOK),
zap.Int64("user_id", user.ID),
zap.Time("timestamp", time.Now()),
)
return fiber.NewError(fiber.StatusBadRequest, "Failed to get wallets of user")
}
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), bet.UserID.Value)
// amount added for fast code owner can be fetched from settings in db
amount := domain.Currency(100)
_, err = h.walletSvc.AddToWallet(c.Context(), staticWallet.ID, amount, domain.ValidInt64{},
_, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, amount, domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to static wallet by referring using fast_code", amount.Float32()))
if err != nil {
h.mongoLoggerSvc.Error("Failed to add reward to static bet",
@ -417,6 +398,9 @@ func (h *Handler) GetAllBet(c *fiber.Ctx) error {
}
}
fmt.Printf("Filters - BranchID: %+v, CompanyID: %+v, IsShopBet: %+v, Query: %+v, CreatedBefore: %+v, CreatedAfter: %+v\n",
branchID, companyID, isShopBet, searchString, createdBefore, createdAfter)
bets, err := h.betSvc.GetAllBets(c.Context(), domain.BetFilter{
BranchID: branchID,
CompanyID: companyID,

View File

@ -54,11 +54,13 @@ func (h *Handler) GetDashboardReport(c *fiber.Ctx) error {
})
}
res := domain.ConvertDashboardSummaryToRes(summary)
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Dashboard reports generated successfully",
Success: true,
StatusCode: 200,
Data: summary,
Data: res,
})
// return c.Status(fiber.StatusOK).JSON(summary)

View File

@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"log/slog"
"strconv"
"time"
@ -49,6 +50,40 @@ func (h *Handler) CreateShopBet(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil)
}
// CashoutBet godoc
// @Summary Cashout bet at branch
// @Description Cashout bet at branch
// @Tags transaction
// @Accept json
// @Produce json
// @Param createBet body domain.CashoutReq true "cashout bet"
// @Success 200 {object} TransactionRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /shop/bet/{id} [get]
func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error {
betIDstr := c.Params("id")
betID, err := strconv.ParseInt(betIDstr, 10, 64)
if err != nil {
h.logger.Error("CashoutReq failed bet id is invalid", "error", nil)
return response.WriteJSON(c, fiber.StatusBadRequest, "bet ID is invalid", nil, nil)
}
bet, err := h.transactionSvc.GetShopBetByBetID(c.Context(), betID)
if err != nil {
h.logger.Error("CashoutReq failed invalid bet id", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid bet ID", err, nil)
}
res := domain.ConvertShopBetDetail(bet)
return response.WriteJSON(c, fiber.StatusOK, "Shop bet fetched successfully", res, nil)
}
// CashoutBet godoc
// @Summary Cashout bet at branch
// @Description Cashout bet at branch
@ -196,18 +231,23 @@ func (h *Handler) DepositForCustomer(c *fiber.Ctx) error {
if err := c.BodyParser(&req); err != nil {
h.logger.Error("CreateTransferReq failed", "error", err)
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", err, nil)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
valErrs, ok := h.validator.Validate(c, req)
if !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req)
if err != nil {
return response.WriteJSON(c, fiber.StatusBadRequest, "Failed to create shop deposit", err, nil)
fmt.Printf("Shop Deposit Error %v \n", err)
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
res := domain.ConvertShopDeposit(deposit)
@ -319,6 +359,34 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error {
return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil)
}
// GetShopBetByTransactionID godoc
// @Summary Gets shop bet by transaction id
// @Description Gets a single shop bet by transaction 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}/bet [get]
func (h *Handler) GetShopBetByTransactionID(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.GetShopBetByShopTransactionID(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 := domain.ConvertShopBetDetail(transaction)
return response.WriteJSON(c, fiber.StatusOK, "Shop bet retrieved successfully", res, nil)
}
type UpdateTransactionVerifiedReq struct {
Verified bool `json:"verified" example:"true"`
}
@ -339,6 +407,7 @@ 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)
branchID := c.Locals("branch_id").(domain.ValidInt64)
role := c.Locals("role").(domain.Role)
id, err := strconv.ParseInt(transactionID, 10, 64)
@ -359,20 +428,7 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error {
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)
err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, role, companyID, branchID)
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")

View File

@ -552,7 +552,7 @@ func (h *Handler) DeleteUser(c *fiber.Ctx) error {
type UpdateUserSuspendReq struct {
UserID int64 `json:"user_id" validate:"required" example:"123"`
Suspended bool `json:"suspended" validate:"required" example:"true"`
Suspended bool `json:"suspended" example:"true"`
}
type UpdateUserSuspendRes struct {
UserID int64 `json:"user_id"`
@ -576,9 +576,13 @@ func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error {
h.logger.Error("Failed to parse UpdateUserSuspend request", "error", err)
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
fmt.Printf("user suspended %v \n", req)
if valErrs, ok := h.validator.Validate(c, req); !ok {
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
var errMsg string
for field, msg := range valErrs {
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
}
return fiber.NewError(fiber.StatusBadRequest, errMsg)
}
err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended)

View File

@ -101,7 +101,7 @@ func (a *App) initAppRoutes() {
a.fiber.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
a.fiber.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
a.fiber.Post("/user/suspend", a.authMiddleware, h.UpdateUserSuspend)
a.fiber.Get("/user/bets", a.authMiddleware, h.GetBetByUserID)
a.fiber.Get("/user/ress", a.authMiddleware, h.GetBetByUserID)
a.fiber.Get("/user/wallet", a.authMiddleware, h.GetCustomerWallet)
a.fiber.Post("/user/search", a.authMiddleware, h.SearchUserByNameOrPhone)
@ -279,13 +279,16 @@ func (a *App) initAppRoutes() {
// Transactions /shop/transactions
a.fiber.Post("/shop/bet", a.authMiddleware, a.CompanyOnly, h.CreateShopBet)
a.fiber.Get("/shop/bet/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByBetID)
a.fiber.Post("/shop/bet/:id/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
a.fiber.Post("/shop/bet/:id/generate", a.authMiddleware, a.CompanyOnly, h.CashoutBet)
a.fiber.Get("/shop/cashout/:id", a.authMiddleware, a.CompanyOnly, h.GetShopBetByCashoutID)
a.fiber.Post("/shop/cashout", a.authMiddleware, a.CompanyOnly, h.CashoutByCashoutID)
a.fiber.Post("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
// a.fiber.Get("/shop/deposit", a.authMiddleware, a.CompanyOnly, h.DepositForCustomer)
a.fiber.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions)
a.fiber.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID)
a.fiber.Get("/shop/transaction/:id/bet", a.authMiddleware, h.GetShopBetByTransactionID)
a.fiber.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified)
// Notification Routes