direct depost and veli games fix
This commit is contained in:
parent
fe7d9ad3b3
commit
1c7e076be5
12
cmd/main.go
12
cmd/main.go
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
directdeposit "github.com/SamuelTariku/FortuneBet-Backend/internal/services/direct_deposit"
|
||||
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
|
||||
|
|
@ -182,7 +183,7 @@ func main() {
|
|||
walletSvc := wallet.NewService(
|
||||
repository.NewWalletStore(store),
|
||||
repository.NewTransferStore(store),
|
||||
repository.NewDirectDepositStore(store),
|
||||
// repository.NewDirectDepositStore(store),
|
||||
notificationSvc,
|
||||
userSvc,
|
||||
domain.MongoDBLogger,
|
||||
|
|
@ -326,6 +327,14 @@ func main() {
|
|||
|
||||
// Start cron jobs for automated reporting
|
||||
|
||||
directdeposit := directdeposit.NewService(
|
||||
*walletSvc,
|
||||
repository.NewTransferStore(store),
|
||||
repository.NewDirectDepositRepository(store),
|
||||
notificationSvc,
|
||||
userSvc,
|
||||
)
|
||||
|
||||
enetPulseSvc := enetpulse.New(
|
||||
*cfg,
|
||||
store,
|
||||
|
|
@ -373,6 +382,7 @@ func main() {
|
|||
|
||||
// Initialize and start HTTP server
|
||||
app := httpserver.NewApp(
|
||||
directdeposit,
|
||||
enetPulseSvc,
|
||||
atlasVirtualGameService,
|
||||
veliVirtualGameService,
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ CREATE TABLE IF NOT EXISTS virtual_game_providers (
|
|||
provider_id VARCHAR(100) UNIQUE NOT NULL,
|
||||
-- providerId from Veli Games
|
||||
provider_name VARCHAR(255) NOT NULL,
|
||||
-- providerName
|
||||
logo_dark TEXT,
|
||||
-- logoForDark (URL)
|
||||
logo_light TEXT,
|
||||
-- logoForLight (URL)
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
|
@ -618,20 +616,23 @@ CREATE TABLE flags (
|
|||
);
|
||||
CREATE TABLE direct_deposits (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
customer_id BIGINT NOT NULL REFERENCES users (id),
|
||||
wallet_id BIGINT NOT NULL REFERENCES wallets (id),
|
||||
amount NUMERIC(15, 2) NOT NULL,
|
||||
bank_reference TEXT NOT NULL,
|
||||
sender_account TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'rejected')),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW (),
|
||||
verified_by BIGINT REFERENCES users (id),
|
||||
verification_notes TEXT,
|
||||
verified_at TIMESTAMP
|
||||
customer_id BIGINT REFERENCES users(id),
|
||||
wallet_id BIGINT REFERENCES wallets(id),
|
||||
bank_name TEXT,
|
||||
account_number TEXT,
|
||||
account_holder TEXT,
|
||||
amount NUMERIC(18,2),
|
||||
reference_number TEXT,
|
||||
transfer_screenshot TEXT,
|
||||
status TEXT CHECK(status IN ('PENDING', 'APPROVED', 'REJECTED')),
|
||||
created_at TIMESTAMPTZ,
|
||||
approved_by BIGINT NULL REFERENCES users(id),
|
||||
approved_at TIMESTAMPTZ NULL,
|
||||
rejection_reason TEXT NULL
|
||||
);
|
||||
CREATE INDEX idx_direct_deposits_status ON direct_deposits (status);
|
||||
CREATE INDEX idx_direct_deposits_customer ON direct_deposits (customer_id);
|
||||
CREATE INDEX idx_direct_deposits_reference ON direct_deposits (bank_reference);
|
||||
-- CREATE INDEX idx_direct_deposits_status ON direct_deposits (status);
|
||||
-- CREATE INDEX idx_direct_deposits_customer ON direct_deposits (customer_id);
|
||||
-- CREATE INDEX idx_direct_deposits_reference ON direct_deposits (bank_reference);
|
||||
CREATE TABLE IF NOT EXISTS raffles (
|
||||
id SERIAL PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ CREATE TABLE virtual_game_sessions (
|
|||
|
||||
CREATE TABLE virtual_game_transactions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
||||
-- session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
company_id BIGINT,
|
||||
provider VARCHAR(100),
|
||||
|
|
@ -26,7 +26,7 @@ CREATE TABLE virtual_game_transactions (
|
|||
|
||||
CREATE TABLE virtual_game_histories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(100), -- nullable
|
||||
-- session_id VARCHAR(100), -- nullable
|
||||
user_id BIGINT NOT NULL,
|
||||
company_id BIGINT,
|
||||
provider VARCHAR(100),
|
||||
|
|
@ -56,7 +56,7 @@ CREATE INDEX idx_virtual_game_game_id ON virtual_game_histories(game_id);
|
|||
CREATE INDEX idx_virtual_game_external_transaction_id ON virtual_game_histories(external_transaction_id);
|
||||
|
||||
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||
-- CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
||||
|
||||
ALTER TABLE favorite_games
|
||||
|
|
|
|||
|
|
@ -1,30 +1,64 @@
|
|||
-- name: CreateDirectDeposit :one
|
||||
INSERT INTO direct_deposits (
|
||||
customer_id,
|
||||
wallet_id,
|
||||
amount,
|
||||
bank_reference,
|
||||
sender_account,
|
||||
status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetDirectDeposit :one
|
||||
SELECT * FROM direct_deposits WHERE id = $1;
|
||||
|
||||
-- name: UpdateDirectDeposit :one
|
||||
UPDATE direct_deposits
|
||||
SET
|
||||
status = $2,
|
||||
verified_by = $3,
|
||||
verification_notes = $4,
|
||||
verified_at = $5
|
||||
WHERE id = $1
|
||||
customer_id, wallet_id, bank_name, account_number,
|
||||
account_holder, amount, reference_number,
|
||||
transfer_screenshot, status
|
||||
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'PENDING')
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetDirectDepositsByStatus :many
|
||||
SELECT * FROM direct_deposits WHERE status = $1 ORDER BY created_at DESC;
|
||||
-- name: GetDirectDepositByID :one
|
||||
SELECT *
|
||||
FROM direct_deposits
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: DeleteDirectDeposit :exec
|
||||
DELETE FROM direct_deposits
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: GetDirectDepositsByStatus :many
|
||||
SELECT
|
||||
id,
|
||||
customer_id,
|
||||
wallet_id,
|
||||
bank_name,
|
||||
account_number,
|
||||
account_holder,
|
||||
amount,
|
||||
reference_number,
|
||||
transfer_screenshot,
|
||||
status,
|
||||
created_at,
|
||||
approved_by,
|
||||
approved_at,
|
||||
rejection_reason
|
||||
FROM direct_deposits
|
||||
WHERE status = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3;
|
||||
|
||||
-- name: CountDirectDepositsByStatus :one
|
||||
SELECT COUNT(*)
|
||||
FROM direct_deposits
|
||||
WHERE status = $1;
|
||||
|
||||
-- name: ApproveDirectDeposit :exec
|
||||
UPDATE direct_deposits
|
||||
SET
|
||||
status = 'APPROVED',
|
||||
approved_by = $2,
|
||||
approved_at = NOW()
|
||||
WHERE
|
||||
id = $1
|
||||
AND status = 'PENDING';
|
||||
|
||||
-- name: RejectDirectDeposit :exec
|
||||
UPDATE direct_deposits
|
||||
SET
|
||||
status = 'REJECTED',
|
||||
approved_by = $2, -- still track the admin who took final action
|
||||
approved_at = NOW(),
|
||||
rejection_reason = $3
|
||||
WHERE
|
||||
id = $1
|
||||
AND status = 'PENDING';
|
||||
|
||||
-- name: GetCustomerDirectDeposits :many
|
||||
SELECT * FROM direct_deposits WHERE customer_id = $1 ORDER BY created_at DESC;
|
||||
|
|
@ -97,7 +97,7 @@ WHERE session_token = $1;
|
|||
|
||||
-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -108,9 +108,9 @@ INSERT INTO virtual_game_transactions (
|
|||
external_transaction_id,
|
||||
status
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id,
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -124,7 +124,7 @@ RETURNING id,
|
|||
updated_at;
|
||||
-- name: CreateVirtualGameHistory :one
|
||||
INSERT INTO virtual_game_histories (
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -148,11 +148,11 @@ VALUES (
|
|||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12
|
||||
$11
|
||||
-- $12
|
||||
)
|
||||
RETURNING id,
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -169,7 +169,7 @@ RETURNING id,
|
|||
|
||||
-- name: GetVirtualGameTransactionByExternalID :one
|
||||
SELECT id,
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
wallet_id,
|
||||
transaction_type,
|
||||
|
|
@ -193,7 +193,7 @@ SELECT c.name AS company_name,
|
|||
COUNT(vgt.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_transactions vgt
|
||||
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
-- JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||
JOIN companies c ON vgt.company_id = c.id
|
||||
WHERE vgt.transaction_type = 'BET'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ services:
|
|||
image: mongo:7.0.11
|
||||
restart: always
|
||||
ports:
|
||||
- "27020:27017"
|
||||
- "27021:27017"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: secret
|
||||
|
|
|
|||
1030
docs/docs.go
1030
docs/docs.go
File diff suppressed because it is too large
Load Diff
1030
docs/swagger.json
1030
docs/swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -174,6 +174,8 @@ definitions:
|
|||
properties:
|
||||
addedTime:
|
||||
$ref: '#/definitions/domain.ValidInt'
|
||||
avgBetAmount:
|
||||
type: integer
|
||||
awayTeam:
|
||||
type: string
|
||||
awayTeamID:
|
||||
|
|
@ -212,6 +214,8 @@ definitions:
|
|||
type: string
|
||||
matchPeriod:
|
||||
$ref: '#/definitions/domain.ValidInt'
|
||||
numberOfBets:
|
||||
type: integer
|
||||
score:
|
||||
$ref: '#/definitions/domain.ValidString'
|
||||
source:
|
||||
|
|
@ -226,8 +230,12 @@ definitions:
|
|||
$ref: '#/definitions/domain.EventStatus'
|
||||
timerStatus:
|
||||
$ref: '#/definitions/domain.ValidString'
|
||||
totalAmount:
|
||||
type: integer
|
||||
totalOddOutcomes:
|
||||
type: integer
|
||||
totalPotentialWinnings:
|
||||
type: integer
|
||||
type: object
|
||||
domain.BaseLeague:
|
||||
properties:
|
||||
|
|
@ -349,6 +357,11 @@ definitions:
|
|||
company_id:
|
||||
example: 1
|
||||
type: integer
|
||||
company_name:
|
||||
example: fortune
|
||||
type: string
|
||||
deducted_stake:
|
||||
type: number
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
|
|
@ -373,9 +386,25 @@ definitions:
|
|||
name:
|
||||
example: 4-kilo Branch
|
||||
type: string
|
||||
number_of_unsettled:
|
||||
type: integer
|
||||
profit_percentage:
|
||||
example: 0.1
|
||||
type: number
|
||||
stats_updated_at:
|
||||
type: string
|
||||
total_bets:
|
||||
type: integer
|
||||
total_cash_backs:
|
||||
type: number
|
||||
total_cash_out:
|
||||
type: number
|
||||
total_cashiers:
|
||||
type: integer
|
||||
total_stake:
|
||||
type: number
|
||||
total_unsettled_amount:
|
||||
type: number
|
||||
wallet_id:
|
||||
example: 1
|
||||
type: integer
|
||||
|
|
@ -608,6 +637,19 @@ definitions:
|
|||
- customerEmail
|
||||
- customerPhone
|
||||
type: object
|
||||
domain.CompanyMarketSettings:
|
||||
properties:
|
||||
companyID:
|
||||
type: integer
|
||||
isActive:
|
||||
$ref: '#/definitions/domain.ValidBool'
|
||||
marketID:
|
||||
type: integer
|
||||
marketName:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
domain.CompanyRes:
|
||||
properties:
|
||||
admin_id:
|
||||
|
|
@ -712,6 +754,17 @@ definitions:
|
|||
- name
|
||||
- operations
|
||||
type: object
|
||||
domain.CreateCompanyMarketSettings:
|
||||
properties:
|
||||
companyID:
|
||||
type: integer
|
||||
isActive:
|
||||
$ref: '#/definitions/domain.ValidBool'
|
||||
marketID:
|
||||
type: integer
|
||||
marketName:
|
||||
type: string
|
||||
type: object
|
||||
domain.CreateCompanyReq:
|
||||
properties:
|
||||
admin_id:
|
||||
|
|
@ -728,6 +781,25 @@ definitions:
|
|||
slug:
|
||||
type: string
|
||||
type: object
|
||||
domain.CreateDirectDeposit:
|
||||
properties:
|
||||
accountHolder:
|
||||
type: string
|
||||
accountNumber:
|
||||
type: string
|
||||
amount:
|
||||
type: number
|
||||
bankName:
|
||||
type: string
|
||||
customerID:
|
||||
type: integer
|
||||
referenceNumber:
|
||||
type: string
|
||||
transferScreenshot:
|
||||
type: string
|
||||
walletID:
|
||||
type: integer
|
||||
type: object
|
||||
domain.CreateSupportedOperationReq:
|
||||
properties:
|
||||
description:
|
||||
|
|
@ -857,8 +929,6 @@ definitions:
|
|||
properties:
|
||||
brandId:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
deviceType:
|
||||
type: string
|
||||
gameId:
|
||||
|
|
@ -867,26 +937,39 @@ definitions:
|
|||
type: string
|
||||
language:
|
||||
type: string
|
||||
playerId:
|
||||
type: string
|
||||
providerId:
|
||||
type: string
|
||||
type: object
|
||||
domain.DirectDepositRequest:
|
||||
domain.DirectDeposit:
|
||||
properties:
|
||||
accountHolder:
|
||||
type: string
|
||||
accountNumber:
|
||||
type: string
|
||||
amount:
|
||||
type: integer
|
||||
bank_reference:
|
||||
type: number
|
||||
approvedAt:
|
||||
type: string
|
||||
customer_id:
|
||||
approvedBy:
|
||||
type: integer
|
||||
sender_account:
|
||||
bankName:
|
||||
type: string
|
||||
required:
|
||||
- amount
|
||||
- bank_reference
|
||||
- customer_id
|
||||
- sender_account
|
||||
createdAt:
|
||||
type: string
|
||||
customerID:
|
||||
type: integer
|
||||
id:
|
||||
type: integer
|
||||
referenceNumber:
|
||||
type: string
|
||||
rejectionReason:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
transferScreenshot:
|
||||
type: string
|
||||
walletID:
|
||||
type: integer
|
||||
type: object
|
||||
domain.EnetpulseFixture:
|
||||
properties:
|
||||
|
|
@ -1272,6 +1355,8 @@ definitions:
|
|||
properties:
|
||||
added_time:
|
||||
type: integer
|
||||
average_bet_amount:
|
||||
type: number
|
||||
away_team:
|
||||
type: string
|
||||
away_team_id:
|
||||
|
|
@ -1314,6 +1399,8 @@ definitions:
|
|||
type: string
|
||||
match_period:
|
||||
type: integer
|
||||
number_of_bets:
|
||||
type: integer
|
||||
score:
|
||||
type: string
|
||||
source:
|
||||
|
|
@ -1328,8 +1415,12 @@ definitions:
|
|||
$ref: '#/definitions/domain.EventStatus'
|
||||
timer_status:
|
||||
type: string
|
||||
total_amount:
|
||||
type: number
|
||||
total_odd_outcomes:
|
||||
type: integer
|
||||
total_potential_winnings:
|
||||
type: number
|
||||
updated_at:
|
||||
type: string
|
||||
winning_upper_limit:
|
||||
|
|
@ -1422,8 +1513,6 @@ definitions:
|
|||
properties:
|
||||
brandId:
|
||||
type: string
|
||||
cashierUrl:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
currency:
|
||||
|
|
@ -1436,18 +1525,12 @@ definitions:
|
|||
type: string
|
||||
language:
|
||||
type: string
|
||||
lobbyUrl:
|
||||
type: string
|
||||
playerId:
|
||||
type: string
|
||||
playerName:
|
||||
type: string
|
||||
providerId:
|
||||
type: string
|
||||
sessionId:
|
||||
type: string
|
||||
userAgent:
|
||||
type: string
|
||||
type: object
|
||||
domain.GameStartResponse:
|
||||
properties:
|
||||
|
|
@ -1569,6 +1652,8 @@ definitions:
|
|||
deducted_percentage:
|
||||
example: 0.1
|
||||
type: number
|
||||
deducted_stake:
|
||||
type: number
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
|
|
@ -1581,9 +1666,35 @@ definitions:
|
|||
name:
|
||||
example: CompanyName
|
||||
type: string
|
||||
number_of_unsettled:
|
||||
type: integer
|
||||
slug:
|
||||
example: slug
|
||||
type: string
|
||||
stats_updated_at:
|
||||
type: string
|
||||
total_admins:
|
||||
type: integer
|
||||
total_approvers:
|
||||
type: integer
|
||||
total_bets:
|
||||
type: integer
|
||||
total_branches:
|
||||
type: integer
|
||||
total_cash_backs:
|
||||
type: number
|
||||
total_cash_out:
|
||||
type: number
|
||||
total_cashiers:
|
||||
type: integer
|
||||
total_customers:
|
||||
type: integer
|
||||
total_managers:
|
||||
type: integer
|
||||
total_stake:
|
||||
type: number
|
||||
total_unsettled_amount:
|
||||
type: number
|
||||
wallet_id:
|
||||
example: 1
|
||||
type: integer
|
||||
|
|
@ -1730,6 +1841,17 @@ definitions:
|
|||
pagination:
|
||||
$ref: '#/definitions/domain.Pagination'
|
||||
type: object
|
||||
domain.MarketSettings:
|
||||
properties:
|
||||
isActive:
|
||||
type: boolean
|
||||
marketID:
|
||||
type: integer
|
||||
marketName:
|
||||
type: string
|
||||
updatedAt:
|
||||
type: string
|
||||
type: object
|
||||
domain.OddMarketFilter:
|
||||
properties:
|
||||
limit:
|
||||
|
|
@ -1758,7 +1880,7 @@ definitions:
|
|||
- 5
|
||||
type: integer
|
||||
x-enum-comments:
|
||||
OUTCOME_STATUS_ERROR: Half Win and Half Given Back
|
||||
OUTCOME_STATUS_ERROR: Error (Unsettled Bet)
|
||||
OUTCOME_STATUS_HALF: Half Win and Half Given Back
|
||||
OUTCOME_STATUS_VOID: Give Back
|
||||
x-enum-varnames:
|
||||
|
|
@ -1776,6 +1898,7 @@ definitions:
|
|||
type: array
|
||||
message:
|
||||
type: string
|
||||
metadata: {}
|
||||
pagination:
|
||||
$ref: '#/definitions/domain.Pagination'
|
||||
status_code:
|
||||
|
|
@ -1946,6 +2069,7 @@ definitions:
|
|||
data: {}
|
||||
message:
|
||||
type: string
|
||||
metadata: {}
|
||||
status_code:
|
||||
type: integer
|
||||
success:
|
||||
|
|
@ -2497,18 +2621,6 @@ definitions:
|
|||
value:
|
||||
type: string
|
||||
type: object
|
||||
domain.VerifyDirectDepositRequest:
|
||||
properties:
|
||||
deposit_id:
|
||||
type: integer
|
||||
is_verified:
|
||||
type: boolean
|
||||
notes:
|
||||
type: string
|
||||
required:
|
||||
- deposit_id
|
||||
- is_verified
|
||||
type: object
|
||||
domain.VirtualGameProvider:
|
||||
properties:
|
||||
created_at:
|
||||
|
|
@ -2795,6 +2907,14 @@ definitions:
|
|||
last_name:
|
||||
example: Smith
|
||||
type: string
|
||||
number_of_deposits:
|
||||
type: integer
|
||||
number_of_transactions:
|
||||
type: integer
|
||||
number_of_transfers:
|
||||
type: integer
|
||||
number_of_withdraws:
|
||||
type: integer
|
||||
phone_number:
|
||||
example: "0911111111"
|
||||
type: string
|
||||
|
|
@ -2820,6 +2940,16 @@ definitions:
|
|||
type: boolean
|
||||
static_updated_at:
|
||||
type: string
|
||||
total_deposits_amount:
|
||||
type: number
|
||||
total_transactions:
|
||||
type: number
|
||||
total_transfers_amount:
|
||||
type: number
|
||||
total_withdraws_amount:
|
||||
type: number
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
handlers.CustomersRes:
|
||||
properties:
|
||||
|
|
@ -3568,6 +3698,120 @@ paths:
|
|||
summary: Set the league to active
|
||||
tags:
|
||||
- leagues
|
||||
/api/v1/{tenant_slug}/market-settings:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Remove all overridden market settings for a specific tenant
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Delete all market settings for a tenant
|
||||
tags:
|
||||
- market_settings
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all market settings overridden for a specific tenant
|
||||
parameters:
|
||||
- description: Number of results to return (default 10)
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- description: Number of results to skip (default 0)
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.CompanyMarketSettings'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve all market settings for a tenant
|
||||
tags:
|
||||
- market_settings
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Insert new market settings for a specific tenant/company
|
||||
parameters:
|
||||
- description: Market Settings
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.CreateCompanyMarketSettings'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Insert company-specific market settings
|
||||
tags:
|
||||
- market_settings
|
||||
/api/v1/{tenant_slug}/market-settings/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Remove a specific overridden market setting for a tenant
|
||||
parameters:
|
||||
- description: Market ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Delete a specific market setting for a tenant
|
||||
tags:
|
||||
- market_settings
|
||||
/api/v1/{tenant_slug}/odds:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -6385,87 +6629,235 @@ paths:
|
|||
summary: Get all customer wallets
|
||||
tags:
|
||||
- wallet
|
||||
/api/v1/direct_deposit:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Customer initiates a direct deposit from mobile banking
|
||||
parameters:
|
||||
- description: Deposit details
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.DirectDepositRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Initiate a direct deposit
|
||||
tags:
|
||||
- Direct Deposits
|
||||
/api/v1/direct_deposit/pending:
|
||||
/api/v1/direct-deposits:
|
||||
get:
|
||||
description: Get list of direct deposits needing verification
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Get pending direct deposits
|
||||
tags:
|
||||
- Direct Deposits
|
||||
/api/v1/direct_deposit/verify:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Cashier verifies a direct deposit transaction
|
||||
description: Fetches direct deposits filtered by status with pagination
|
||||
parameters:
|
||||
- description: Verification details
|
||||
in: body
|
||||
name: request
|
||||
- description: Deposit status (e.g., PENDING, APPROVED, REJECTED)
|
||||
in: query
|
||||
name: status
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VerifyDirectDepositRequest'
|
||||
type: string
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: pageSize
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/domain.DirectDeposit'
|
||||
type: array
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Verify a direct deposit
|
||||
summary: Get direct deposits by status
|
||||
tags:
|
||||
- Direct Deposits
|
||||
- DirectDeposit
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a direct deposit for a customer and notifies both the customer
|
||||
and admins
|
||||
parameters:
|
||||
- description: Direct deposit details
|
||||
in: body
|
||||
name: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.CreateDirectDeposit'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.DirectDeposit'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Create a new direct deposit
|
||||
tags:
|
||||
- DirectDeposit
|
||||
/api/v1/direct-deposits/{depositID}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Deletes a direct deposit by its ID
|
||||
parameters:
|
||||
- description: Deposit ID
|
||||
in: path
|
||||
name: depositID
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Delete a direct deposit
|
||||
tags:
|
||||
- DirectDeposit
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Fetches a single direct deposit by its ID
|
||||
parameters:
|
||||
- description: Deposit ID
|
||||
in: path
|
||||
name: depositID
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.DirectDeposit'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Get a direct deposit by ID
|
||||
tags:
|
||||
- DirectDeposit
|
||||
/api/v1/direct-deposits/{depositID}/approve:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Approves a direct deposit by admin and credits customer wallet
|
||||
parameters:
|
||||
- description: Deposit ID
|
||||
in: path
|
||||
name: depositID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Admin ID performing the approval
|
||||
in: query
|
||||
name: adminID
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Approve a direct deposit
|
||||
tags:
|
||||
- DirectDeposit
|
||||
/api/v1/direct-deposits/{depositID}/reject:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Rejects a direct deposit by admin and notifies the customer
|
||||
parameters:
|
||||
- description: Deposit ID
|
||||
in: path
|
||||
name: depositID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Admin ID performing the rejection
|
||||
in: query
|
||||
name: adminID
|
||||
required: true
|
||||
type: integer
|
||||
- description: Reason for rejection
|
||||
in: query
|
||||
name: reason
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"502":
|
||||
description: Bad Gateway
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Reject a direct deposit
|
||||
tags:
|
||||
- DirectDeposit
|
||||
/api/v1/enetpulse/betting-offers:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -7303,6 +7695,40 @@ paths:
|
|||
summary: Update Managers
|
||||
tags:
|
||||
- manager
|
||||
/api/v1/market-settings:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get all market settings that apply globally
|
||||
parameters:
|
||||
- description: Number of results to return (default 10)
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- description: Number of results to skip (default 0)
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.MarketSettings'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve all global market settings
|
||||
tags:
|
||||
- market_settings
|
||||
/api/v1/odds:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -9643,37 +10069,6 @@ paths:
|
|||
summary: Process Alea Play game callback
|
||||
tags:
|
||||
- Alea Virtual Games
|
||||
/api/v1/win:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Processes win callbacks from either Veli or PopOK providers by
|
||||
auto-detecting the format
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Win processing result
|
||||
schema: {}
|
||||
"400":
|
||||
description: Invalid request format
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"401":
|
||||
description: Authentication failed
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"409":
|
||||
description: Duplicate transaction
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Handle win callback (Veli or PopOK)
|
||||
tags:
|
||||
- Wins
|
||||
/betwin:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -9942,6 +10337,37 @@ paths:
|
|||
summary: Launch a PopOK virtual game
|
||||
tags:
|
||||
- Virtual Games - PopOK
|
||||
/win:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Processes win callbacks from either Veli or PopOK providers by
|
||||
auto-detecting the format
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Win processing result
|
||||
schema: {}
|
||||
"400":
|
||||
description: Invalid request format
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"401":
|
||||
description: Authentication failed
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"409":
|
||||
description: Duplicate transaction
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Handle win callback (Veli or PopOK)
|
||||
tags:
|
||||
- Wins
|
||||
securityDefinitions:
|
||||
Bearer:
|
||||
in: header
|
||||
|
|
|
|||
|
|
@ -11,119 +11,159 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const ApproveDirectDeposit = `-- name: ApproveDirectDeposit :exec
|
||||
UPDATE direct_deposits
|
||||
SET
|
||||
status = 'APPROVED',
|
||||
approved_by = $2,
|
||||
approved_at = NOW()
|
||||
WHERE
|
||||
id = $1
|
||||
AND status = 'PENDING'
|
||||
`
|
||||
|
||||
type ApproveDirectDepositParams struct {
|
||||
ID int64 `json:"id"`
|
||||
ApprovedBy pgtype.Int8 `json:"approved_by"`
|
||||
}
|
||||
|
||||
func (q *Queries) ApproveDirectDeposit(ctx context.Context, arg ApproveDirectDepositParams) error {
|
||||
_, err := q.db.Exec(ctx, ApproveDirectDeposit, arg.ID, arg.ApprovedBy)
|
||||
return err
|
||||
}
|
||||
|
||||
const CountDirectDepositsByStatus = `-- name: CountDirectDepositsByStatus :one
|
||||
SELECT COUNT(*)
|
||||
FROM direct_deposits
|
||||
WHERE status = $1
|
||||
`
|
||||
|
||||
func (q *Queries) CountDirectDepositsByStatus(ctx context.Context, status pgtype.Text) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, CountDirectDepositsByStatus, status)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const CreateDirectDeposit = `-- name: CreateDirectDeposit :one
|
||||
INSERT INTO direct_deposits (
|
||||
customer_id,
|
||||
wallet_id,
|
||||
amount,
|
||||
bank_reference,
|
||||
sender_account,
|
||||
status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at
|
||||
customer_id, wallet_id, bank_name, account_number,
|
||||
account_holder, amount, reference_number,
|
||||
transfer_screenshot, status
|
||||
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'PENDING')
|
||||
RETURNING id, customer_id, wallet_id, bank_name, account_number, account_holder, amount, reference_number, transfer_screenshot, status, created_at, approved_by, approved_at, rejection_reason
|
||||
`
|
||||
|
||||
type CreateDirectDepositParams struct {
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
Amount pgtype.Numeric `json:"amount"`
|
||||
BankReference string `json:"bank_reference"`
|
||||
SenderAccount string `json:"sender_account"`
|
||||
Status string `json:"status"`
|
||||
CustomerID pgtype.Int8 `json:"customer_id"`
|
||||
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||
BankName pgtype.Text `json:"bank_name"`
|
||||
AccountNumber pgtype.Text `json:"account_number"`
|
||||
AccountHolder pgtype.Text `json:"account_holder"`
|
||||
Amount pgtype.Numeric `json:"amount"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
TransferScreenshot pgtype.Text `json:"transfer_screenshot"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateDirectDeposit(ctx context.Context, arg CreateDirectDepositParams) (DirectDeposit, error) {
|
||||
row := q.db.QueryRow(ctx, CreateDirectDeposit,
|
||||
arg.CustomerID,
|
||||
arg.WalletID,
|
||||
arg.BankName,
|
||||
arg.AccountNumber,
|
||||
arg.AccountHolder,
|
||||
arg.Amount,
|
||||
arg.BankReference,
|
||||
arg.SenderAccount,
|
||||
arg.Status,
|
||||
arg.ReferenceNumber,
|
||||
arg.TransferScreenshot,
|
||||
)
|
||||
var i DirectDeposit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.WalletID,
|
||||
&i.BankName,
|
||||
&i.AccountNumber,
|
||||
&i.AccountHolder,
|
||||
&i.Amount,
|
||||
&i.BankReference,
|
||||
&i.SenderAccount,
|
||||
&i.ReferenceNumber,
|
||||
&i.TransferScreenshot,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.VerifiedBy,
|
||||
&i.VerificationNotes,
|
||||
&i.VerifiedAt,
|
||||
&i.ApprovedBy,
|
||||
&i.ApprovedAt,
|
||||
&i.RejectionReason,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetCustomerDirectDeposits = `-- name: GetCustomerDirectDeposits :many
|
||||
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE customer_id = $1 ORDER BY created_at DESC
|
||||
const DeleteDirectDeposit = `-- name: DeleteDirectDeposit :exec
|
||||
DELETE FROM direct_deposits
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]DirectDeposit, error) {
|
||||
rows, err := q.db.Query(ctx, GetCustomerDirectDeposits, customerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DirectDeposit
|
||||
for rows.Next() {
|
||||
var i DirectDeposit
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.WalletID,
|
||||
&i.Amount,
|
||||
&i.BankReference,
|
||||
&i.SenderAccount,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.VerifiedBy,
|
||||
&i.VerificationNotes,
|
||||
&i.VerifiedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
func (q *Queries) DeleteDirectDeposit(ctx context.Context, id int64) error {
|
||||
_, err := q.db.Exec(ctx, DeleteDirectDeposit, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const GetDirectDeposit = `-- name: GetDirectDeposit :one
|
||||
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE id = $1
|
||||
const GetDirectDepositByID = `-- name: GetDirectDepositByID :one
|
||||
SELECT id, customer_id, wallet_id, bank_name, account_number, account_holder, amount, reference_number, transfer_screenshot, status, created_at, approved_by, approved_at, rejection_reason
|
||||
FROM direct_deposits
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetDirectDeposit(ctx context.Context, id int64) (DirectDeposit, error) {
|
||||
row := q.db.QueryRow(ctx, GetDirectDeposit, id)
|
||||
func (q *Queries) GetDirectDepositByID(ctx context.Context, id int64) (DirectDeposit, error) {
|
||||
row := q.db.QueryRow(ctx, GetDirectDepositByID, id)
|
||||
var i DirectDeposit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.WalletID,
|
||||
&i.BankName,
|
||||
&i.AccountNumber,
|
||||
&i.AccountHolder,
|
||||
&i.Amount,
|
||||
&i.BankReference,
|
||||
&i.SenderAccount,
|
||||
&i.ReferenceNumber,
|
||||
&i.TransferScreenshot,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.VerifiedBy,
|
||||
&i.VerificationNotes,
|
||||
&i.VerifiedAt,
|
||||
&i.ApprovedBy,
|
||||
&i.ApprovedAt,
|
||||
&i.RejectionReason,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetDirectDepositsByStatus = `-- name: GetDirectDepositsByStatus :many
|
||||
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE status = $1 ORDER BY created_at DESC
|
||||
SELECT
|
||||
id,
|
||||
customer_id,
|
||||
wallet_id,
|
||||
bank_name,
|
||||
account_number,
|
||||
account_holder,
|
||||
amount,
|
||||
reference_number,
|
||||
transfer_screenshot,
|
||||
status,
|
||||
created_at,
|
||||
approved_by,
|
||||
approved_at,
|
||||
rejection_reason
|
||||
FROM direct_deposits
|
||||
WHERE status = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
func (q *Queries) GetDirectDepositsByStatus(ctx context.Context, status string) ([]DirectDeposit, error) {
|
||||
rows, err := q.db.Query(ctx, GetDirectDepositsByStatus, status)
|
||||
type GetDirectDepositsByStatusParams struct {
|
||||
Status pgtype.Text `json:"status"`
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetDirectDepositsByStatus(ctx context.Context, arg GetDirectDepositsByStatusParams) ([]DirectDeposit, error) {
|
||||
rows, err := q.db.Query(ctx, GetDirectDepositsByStatus, arg.Status, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -135,14 +175,17 @@ func (q *Queries) GetDirectDepositsByStatus(ctx context.Context, status string)
|
|||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.WalletID,
|
||||
&i.BankName,
|
||||
&i.AccountNumber,
|
||||
&i.AccountHolder,
|
||||
&i.Amount,
|
||||
&i.BankReference,
|
||||
&i.SenderAccount,
|
||||
&i.ReferenceNumber,
|
||||
&i.TransferScreenshot,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.VerifiedBy,
|
||||
&i.VerificationNotes,
|
||||
&i.VerifiedAt,
|
||||
&i.ApprovedBy,
|
||||
&i.ApprovedAt,
|
||||
&i.RejectionReason,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -154,46 +197,25 @@ func (q *Queries) GetDirectDepositsByStatus(ctx context.Context, status string)
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateDirectDeposit = `-- name: UpdateDirectDeposit :one
|
||||
const RejectDirectDeposit = `-- name: RejectDirectDeposit :exec
|
||||
UPDATE direct_deposits
|
||||
SET
|
||||
status = $2,
|
||||
verified_by = $3,
|
||||
verification_notes = $4,
|
||||
verified_at = $5
|
||||
WHERE id = $1
|
||||
RETURNING id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at
|
||||
status = 'REJECTED',
|
||||
approved_by = $2, -- still track the admin who took final action
|
||||
approved_at = NOW(),
|
||||
rejection_reason = $3
|
||||
WHERE
|
||||
id = $1
|
||||
AND status = 'PENDING'
|
||||
`
|
||||
|
||||
type UpdateDirectDepositParams struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
VerifiedBy pgtype.Int8 `json:"verified_by"`
|
||||
VerificationNotes pgtype.Text `json:"verification_notes"`
|
||||
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
||||
type RejectDirectDepositParams struct {
|
||||
ID int64 `json:"id"`
|
||||
ApprovedBy pgtype.Int8 `json:"approved_by"`
|
||||
RejectionReason pgtype.Text `json:"rejection_reason"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateDirectDeposit(ctx context.Context, arg UpdateDirectDepositParams) (DirectDeposit, error) {
|
||||
row := q.db.QueryRow(ctx, UpdateDirectDeposit,
|
||||
arg.ID,
|
||||
arg.Status,
|
||||
arg.VerifiedBy,
|
||||
arg.VerificationNotes,
|
||||
arg.VerifiedAt,
|
||||
)
|
||||
var i DirectDeposit
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.CustomerID,
|
||||
&i.WalletID,
|
||||
&i.Amount,
|
||||
&i.BankReference,
|
||||
&i.SenderAccount,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.VerifiedBy,
|
||||
&i.VerificationNotes,
|
||||
&i.VerifiedAt,
|
||||
)
|
||||
return i, err
|
||||
func (q *Queries) RejectDirectDeposit(ctx context.Context, arg RejectDirectDepositParams) error {
|
||||
_, err := q.db.Exec(ctx, RejectDirectDeposit, arg.ID, arg.ApprovedBy, arg.RejectionReason)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,17 +312,20 @@ type CustomerWalletDetail struct {
|
|||
}
|
||||
|
||||
type DirectDeposit struct {
|
||||
ID int64 `json:"id"`
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
Amount pgtype.Numeric `json:"amount"`
|
||||
BankReference string `json:"bank_reference"`
|
||||
SenderAccount string `json:"sender_account"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
VerifiedBy pgtype.Int8 `json:"verified_by"`
|
||||
VerificationNotes pgtype.Text `json:"verification_notes"`
|
||||
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
||||
ID int64 `json:"id"`
|
||||
CustomerID pgtype.Int8 `json:"customer_id"`
|
||||
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||
BankName pgtype.Text `json:"bank_name"`
|
||||
AccountNumber pgtype.Text `json:"account_number"`
|
||||
AccountHolder pgtype.Text `json:"account_holder"`
|
||||
Amount pgtype.Numeric `json:"amount"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
TransferScreenshot pgtype.Text `json:"transfer_screenshot"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
ApprovedBy pgtype.Int8 `json:"approved_by"`
|
||||
ApprovedAt pgtype.Timestamptz `json:"approved_at"`
|
||||
RejectionReason pgtype.Text `json:"rejection_reason"`
|
||||
}
|
||||
|
||||
type EnetpulseFixture struct {
|
||||
|
|
@ -1223,7 +1226,6 @@ type VirtualGameSession struct {
|
|||
|
||||
type VirtualGameTransaction struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
|
|
|
|||
|
|
@ -445,7 +445,7 @@ func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtua
|
|||
|
||||
const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -456,9 +456,9 @@ INSERT INTO virtual_game_transactions (
|
|||
external_transaction_id,
|
||||
status
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id,
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
company_id,
|
||||
provider,
|
||||
|
|
@ -473,7 +473,6 @@ RETURNING id,
|
|||
`
|
||||
|
||||
type CreateVirtualGameTransactionParams struct {
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
|
|
@ -487,7 +486,6 @@ type CreateVirtualGameTransactionParams struct {
|
|||
|
||||
type CreateVirtualGameTransactionRow struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
Provider pgtype.Text `json:"provider"`
|
||||
|
|
@ -503,7 +501,6 @@ type CreateVirtualGameTransactionRow struct {
|
|||
|
||||
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (CreateVirtualGameTransactionRow, error) {
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameTransaction,
|
||||
arg.SessionID,
|
||||
arg.UserID,
|
||||
arg.CompanyID,
|
||||
arg.Provider,
|
||||
|
|
@ -517,7 +514,6 @@ func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVi
|
|||
var i CreateVirtualGameTransactionRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.CompanyID,
|
||||
&i.Provider,
|
||||
|
|
@ -785,7 +781,7 @@ SELECT c.name AS company_name,
|
|||
COUNT(vgt.id) AS number_of_bets,
|
||||
COALESCE(SUM(vgt.amount), 0) AS total_transaction_sum
|
||||
FROM virtual_game_transactions vgt
|
||||
JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
-- JOIN virtual_game_sessions vgs ON vgt.session_id = vgs.id
|
||||
JOIN virtual_games vg ON vgs.game_id = vg.id
|
||||
JOIN companies c ON vgt.company_id = c.id
|
||||
WHERE vgt.transaction_type = 'BET'
|
||||
|
|
@ -833,7 +829,7 @@ func (q *Queries) GetVirtualGameSummaryInRange(ctx context.Context, arg GetVirtu
|
|||
|
||||
const GetVirtualGameTransactionByExternalID = `-- name: GetVirtualGameTransactionByExternalID :one
|
||||
SELECT id,
|
||||
session_id,
|
||||
-- session_id,
|
||||
user_id,
|
||||
wallet_id,
|
||||
transaction_type,
|
||||
|
|
@ -849,7 +845,6 @@ WHERE external_transaction_id = $1
|
|||
|
||||
type GetVirtualGameTransactionByExternalIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"`
|
||||
|
|
@ -866,7 +861,6 @@ func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, ext
|
|||
var i GetVirtualGameTransactionByExternalIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.WalletID,
|
||||
&i.TransactionType,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ type Response struct {
|
|||
Data interface{} `json:"data,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
StatusCode int `json:"status_code"`
|
||||
MetaData interface{} `json:"metadata"`
|
||||
}
|
||||
|
||||
type CallbackErrorResponse struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func CalculateWinnings(amount Currency, totalOdds float32) Currency {
|
||||
|
|
|
|||
|
|
@ -1,113 +1,39 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
"math/big"
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
import "time"
|
||||
|
||||
type DirectDepositStatus string
|
||||
|
||||
const (
|
||||
DepositStatusPending DirectDepositStatus = "pending"
|
||||
DepositStatusCompleted DirectDepositStatus = "completed"
|
||||
DepositStatusRejected DirectDepositStatus = "rejected"
|
||||
DepositStatusPending DirectDepositStatus = "PENDING"
|
||||
DepositStatusCompleted DirectDepositStatus = "COMPLETED"
|
||||
DepositStatusRejected DirectDepositStatus = "REJECTED"
|
||||
)
|
||||
|
||||
type DirectDeposit struct {
|
||||
ID int64 `json:"id"`
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
Wallet Wallet `json:"wallet"`
|
||||
Amount Currency `json:"amount"`
|
||||
BankReference string `json:"bank_reference"`
|
||||
SenderAccount string `json:"sender_account"`
|
||||
Status DirectDepositStatus `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
VerifiedBy *int64 `json:"verified_by"`
|
||||
VerificationNotes string `json:"verification_notes"`
|
||||
VerifiedAt *time.Time `json:"verified_at"`
|
||||
ID int
|
||||
CustomerID int
|
||||
WalletID int
|
||||
BankName string
|
||||
AccountNumber string
|
||||
AccountHolder string
|
||||
Amount float64
|
||||
ReferenceNumber string
|
||||
TransferScreenshot string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
ApprovedBy *int
|
||||
ApprovedAt *time.Time
|
||||
RejectionReason *string
|
||||
}
|
||||
|
||||
type CreateDirectDeposit struct {
|
||||
CustomerID int64
|
||||
WalletID int64
|
||||
Amount Currency
|
||||
BankReference string
|
||||
SenderAccount string
|
||||
Status DirectDepositStatus
|
||||
}
|
||||
|
||||
type UpdateDirectDeposit struct {
|
||||
ID int64
|
||||
Status DirectDepositStatus
|
||||
VerifiedBy int64
|
||||
VerificationNotes string
|
||||
VerifiedAt time.Time
|
||||
}
|
||||
|
||||
type DirectDepositRequest struct {
|
||||
CustomerID int64 `json:"customer_id" binding:"required"`
|
||||
Amount Currency `json:"amount" binding:"required,gt=0"`
|
||||
BankReference string `json:"bank_reference" binding:"required"`
|
||||
SenderAccount string `json:"sender_account" binding:"required"`
|
||||
}
|
||||
|
||||
type VerifyDirectDepositRequest struct {
|
||||
DepositID int64 `json:"deposit_id" binding:"required"`
|
||||
IsVerified bool `json:"is_verified" binding:"required"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
|
||||
func ConvertDBDirectDeposit(deposit dbgen.DirectDeposit) DirectDeposit {
|
||||
return DirectDeposit{
|
||||
ID: deposit.ID,
|
||||
CustomerID: deposit.CustomerID,
|
||||
WalletID: deposit.WalletID,
|
||||
Amount: Currency(deposit.Amount.Int.Int64()),
|
||||
BankReference: deposit.BankReference,
|
||||
SenderAccount: deposit.SenderAccount,
|
||||
Status: DirectDepositStatus(deposit.Status),
|
||||
CreatedAt: deposit.CreatedAt.Time,
|
||||
VerifiedBy: convertPgInt64ToPtr(deposit.VerifiedBy),
|
||||
VerificationNotes: deposit.VerificationNotes.String,
|
||||
VerifiedAt: convertPgTimeToPtr(deposit.VerifiedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateDirectDeposit(deposit CreateDirectDeposit) dbgen.CreateDirectDepositParams {
|
||||
return dbgen.CreateDirectDepositParams{
|
||||
CustomerID: deposit.CustomerID,
|
||||
WalletID: deposit.WalletID,
|
||||
Amount: pgtype.Numeric{Int: big.NewInt(int64(deposit.Amount)), Valid: true},
|
||||
BankReference: deposit.BankReference,
|
||||
SenderAccount: deposit.SenderAccount,
|
||||
Status: string(deposit.Status),
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertUpdateDirectDeposit(deposit UpdateDirectDeposit) dbgen.UpdateDirectDepositParams {
|
||||
return dbgen.UpdateDirectDepositParams{
|
||||
ID: deposit.ID,
|
||||
Status: string(deposit.Status),
|
||||
VerifiedBy: pgtype.Int8{Int64: deposit.VerifiedBy, Valid: true},
|
||||
VerificationNotes: pgtype.Text{String: deposit.VerificationNotes, Valid: deposit.VerificationNotes != ""},
|
||||
VerifiedAt: pgtype.Timestamp{Time: deposit.VerifiedAt, Valid: true},
|
||||
}
|
||||
}
|
||||
|
||||
func convertPgInt64ToPtr(i pgtype.Int8) *int64 {
|
||||
if i.Valid {
|
||||
return &i.Int64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertPgTimeToPtr(t pgtype.Timestamp) *time.Time {
|
||||
if t.Valid {
|
||||
return &t.Time
|
||||
}
|
||||
return nil
|
||||
CustomerID int
|
||||
WalletID int
|
||||
BankName string
|
||||
AccountNumber string
|
||||
AccountHolder string
|
||||
Amount float64
|
||||
ReferenceNumber string
|
||||
TransferScreenshot string
|
||||
}
|
||||
|
|
@ -47,13 +47,13 @@ type ApprovalStore interface {
|
|||
GetPendingApprovals(ctx context.Context) ([]domain.TransferDetail, error)
|
||||
}
|
||||
|
||||
type DirectDepositStore interface {
|
||||
CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error)
|
||||
UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error)
|
||||
GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error)
|
||||
}
|
||||
// type DirectDepositStore interface {
|
||||
// CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error)
|
||||
// GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error)
|
||||
// UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error)
|
||||
// GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error)
|
||||
// GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error)
|
||||
// }
|
||||
|
||||
type WalletStatStore interface {
|
||||
UpdateWalletStats(ctx context.Context) error
|
||||
|
|
|
|||
|
|
@ -2,61 +2,195 @@ package repository
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/ports"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
// Interface for creating new wallet store
|
||||
func NewDirectDepositStore(s *Store) ports.DirectDepositStore { return s }
|
||||
|
||||
|
||||
func (s *Store) CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error) {
|
||||
newDeposit, err := s.queries.CreateDirectDeposit(ctx, domain.ConvertCreateDirectDeposit(deposit))
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
return domain.ConvertDBDirectDeposit(newDeposit), nil
|
||||
type DirectDepositRepository interface {
|
||||
CreateDirectDeposit(ctx context.Context, deposit *domain.DirectDeposit) error
|
||||
GetDirectDepositsByStatus(ctx context.Context, status string, page int, pageSize int) ([]domain.DirectDeposit, int64, error)
|
||||
ApproveDirectDeposit(ctx context.Context, depositID int, adminID int) error
|
||||
RejectDirectDeposit(ctx context.Context, depositID int, adminID int, reason string) error
|
||||
DeleteDirectDeposit(ctx context.Context, id int) error
|
||||
GetDirectDepositByID(ctx context.Context, id int) (*domain.DirectDeposit, error)
|
||||
}
|
||||
|
||||
func (s *Store) GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error) {
|
||||
deposit, err := s.queries.GetDirectDeposit(ctx, id)
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
return domain.ConvertDBDirectDeposit(deposit), nil
|
||||
type DirectDepositRepo struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
func (s *Store) UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error) {
|
||||
updatedDeposit, err := s.queries.UpdateDirectDeposit(ctx, domain.ConvertUpdateDirectDeposit(deposit))
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
return domain.ConvertDBDirectDeposit(updatedDeposit), nil
|
||||
func NewDirectDepositRepository(store *Store) DirectDepositRepository {
|
||||
return &DirectDepositRepo{store: store}
|
||||
}
|
||||
|
||||
func (s *Store) GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error) {
|
||||
deposits, err := s.queries.GetDirectDepositsByStatus(ctx, string(status))
|
||||
func (r *DirectDepositRepo) CreateDirectDeposit(ctx context.Context, deposit *domain.DirectDeposit) error {
|
||||
params := dbgen.CreateDirectDepositParams{
|
||||
CustomerID: pgtype.Int8{Int64: int64(deposit.CustomerID)},
|
||||
WalletID: pgtype.Int8{Int64: int64(deposit.WalletID)},
|
||||
BankName: pgtype.Text{String: deposit.BankName},
|
||||
AccountNumber: pgtype.Text{String: deposit.AccountNumber},
|
||||
AccountHolder: pgtype.Text{String: deposit.AccountHolder},
|
||||
Amount: pgtype.Numeric{Exp: int32(deposit.Amount)},
|
||||
ReferenceNumber: pgtype.Text{String: deposit.ReferenceNumber},
|
||||
TransferScreenshot: pgtype.Text{String: deposit.TransferScreenshot},
|
||||
}
|
||||
|
||||
dbDeposit, err := r.store.queries.CreateDirectDeposit(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Map back to domain struct
|
||||
deposit.ID = int(dbDeposit.ID)
|
||||
deposit.Status = dbDeposit.Status.String
|
||||
deposit.CreatedAt = dbDeposit.CreatedAt.Time
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DirectDepositRepo) GetDirectDepositsByStatus(
|
||||
ctx context.Context,
|
||||
status string,
|
||||
page int,
|
||||
pageSize int,
|
||||
) ([]domain.DirectDeposit, int64, error) {
|
||||
|
||||
// Default pagination rules
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 50
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
params := dbgen.GetDirectDepositsByStatusParams{
|
||||
Status: pgtype.Text{String: status},
|
||||
Limit: int32(pageSize),
|
||||
Offset: int32(offset),
|
||||
}
|
||||
|
||||
dbItems, err := r.store.queries.GetDirectDepositsByStatus(ctx, params)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
total, err := r.store.queries.CountDirectDepositsByStatus(ctx, pgtype.Text{String: status})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
deposits := make([]domain.DirectDeposit, len(dbItems))
|
||||
for i, d := range dbItems {
|
||||
deposits[i] = *mapDBDirectDepositToDomain(&d)
|
||||
}
|
||||
|
||||
return deposits, total, nil
|
||||
}
|
||||
|
||||
func (r *DirectDepositRepo) ApproveDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
adminID int,
|
||||
) error {
|
||||
|
||||
params := dbgen.ApproveDirectDepositParams{
|
||||
ID: int64(depositID),
|
||||
ApprovedBy: pgtype.Int8{Int64: int64(adminID)},
|
||||
}
|
||||
|
||||
err := r.store.queries.ApproveDirectDeposit(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DirectDepositRepo) GetDirectDepositByID(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
) (*domain.DirectDeposit, error) {
|
||||
|
||||
dbDeposit, err := r.store.queries.GetDirectDepositByID(ctx, int64(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]domain.DirectDeposit, 0, len(deposits))
|
||||
for _, deposit := range deposits {
|
||||
result = append(result, domain.ConvertDBDirectDeposit(deposit))
|
||||
}
|
||||
return result, nil
|
||||
deposit := mapDBDirectDepositToDomain(&dbDeposit)
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error) {
|
||||
deposits, err := s.queries.GetCustomerDirectDeposits(ctx, customerID)
|
||||
func (r *DirectDepositRepo) DeleteDirectDeposit(
|
||||
ctx context.Context,
|
||||
id int,
|
||||
) error {
|
||||
|
||||
err := r.store.queries.DeleteDirectDeposit(ctx, int64(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
result := make([]domain.DirectDeposit, 0, len(deposits))
|
||||
for _, deposit := range deposits {
|
||||
result = append(result, domain.ConvertDBDirectDeposit(deposit))
|
||||
}
|
||||
return result, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DirectDepositRepo) RejectDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
adminID int,
|
||||
reason string,
|
||||
) error {
|
||||
|
||||
params := dbgen.RejectDirectDepositParams{
|
||||
ID: int64(depositID),
|
||||
ApprovedBy: pgtype.Int8{Int64: int64(adminID)},
|
||||
RejectionReason: pgtype.Text{String: reason},
|
||||
}
|
||||
|
||||
err := r.store.queries.RejectDirectDeposit(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapDBDirectDepositToDomain(d *dbgen.DirectDeposit) *domain.DirectDeposit {
|
||||
var approvedBy *int
|
||||
if d.ApprovedBy.Valid {
|
||||
v := int(d.ApprovedBy.Int64)
|
||||
approvedBy = &v
|
||||
}
|
||||
|
||||
var approvedAt *time.Time
|
||||
if d.ApprovedAt.Valid {
|
||||
t := d.ApprovedAt.Time
|
||||
approvedAt = &t
|
||||
}
|
||||
|
||||
var rejectionReason *string
|
||||
if d.RejectionReason.Valid {
|
||||
r := d.RejectionReason.String
|
||||
rejectionReason = &r
|
||||
}
|
||||
|
||||
return &domain.DirectDeposit{
|
||||
ID: int(d.ID),
|
||||
CustomerID: int(d.CustomerID.Int64),
|
||||
WalletID: int(d.WalletID.Int64),
|
||||
BankName: d.BankName.String,
|
||||
AccountNumber: d.AccountNumber.String,
|
||||
AccountHolder: d.AccountHolder.String,
|
||||
Amount: float64(d.Amount.Exp),
|
||||
ReferenceNumber: d.ReferenceNumber.String,
|
||||
TransferScreenshot: d.TransferScreenshot.String,
|
||||
Status: d.Status.String,
|
||||
CreatedAt: d.CreatedAt.Time,
|
||||
ApprovedBy: approvedBy,
|
||||
ApprovedAt: approvedAt,
|
||||
RejectionReason: rejectionReason,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, toke
|
|||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
||||
params := dbgen.CreateVirtualGameTransactionParams{
|
||||
SessionID: tx.SessionID,
|
||||
// SessionID: tx.SessionID,
|
||||
UserID: tx.UserID,
|
||||
WalletID: tx.WalletID,
|
||||
TransactionType: tx.TransactionType,
|
||||
|
|
@ -239,7 +239,7 @@ func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *
|
|||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error {
|
||||
params := dbgen.CreateVirtualGameHistoryParams{
|
||||
SessionID: pgtype.Text{String: his.SessionID, Valid: true},
|
||||
// SessionID: pgtype.Text{String: his.SessionID, Valid: true},
|
||||
UserID: his.UserID,
|
||||
// WalletID: pgtype.Int8{Int64: *his.WalletID, Valid: true},
|
||||
TransactionType: his.TransactionType,
|
||||
|
|
@ -262,7 +262,7 @@ func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Cont
|
|||
}
|
||||
return &domain.VirtualGameTransaction{
|
||||
ID: dbTx.ID,
|
||||
SessionID: dbTx.SessionID,
|
||||
// SessionID: dbTx.SessionID,
|
||||
UserID: dbTx.UserID,
|
||||
WalletID: dbTx.WalletID,
|
||||
TransactionType: dbTx.TransactionType,
|
||||
|
|
|
|||
305
internal/services/direct_deposit/service.go
Normal file
305
internal/services/direct_deposit/service.go
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
package directdeposit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/ports"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
walletSvc wallet.Service
|
||||
transferStore ports.TransferStore
|
||||
directDepositStore repository.DirectDepositRepository
|
||||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
// mongoLogger *zap.Logger
|
||||
// logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewService(
|
||||
walletSvc wallet.Service,
|
||||
transferStore ports.TransferStore,
|
||||
directDepositStore repository.DirectDepositRepository,
|
||||
notificationSvc *notificationservice.Service,
|
||||
userSvc *user.Service,
|
||||
// mongoLogger *zap.Logger,
|
||||
// logger *slog.Logger,
|
||||
) *Service {
|
||||
return &Service{
|
||||
walletSvc: walletSvc,
|
||||
transferStore: transferStore,
|
||||
directDepositStore: directDepositStore,
|
||||
notificationSvc: notificationSvc,
|
||||
userSvc: userSvc,
|
||||
// mongoLogger: mongoLogger,
|
||||
// logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CreateDirectDeposit(
|
||||
ctx context.Context,
|
||||
req domain.CreateDirectDeposit,
|
||||
) (domain.DirectDeposit, error) {
|
||||
|
||||
deposit := domain.DirectDeposit{
|
||||
CustomerID: req.CustomerID,
|
||||
WalletID: req.WalletID,
|
||||
BankName: req.BankName,
|
||||
AccountNumber: req.AccountNumber,
|
||||
AccountHolder: req.AccountHolder,
|
||||
Amount: req.Amount,
|
||||
ReferenceNumber: req.ReferenceNumber,
|
||||
TransferScreenshot: req.TransferScreenshot,
|
||||
Status: "PENDING",
|
||||
}
|
||||
|
||||
// Step 1: create the deposit in DB
|
||||
if err := s.directDepositStore.CreateDirectDeposit(ctx, &deposit); err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
|
||||
// Step 2: prepare common notification metadata
|
||||
raw, _ := json.Marshal(map[string]any{
|
||||
"deposit_id": deposit.ID,
|
||||
"customer_id": deposit.CustomerID,
|
||||
"amount": deposit.Amount,
|
||||
"status": deposit.Status,
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
|
||||
// -------------------------------
|
||||
// Step 3a: notify the customer
|
||||
customerNotification := &domain.Notification{
|
||||
RecipientID: int64(deposit.CustomerID),
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
Type: domain.NOTIFICATION_TYPE_WALLET,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Level: domain.NotificationLevelInfo,
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "Direct Deposit Created",
|
||||
Message: fmt.Sprintf("Your direct deposit of %.2f is now pending approval.", deposit.Amount),
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, customerNotification); err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Step 3b: notify admins
|
||||
adminNotification := &domain.Notification{
|
||||
RecipientID: 0, // 0 or special ID for admin-wide notifications
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Reciever: domain.NotificationRecieverSideAdmin,
|
||||
Type: domain.NOTIFICATION_TYPE_APPROVAL_REQUIRED,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Level: domain.NotificationLevelInfo,
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "New Direct Deposit Pending Approval",
|
||||
Message: fmt.Sprintf("Customer #%d has created a direct deposit of %.2f that requires your approval.", deposit.CustomerID, deposit.Amount),
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, adminNotification); err != nil {
|
||||
return domain.DirectDeposit{}, err
|
||||
}
|
||||
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetDirectDepositsByStatus(
|
||||
ctx context.Context,
|
||||
status string,
|
||||
page int,
|
||||
pageSize int,
|
||||
) ([]domain.DirectDeposit, int64, error) {
|
||||
|
||||
deposits, total, err := s.directDepositStore.GetDirectDepositsByStatus(
|
||||
ctx,
|
||||
status,
|
||||
page,
|
||||
pageSize,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return deposits, total, nil
|
||||
}
|
||||
|
||||
func (s *Service) ApproveDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
adminID int,
|
||||
) error {
|
||||
|
||||
// Step 1: fetch deposit (ensure it exists)
|
||||
deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 2: approve in DB
|
||||
if err := s.directDepositStore.ApproveDirectDeposit(ctx, depositID, adminID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 3: credit wallet balance
|
||||
wallet, err := s.walletSvc.GetCustomerWallet(ctx, int64(deposit.CustomerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := s.walletSvc.AddToWallet(ctx,
|
||||
wallet.RegularID,
|
||||
domain.Currency(deposit.Amount),
|
||||
domain.ValidInt64{},
|
||||
domain.TRANSFER_DIRECT,
|
||||
domain.PaymentDetails{},
|
||||
"",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 4: record transfer
|
||||
transfer := domain.CreateTransfer{
|
||||
Amount: domain.Currency(deposit.Amount),
|
||||
Verified: true,
|
||||
Message: "Direct deposit approved and credited",
|
||||
Type: domain.DEPOSIT,
|
||||
PaymentMethod: domain.TRANSFER_DIRECT,
|
||||
ReceiverWalletID: domain.ValidInt64{Valid: true, Value: wallet.RegularID},
|
||||
ReferenceNumber: deposit.ReferenceNumber,
|
||||
Status: string(domain.DepositStatusCompleted),
|
||||
}
|
||||
|
||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 5: send customer notification
|
||||
raw, _ := json.Marshal(map[string]any{
|
||||
"deposit_id": deposit.ID,
|
||||
"amount": deposit.Amount,
|
||||
"status": "APPROVED",
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
|
||||
notification := &domain.Notification{
|
||||
RecipientID: int64(deposit.CustomerID),
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
Type: domain.NOTIFICATION_TYPE_TRANSFER_SUCCESS,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Level: domain.NotificationLevelInfo,
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "Direct Deposit Approved",
|
||||
Message: fmt.Sprintf("Your direct deposit of %.2f has been approved and credited to your wallet.", deposit.Amount),
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) RejectDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
adminID int,
|
||||
reason string,
|
||||
) error {
|
||||
|
||||
// Step 1: fetch deposit to ensure it exists
|
||||
deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 2: reject operation
|
||||
if err := s.directDepositStore.RejectDirectDeposit(ctx, depositID, adminID, reason); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 3: send customer notification
|
||||
raw, _ := json.Marshal(map[string]any{
|
||||
"deposit_id": deposit.ID,
|
||||
"amount": deposit.Amount,
|
||||
"status": "REJECTED",
|
||||
"reason": reason,
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
|
||||
notification := &domain.Notification{
|
||||
RecipientID: int64(deposit.CustomerID),
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
Type: domain.NOTIFICATION_TYPE_TRANSFER_REJECTED,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Level: domain.NotificationLevelWarning,
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "Direct Deposit Rejected",
|
||||
Message: fmt.Sprintf("Your direct deposit of %.2f was rejected. Reason: %s", deposit.Amount, reason),
|
||||
},
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetDirectDepositByID(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
) (*domain.DirectDeposit, error) {
|
||||
|
||||
deposit, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int,
|
||||
) error {
|
||||
|
||||
// Optional: fetch first to ensure deposit exists
|
||||
_, err := s.directDepositStore.GetDirectDepositByID(ctx, depositID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform deletion
|
||||
if err := s.directDepositStore.DeleteDirectDeposit(ctx, depositID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
|||
|
||||
// 3. Record virtual game history (optional but recommended)
|
||||
history := &domain.VirtualGameHistory{
|
||||
SessionID: sessionID,
|
||||
// SessionID: sessionID,
|
||||
UserID: userID,
|
||||
CompanyID: user.CompanyID.Value,
|
||||
Provider: string(domain.PROVIDER_POPOK),
|
||||
|
|
|
|||
|
|
@ -1,216 +0,0 @@
|
|||
package wallet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
// InitiateDirectDeposit creates a pending deposit request
|
||||
func (s *Service) InitiateDirectDeposit(
|
||||
ctx context.Context,
|
||||
customerID int64,
|
||||
amount domain.Currency,
|
||||
bankRef string, // Mobile banking transaction reference
|
||||
senderAccount string, // Customer's account number
|
||||
) (domain.DirectDeposit, error) {
|
||||
// Get customer's betting wallet
|
||||
customerWallet, err := s.GetCustomerWallet(ctx, customerID)
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, fmt.Errorf("failed to get customer wallet: %w", err)
|
||||
}
|
||||
|
||||
// Create pending deposit record
|
||||
deposit, err := s.directDepositStore.CreateDirectDeposit(ctx, domain.CreateDirectDeposit{
|
||||
CustomerID: customerID,
|
||||
WalletID: customerWallet.ID,
|
||||
Amount: amount,
|
||||
BankReference: bankRef,
|
||||
SenderAccount: senderAccount,
|
||||
Status: domain.DepositStatusPending,
|
||||
})
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, fmt.Errorf("failed to create deposit record: %w", err)
|
||||
}
|
||||
|
||||
// Notify cashiers for manual verification
|
||||
go s.notifyCashiersForVerification(ctx, deposit.ID, customerID, amount)
|
||||
|
||||
return deposit, nil
|
||||
}
|
||||
|
||||
// VerifyDirectDeposit verifies and processes the deposit
|
||||
func (s *Service) VerifyDirectDeposit(
|
||||
ctx context.Context,
|
||||
depositID int64,
|
||||
cashierID int64,
|
||||
isVerified bool,
|
||||
verificationNotes string,
|
||||
) (domain.DirectDeposit, error) {
|
||||
// Get the deposit record
|
||||
deposit, err := s.directDepositStore.GetDirectDeposit(ctx, depositID)
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, fmt.Errorf("failed to get deposit: %w", err)
|
||||
}
|
||||
|
||||
// Validate deposit status
|
||||
if deposit.Status != domain.DepositStatusPending {
|
||||
return domain.DirectDeposit{}, errors.New("only pending deposits can be verified")
|
||||
}
|
||||
|
||||
// Update based on verification result
|
||||
if isVerified {
|
||||
// Credit the wallet
|
||||
err = s.walletStore.UpdateBalance(ctx, deposit.WalletID,
|
||||
deposit.Wallet.Balance+deposit.Amount)
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||
}
|
||||
|
||||
// Publish wallet update event
|
||||
// go s.publishWalletUpdate(ctx, deposit.WalletID, deposit.Wallet.UserID,
|
||||
// deposit.Wallet.Balance+deposit.Amount, "direct_deposit_verified")
|
||||
|
||||
// Update deposit status
|
||||
deposit.Status = domain.DepositStatusCompleted
|
||||
} else {
|
||||
deposit.Status = domain.DepositStatusRejected
|
||||
}
|
||||
|
||||
// Update deposit record
|
||||
updatedDeposit, err := s.directDepositStore.UpdateDirectDeposit(ctx, domain.UpdateDirectDeposit{
|
||||
ID: depositID,
|
||||
Status: deposit.Status,
|
||||
VerifiedBy: cashierID,
|
||||
VerificationNotes: verificationNotes,
|
||||
VerifiedAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.DirectDeposit{}, fmt.Errorf("failed to update deposit: %w", err)
|
||||
}
|
||||
|
||||
// Notify customer of verification result
|
||||
go s.notifyCustomerVerificationResult(ctx, updatedDeposit)
|
||||
|
||||
return updatedDeposit, nil
|
||||
}
|
||||
|
||||
// GetPendingDirectDeposits returns deposits needing verification
|
||||
func (s *Service) GetPendingDirectDeposits(ctx context.Context) ([]domain.DirectDeposit, error) {
|
||||
return s.directDepositStore.GetDirectDepositsByStatus(ctx, domain.DepositStatusPending)
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func (s *Service) notifyCashiersForVerification(ctx context.Context, depositID, customerID int64, amount domain.Currency) {
|
||||
cashiers, _, err := s.userSvc.GetAllCashiers(ctx, domain.UserFilter{Role: string(domain.RoleCashier)})
|
||||
if err != nil {
|
||||
s.logger.Error("failed to get cashiers for notification",
|
||||
"error", err,
|
||||
"deposit_id", depositID)
|
||||
return
|
||||
}
|
||||
|
||||
customer, err := s.userSvc.GetUserByID(ctx, customerID)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to get customer details",
|
||||
"error", err,
|
||||
"customer_id", customerID)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cashier := range cashiers {
|
||||
metadataMap := map[string]interface{}{
|
||||
"deposit_id": depositID,
|
||||
"customer_id": customerID,
|
||||
"amount": amount.Float32(),
|
||||
}
|
||||
metadataJSON, err := json.Marshal(metadataMap)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal notification metadata",
|
||||
"error", err,
|
||||
"deposit_id", depositID)
|
||||
continue
|
||||
}
|
||||
notification := &domain.Notification{
|
||||
RecipientID: cashier.ID,
|
||||
Type: domain.NotificationTypeDepositVerification,
|
||||
Level: domain.NotificationLevelInfo,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: "Direct Deposit Requires Verification",
|
||||
Message: fmt.Sprintf("Customer %s deposited %.2f - please verify", customer.FirstName+" "+customer.LastName, amount.Float32()),
|
||||
},
|
||||
Metadata: metadataJSON,
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
|
||||
s.logger.Error("failed to send verification notification",
|
||||
"cashier_id", cashier.ID,
|
||||
"error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) notifyCustomerVerificationResult(ctx context.Context, deposit domain.DirectDeposit) {
|
||||
var (
|
||||
headline string
|
||||
message string
|
||||
level domain.NotificationLevel
|
||||
)
|
||||
|
||||
if deposit.Status == domain.DepositStatusCompleted {
|
||||
headline = "Deposit Verified"
|
||||
message = fmt.Sprintf("Your deposit of %.2f has been credited to your wallet", deposit.Amount.Float32())
|
||||
level = domain.NotificationLevelSuccess
|
||||
} else {
|
||||
headline = "Deposit Rejected"
|
||||
message = fmt.Sprintf("Your deposit of %.2f was not verified. Reason: %s",
|
||||
deposit.Amount.Float32(), deposit.VerificationNotes)
|
||||
level = domain.NotificationLevelError
|
||||
}
|
||||
|
||||
metadataMap := map[string]interface{}{
|
||||
"deposit_id": deposit.ID,
|
||||
"amount": deposit.Amount.Float32(),
|
||||
"status": string(deposit.Status),
|
||||
}
|
||||
metadataJSON, err := json.Marshal(metadataMap)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to marshal notification metadata",
|
||||
"error", err,
|
||||
"deposit_id", deposit.ID)
|
||||
return
|
||||
}
|
||||
|
||||
notification := &domain.Notification{
|
||||
RecipientID: deposit.CustomerID,
|
||||
Type: domain.NotificationTypeDepositResult,
|
||||
Level: level,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Metadata: metadataJSON,
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, notification); err != nil {
|
||||
s.logger.Error("failed to send deposit result notification",
|
||||
"customer_id", deposit.CustomerID,
|
||||
"error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// func (s *Service) publishWalletUpdate(ctx context.Context, walletID, userID int64, newBalance domain.Currency, trigger string) {
|
||||
// s.kafkaProducer.Publish(ctx, fmt.Sprint(walletID), event.WalletEvent{
|
||||
// EventType: event.WalletBalanceUpdated,
|
||||
// WalletID: walletID,
|
||||
// UserID: userID,
|
||||
// Balance: newBalance,
|
||||
// Trigger: trigger,
|
||||
// })
|
||||
// }
|
||||
|
|
@ -14,7 +14,7 @@ type Service struct {
|
|||
// approvalStore ApprovalStore
|
||||
walletStore ports.WalletStore
|
||||
transferStore ports.TransferStore
|
||||
directDepositStore ports.DirectDepositStore
|
||||
// directDepositStore ports.DirectDepositStore
|
||||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
mongoLogger *zap.Logger
|
||||
|
|
@ -24,7 +24,7 @@ type Service struct {
|
|||
func NewService(
|
||||
walletStore ports.WalletStore,
|
||||
transferStore ports.TransferStore,
|
||||
directDepositStore ports.DirectDepositStore,
|
||||
// directDepositStore ports.DirectDepositStore,
|
||||
notificationSvc *notificationservice.Service,
|
||||
userSvc *user.Service,
|
||||
mongoLogger *zap.Logger,
|
||||
|
|
@ -33,7 +33,7 @@ func NewService(
|
|||
return &Service{
|
||||
walletStore: walletStore,
|
||||
transferStore: transferStore,
|
||||
directDepositStore: directDepositStore,
|
||||
// directDepositStore: directDepositStore,
|
||||
// approvalStore: approvalStore,
|
||||
notificationSvc: notificationSvc,
|
||||
userSvc: userSvc,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
directdeposit "github.com/SamuelTariku/FortuneBet-Backend/internal/services/direct_deposit"
|
||||
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
|
||||
|
|
@ -48,6 +49,7 @@ import (
|
|||
)
|
||||
|
||||
type App struct {
|
||||
directDepositSvc *directdeposit.Service
|
||||
enetPulseSvc *enetpulse.Service
|
||||
atlasVirtualGameService atlas.AtlasVirtualGameService
|
||||
veliVirtualGameService *veli.Service
|
||||
|
|
@ -92,6 +94,7 @@ type App struct {
|
|||
}
|
||||
|
||||
func NewApp(
|
||||
directDepositSvc *directdeposit.Service,
|
||||
enetPulseSvc *enetpulse.Service,
|
||||
atlasVirtualGameService atlas.AtlasVirtualGameService,
|
||||
veliVirtualGameService *veli.Service,
|
||||
|
|
@ -149,6 +152,7 @@ func NewApp(
|
|||
app.Static("/static", "./static")
|
||||
|
||||
s := &App{
|
||||
directDepositSvc: directDepositSvc,
|
||||
enetPulseSvc: enetPulseSvc,
|
||||
atlasVirtualGameService: atlasVirtualGameService,
|
||||
veliVirtualGameService: veliVirtualGameService,
|
||||
|
|
|
|||
|
|
@ -1,124 +1,267 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// InitiateDirectDeposit godoc
|
||||
// @Summary Initiate a direct deposit
|
||||
// @Description Customer initiates a direct deposit from mobile banking
|
||||
// @Tags Direct Deposits
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.DirectDepositRequest true "Deposit details"
|
||||
// @Success 201 {object} domain.Response
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct_deposit [post]
|
||||
func (h *Handler) InitiateDirectDeposit(c *fiber.Ctx) error {
|
||||
var req domain.DirectDepositRequest
|
||||
// CreateDirectDeposit godoc
|
||||
// @Summary Create a new direct deposit
|
||||
// @Description Creates a direct deposit for a customer and notifies both the customer and admins
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body domain.CreateDirectDeposit true "Direct deposit details"
|
||||
// @Success 200 {object} domain.Response{data=domain.DirectDeposit}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits [post]
|
||||
func (h *Handler) CreateDirectDeposit(c *fiber.Ctx) error {
|
||||
var req domain.CreateDirectDeposit
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
Message: "Invalid request payload",
|
||||
})
|
||||
}
|
||||
|
||||
deposit, err := h.walletSvc.InitiateDirectDeposit(
|
||||
c.Context(),
|
||||
req.CustomerID,
|
||||
req.Amount,
|
||||
req.BankReference,
|
||||
req.SenderAccount,
|
||||
)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
Message: "Failed to initiate direct deposit",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||
Message: "Direct deposit initiated successfully",
|
||||
// Call service
|
||||
deposit, err := h.directDepositSvc.CreateDirectDeposit(c.Context(), req)
|
||||
if err != nil {
|
||||
h.logger.Error("CreateDirectDeposit error", err.Error())
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to create direct deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Direct deposit created successfully",
|
||||
Data: deposit,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusCreated,
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyDirectDeposit godoc
|
||||
// @Summary Verify a direct deposit
|
||||
// @Description Cashier verifies a direct deposit transaction
|
||||
// @Tags Direct Deposits
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.VerifyDirectDepositRequest true "Verification details"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 401 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct_deposit/verify [post]
|
||||
func (h *Handler) VerifyDirectDeposit(c *fiber.Ctx) error {
|
||||
var req domain.VerifyDirectDepositRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
// GetDirectDepositsByStatus godoc
|
||||
// @Summary Get direct deposits by status
|
||||
// @Description Fetches direct deposits filtered by status with pagination
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param status query string true "Deposit status (e.g., PENDING, APPROVED, REJECTED)"
|
||||
// @Param page query int false "Page number"
|
||||
// @Param pageSize query int false "Page size"
|
||||
// @Success 200 {object} domain.Response{data=[]domain.DirectDeposit}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits [get]
|
||||
func (h *Handler) GetDirectDepositsByStatus(c *fiber.Ctx) error {
|
||||
status := c.Query("status")
|
||||
if status == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
Message: "Invalid verification request",
|
||||
Message: "status query parameter is required",
|
||||
})
|
||||
}
|
||||
|
||||
cashierID := c.Locals("user_id")
|
||||
if cashierID == nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
Error: "missing user_id in context",
|
||||
Message: "Unauthorized access",
|
||||
})
|
||||
}
|
||||
page, _ := strconv.Atoi(c.Query("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("pageSize", "50"))
|
||||
|
||||
deposit, err := h.walletSvc.VerifyDirectDeposit(
|
||||
c.Context(),
|
||||
req.DepositID,
|
||||
cashierID.(int64),
|
||||
req.IsVerified,
|
||||
req.Notes,
|
||||
)
|
||||
deposits, total, err := h.directDepositSvc.GetDirectDepositsByStatus(c.Context(), status, page, pageSize)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
h.logger.Error("GetDirectDepositsByStatus error", err)
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to fetch direct deposits",
|
||||
Error: err.Error(),
|
||||
Message: "Failed to verify deposit",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Deposit verification processed successfully",
|
||||
Data: deposit,
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// GetPendingDeposits godoc
|
||||
// @Summary Get pending direct deposits
|
||||
// @Description Get list of direct deposits needing verification
|
||||
// @Tags Direct Deposits
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct_deposit/pending [get]
|
||||
func (h *Handler) GetPendingDirectDeposits(c *fiber.Ctx) error {
|
||||
deposits, err := h.walletSvc.GetPendingDirectDeposits(c.Context())
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
Message: "Failed to retrieve pending deposits",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Pending deposits retrieved successfully",
|
||||
Message: fmt.Sprintf("Direct deposits with status '%s' fetched successfully", status),
|
||||
Data: deposits,
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
// Optional: include pagination info
|
||||
MetaData: map[string]any{
|
||||
"page": page,
|
||||
"pageSize": pageSize,
|
||||
"total": total,
|
||||
"totalPage": int(math.Ceil(float64(total) / float64(pageSize))),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ApproveDirectDeposit godoc
|
||||
// @Summary Approve a direct deposit
|
||||
// @Description Approves a direct deposit by admin and credits customer wallet
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param depositID path int true "Deposit ID"
|
||||
// @Param adminID query int true "Admin ID performing the approval"
|
||||
// @Success 200 {object} domain.Response{data=string}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits/{depositID}/approve [post]
|
||||
func (h *Handler) ApproveDirectDeposit(c *fiber.Ctx) error {
|
||||
depositID, err := strconv.Atoi(c.Params("depositID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid deposit ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
adminID, err := strconv.Atoi(c.Query("adminID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid admin ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if err := h.directDepositSvc.ApproveDirectDeposit(c.Context(), depositID, adminID); err != nil {
|
||||
h.logger.Error("ApproveDirectDeposit error", err)
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to approve direct deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: fmt.Sprintf("Direct deposit #%d approved successfully", depositID),
|
||||
Data: fmt.Sprintf("Deposit #%d approved", depositID),
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
// RejectDirectDeposit godoc
|
||||
// @Summary Reject a direct deposit
|
||||
// @Description Rejects a direct deposit by admin and notifies the customer
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param depositID path int true "Deposit ID"
|
||||
// @Param adminID query int true "Admin ID performing the rejection"
|
||||
// @Param reason query string true "Reason for rejection"
|
||||
// @Success 200 {object} domain.Response{data=string}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits/{depositID}/reject [post]
|
||||
func (h *Handler) RejectDirectDeposit(c *fiber.Ctx) error {
|
||||
depositID, err := strconv.Atoi(c.Params("depositID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid deposit ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
adminID, err := strconv.Atoi(c.Query("adminID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid admin ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
reason := c.Query("reason")
|
||||
if reason == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Rejection reason is required",
|
||||
})
|
||||
}
|
||||
|
||||
if err := h.directDepositSvc.RejectDirectDeposit(c.Context(), depositID, adminID, reason); err != nil {
|
||||
h.logger.Error("RejectDirectDeposit error", err)
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to reject direct deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: fmt.Sprintf("Direct deposit #%d rejected successfully", depositID),
|
||||
Data: fmt.Sprintf("Deposit #%d rejected", depositID),
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
// GetDirectDepositByID godoc
|
||||
// @Summary Get a direct deposit by ID
|
||||
// @Description Fetches a single direct deposit by its ID
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param depositID path int true "Deposit ID"
|
||||
// @Success 200 {object} domain.Response{data=domain.DirectDeposit}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits/{depositID} [get]
|
||||
func (h *Handler) GetDirectDepositByID(c *fiber.Ctx) error {
|
||||
depositID, err := strconv.Atoi(c.Params("depositID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid deposit ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
deposit, err := h.directDepositSvc.GetDirectDepositByID(c.Context(), depositID)
|
||||
if err != nil {
|
||||
h.logger.Error("GetDirectDepositByID error", err)
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to fetch direct deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: fmt.Sprintf("Direct deposit #%d fetched successfully", depositID),
|
||||
Data: deposit,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteDirectDeposit godoc
|
||||
// @Summary Delete a direct deposit
|
||||
// @Description Deletes a direct deposit by its ID
|
||||
// @Tags DirectDeposit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param depositID path int true "Deposit ID"
|
||||
// @Success 200 {object} domain.Response{data=string}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 502 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/direct-deposits/{depositID} [delete]
|
||||
func (h *Handler) DeleteDirectDeposit(c *fiber.Ctx) error {
|
||||
depositID, err := strconv.Atoi(c.Params("depositID"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid deposit ID",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if err := h.directDepositSvc.DeleteDirectDeposit(c.Context(), depositID); err != nil {
|
||||
h.logger.Error("DeleteDirectDeposit error", err)
|
||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to delete direct deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: fmt.Sprintf("Direct deposit #%d deleted successfully", depositID),
|
||||
Data: fmt.Sprintf("Deposit #%d deleted", depositID),
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
directdeposit "github.com/SamuelTariku/FortuneBet-Backend/internal/services/direct_deposit"
|
||||
enetpulse "github.com/SamuelTariku/FortuneBet-Backend/internal/services/enet_pulse"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
|
||||
|
|
@ -43,6 +44,7 @@ import (
|
|||
)
|
||||
|
||||
type Handler struct {
|
||||
directDepositSvc *directdeposit.Service
|
||||
orchestrationSvc *orchestration.Service
|
||||
enetPulseSvc *enetpulse.Service
|
||||
telebirrSvc *telebirr.TelebirrService
|
||||
|
|
@ -84,6 +86,7 @@ type Handler struct {
|
|||
}
|
||||
|
||||
func New(
|
||||
directDepositSvc *directdeposit.Service,
|
||||
orchestrationSvc *orchestration.Service,
|
||||
enetPulseSvc *enetpulse.Service,
|
||||
telebirrSvc *telebirr.TelebirrService,
|
||||
|
|
@ -124,6 +127,7 @@ func New(
|
|||
mongoLoggerSvc *zap.Logger,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
directDepositSvc: directDepositSvc,
|
||||
orchestrationSvc: orchestrationSvc,
|
||||
enetPulseSvc: enetPulseSvc,
|
||||
telebirrSvc: telebirrSvc,
|
||||
|
|
|
|||
|
|
@ -346,17 +346,17 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
|||
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "Duplicate transaction",
|
||||
Error: veli.ErrDuplicateTransaction.Error(),
|
||||
})
|
||||
} else if strings.Contains(err.Error(), veli.ErrInsufficientBalance.Error()) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "Wallet balance is insufficient",
|
||||
Error: veli.ErrInsufficientBalance.Error(),
|
||||
})
|
||||
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "User not found",
|
||||
Error: veli.ErrPlayerNotFound.Error(),
|
||||
})
|
||||
|
|
@ -473,12 +473,12 @@ func (h *Handler) HandleWin(c *fiber.Ctx) error {
|
|||
res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req)
|
||||
if err != nil {
|
||||
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "Duplicate transaction",
|
||||
Error: veli.ErrDuplicateTransaction.Error(),
|
||||
})
|
||||
} else if errors.Is(err, veli.ErrPlayerNotFound) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "Duplicate transaction",
|
||||
Error: veli.ErrPlayerNotFound.Error(),
|
||||
})
|
||||
|
|
@ -550,12 +550,12 @@ func (h *Handler) HandleCancel(c *fiber.Ctx) error {
|
|||
res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), veli.ErrDuplicateTransaction.Error()) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "Duplicate transaction",
|
||||
Error: veli.ErrDuplicateTransaction.Error(),
|
||||
})
|
||||
} else if strings.Contains(err.Error(), veli.ErrPlayerNotFound.Error()) {
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
|
||||
return c.Status(fiber.StatusConflict).JSON(domain.CallbackErrorResponse{
|
||||
// Message: "User not found",
|
||||
Error: veli.ErrPlayerNotFound.Error(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
func (a *App) initAppRoutes() {
|
||||
h := handlers.New(
|
||||
a.directDepositSvc,
|
||||
a.orchestrationSvc,
|
||||
a.enetPulseSvc,
|
||||
a.telebirrSvc,
|
||||
|
|
@ -103,9 +104,12 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Get("/tenant", a.authMiddleware, h.GetTenantSlugByToken)
|
||||
|
||||
//Direct_deposit
|
||||
groupV1.Post("/direct_deposit", a.authMiddleware, h.InitiateDirectDeposit)
|
||||
groupV1.Post("/direct_deposit/verify", a.authMiddleware, h.VerifyDirectDeposit)
|
||||
groupV1.Get("/direct_deposit/pending", a.authMiddleware, h.GetPendingDirectDeposits)
|
||||
groupV1.Post("/direct-deposits", a.authMiddleware, h.CreateDirectDeposit)
|
||||
groupV1.Post("/direct-deposits/:depositID/approve", a.authMiddleware, h.ApproveDirectDeposit)
|
||||
groupV1.Post("/direct-deposits/:depositID/reject", a.authMiddleware, h.RejectDirectDeposit)
|
||||
groupV1.Get("/direct-deposits", a.authMiddleware, h.GetDirectDepositsByStatus)
|
||||
groupV1.Get("/direct-deposits/:depositID", a.authMiddleware, h.GetDirectDepositByID)
|
||||
groupV1.Delete("/direct-deposits/:depositID", a.authMiddleware, h.DeleteDirectDeposit)
|
||||
|
||||
// Swagger
|
||||
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user