From 3e9c707f0052d229028de0e374e90f06fc9f99cc Mon Sep 17 00:00:00 2001 From: Samuel Tariku Date: Mon, 30 Jun 2025 18:58:18 +0300 Subject: [PATCH] fix: deposit for customer --- db/migrations/000001_fortune.up.sql | 10 +- db/query/transactions.sql | 20 +- gen/db/models.go | 54 ++--- gen/db/transactions.sql.go | 58 +++--- internal/domain/transaction.go | 17 +- internal/repository/transaction.go | 50 ++--- internal/services/transaction/port.go | 12 +- internal/services/transaction/service.go | 20 +- .../handlers/transaction_handler.go | 189 ++++++++++++++---- .../web_server/handlers/transfer_handler.go | 2 +- internal/web_server/routes.go | 10 +- 11 files changed, 281 insertions(+), 161 deletions(-) diff --git a/db/migrations/000001_fortune.up.sql b/db/migrations/000001_fortune.up.sql index 0c9a717..57fd09a 100644 --- a/db/migrations/000001_fortune.up.sql +++ b/db/migrations/000001_fortune.up.sql @@ -168,7 +168,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS transactions ( +CREATE TABLE IF NOT EXISTS shop_transactions ( id BIGSERIAL PRIMARY KEY, amount BIGINT NOT NULL, branch_id BIGINT NOT NULL, @@ -381,10 +381,10 @@ ALTER TABLE wallet_transfer ADD CONSTRAINT fk_wallet_transfer_receiver_wallet FOREIGN KEY (receiver_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_sender_wallet FOREIGN KEY (sender_wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_wallet_transfer_cashier FOREIGN KEY (cashier_id) REFERENCES users(id); -ALTER TABLE transactions -ADD CONSTRAINT fk_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), - ADD CONSTRAINT fk_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), - ADD CONSTRAINT fk_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id); +ALTER TABLE shop_transactions +ADD CONSTRAINT fk_shop_transactions_branches FOREIGN KEY (branch_id) REFERENCES branches(id), + ADD CONSTRAINT fk_shop_transactions_cashiers FOREIGN KEY (cashier_id) REFERENCES users(id), + ADD CONSTRAINT fk_shop_transactions_bets FOREIGN KEY (bet_id) REFERENCES bets(id); ALTER TABLE branches ADD CONSTRAINT fk_branches_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id), ADD CONSTRAINT fk_branches_manager FOREIGN KEY (branch_manager_id) REFERENCES users(id); diff --git a/db/query/transactions.sql b/db/query/transactions.sql index 199e987..820c096 100644 --- a/db/query/transactions.sql +++ b/db/query/transactions.sql @@ -1,5 +1,5 @@ --- name: CreateTransaction :one -INSERT INTO transactions ( +-- name: CreateShopTransaction :one +INSERT INTO shop_transactions ( amount, branch_id, cashier_id, @@ -40,9 +40,9 @@ VALUES ( $18 ) RETURNING *; --- name: GetAllTransactions :many +-- name: GetAllShopTransactions :many SELECT * -FROM transactions +FROM shop_transactions wHERE ( branch_id = sqlc.narg('branch_id') OR sqlc.narg('branch_id') IS NULL @@ -68,16 +68,16 @@ wHERE ( created_at < sqlc.narg('created_after') OR sqlc.narg('created_after') IS NULL ); --- name: GetTransactionByID :one +-- name: GetShopTransactionByID :one SELECT * -FROM transactions +FROM shop_transactions WHERE id = $1; --- name: GetTransactionByBranch :many +-- name: GetShopTransactionByBranch :many SELECT * -FROM transactions +FROM shop_transactions WHERE branch_id = $1; --- name: UpdateTransactionVerified :exec -UPDATE transactions +-- name: UpdateShopTransactionVerified :exec +UPDATE shop_transactions SET verified = $2, approved_by = $3, approver_name = $4, diff --git a/gen/db/models.go b/gen/db/models.go index c890cb9..186429f 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -391,6 +391,33 @@ type Setting struct { UpdatedAt pgtype.Timestamp `json:"updated_at"` } +type ShopTransaction struct { + ID int64 `json:"id"` + Amount int64 `json:"amount"` + BranchID int64 `json:"branch_id"` + CompanyID pgtype.Int8 `json:"company_id"` + CashierID pgtype.Int8 `json:"cashier_id"` + CashierName pgtype.Text `json:"cashier_name"` + BetID pgtype.Int8 `json:"bet_id"` + NumberOfOutcomes pgtype.Int8 `json:"number_of_outcomes"` + Type pgtype.Int8 `json:"type"` + PaymentOption pgtype.Int8 `json:"payment_option"` + FullName pgtype.Text `json:"full_name"` + PhoneNumber pgtype.Text `json:"phone_number"` + BankCode pgtype.Text `json:"bank_code"` + BeneficiaryName pgtype.Text `json:"beneficiary_name"` + AccountName pgtype.Text `json:"account_name"` + AccountNumber pgtype.Text `json:"account_number"` + ReferenceNumber pgtype.Text `json:"reference_number"` + Verified bool `json:"verified"` + ApprovedBy pgtype.Int8 `json:"approved_by"` + ApproverName pgtype.Text `json:"approver_name"` + BranchLocation pgtype.Text `json:"branch_location"` + BranchName pgtype.Text `json:"branch_name"` + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + type SupportedOperation struct { ID int64 `json:"id"` Name string `json:"name"` @@ -441,33 +468,6 @@ type TicketWithOutcome struct { Outcomes []TicketOutcome `json:"outcomes"` } -type Transaction struct { - ID int64 `json:"id"` - Amount int64 `json:"amount"` - BranchID int64 `json:"branch_id"` - CompanyID pgtype.Int8 `json:"company_id"` - CashierID pgtype.Int8 `json:"cashier_id"` - CashierName pgtype.Text `json:"cashier_name"` - BetID pgtype.Int8 `json:"bet_id"` - NumberOfOutcomes pgtype.Int8 `json:"number_of_outcomes"` - Type pgtype.Int8 `json:"type"` - PaymentOption pgtype.Int8 `json:"payment_option"` - FullName pgtype.Text `json:"full_name"` - PhoneNumber pgtype.Text `json:"phone_number"` - BankCode pgtype.Text `json:"bank_code"` - BeneficiaryName pgtype.Text `json:"beneficiary_name"` - AccountName pgtype.Text `json:"account_name"` - AccountNumber pgtype.Text `json:"account_number"` - ReferenceNumber pgtype.Text `json:"reference_number"` - Verified bool `json:"verified"` - ApprovedBy pgtype.Int8 `json:"approved_by"` - ApproverName pgtype.Text `json:"approver_name"` - BranchLocation pgtype.Text `json:"branch_location"` - BranchName pgtype.Text `json:"branch_name"` - CreatedAt pgtype.Timestamp `json:"created_at"` - UpdatedAt pgtype.Timestamp `json:"updated_at"` -} - type User struct { ID int64 `json:"id"` FirstName string `json:"first_name"` diff --git a/gen/db/transactions.sql.go b/gen/db/transactions.sql.go index 6bf08fc..2050b97 100644 --- a/gen/db/transactions.sql.go +++ b/gen/db/transactions.sql.go @@ -11,8 +11,8 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) -const CreateTransaction = `-- name: CreateTransaction :one -INSERT INTO transactions ( +const CreateShopTransaction = `-- name: CreateShopTransaction :one +INSERT INTO shop_transactions ( amount, branch_id, cashier_id, @@ -55,7 +55,7 @@ VALUES ( RETURNING id, amount, branch_id, company_id, cashier_id, cashier_name, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, approver_name, branch_location, branch_name, created_at, updated_at ` -type CreateTransactionParams struct { +type CreateShopTransactionParams struct { Amount int64 `json:"amount"` BranchID int64 `json:"branch_id"` CashierID pgtype.Int8 `json:"cashier_id"` @@ -76,8 +76,8 @@ type CreateTransactionParams struct { CashierName pgtype.Text `json:"cashier_name"` } -func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { - row := q.db.QueryRow(ctx, CreateTransaction, +func (q *Queries) CreateShopTransaction(ctx context.Context, arg CreateShopTransactionParams) (ShopTransaction, error) { + row := q.db.QueryRow(ctx, CreateShopTransaction, arg.Amount, arg.BranchID, arg.CashierID, @@ -97,7 +97,7 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa arg.CompanyID, arg.CashierName, ) - var i Transaction + var i ShopTransaction err := row.Scan( &i.ID, &i.Amount, @@ -127,9 +127,9 @@ func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionPa return i, err } -const GetAllTransactions = `-- name: GetAllTransactions :many +const GetAllShopTransactions = `-- name: GetAllShopTransactions :many SELECT id, amount, branch_id, company_id, cashier_id, cashier_name, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, approver_name, branch_location, branch_name, created_at, updated_at -FROM transactions +FROM shop_transactions wHERE ( branch_id = $1 OR $1 IS NULL @@ -157,7 +157,7 @@ wHERE ( ) ` -type GetAllTransactionsParams struct { +type GetAllShopTransactionsParams struct { BranchID pgtype.Int8 `json:"branch_id"` CompanyID pgtype.Int8 `json:"company_id"` CashierID pgtype.Int8 `json:"cashier_id"` @@ -166,8 +166,8 @@ type GetAllTransactionsParams struct { CreatedAfter pgtype.Timestamp `json:"created_after"` } -func (q *Queries) GetAllTransactions(ctx context.Context, arg GetAllTransactionsParams) ([]Transaction, error) { - rows, err := q.db.Query(ctx, GetAllTransactions, +func (q *Queries) GetAllShopTransactions(ctx context.Context, arg GetAllShopTransactionsParams) ([]ShopTransaction, error) { + rows, err := q.db.Query(ctx, GetAllShopTransactions, arg.BranchID, arg.CompanyID, arg.CashierID, @@ -179,9 +179,9 @@ func (q *Queries) GetAllTransactions(ctx context.Context, arg GetAllTransactions return nil, err } defer rows.Close() - var items []Transaction + var items []ShopTransaction for rows.Next() { - var i Transaction + var i ShopTransaction if err := rows.Scan( &i.ID, &i.Amount, @@ -218,21 +218,21 @@ func (q *Queries) GetAllTransactions(ctx context.Context, arg GetAllTransactions return items, nil } -const GetTransactionByBranch = `-- name: GetTransactionByBranch :many +const GetShopTransactionByBranch = `-- name: GetShopTransactionByBranch :many SELECT id, amount, branch_id, company_id, cashier_id, cashier_name, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, approver_name, branch_location, branch_name, created_at, updated_at -FROM transactions +FROM shop_transactions WHERE branch_id = $1 ` -func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([]Transaction, error) { - rows, err := q.db.Query(ctx, GetTransactionByBranch, branchID) +func (q *Queries) GetShopTransactionByBranch(ctx context.Context, branchID int64) ([]ShopTransaction, error) { + rows, err := q.db.Query(ctx, GetShopTransactionByBranch, branchID) if err != nil { return nil, err } defer rows.Close() - var items []Transaction + var items []ShopTransaction for rows.Next() { - var i Transaction + var i ShopTransaction if err := rows.Scan( &i.ID, &i.Amount, @@ -269,15 +269,15 @@ func (q *Queries) GetTransactionByBranch(ctx context.Context, branchID int64) ([ return items, nil } -const GetTransactionByID = `-- name: GetTransactionByID :one +const GetShopTransactionByID = `-- name: GetShopTransactionByID :one SELECT id, amount, branch_id, company_id, cashier_id, cashier_name, bet_id, number_of_outcomes, type, payment_option, full_name, phone_number, bank_code, beneficiary_name, account_name, account_number, reference_number, verified, approved_by, approver_name, branch_location, branch_name, created_at, updated_at -FROM transactions +FROM shop_transactions WHERE id = $1 ` -func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction, error) { - row := q.db.QueryRow(ctx, GetTransactionByID, id) - var i Transaction +func (q *Queries) GetShopTransactionByID(ctx context.Context, id int64) (ShopTransaction, error) { + row := q.db.QueryRow(ctx, GetShopTransactionByID, id) + var i ShopTransaction err := row.Scan( &i.ID, &i.Amount, @@ -307,8 +307,8 @@ func (q *Queries) GetTransactionByID(ctx context.Context, id int64) (Transaction return i, err } -const UpdateTransactionVerified = `-- name: UpdateTransactionVerified :exec -UPDATE transactions +const UpdateShopTransactionVerified = `-- name: UpdateShopTransactionVerified :exec +UPDATE shop_transactions SET verified = $2, approved_by = $3, approver_name = $4, @@ -316,15 +316,15 @@ SET verified = $2, WHERE id = $1 ` -type UpdateTransactionVerifiedParams struct { +type UpdateShopTransactionVerifiedParams struct { ID int64 `json:"id"` Verified bool `json:"verified"` ApprovedBy pgtype.Int8 `json:"approved_by"` ApproverName pgtype.Text `json:"approver_name"` } -func (q *Queries) UpdateTransactionVerified(ctx context.Context, arg UpdateTransactionVerifiedParams) error { - _, err := q.db.Exec(ctx, UpdateTransactionVerified, +func (q *Queries) UpdateShopTransactionVerified(ctx context.Context, arg UpdateShopTransactionVerifiedParams) error { + _, err := q.db.Exec(ctx, UpdateShopTransactionVerified, arg.ID, arg.Verified, arg.ApprovedBy, diff --git a/internal/domain/transaction.go b/internal/domain/transaction.go index d489e2a..9e632d6 100644 --- a/internal/domain/transaction.go +++ b/internal/domain/transaction.go @@ -2,10 +2,10 @@ package domain import "time" -type TransactionType int +type ShopTransactionType int const ( - TRANSACTION_CASHOUT TransactionType = iota + TRANSACTION_CASHOUT ShopTransactionType = iota TRANSACTION_DEPOSIT ) @@ -18,8 +18,9 @@ const ( BANK ) -// Transaction only represents branch transactions -type Transaction struct { +// ShopTransaction only represents branch transactions +// This is only used for statistic data +type ShopTransaction struct { ID int64 Amount Currency BranchID int64 @@ -30,7 +31,7 @@ type Transaction struct { CashierName string BetID int64 NumberOfOutcomes int64 - Type TransactionType + Type ShopTransactionType PaymentOption PaymentOption FullName string PhoneNumber string @@ -47,7 +48,7 @@ type Transaction struct { CreatedAt time.Time } -type TransactionFilter struct { +type ShopTransactionFilter struct { CompanyID ValidInt64 BranchID ValidInt64 CashierID ValidInt64 @@ -55,13 +56,13 @@ type TransactionFilter struct { CreatedBefore ValidTime CreatedAfter ValidTime } -type CreateTransaction struct { +type CreateShopTransaction struct { Amount Currency BranchID int64 CashierID int64 BetID int64 NumberOfOutcomes int64 - Type TransactionType + Type ShopTransactionType PaymentOption PaymentOption FullName string PhoneNumber string diff --git a/internal/repository/transaction.go b/internal/repository/transaction.go index 48fd0ce..9a1d2de 100644 --- a/internal/repository/transaction.go +++ b/internal/repository/transaction.go @@ -9,15 +9,15 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) -func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { - return domain.Transaction{ +func convertDBShopTransaction(transaction dbgen.ShopTransaction) domain.ShopTransaction { + return domain.ShopTransaction{ ID: transaction.ID, Amount: domain.Currency(transaction.Amount), BranchID: transaction.BranchID, CashierID: transaction.CashierID.Int64, BetID: transaction.BetID.Int64, NumberOfOutcomes: transaction.NumberOfOutcomes.Int64, - Type: domain.TransactionType(transaction.Type.Int64), + Type: domain.ShopTransactionType(transaction.Type.Int64), PaymentOption: domain.PaymentOption(transaction.PaymentOption.Int64), FullName: transaction.FullName.String, PhoneNumber: transaction.PhoneNumber.String, @@ -44,8 +44,8 @@ func convertDBTransaction(transaction dbgen.Transaction) domain.Transaction { } } -func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.CreateTransactionParams { - return dbgen.CreateTransactionParams{ +func convertCreateTransaction(transaction domain.CreateShopTransaction) dbgen.CreateShopTransactionParams { + return dbgen.CreateShopTransactionParams{ Amount: int64(transaction.Amount), BranchID: transaction.BranchID, CashierID: pgtype.Int8{Int64: transaction.CashierID, Valid: true}, @@ -67,26 +67,26 @@ func convertCreateTransaction(transaction domain.CreateTransaction) dbgen.Create } } -func (s *Store) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { +func (s *Store) CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) { - newTransaction, err := s.queries.CreateTransaction(ctx, convertCreateTransaction(transaction)) + newTransaction, err := s.queries.CreateShopTransaction(ctx, convertCreateTransaction(transaction)) if err != nil { - return domain.Transaction{}, err + return domain.ShopTransaction{}, err } - return convertDBTransaction(newTransaction), err + return convertDBShopTransaction(newTransaction), err } -func (s *Store) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { - transaction, err := s.queries.GetTransactionByID(ctx, id) +func (s *Store) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) { + transaction, err := s.queries.GetShopTransactionByID(ctx, id) if err != nil { - return domain.Transaction{}, err + return domain.ShopTransaction{}, err } - return convertDBTransaction(transaction), nil + return convertDBShopTransaction(transaction), nil } -func (s *Store) GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error) { - transaction, err := s.queries.GetAllTransactions(ctx, dbgen.GetAllTransactionsParams{ +func (s *Store) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) { + transaction, err := s.queries.GetAllShopTransactions(ctx, dbgen.GetAllShopTransactionsParams{ BranchID: pgtype.Int8{ Int64: filter.BranchID.Value, Valid: filter.BranchID.Valid, @@ -117,28 +117,28 @@ func (s *Store) GetAllTransactions(ctx context.Context, filter domain.Transactio return nil, err } - var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) + var result []domain.ShopTransaction = make([]domain.ShopTransaction, 0, len(transaction)) for _, ticket := range transaction { - result = append(result, convertDBTransaction(ticket)) + result = append(result, convertDBShopTransaction(ticket)) } return result, nil } -func (s *Store) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { - transaction, err := s.queries.GetTransactionByBranch(ctx, id) +func (s *Store) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) { + transaction, err := s.queries.GetShopTransactionByBranch(ctx, id) if err != nil { return nil, err } - var result []domain.Transaction = make([]domain.Transaction, 0, len(transaction)) + var result []domain.ShopTransaction = make([]domain.ShopTransaction, 0, len(transaction)) for _, ticket := range transaction { - result = append(result, convertDBTransaction(ticket)) + result = append(result, convertDBShopTransaction(ticket)) } return result, nil } -func (s *Store) UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error { - err := s.queries.UpdateTransactionVerified(ctx, dbgen.UpdateTransactionVerifiedParams{ +func (s *Store) UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error { + err := s.queries.UpdateShopTransactionVerified(ctx, dbgen.UpdateShopTransactionVerifiedParams{ ID: id, ApprovedBy: pgtype.Int8{ Int64: approvedBy, @@ -158,7 +158,7 @@ func (s *Store) GetTransactionTotals(ctx context.Context, filter domain.ReportFi query := `SELECT COALESCE(SUM(CASE WHEN type = 1 THEN amount ELSE 0 END), 0) as deposits, COALESCE(SUM(CASE WHEN type = 0 THEN amount ELSE 0 END), 0) as withdrawals - FROM transactions` + FROM shop_transactions` args := []interface{}{} argPos := 1 @@ -207,7 +207,7 @@ func (s *Store) GetBranchTransactionTotals(ctx context.Context, filter domain.Re branch_id, COALESCE(SUM(CASE WHEN type = 1 THEN amount ELSE 0 END), 0) as deposits, COALESCE(SUM(CASE WHEN type = 0 THEN amount ELSE 0 END), 0) as withdrawals - FROM transactions` + FROM shop_transactions` args := []interface{}{} argPos := 1 diff --git a/internal/services/transaction/port.go b/internal/services/transaction/port.go index 3183869..9d5fe09 100644 --- a/internal/services/transaction/port.go +++ b/internal/services/transaction/port.go @@ -7,12 +7,12 @@ import ( ) type TransactionStore interface { - CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) - GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) - GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error) - GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) - UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error - + CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) + GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) + GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) + GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) + UpdateShopTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) 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) } diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 658b938..166aaab 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -16,18 +16,18 @@ func NewService(transactionStore TransactionStore) *Service { } } -func (s *Service) CreateTransaction(ctx context.Context, transaction domain.CreateTransaction) (domain.Transaction, error) { - return s.transactionStore.CreateTransaction(ctx, transaction) +func (s *Service) CreateShopTransaction(ctx context.Context, transaction domain.CreateShopTransaction) (domain.ShopTransaction, error) { + return s.transactionStore.CreateShopTransaction(ctx, transaction) } -func (s *Service) GetTransactionByID(ctx context.Context, id int64) (domain.Transaction, error) { - return s.transactionStore.GetTransactionByID(ctx, id) +func (s *Service) GetShopTransactionByID(ctx context.Context, id int64) (domain.ShopTransaction, error) { + return s.transactionStore.GetShopTransactionByID(ctx, id) } -func (s *Service) GetAllTransactions(ctx context.Context, filter domain.TransactionFilter) ([]domain.Transaction, error) { - return s.transactionStore.GetAllTransactions(ctx, filter) +func (s *Service) GetAllShopTransactions(ctx context.Context, filter domain.ShopTransactionFilter) ([]domain.ShopTransaction, error) { + return s.transactionStore.GetAllShopTransactions(ctx, filter) } -func (s *Service) GetTransactionByBranch(ctx context.Context, id int64) ([]domain.Transaction, error) { - return s.transactionStore.GetTransactionByBranch(ctx, id) +func (s *Service) GetShopTransactionByBranch(ctx context.Context, id int64) ([]domain.ShopTransaction, error) { + return s.transactionStore.GetShopTransactionByBranch(ctx, id) } -func (s *Service) UpdateTransactionVerified(ctx context.Context, id int64, verified bool, approvedBy int64, approverName string) error { - return s.transactionStore.UpdateTransactionVerified(ctx, id, verified, approvedBy, approverName) +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) } diff --git a/internal/web_server/handlers/transaction_handler.go b/internal/web_server/handlers/transaction_handler.go index 817f6b0..087ab30 100644 --- a/internal/web_server/handlers/transaction_handler.go +++ b/internal/web_server/handlers/transaction_handler.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "log/slog" "strconv" "time" @@ -8,9 +9,10 @@ import ( "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 TransactionRes struct { +type ShopTransactionRes struct { ID int64 `json:"id" example:"1"` Amount float32 `json:"amount" example:"100.0"` BranchID int64 `json:"branch_id" example:"1"` @@ -37,7 +39,7 @@ type TransactionRes struct { CreatedAt time.Time `json:"created_at"` } -type CreateTransactionReq struct { +type CashoutReq struct { CashoutID string `json:"cashout_id" example:"191212"` Amount float32 `json:"amount" example:"100.0"` BetID int64 `json:"bet_id" example:"1"` @@ -53,8 +55,8 @@ type CreateTransactionReq struct { BranchID *int64 `json:"branch_id,omitempty" example:"1"` } -func convertTransaction(transaction domain.Transaction) TransactionRes { - newTransaction := TransactionRes{ +func convertShopTransaction(transaction domain.ShopTransaction) ShopTransactionRes { + newTransaction := ShopTransactionRes{ ID: transaction.ID, Amount: transaction.Amount.Float32(), BranchID: transaction.BranchID, @@ -88,39 +90,39 @@ func convertTransaction(transaction domain.Transaction) TransactionRes { return newTransaction } -// CreateTransaction godoc -// @Summary Create a transaction -// @Description Creates a transaction +// CashoutBet godoc +// @Summary Cashout bet at branch +// @Description Cashout bet at branch // @Tags transaction // @Accept json // @Produce json -// @Param createBet body CreateTransactionReq true "Creates transaction" +// @Param createBet body CashoutReq true "cashout bet" // @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 { +// @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("CreateTransactionReq failed due to unauthorized access") + h.logger.Error("CashoutReq failed due to unauthorized access") return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ "error": "unauthorized access", }) } - var req CreateTransactionReq + var req CashoutReq if err := c.BodyParser(&req); err != nil { - h.logger.Error("CreateTransaction failed to parse request", "error", err) + 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("CreateTransactionReq failed v", "error", valErrs) + h.logger.Error("CashoutReq failed v", "error", valErrs) return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } @@ -130,12 +132,12 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { 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") + 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("CreateTransactionReq no branches") + h.logger.Error("CashoutReq no branches") return response.WriteJSON(c, fiber.StatusBadRequest, "cannot find Branch ID", err, nil) } @@ -146,7 +148,7 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { } else { branch, err := h.branchSvc.GetBranchByCashier(c.Context(), userID) if err != nil { - h.logger.Error("CreateTransactionReq failed, branch id invalid") + h.logger.Error("CashoutReq failed, branch id invalid") return response.WriteJSON(c, fiber.StatusBadRequest, "Branch ID invalid", err, nil) } branchID = branch.ID @@ -157,12 +159,12 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { bet, err := h.betSvc.GetBetByID(c.Context(), req.BetID) if err != nil { - h.logger.Error("CreateTransactionReq failed", "error", err) + 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("CreateTransactionReq failed, bet has not won") + // h.logger.Error("CashoutReq failed, bet has not won") // return response.WriteJSON(c, fiber.StatusBadRequest, "User has not won bet", err, nil) // } @@ -173,16 +175,16 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { - h.logger.Error("CreateTransactionReq failed, user id invalid", "error", err) + 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.CreateTransaction(c.Context(), domain.CreateTransaction{ + 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.TransactionType(req.Type), + Type: domain.ShopTransactionType(req.Type), PaymentOption: domain.PaymentOption(req.PaymentOption), FullName: req.FullName, PhoneNumber: req.PhoneNumber, @@ -198,21 +200,138 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { }) if err != nil { - h.logger.Error("CreateTransactionReq failed", "error", err) + 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("CreateTransactionReq failed", "error", err) + h.logger.Error("CashoutReq failed", "error", err) return response.WriteJSON(c, fiber.StatusInternalServerError, "Internal Server Error", err, nil) } - res := convertTransaction(transaction) + 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 @@ -222,7 +341,7 @@ func (h *Handler) CreateTransaction(c *fiber.Ctx) error { // @Success 200 {array} TransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /transaction [get] +// @Router /shop/transaction [get] func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { // Get user_id from middleware // userID := c.Locals("user_id").(int64) @@ -265,7 +384,7 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { } // Check user role and fetch transactions accordingly - transactions, err := h.transactionSvc.GetAllTransactions(c.Context(), domain.TransactionFilter{ + transactions, err := h.transactionSvc.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{ CompanyID: companyID, BranchID: branchID, Query: searchString, @@ -278,9 +397,9 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to retrieve transactions", err, nil) } - res := make([]TransactionRes, len(transactions)) + res := make([]ShopTransactionRes, len(transactions)) for i, transaction := range transactions { - res[i] = convertTransaction(transaction) + res[i] = convertShopTransaction(transaction) } return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) @@ -297,7 +416,7 @@ func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { // @Success 200 {object} TransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /transaction/{id} [get] +// @Router /shop/transaction/{id} [get] func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) @@ -306,13 +425,13 @@ func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } - transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), 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 := convertTransaction(transaction) + res := convertShopTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) } @@ -331,7 +450,7 @@ type UpdateTransactionVerifiedReq struct { // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse -// @Router /transaction/{id} [put] +// @Router /shop/transaction/{id} [put] func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { transactionID := c.Params("id") userID := c.Locals("user_id").(int64) @@ -356,7 +475,7 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } - transaction, err := h.transactionSvc.GetTransactionByID(c.Context(), id) + 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) @@ -369,7 +488,7 @@ func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { 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) + 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") diff --git a/internal/web_server/handlers/transfer_handler.go b/internal/web_server/handlers/transfer_handler.go index 0d1d74f..02ccd07 100644 --- a/internal/web_server/handlers/transfer_handler.go +++ b/internal/web_server/handlers/transfer_handler.go @@ -241,9 +241,9 @@ func (h *Handler) TransferToWallet(c *fiber.Ctx) error { res := convertTransfer(transfer) return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) - } + // RefillWallet godoc // @Summary Refill wallet // @Description Super Admin route to refill a wallet diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index 8e6e4aa..11f9240 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -271,11 +271,11 @@ func (a *App) initAppRoutes() { // Recommendation Routes // group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) - // Transactions /transactions - a.fiber.Post("/transaction", a.authMiddleware, h.CreateTransaction) - a.fiber.Get("/transaction", a.authMiddleware, h.GetAllTransactions) - a.fiber.Get("/transaction/:id", a.authMiddleware, h.GetTransactionByID) - a.fiber.Put("/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) + // Transactions /shop/transactions + a.fiber.Post("/shop/cashout", a.authMiddleware, h.CashoutBet) + a.fiber.Get("/shop/transaction", a.authMiddleware, h.GetAllTransactions) + a.fiber.Get("/shop/transaction/:id", a.authMiddleware, h.GetTransactionByID) + a.fiber.Put("/shop/transaction/:id", a.authMiddleware, h.UpdateTransactionVerified) // Notification Routes a.fiber.Get("/ws/connect", a.WebsocketAuthMiddleware, h.ConnectSocket)