Merge branch 'main' into ticket-bet
This commit is contained in:
commit
cfced1951c
7
.env
7
.env
|
|
@ -27,7 +27,14 @@ POPOK_BASE_URL=https://st.pokgaming.com/ #Staging
|
||||||
|
|
||||||
POPOK_CALLBACK_URL=1
|
POPOK_CALLBACK_URL=1
|
||||||
|
|
||||||
|
#Muli-currency Support
|
||||||
|
FIXER_API_KEY=3b0f1eb30d-63c875026d-sxy9pl
|
||||||
|
BASE_CURRENCY=ETB
|
||||||
|
FIXER_BASE_URL=https://api.apilayer.com/fixer
|
||||||
|
|
||||||
# Chapa API Configuration
|
# Chapa API Configuration
|
||||||
|
CHAPA_TRANSFER_TYPE="Payout"
|
||||||
|
CHAPA_PAYMENT_TYPE="API"
|
||||||
CHAPA_BASE_URL="https://api.chapa.co/v1"
|
CHAPA_BASE_URL="https://api.chapa.co/v1"
|
||||||
CHAPA_ENCRYPTION_KEY=zLdYrjnBCknMvFikmP5jBfen
|
CHAPA_ENCRYPTION_KEY=zLdYrjnBCknMvFikmP5jBfen
|
||||||
CHAPA_PUBLIC_KEY=CHAPUBK_TEST-HJR0qhQRPLTkauNy9Q8UrmskPTOR31aC
|
CHAPA_PUBLIC_KEY=CHAPUBK_TEST-HJR0qhQRPLTkauNy9Q8UrmskPTOR31aC
|
||||||
|
|
|
||||||
24
cmd/main.go
24
cmd/main.go
|
|
@ -4,6 +4,7 @@ import (
|
||||||
// "context"
|
// "context"
|
||||||
|
|
||||||
// "context"
|
// "context"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
@ -33,6 +34,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
|
|
@ -134,13 +136,18 @@ func main() {
|
||||||
|
|
||||||
chapaSvc := chapa.NewService(
|
chapaSvc := chapa.NewService(
|
||||||
wallet.TransferStore(store),
|
wallet.TransferStore(store),
|
||||||
wallet.WalletStore(store),
|
*walletSvc,
|
||||||
user.UserStore(store),
|
user.UserStore(store),
|
||||||
chapaClient,
|
chapaClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initialize reporting components
|
|
||||||
reportRepo := repository.NewReportRepo(store)
|
reportRepo := repository.NewReportRepo(store)
|
||||||
|
currRepo := repository.NewCurrencyPostgresRepository(store)
|
||||||
|
|
||||||
|
fixerFertcherSvc := currency.NewFixerFetcher(
|
||||||
|
cfg.FIXER_API_KEY,
|
||||||
|
cfg.FIXER_BASE_URL,
|
||||||
|
)
|
||||||
|
|
||||||
reportSvc := report.NewService(
|
reportSvc := report.NewService(
|
||||||
bet.BetStore(store),
|
bet.BetStore(store),
|
||||||
|
|
@ -175,7 +182,17 @@ func main() {
|
||||||
logger,
|
logger,
|
||||||
5*time.Minute,
|
5*time.Minute,
|
||||||
)
|
)
|
||||||
walletMonitorSvc.Start()
|
|
||||||
|
currSvc := currency.NewService(
|
||||||
|
currRepo,
|
||||||
|
cfg.BASE_CURRENCY,
|
||||||
|
fixerFertcherSvc,
|
||||||
|
)
|
||||||
|
|
||||||
|
exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg)
|
||||||
|
exchangeWorker.Start(context.Background())
|
||||||
|
defer exchangeWorker.Stop()
|
||||||
|
go walletMonitorSvc.Start()
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
||||||
httpserver.StartTicketCrons(*ticketSvc)
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
|
|
@ -183,6 +200,7 @@ func main() {
|
||||||
|
|
||||||
// Initialize and start HTTP server
|
// Initialize and start HTTP server
|
||||||
app := httpserver.NewApp(
|
app := httpserver.NewApp(
|
||||||
|
currSvc,
|
||||||
cfg.Port,
|
cfg.Port,
|
||||||
v,
|
v,
|
||||||
authSvc,
|
authSvc,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,15 @@ CREATE TABLE IF NOT EXISTS tickets (
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
CREATE TABLE exchange_rates (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
from_currency VARCHAR(3) NOT NULL,
|
||||||
|
to_currency VARCHAR(3) NOT NULL,
|
||||||
|
rate DECIMAL(19, 6) NOT NULL,
|
||||||
|
valid_until TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE (from_currency, to_currency)
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS bet_outcomes (
|
CREATE TABLE IF NOT EXISTS bet_outcomes (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
bet_id BIGINT NOT NULL,
|
bet_id BIGINT NOT NULL,
|
||||||
|
|
@ -123,14 +132,15 @@ CREATE TABLE IF NOT EXISTS customer_wallets (
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS wallet_transfer (
|
CREATE TABLE IF NOT EXISTS wallet_transfer (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
amount BIGINT NOT NULL,
|
amount BIGINT,
|
||||||
type VARCHAR(255) NOT NULL,
|
type VARCHAR(255),
|
||||||
receiver_wallet_id BIGINT,
|
receiver_wallet_id BIGINT,
|
||||||
sender_wallet_id BIGINT,
|
sender_wallet_id BIGINT,
|
||||||
cashier_id BIGINT,
|
cashier_id BIGINT,
|
||||||
verified BOOLEAN NOT NULL DEFAULT false,
|
verified BOOLEAN DEFAULT false,
|
||||||
reference_number VARCHAR(255) NOT NULL,
|
reference_number VARCHAR(255),
|
||||||
payment_method VARCHAR(255) NOT NULL,
|
status VARCHAR(255),
|
||||||
|
payment_method VARCHAR(255),
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
@ -298,7 +308,8 @@ ALTER TABLE bets
|
||||||
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
||||||
ALTER TABLE wallets
|
ALTER TABLE wallets
|
||||||
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id);
|
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id);
|
||||||
|
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
|
||||||
ALTER TABLE customer_wallets
|
ALTER TABLE customer_wallets
|
||||||
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id),
|
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
DROP TABLE IF EXISTS vitrual_games;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS virtual_game_transactions;
|
DROP TABLE IF EXISTS virtual_game_transactions;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS virtual_game_sessions;
|
DROP TABLE IF EXISTS virtual_game_sessions;
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: GetAllTransfers :many
|
-- name: GetAllTransfers :many
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
@ -32,3 +33,9 @@ UPDATE wallet_transfer
|
||||||
SET verified = $1,
|
SET verified = $1,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $2;
|
WHERE id = $2;
|
||||||
|
|
||||||
|
-- name: UpdateTransferStatus :exec
|
||||||
|
UPDATE wallet_transfer
|
||||||
|
SET status = $1,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $2;
|
||||||
1094
docs/docs.go
1094
docs/docs.go
File diff suppressed because it is too large
Load Diff
1094
docs/swagger.json
1094
docs/swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -31,39 +31,6 @@ definitions:
|
||||||
user_id:
|
user_id:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
domain.Bank:
|
|
||||||
properties:
|
|
||||||
acct_length:
|
|
||||||
type: integer
|
|
||||||
active:
|
|
||||||
type: integer
|
|
||||||
country_id:
|
|
||||||
type: integer
|
|
||||||
created_at:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
is_24hrs:
|
|
||||||
description: nullable
|
|
||||||
type: integer
|
|
||||||
is_active:
|
|
||||||
type: integer
|
|
||||||
is_mobilemoney:
|
|
||||||
description: nullable
|
|
||||||
type: integer
|
|
||||||
is_rtgs:
|
|
||||||
type: integer
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
slug:
|
|
||||||
type: string
|
|
||||||
swift:
|
|
||||||
type: string
|
|
||||||
updated_at:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.BetOutcome:
|
domain.BetOutcome:
|
||||||
properties:
|
properties:
|
||||||
away_team_name:
|
away_team_name:
|
||||||
|
|
@ -193,6 +160,22 @@ definitions:
|
||||||
tx_ref:
|
tx_ref:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.ChapaWithdrawalRequest:
|
||||||
|
properties:
|
||||||
|
account_name:
|
||||||
|
type: string
|
||||||
|
account_number:
|
||||||
|
type: string
|
||||||
|
amount:
|
||||||
|
description: string because Chapa API uses string for monetary values
|
||||||
|
type: string
|
||||||
|
bank_code:
|
||||||
|
type: integer
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
reference:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.CreateBetOutcomeReq:
|
domain.CreateBetOutcomeReq:
|
||||||
properties:
|
properties:
|
||||||
event_id:
|
event_id:
|
||||||
|
|
@ -228,6 +211,81 @@ definitions:
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
example: 1
|
example: 1
|
||||||
type: object
|
type: object
|
||||||
|
domain.DashboardSummary:
|
||||||
|
properties:
|
||||||
|
active_admins:
|
||||||
|
type: integer
|
||||||
|
active_bets:
|
||||||
|
type: integer
|
||||||
|
active_branches:
|
||||||
|
type: integer
|
||||||
|
active_cashiers:
|
||||||
|
type: integer
|
||||||
|
active_companies:
|
||||||
|
type: integer
|
||||||
|
active_customers:
|
||||||
|
type: integer
|
||||||
|
active_games:
|
||||||
|
type: integer
|
||||||
|
active_managers:
|
||||||
|
type: integer
|
||||||
|
average_stake:
|
||||||
|
type: integer
|
||||||
|
branches_count:
|
||||||
|
type: integer
|
||||||
|
customer_count:
|
||||||
|
type: integer
|
||||||
|
inactive_admins:
|
||||||
|
type: integer
|
||||||
|
inactive_branches:
|
||||||
|
type: integer
|
||||||
|
inactive_cashiers:
|
||||||
|
type: integer
|
||||||
|
inactive_companies:
|
||||||
|
type: integer
|
||||||
|
inactive_customers:
|
||||||
|
type: integer
|
||||||
|
inactive_games:
|
||||||
|
type: integer
|
||||||
|
inactive_managers:
|
||||||
|
type: integer
|
||||||
|
profit:
|
||||||
|
type: integer
|
||||||
|
read_notifications:
|
||||||
|
type: integer
|
||||||
|
total_admins:
|
||||||
|
type: integer
|
||||||
|
total_bets:
|
||||||
|
type: integer
|
||||||
|
total_cashiers:
|
||||||
|
type: integer
|
||||||
|
total_companies:
|
||||||
|
type: integer
|
||||||
|
total_deposits:
|
||||||
|
type: integer
|
||||||
|
total_games:
|
||||||
|
type: integer
|
||||||
|
total_losses:
|
||||||
|
type: integer
|
||||||
|
total_managers:
|
||||||
|
type: integer
|
||||||
|
total_notifications:
|
||||||
|
type: integer
|
||||||
|
total_stakes:
|
||||||
|
type: integer
|
||||||
|
total_wallets:
|
||||||
|
type: integer
|
||||||
|
total_wins:
|
||||||
|
type: integer
|
||||||
|
total_withdrawals:
|
||||||
|
type: integer
|
||||||
|
unread_notifications:
|
||||||
|
type: integer
|
||||||
|
win_balance:
|
||||||
|
type: integer
|
||||||
|
win_rate:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
domain.ErrorResponse:
|
domain.ErrorResponse:
|
||||||
properties:
|
properties:
|
||||||
error:
|
error:
|
||||||
|
|
@ -235,6 +293,57 @@ definitions:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.EventStatus:
|
||||||
|
enum:
|
||||||
|
- upcoming
|
||||||
|
- in_play
|
||||||
|
- to_be_fixed
|
||||||
|
- ended
|
||||||
|
- postponed
|
||||||
|
- cancelled
|
||||||
|
- walkover
|
||||||
|
- interrupted
|
||||||
|
- abandoned
|
||||||
|
- retired
|
||||||
|
- suspended
|
||||||
|
- decided_by_fa
|
||||||
|
- removed
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- STATUS_PENDING
|
||||||
|
- STATUS_IN_PLAY
|
||||||
|
- STATUS_TO_BE_FIXED
|
||||||
|
- STATUS_ENDED
|
||||||
|
- STATUS_POSTPONED
|
||||||
|
- STATUS_CANCELLED
|
||||||
|
- STATUS_WALKOVER
|
||||||
|
- STATUS_INTERRUPTED
|
||||||
|
- STATUS_ABANDONED
|
||||||
|
- STATUS_RETIRED
|
||||||
|
- STATUS_SUSPENDED
|
||||||
|
- STATUS_DECIDED_BY_FA
|
||||||
|
- STATUS_REMOVED
|
||||||
|
domain.League:
|
||||||
|
properties:
|
||||||
|
bet365_id:
|
||||||
|
example: 1121
|
||||||
|
type: integer
|
||||||
|
cc:
|
||||||
|
example: uk
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
is_active:
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
example: BPL
|
||||||
|
type: string
|
||||||
|
sport_id:
|
||||||
|
example: 1
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
domain.Odd:
|
domain.Odd:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
|
|
@ -404,6 +513,16 @@ definitions:
|
||||||
totalRewardEarned:
|
totalRewardEarned:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
domain.Response:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
domain.Role:
|
domain.Role:
|
||||||
enum:
|
enum:
|
||||||
- super_admin
|
- super_admin
|
||||||
|
|
@ -466,48 +585,52 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
domain.UpcomingEvent:
|
domain.UpcomingEvent:
|
||||||
properties:
|
properties:
|
||||||
awayKitImage:
|
away_kit_image:
|
||||||
description: Kit or image for away team (optional)
|
description: Kit or image for away team (optional)
|
||||||
type: string
|
type: string
|
||||||
awayTeam:
|
away_team:
|
||||||
description: Away team name (can be empty/null)
|
description: Away team name (can be empty/null)
|
||||||
type: string
|
type: string
|
||||||
awayTeamID:
|
away_team_id:
|
||||||
description: Away team ID (can be empty/null)
|
description: Away team ID (can be empty/null)
|
||||||
type: integer
|
type: integer
|
||||||
homeKitImage:
|
home_kit_image:
|
||||||
description: Kit or image for home team (optional)
|
description: Kit or image for home team (optional)
|
||||||
type: string
|
type: string
|
||||||
homeTeam:
|
home_team:
|
||||||
description: Home team name (if available)
|
description: Home team name (if available)
|
||||||
type: string
|
type: string
|
||||||
homeTeamID:
|
home_team_id:
|
||||||
description: Home team ID
|
description: Home team ID
|
||||||
type: integer
|
type: integer
|
||||||
id:
|
id:
|
||||||
description: Event ID
|
description: Event ID
|
||||||
type: string
|
type: string
|
||||||
leagueCC:
|
league_cc:
|
||||||
description: League country code
|
description: League country code
|
||||||
type: string
|
type: string
|
||||||
leagueID:
|
league_id:
|
||||||
description: League ID
|
description: League ID
|
||||||
type: integer
|
type: integer
|
||||||
leagueName:
|
league_name:
|
||||||
description: League name
|
description: League name
|
||||||
type: string
|
type: string
|
||||||
matchName:
|
match_name:
|
||||||
description: Match or event name
|
description: Match or event name
|
||||||
type: string
|
type: string
|
||||||
source:
|
source:
|
||||||
description: bet api provider (bet365, betfair)
|
description: bet api provider (bet365, betfair)
|
||||||
type: string
|
type: string
|
||||||
sportID:
|
sport_id:
|
||||||
description: Sport ID
|
description: Sport ID
|
||||||
type: integer
|
type: integer
|
||||||
startTime:
|
start_time:
|
||||||
description: Converted from "time" field in UNIX format
|
description: Converted from "time" field in UNIX format
|
||||||
type: string
|
type: string
|
||||||
|
status:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.EventStatus'
|
||||||
|
description: Match Status for event
|
||||||
type: object
|
type: object
|
||||||
domain.VeliCallback:
|
domain.VeliCallback:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -550,6 +673,8 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
is_active:
|
||||||
|
type: boolean
|
||||||
is_featured:
|
is_featured:
|
||||||
type: boolean
|
type: boolean
|
||||||
max_bet:
|
max_bet:
|
||||||
|
|
@ -602,6 +727,9 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
handlers.BranchDetailRes:
|
handlers.BranchDetailRes:
|
||||||
properties:
|
properties:
|
||||||
|
balance:
|
||||||
|
example: 100.5
|
||||||
|
type: number
|
||||||
branch_manager_id:
|
branch_manager_id:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -928,6 +1056,12 @@ definitions:
|
||||||
properties:
|
properties:
|
||||||
branch_id:
|
branch_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
branch_location:
|
||||||
|
type: string
|
||||||
|
branch_name:
|
||||||
|
type: string
|
||||||
|
branch_wallet:
|
||||||
|
type: integer
|
||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
email:
|
email:
|
||||||
|
|
@ -1045,6 +1179,13 @@ definitions:
|
||||||
- otp
|
- otp
|
||||||
- password
|
- password
|
||||||
type: object
|
type: object
|
||||||
|
handlers.ResultRes:
|
||||||
|
properties:
|
||||||
|
outcomes:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.BetOutcome'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
handlers.SearchUserByNameOrPhoneReq:
|
handlers.SearchUserByNameOrPhoneReq:
|
||||||
properties:
|
properties:
|
||||||
query:
|
query:
|
||||||
|
|
@ -1296,9 +1437,9 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
mode:
|
mode:
|
||||||
enum:
|
enum:
|
||||||
- REAL
|
- fun
|
||||||
- DEMO
|
- real
|
||||||
example: REAL
|
example: real
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- currency
|
- currency
|
||||||
|
|
@ -1395,39 +1536,6 @@ definitions:
|
||||||
example: false
|
example: false
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
report.DashboardSummary:
|
|
||||||
properties:
|
|
||||||
active_bets:
|
|
||||||
type: integer
|
|
||||||
active_branches:
|
|
||||||
type: integer
|
|
||||||
active_customers:
|
|
||||||
type: integer
|
|
||||||
average_stake:
|
|
||||||
type: integer
|
|
||||||
branches_count:
|
|
||||||
type: integer
|
|
||||||
customer_count:
|
|
||||||
type: integer
|
|
||||||
profit:
|
|
||||||
type: integer
|
|
||||||
total_bets:
|
|
||||||
type: integer
|
|
||||||
total_deposits:
|
|
||||||
type: integer
|
|
||||||
total_losses:
|
|
||||||
type: integer
|
|
||||||
total_stakes:
|
|
||||||
type: integer
|
|
||||||
total_wins:
|
|
||||||
type: integer
|
|
||||||
total_withdrawals:
|
|
||||||
type: integer
|
|
||||||
win_balance:
|
|
||||||
type: integer
|
|
||||||
win_rate:
|
|
||||||
type: number
|
|
||||||
type: object
|
|
||||||
response.APIResponse:
|
response.APIResponse:
|
||||||
properties:
|
properties:
|
||||||
data: {}
|
data: {}
|
||||||
|
|
@ -1646,6 +1754,25 @@ paths:
|
||||||
summary: Launch an Alea Play virtual game
|
summary: Launch an Alea Play virtual game
|
||||||
tags:
|
tags:
|
||||||
- Alea Virtual Games
|
- Alea Virtual Games
|
||||||
|
/api/v1/chapa/banks:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get list of banks supported by Chapa
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Get supported banks
|
||||||
|
tags:
|
||||||
|
- Chapa
|
||||||
/api/v1/chapa/payments/deposit:
|
/api/v1/chapa/payments/deposit:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1673,6 +1800,8 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ErrorResponse'
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
summary: Initiate a deposit
|
summary: Initiate a deposit
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
|
@ -1736,6 +1865,105 @@ paths:
|
||||||
summary: Chapa payment webhook callback (used by Chapa)
|
summary: Chapa payment webhook callback (used by Chapa)
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
/api/v1/chapa/payments/withdraw:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Initiates a withdrawal request to transfer funds to a bank account
|
||||||
|
via Chapa
|
||||||
|
parameters:
|
||||||
|
- description: Withdrawal request details
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ChapaWithdrawalRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Chapa withdrawal process initiated successfully
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"400":
|
||||||
|
description: Invalid request body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"422":
|
||||||
|
description: Unprocessable entity
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Initiate a withdrawal
|
||||||
|
tags:
|
||||||
|
- Chapa
|
||||||
|
/api/v1/currencies:
|
||||||
|
get:
|
||||||
|
description: Returns list of supported currencies
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
summary: Get supported currencies
|
||||||
|
tags:
|
||||||
|
- Multi-Currency
|
||||||
|
/api/v1/currencies/convert:
|
||||||
|
get:
|
||||||
|
description: Converts amount from one currency to another
|
||||||
|
parameters:
|
||||||
|
- description: Source currency code (e.g., USD)
|
||||||
|
in: query
|
||||||
|
name: from
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Target currency code (e.g., ETB)
|
||||||
|
in: query
|
||||||
|
name: to
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Amount to convert
|
||||||
|
in: query
|
||||||
|
name: amount
|
||||||
|
required: true
|
||||||
|
type: number
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Convert currency
|
||||||
|
tags:
|
||||||
|
- Multi-Currency
|
||||||
/api/v1/reports/dashboard:
|
/api/v1/reports/dashboard:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1776,7 +2004,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/report.DashboardSummary'
|
$ref: '#/definitions/domain.DashboardSummary'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -1997,27 +2225,6 @@ paths:
|
||||||
summary: Refresh token
|
summary: Refresh token
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
/banks:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Get list of banks supported by Chapa
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/domain.Bank'
|
|
||||||
type: array
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.ErrorResponse'
|
|
||||||
summary: Get supported banks
|
|
||||||
tags:
|
|
||||||
- Chapa
|
|
||||||
/bet:
|
/bet:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -2458,6 +2665,35 @@ paths:
|
||||||
summary: Delete the branch operation
|
summary: Delete the branch operation
|
||||||
tags:
|
tags:
|
||||||
- branch
|
- branch
|
||||||
|
/branchCashier:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Gets branch for cahier
|
||||||
|
parameters:
|
||||||
|
- description: Branch ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.BranchDetailRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Gets branch for cahier
|
||||||
|
tags:
|
||||||
|
- branch
|
||||||
/branchWallet:
|
/branchWallet:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -2516,6 +2752,39 @@ paths:
|
||||||
summary: Get cashier by id
|
summary: Get cashier by id
|
||||||
tags:
|
tags:
|
||||||
- cashier
|
- cashier
|
||||||
|
/cashierWallet:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get wallet for cashier
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.UserProfileRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Get wallet for cashier
|
||||||
|
tags:
|
||||||
|
- cashier
|
||||||
/cashiers:
|
/cashiers:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -2800,6 +3069,138 @@ paths:
|
||||||
summary: Gets branches by company id
|
summary: Gets branches by company id
|
||||||
tags:
|
tags:
|
||||||
- branch
|
- branch
|
||||||
|
/events:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve all upcoming events from the database
|
||||||
|
parameters:
|
||||||
|
- description: Page number
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: Page size
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
- description: League ID Filter
|
||||||
|
in: query
|
||||||
|
name: league_id
|
||||||
|
type: string
|
||||||
|
- description: Sport ID Filter
|
||||||
|
in: query
|
||||||
|
name: sport_id
|
||||||
|
type: string
|
||||||
|
- description: Country Code Filter
|
||||||
|
in: query
|
||||||
|
name: cc
|
||||||
|
type: string
|
||||||
|
- description: Start Time
|
||||||
|
in: query
|
||||||
|
name: first_start_time
|
||||||
|
type: string
|
||||||
|
- description: End Time
|
||||||
|
in: query
|
||||||
|
name: last_start_time
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.UpcomingEvent'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Retrieve all upcoming events
|
||||||
|
tags:
|
||||||
|
- prematch
|
||||||
|
/events/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Set the event status to removed
|
||||||
|
parameters:
|
||||||
|
- description: Event 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: Set the event status to removed
|
||||||
|
tags:
|
||||||
|
- event
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieve an upcoming event by ID
|
||||||
|
parameters:
|
||||||
|
- description: ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.UpcomingEvent'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Retrieve an upcoming by ID
|
||||||
|
tags:
|
||||||
|
- prematch
|
||||||
|
/leagues:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Gets all leagues
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.League'
|
||||||
|
type: array
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Gets all leagues
|
||||||
|
tags:
|
||||||
|
- leagues
|
||||||
/manager/{id}/branch:
|
/manager/{id}/branch:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -2966,112 +3367,7 @@ paths:
|
||||||
summary: Update Managers
|
summary: Update Managers
|
||||||
tags:
|
tags:
|
||||||
- manager
|
- manager
|
||||||
/operation:
|
/odds:
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Creates a operation
|
|
||||||
parameters:
|
|
||||||
- description: Creates operation
|
|
||||||
in: body
|
|
||||||
name: createBranchOperation
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/handlers.CreateBranchOperationReq'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/handlers.BranchOperationRes'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
summary: Create a operation
|
|
||||||
tags:
|
|
||||||
- branch
|
|
||||||
/prematch/events:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Retrieve all upcoming events from the database
|
|
||||||
parameters:
|
|
||||||
- description: Page number
|
|
||||||
in: query
|
|
||||||
name: page
|
|
||||||
type: integer
|
|
||||||
- description: Page size
|
|
||||||
in: query
|
|
||||||
name: page_size
|
|
||||||
type: integer
|
|
||||||
- description: League ID Filter
|
|
||||||
in: query
|
|
||||||
name: league_id
|
|
||||||
type: string
|
|
||||||
- description: Sport ID Filter
|
|
||||||
in: query
|
|
||||||
name: sport_id
|
|
||||||
type: string
|
|
||||||
- description: Start Time
|
|
||||||
in: query
|
|
||||||
name: first_start_time
|
|
||||||
type: string
|
|
||||||
- description: End Time
|
|
||||||
in: query
|
|
||||||
name: last_start_time
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/domain.UpcomingEvent'
|
|
||||||
type: array
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
summary: Retrieve all upcoming events
|
|
||||||
tags:
|
|
||||||
- prematch
|
|
||||||
/prematch/events/{id}:
|
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Retrieve an upcoming event by ID
|
|
||||||
parameters:
|
|
||||||
- description: ID
|
|
||||||
in: path
|
|
||||||
name: id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.UpcomingEvent'
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
summary: Retrieve an upcoming by ID
|
|
||||||
tags:
|
|
||||||
- prematch
|
|
||||||
/prematch/odds:
|
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
|
@ -3092,38 +3388,7 @@ paths:
|
||||||
summary: Retrieve all prematch odds
|
summary: Retrieve all prematch odds
|
||||||
tags:
|
tags:
|
||||||
- prematch
|
- prematch
|
||||||
/prematch/odds/{event_id}:
|
/odds/upcoming/{upcoming_id}:
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Retrieve prematch odds for a specific event by event ID
|
|
||||||
parameters:
|
|
||||||
- description: Event ID
|
|
||||||
in: path
|
|
||||||
name: event_id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/domain.Odd'
|
|
||||||
type: array
|
|
||||||
"400":
|
|
||||||
description: Bad Request
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
"500":
|
|
||||||
description: Internal Server Error
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/response.APIResponse'
|
|
||||||
summary: Retrieve prematch odds for an event
|
|
||||||
tags:
|
|
||||||
- prematch
|
|
||||||
/prematch/odds/upcoming/{upcoming_id}:
|
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
|
@ -3163,7 +3428,7 @@ paths:
|
||||||
summary: Retrieve prematch odds by upcoming ID (FI)
|
summary: Retrieve prematch odds by upcoming ID (FI)
|
||||||
tags:
|
tags:
|
||||||
- prematch
|
- prematch
|
||||||
/prematch/odds/upcoming/{upcoming_id}/market/{market_id}:
|
/odds/upcoming/{upcoming_id}/market/{market_id}:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
|
@ -3199,6 +3464,36 @@ paths:
|
||||||
summary: Retrieve raw odds by Market ID
|
summary: Retrieve raw odds by Market ID
|
||||||
tags:
|
tags:
|
||||||
- prematch
|
- prematch
|
||||||
|
/operation:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Creates a operation
|
||||||
|
parameters:
|
||||||
|
- description: Creates operation
|
||||||
|
in: body
|
||||||
|
name: createBranchOperation
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.CreateBranchOperationReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.BranchOperationRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Create a operation
|
||||||
|
tags:
|
||||||
|
- branch
|
||||||
/random/bet:
|
/random/bet:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -3336,7 +3631,7 @@ paths:
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/domain.BetOutcome'
|
$ref: '#/definitions/handlers.ResultRes'
|
||||||
type: array
|
type: array
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: auth.sql
|
// source: auth.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: bet.sql
|
// source: bet.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: branch.sql
|
// source: branch.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: cashier.sql
|
// source: cashier.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: company.sql
|
// source: company.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: copyfrom.go
|
// source: copyfrom.go
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: events.sql
|
// source: events.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: leagues.sql
|
// source: leagues.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
||||||
|
|
@ -482,14 +482,15 @@ type WalletThresholdNotification struct {
|
||||||
|
|
||||||
type WalletTransfer struct {
|
type WalletTransfer struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Amount int64 `json:"amount"`
|
Amount pgtype.Int8 `json:"amount"`
|
||||||
Type string `json:"type"`
|
Type pgtype.Text `json:"type"`
|
||||||
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
||||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||||
PaymentMethod string `json:"payment_method"`
|
Status pgtype.Text `json:"status"`
|
||||||
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: monitor.sql
|
// source: monitor.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: notification.sql
|
// source: notification.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: odds.sql
|
// source: odds.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: otp.sql
|
// source: otp.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: referal.sql
|
// source: referal.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: result.sql
|
// source: result.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: ticket.sql
|
// source: ticket.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: transactions.sql
|
// source: transactions.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: transfer.sql
|
// source: transfer.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
@ -20,21 +20,23 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTransferParams struct {
|
type CreateTransferParams struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount pgtype.Int8 `json:"amount"`
|
||||||
Type string `json:"type"`
|
Type pgtype.Text `json:"type"`
|
||||||
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
||||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||||
PaymentMethod string `json:"payment_method"`
|
Status pgtype.Text `json:"status"`
|
||||||
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) {
|
func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) {
|
||||||
|
|
@ -46,6 +48,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
arg.CashierID,
|
arg.CashierID,
|
||||||
arg.Verified,
|
arg.Verified,
|
||||||
arg.ReferenceNumber,
|
arg.ReferenceNumber,
|
||||||
|
arg.Status,
|
||||||
arg.PaymentMethod,
|
arg.PaymentMethod,
|
||||||
)
|
)
|
||||||
var i WalletTransfer
|
var i WalletTransfer
|
||||||
|
|
@ -58,6 +61,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -66,7 +70,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllTransfers = `-- name: GetAllTransfers :many
|
const GetAllTransfers = `-- name: GetAllTransfers :many
|
||||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||||
FROM wallet_transfer
|
FROM wallet_transfer
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -88,6 +92,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error)
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -103,7 +108,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByID = `-- name: GetTransferByID :one
|
const GetTransferByID = `-- name: GetTransferByID :one
|
||||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||||
FROM wallet_transfer
|
FROM wallet_transfer
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -120,6 +125,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -128,12 +134,12 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByReference = `-- name: GetTransferByReference :one
|
const GetTransferByReference = `-- name: GetTransferByReference :one
|
||||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||||
FROM wallet_transfer
|
FROM wallet_transfer
|
||||||
WHERE reference_number = $1
|
WHERE reference_number = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber string) (WalletTransfer, error) {
|
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber pgtype.Text) (WalletTransfer, error) {
|
||||||
row := q.db.QueryRow(ctx, GetTransferByReference, referenceNumber)
|
row := q.db.QueryRow(ctx, GetTransferByReference, referenceNumber)
|
||||||
var i WalletTransfer
|
var i WalletTransfer
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
|
@ -145,6 +151,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -153,7 +160,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
||||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||||
FROM wallet_transfer
|
FROM wallet_transfer
|
||||||
WHERE receiver_wallet_id = $1
|
WHERE receiver_wallet_id = $1
|
||||||
OR sender_wallet_id = $1
|
OR sender_wallet_id = $1
|
||||||
|
|
@ -177,6 +184,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
|
@ -191,6 +199,23 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UpdateTransferStatus = `-- name: UpdateTransferStatus :exec
|
||||||
|
UPDATE wallet_transfer
|
||||||
|
SET status = $1,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateTransferStatusParams struct {
|
||||||
|
Status pgtype.Text `json:"status"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateTransferStatus(ctx context.Context, arg UpdateTransferStatusParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, UpdateTransferStatus, arg.Status, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec
|
const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec
|
||||||
UPDATE wallet_transfer
|
UPDATE wallet_transfer
|
||||||
SET verified = $1,
|
SET verified = $1,
|
||||||
|
|
@ -199,8 +224,8 @@ WHERE id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTransferVerificationParams struct {
|
type UpdateTransferVerificationParams struct {
|
||||||
Verified bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error {
|
func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: user.sql
|
// source: user.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: virtual_games.sql
|
// source: virtual_games.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: wallet.sql
|
// source: wallet.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ type VeliGamesConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
FIXER_API_KEY string
|
||||||
|
FIXER_BASE_URL string
|
||||||
|
BASE_CURRENCY domain.IntCurrency
|
||||||
Port int
|
Port int
|
||||||
DbUrl string
|
DbUrl string
|
||||||
RefreshExpiry int
|
RefreshExpiry int
|
||||||
|
|
@ -68,6 +71,8 @@ type Config struct {
|
||||||
AFRO_SMS_SENDER_NAME string
|
AFRO_SMS_SENDER_NAME string
|
||||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||||
ADRO_SMS_HOST_URL string
|
ADRO_SMS_HOST_URL string
|
||||||
|
CHAPA_TRANSFER_TYPE string
|
||||||
|
CHAPA_PAYMENT_TYPE string
|
||||||
CHAPA_SECRET_KEY string
|
CHAPA_SECRET_KEY string
|
||||||
CHAPA_PUBLIC_KEY string
|
CHAPA_PUBLIC_KEY string
|
||||||
CHAPA_BASE_URL string
|
CHAPA_BASE_URL string
|
||||||
|
|
@ -104,6 +109,13 @@ func (c *Config) loadEnv() error {
|
||||||
|
|
||||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||||
|
|
||||||
|
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
||||||
|
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
||||||
|
|
||||||
|
c.FIXER_API_KEY = os.Getenv("FIXER_API_KEY")
|
||||||
|
c.BASE_CURRENCY = domain.IntCurrency(os.Getenv("BASE_CURRENCY"))
|
||||||
|
c.FIXER_BASE_URL = os.Getenv("FIXER_BASE_URL")
|
||||||
|
|
||||||
portStr := os.Getenv("PORT")
|
portStr := os.Getenv("PORT")
|
||||||
if portStr == "" {
|
if portStr == "" {
|
||||||
return ErrInvalidPort
|
return ErrInvalidPort
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,27 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInsufficientBalance = errors.New("insufficient balance")
|
||||||
|
ErrInvalidWithdrawalAmount = errors.New("invalid withdrawal amount")
|
||||||
|
ErrWithdrawalNotFound = errors.New("withdrawal not found")
|
||||||
|
)
|
||||||
|
|
||||||
type PaymentStatus string
|
type PaymentStatus string
|
||||||
|
|
||||||
|
type WithdrawalStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WithdrawalStatusPending WithdrawalStatus = "pending"
|
||||||
|
WithdrawalStatusProcessing WithdrawalStatus = "processing"
|
||||||
|
WithdrawalStatusCompleted WithdrawalStatus = "completed"
|
||||||
|
WithdrawalStatusFailed WithdrawalStatus = "failed"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PaymentStatusPending PaymentStatus = "pending"
|
PaymentStatusPending PaymentStatus = "pending"
|
||||||
PaymentStatusCompleted PaymentStatus = "completed"
|
PaymentStatusCompleted PaymentStatus = "completed"
|
||||||
|
|
@ -91,3 +109,86 @@ type BankData struct {
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaWithdrawal struct {
|
||||||
|
ID string
|
||||||
|
UserID int64
|
||||||
|
Amount Currency
|
||||||
|
AccountNumber string
|
||||||
|
BankCode string
|
||||||
|
Status WithdrawalStatus
|
||||||
|
Reference string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWithdrawalRequest struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Amount string `json:"amount"` // string because Chapa API uses string for monetary values
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
BankCode int `json:"bank_code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// type ChapaWithdrawalRequest struct {
|
||||||
|
// AccountName string `json:"account_name"`
|
||||||
|
// AccountNumber string `json:"account_number"`
|
||||||
|
// Amount Currency `json:"amount"`
|
||||||
|
// Currency string `json:"currency"`
|
||||||
|
// BeneficiaryName string `json:"beneficiary_name"`
|
||||||
|
// BankCode string `json:"bank_code"`
|
||||||
|
// PhoneNumber string `json:"phone_number"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
type ChapaWithdrawalResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransactionType struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWebHookTransfer struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
BankId string `json:"bank_id"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
ChapaReference string `json:"chapa_reference"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWebHookPayment struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Mobile interface{} `json:"mobile"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customization struct {
|
||||||
|
Title interface{} `json:"title"`
|
||||||
|
Description interface{} `json:"description"`
|
||||||
|
Logo interface{} `json:"logo"`
|
||||||
|
} `json:"customization"`
|
||||||
|
Meta string `json:"meta"`
|
||||||
|
}
|
||||||
|
|
|
||||||
88
internal/domain/currency.go
Normal file
88
internal/domain/currency.go
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntCurrency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ETB IntCurrency = "ETB" // Ethiopian Birr
|
||||||
|
NGN IntCurrency = "NGN" // Nigerian Naira
|
||||||
|
ZAR IntCurrency = "ZAR" // South African Rand
|
||||||
|
EGP IntCurrency = "EGP" // Egyptian Pound
|
||||||
|
KES IntCurrency = "KES" // Kenyan Shilling
|
||||||
|
UGX IntCurrency = "UGX" // Ugandan Shilling
|
||||||
|
TZS IntCurrency = "TZS" // Tanzanian Shilling
|
||||||
|
RWF IntCurrency = "RWF" // Rwandan Franc
|
||||||
|
BIF IntCurrency = "BIF" // Burundian Franc
|
||||||
|
XOF IntCurrency = "XOF" // West African CFA Franc (BCEAO)
|
||||||
|
XAF IntCurrency = "XAF" // Central African CFA Franc (BEAC)
|
||||||
|
GHS IntCurrency = "GHS" // Ghanaian Cedi
|
||||||
|
SDG IntCurrency = "SDG" // Sudanese Pound
|
||||||
|
SSP IntCurrency = "SSP" // South Sudanese Pound
|
||||||
|
DZD IntCurrency = "DZD" // Algerian Dinar
|
||||||
|
MAD IntCurrency = "MAD" // Moroccan Dirham
|
||||||
|
TND IntCurrency = "TND" // Tunisian Dinar
|
||||||
|
LYD IntCurrency = "LYD" // Libyan Dinar
|
||||||
|
MZN IntCurrency = "MZN" // Mozambican Metical
|
||||||
|
AOA IntCurrency = "AOA" // Angolan Kwanza
|
||||||
|
BWP IntCurrency = "BWP" // Botswana Pula
|
||||||
|
ZMW IntCurrency = "ZMW" // Zambian Kwacha
|
||||||
|
MWK IntCurrency = "MWK" // Malawian Kwacha
|
||||||
|
LSL IntCurrency = "LSL" // Lesotho Loti
|
||||||
|
NAD IntCurrency = "NAD" // Namibian Dollar
|
||||||
|
SZL IntCurrency = "SZL" // Swazi Lilangeni
|
||||||
|
CVE IntCurrency = "CVE" // Cape Verdean Escudo
|
||||||
|
GMD IntCurrency = "GMD" // Gambian Dalasi
|
||||||
|
SLL IntCurrency = "SLL" // Sierra Leonean Leone
|
||||||
|
LRD IntCurrency = "LRD" // Liberian Dollar
|
||||||
|
GNF IntCurrency = "GNF" // Guinean Franc
|
||||||
|
XCD IntCurrency = "XCD" // Eastern Caribbean Dollar (used in Saint Lucia)
|
||||||
|
MRU IntCurrency = "MRU" // Mauritanian Ouguiya
|
||||||
|
KMF IntCurrency = "KMF" // Comorian Franc
|
||||||
|
DJF IntCurrency = "DJF" // Djiboutian Franc
|
||||||
|
SOS IntCurrency = "SOS" // Somali Shilling
|
||||||
|
ERN IntCurrency = "ERN" // Eritrean Nakfa
|
||||||
|
MGA IntCurrency = "MGA" // Malagasy Ariary
|
||||||
|
SCR IntCurrency = "SCR" // Seychellois Rupee
|
||||||
|
MUR IntCurrency = "MUR" // Mauritian Rupee
|
||||||
|
|
||||||
|
// International currencies (already listed)
|
||||||
|
USD IntCurrency = "USD" // US Dollar
|
||||||
|
EUR IntCurrency = "EUR" // Euro
|
||||||
|
GBP IntCurrency = "GBP" // British Pound
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnsupportedIntCurrency = errors.New("unsupported IntCurrency")
|
||||||
|
ErrIntCurrencyConversion = errors.New("IntCurrency conversion failed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntCurrencyRate represents exchange rate between two currencies
|
||||||
|
type IntCurrencyRate struct {
|
||||||
|
From IntCurrency
|
||||||
|
To IntCurrency
|
||||||
|
Rate float64
|
||||||
|
ValidUntil time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert converts amount from one IntCurrency to another
|
||||||
|
func (cr IntCurrencyRate) Convert(amount float64) (float64, error) {
|
||||||
|
if time.Now().After(cr.ValidUntil) {
|
||||||
|
return 0, fmt.Errorf("%w: rate expired", ErrIntCurrencyConversion)
|
||||||
|
}
|
||||||
|
return amount * cr.Rate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateIntCurrency checks if IntCurrency is supported
|
||||||
|
func ValidateIntCurrency(c IntCurrency) error {
|
||||||
|
switch c {
|
||||||
|
case ETB, USD, EUR, GBP:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrUnsupportedIntCurrency, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,14 @@ func UnProcessableEntityResponse(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnExpectedErrorResponse(c *fiber.Ctx) error {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
||||||
|
Message: "Unexpected internal error",
|
||||||
|
StatusCode: fiber.StatusInternalServerError,
|
||||||
|
Success: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func FiberErrorResponse(c *fiber.Ctx, err error) error {
|
func FiberErrorResponse(c *fiber.Ctx, err error) error {
|
||||||
var statusCode int
|
var statusCode int
|
||||||
var message string
|
var message string
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ type Transfer struct {
|
||||||
ReceiverWalletID ValidInt64
|
ReceiverWalletID ValidInt64
|
||||||
SenderWalletID ValidInt64
|
SenderWalletID ValidInt64
|
||||||
ReferenceNumber string
|
ReferenceNumber string
|
||||||
|
Status string
|
||||||
CashierID ValidInt64
|
CashierID ValidInt64
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|
@ -50,6 +51,7 @@ type CreateTransfer struct {
|
||||||
Amount Currency
|
Amount Currency
|
||||||
Verified bool
|
Verified bool
|
||||||
ReferenceNumber string
|
ReferenceNumber string
|
||||||
|
Status string
|
||||||
ReceiverWalletID ValidInt64
|
ReceiverWalletID ValidInt64
|
||||||
SenderWalletID ValidInt64
|
SenderWalletID ValidInt64
|
||||||
CashierID ValidInt64
|
CashierID ValidInt64
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import "time"
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
ID int64
|
ID int64
|
||||||
Balance Currency
|
Balance Currency
|
||||||
|
Currency IntCurrency
|
||||||
IsWithdraw bool
|
IsWithdraw bool
|
||||||
IsBettable bool
|
IsBettable bool
|
||||||
IsTransferable bool
|
IsTransferable bool
|
||||||
|
|
|
||||||
96
internal/repository/currency.go
Normal file
96
internal/repository/currency.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CurrencyRepository interface {
|
||||||
|
GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error)
|
||||||
|
StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error
|
||||||
|
GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CurrencyPostgresRepository struct {
|
||||||
|
store *Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCurrencyPostgresRepository(store *Store) *CurrencyPostgresRepository {
|
||||||
|
return &CurrencyPostgresRepository{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CurrencyPostgresRepository) GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error) {
|
||||||
|
const query = `
|
||||||
|
SELECT from_currency, to_currency, rate, precision, valid_until
|
||||||
|
FROM exchange_rates
|
||||||
|
WHERE from_currency = $1 AND to_currency = $2 AND valid_until > NOW()
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1`
|
||||||
|
|
||||||
|
var rate domain.IntCurrencyRate
|
||||||
|
err := r.store.conn.QueryRow(ctx, query, from, to).Scan(
|
||||||
|
&rate.From,
|
||||||
|
&rate.To,
|
||||||
|
&rate.Rate,
|
||||||
|
&rate.ValidUntil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return domain.IntCurrencyRate{}, fmt.Errorf("%w: no rate found for %s to %s",
|
||||||
|
domain.ErrIntCurrencyConversion, from, to)
|
||||||
|
}
|
||||||
|
return domain.IntCurrencyRate{}, fmt.Errorf("failed to get exchange rate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CurrencyPostgresRepository) StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO exchange_rates (from_currency, to_currency, rate, precision, valid_until)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
ON CONFLICT (from_currency, to_currency)
|
||||||
|
DO UPDATE SET
|
||||||
|
rate = EXCLUDED.rate,
|
||||||
|
precision = EXCLUDED.precision,
|
||||||
|
valid_until = EXCLUDED.valid_until,
|
||||||
|
created_at = NOW()`
|
||||||
|
|
||||||
|
_, err := r.store.conn.Exec(ctx, query,
|
||||||
|
rate.From,
|
||||||
|
rate.To,
|
||||||
|
rate.Rate,
|
||||||
|
rate.ValidUntil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to store exchange rate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CurrencyPostgresRepository) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
|
||||||
|
const query = `SELECT DISTINCT currency FROM supported_currencies ORDER BY currency`
|
||||||
|
|
||||||
|
var currencies []domain.IntCurrency
|
||||||
|
rows, err := r.store.conn.Query(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get supported currencies: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var currency domain.IntCurrency
|
||||||
|
if err := rows.Scan(¤cy); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan currency: %w", err)
|
||||||
|
}
|
||||||
|
currencies = append(currencies, currency)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("row iteration error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return currencies, nil
|
||||||
|
}
|
||||||
|
|
@ -22,21 +22,27 @@ func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer {
|
||||||
Value: transfer.SenderWalletID.Int64,
|
Value: transfer.SenderWalletID.Int64,
|
||||||
Valid: transfer.SenderWalletID.Valid,
|
Valid: transfer.SenderWalletID.Valid,
|
||||||
},
|
},
|
||||||
|
ID: transfer.ID,
|
||||||
|
Amount: domain.Currency(transfer.Amount.Int64),
|
||||||
|
Type: domain.TransferType(transfer.Type.String),
|
||||||
|
Verified: transfer.Verified.Bool,
|
||||||
|
ReceiverWalletID: transfer.ReceiverWalletID.Int64,
|
||||||
|
SenderWalletID: transfer.SenderWalletID.Int64,
|
||||||
CashierID: domain.ValidInt64{
|
CashierID: domain.ValidInt64{
|
||||||
Value: transfer.CashierID.Int64,
|
Value: transfer.CashierID.Int64,
|
||||||
Valid: transfer.CashierID.Valid,
|
Valid: transfer.CashierID.Valid,
|
||||||
},
|
},
|
||||||
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod),
|
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams {
|
func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams {
|
||||||
return dbgen.CreateTransferParams{
|
return dbgen.CreateTransferParams{
|
||||||
Amount: int64(transfer.Amount),
|
Amount: pgtype.Int8{Int64: int64(transfer.Amount), Valid: true},
|
||||||
Type: string(transfer.Type),
|
Type: pgtype.Text{String: string(transfer.Type), Valid: true},
|
||||||
ReceiverWalletID: pgtype.Int8{
|
ReceiverWalletID: pgtype.Int8{
|
||||||
Int64: transfer.ReceiverWalletID.Value,
|
Int64: transfer.ReceiverWalletID,
|
||||||
Valid: transfer.ReceiverWalletID.Valid,
|
Valid: true,
|
||||||
},
|
},
|
||||||
SenderWalletID: pgtype.Int8{
|
SenderWalletID: pgtype.Int8{
|
||||||
Int64: transfer.SenderWalletID.Value,
|
Int64: transfer.SenderWalletID.Value,
|
||||||
|
|
@ -46,7 +52,7 @@ func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferP
|
||||||
Int64: transfer.CashierID.Value,
|
Int64: transfer.CashierID.Value,
|
||||||
Valid: transfer.CashierID.Valid,
|
Valid: transfer.CashierID.Valid,
|
||||||
},
|
},
|
||||||
PaymentMethod: string(transfer.PaymentMethod),
|
PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,10 +77,7 @@ func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
||||||
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{
|
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{Int64: walletID, Valid: true})
|
||||||
Int64: walletID,
|
|
||||||
Valid: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +91,7 @@ func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]dom
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error) {
|
func (s *Store) GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error) {
|
||||||
transfer, err := s.queries.GetTransferByReference(ctx, reference)
|
transfer, err := s.queries.GetTransferByReference(ctx, pgtype.Text{String: reference, Valid: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.Transfer{}, nil
|
return domain.Transfer{}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +109,16 @@ func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.Transfer,
|
||||||
func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
||||||
err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{
|
err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{
|
||||||
ID: id,
|
ID: id,
|
||||||
Verified: verified,
|
Verified: pgtype.Bool{Bool: verified, Valid: true},
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||||
|
err := s.queries.UpdateTransferStatus(ctx, dbgen.UpdateTransferStatusParams{
|
||||||
|
ID: id,
|
||||||
|
Status: pgtype.Text{String: status, Valid: true},
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -211,24 +212,78 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return banks, nil
|
return banks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to generate account regex based on bank type
|
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
||||||
// func GetAccountRegex(bank domain.Bank) string {
|
// base, err := url.Parse(c.baseURL)
|
||||||
// if bank.IsMobileMoney != nil && bank.IsMobileMoney == 1 {
|
// if err != nil {
|
||||||
// return `^09[0-9]{8}$` // Ethiopian mobile money pattern
|
// return false, fmt.Errorf("invalid base URL: %w", err)
|
||||||
// }
|
// }
|
||||||
// return fmt.Sprintf(`^[0-9]{%d}$`, bank.AcctLength)
|
endpoint := c.baseURL + "/transfers"
|
||||||
// }
|
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
||||||
|
|
||||||
// // Helper method to generate example account number
|
reqBody, err := json.Marshal(req)
|
||||||
// func GetExampleAccount(bank domain.Bank) string {
|
if err != nil {
|
||||||
// if bank.IsMobileMoney != nil && *bank.IsMobileMoney {
|
return false, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
// return "0912345678" // Ethiopian mobile number example
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// // Generate example based on length
|
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody))
|
||||||
// example := "1"
|
if err != nil {
|
||||||
// for i := 1; i < bank.AcctLength; i++ {
|
return false, fmt.Errorf("failed to create request: %w", err)
|
||||||
// example += fmt.Sprintf("%d", i%10)
|
}
|
||||||
// }
|
|
||||||
// return example
|
c.setHeaders(httpReq)
|
||||||
// }
|
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return false, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response domain.ChapaWithdrawalResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return false, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Status == string(domain.WithdrawalStatusProcessing), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
||||||
|
base, err := url.Parse(c.baseURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid base URL: %w", err)
|
||||||
|
}
|
||||||
|
endpoint := base.ResolveReference(&url.URL{Path: fmt.Sprintf("/v1/transfers/%s/verify", reference)})
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.setHeaders(httpReq)
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var verification domain.ChapaVerificationResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &verification, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) setHeaders(req *http.Request) {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ import (
|
||||||
|
|
||||||
type ChapaStore interface {
|
type ChapaStore interface {
|
||||||
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
|
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
|
||||||
VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
|
// VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
|
||||||
ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
||||||
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
|
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||||
|
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
|
||||||
|
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -20,7 +21,7 @@ var (
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
transferStore wallet.TransferStore
|
transferStore wallet.TransferStore
|
||||||
walletStore wallet.WalletStore
|
walletStore wallet.Service
|
||||||
userStore user.UserStore
|
userStore user.UserStore
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
chapaClient *Client
|
chapaClient *Client
|
||||||
|
|
@ -28,7 +29,7 @@ type Service struct {
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
transferStore wallet.TransferStore,
|
transferStore wallet.TransferStore,
|
||||||
walletStore wallet.WalletStore,
|
walletStore wallet.Service,
|
||||||
userStore user.UserStore,
|
userStore user.UserStore,
|
||||||
chapaClient *Client,
|
chapaClient *Client,
|
||||||
|
|
||||||
|
|
@ -113,47 +114,100 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
|
|
||||||
return response.CheckoutURL, nil
|
return response.CheckoutURL, nil
|
||||||
}
|
}
|
||||||
|
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||||
|
// Parse and validate amount
|
||||||
|
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
||||||
|
if err != nil || amount <= 0 {
|
||||||
|
return nil, domain.ErrInvalidWithdrawalAmount
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyDeposit handles payment verification from webhook
|
// Get user details
|
||||||
func (s *Service) VerifyDeposit(ctx context.Context, reference string) error {
|
// user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
// Find payment by reference
|
// if err != nil {
|
||||||
payment, err := s.transferStore.GetTransferByReference(ctx, reference)
|
// return nil, fmt.Errorf("failed to get user: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Get user's wallet
|
||||||
|
wallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrPaymentNotFound
|
return nil, fmt.Errorf("failed to get user wallets: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// just making sure that the sender id is valid
|
var withdrawWallet domain.Wallet
|
||||||
if !payment.SenderWalletID.Valid {
|
for _, wallet := range wallets {
|
||||||
return fmt.Errorf("sender wallet is invalid %v \n", payment.SenderWalletID)
|
if wallet.IsWithdraw {
|
||||||
}
|
withdrawWallet = wallet
|
||||||
|
break
|
||||||
// Skip if already completed
|
|
||||||
if payment.Verified {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify payment with Chapa
|
|
||||||
verification, err := s.chapaClient.VerifyPayment(ctx, reference)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to verify payment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update payment status
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If payment is completed, credit user's wallet
|
|
||||||
if verification.Status == domain.PaymentStatusCompleted {
|
|
||||||
if err := s.walletStore.UpdateBalance(ctx, payment.SenderWalletID.Value, payment.Amount); err != nil {
|
|
||||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if withdrawWallet.ID == 0 {
|
||||||
|
return nil, errors.New("withdrawal wallet not found")
|
||||||
|
}
|
||||||
|
// Check balance
|
||||||
|
if withdrawWallet.Balance < domain.Currency(amount) {
|
||||||
|
return nil, domain.ErrInsufficientBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique reference
|
||||||
|
reference := uuid.New().String()
|
||||||
|
|
||||||
|
createTransfer := domain.CreateTransfer{
|
||||||
|
Amount: domain.Currency(amount),
|
||||||
|
Type: domain.WITHDRAW,
|
||||||
|
ReceiverWalletID: 1,
|
||||||
|
SenderWalletID: withdrawWallet.ID,
|
||||||
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
Verified: false,
|
||||||
|
ReferenceNumber: reference,
|
||||||
|
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer, err := s.transferStore.CreateTransfer(ctx, createTransfer)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create transfer record: %w", err)
|
||||||
|
}
|
||||||
|
// Initiate transfer with Chapa
|
||||||
|
transferReq := domain.ChapaWithdrawalRequest{
|
||||||
|
AccountName: req.AccountName,
|
||||||
|
AccountNumber: req.AccountNumber,
|
||||||
|
Amount: fmt.Sprintf("%d", amount),
|
||||||
|
Currency: req.Currency,
|
||||||
|
Reference: reference,
|
||||||
|
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err := s.chapaClient.InitiateTransfer(ctx, transferReq)
|
||||||
|
if err != nil || !success {
|
||||||
|
// Update withdrawal status to failed
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update withdrawal status to processing
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
||||||
|
}
|
||||||
|
// Deduct from wallet (or wait for webhook confirmation depending on your flow)
|
||||||
|
newBalance := withdrawWallet.Balance - domain.Currency(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, withdrawWallet.ID, newBalance); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &transfer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||||
|
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||||
|
}
|
||||||
|
return banks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
// First check if we already have a verified record
|
// First check if we already have a verified record
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
if err == nil && transfer.Verified {
|
if err == nil && transfer.Verified {
|
||||||
|
|
@ -196,10 +250,76 @@ func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domai
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error {
|
||||||
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
// Find payment by reference
|
||||||
|
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.Reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
return ErrPaymentNotFound
|
||||||
}
|
}
|
||||||
return banks, nil
|
|
||||||
|
if payment.Verified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify payment with Chapa
|
||||||
|
// verification, err := s.chapaClient.VerifyPayment(ctx, transfer.Reference)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("failed to verify payment: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Update payment status
|
||||||
|
// verified := false
|
||||||
|
// if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
// verified = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If payment is completed, credit user's wallet
|
||||||
|
if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
if err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID, payment.Amount); err != nil {
|
||||||
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error {
|
||||||
|
// Find payment by reference
|
||||||
|
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
||||||
|
if err != nil {
|
||||||
|
return ErrPaymentNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if transfer.Verified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify payment with Chapa
|
||||||
|
// verification, err := s.chapaClient.VerifyPayment(ctx, payment.Reference)
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("failed to verify payment: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Update payment status
|
||||||
|
// verified := false
|
||||||
|
// if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
// verified = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If payment is completed, credit user's wallet
|
||||||
|
if payment.Status == string(domain.PaymentStatusFailed) {
|
||||||
|
if err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID, transfer.Amount); err != nil {
|
||||||
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
69
internal/services/currency/fetcher.go
Normal file
69
internal/services/currency/fetcher.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package currency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FixerFetcher struct {
|
||||||
|
apiKey string
|
||||||
|
baseURL string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFixerFetcher(apiKey string, baseURL string) *FixerFetcher {
|
||||||
|
return &FixerFetcher{
|
||||||
|
apiKey: apiKey,
|
||||||
|
baseURL: baseURL,
|
||||||
|
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fixerResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Base string `json:"base"`
|
||||||
|
Date string `json:"date"`
|
||||||
|
Rates map[string]float64 `json:"rates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FixerFetcher) FetchLatestRates(ctx context.Context, baseCurrency domain.IntCurrency) (map[domain.IntCurrency]float64, error) {
|
||||||
|
url := fmt.Sprintf("%s/latest?base=%s", f.baseURL, baseCurrency)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("apikey", f.apiKey)
|
||||||
|
|
||||||
|
resp, err := f.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch rates: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result fixerResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result.Success {
|
||||||
|
return nil, fmt.Errorf("api returned unsuccessful response")
|
||||||
|
}
|
||||||
|
|
||||||
|
rates := make(map[domain.IntCurrency]float64)
|
||||||
|
for currency, rate := range result.Rates {
|
||||||
|
rates[domain.IntCurrency(currency)] = rate
|
||||||
|
}
|
||||||
|
|
||||||
|
return rates, nil
|
||||||
|
}
|
||||||
125
internal/services/currency/service.go
Normal file
125
internal/services/currency/service.go
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
package currency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo repository.CurrencyRepository
|
||||||
|
baseCurrency domain.IntCurrency
|
||||||
|
fixerFetcher *FixerFetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(repo repository.CurrencyRepository, baseCurrency domain.IntCurrency, fixerFetcher *FixerFetcher) *Service {
|
||||||
|
return &Service{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Convert(ctx context.Context, amount float64, from, to domain.IntCurrency) (float64, error) {
|
||||||
|
if from == to {
|
||||||
|
return amount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rate, err := s.repo.GetExchangeRate(ctx, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rate.Convert(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
|
||||||
|
return s.repo.GetSupportedCurrencies(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateRates(ctx context.Context) error {
|
||||||
|
// Implement fetching from external API (e.g., Fixer, Open Exchange Rates)
|
||||||
|
rates := map[domain.IntCurrency]map[domain.IntCurrency]float64{
|
||||||
|
domain.ETB: {
|
||||||
|
domain.USD: 0.018,
|
||||||
|
domain.EUR: 0.016,
|
||||||
|
domain.GBP: 0.014,
|
||||||
|
},
|
||||||
|
// Add other currencies...
|
||||||
|
}
|
||||||
|
|
||||||
|
for from, toRates := range rates {
|
||||||
|
for to, rate := range toRates {
|
||||||
|
err := s.repo.StoreExchangeRate(ctx, domain.IntCurrencyRate{
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
Rate: rate,
|
||||||
|
ValidUntil: time.Now().Add(24 * time.Hour), // Refresh daily
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAndStoreRates(ctx context.Context) error {
|
||||||
|
// s.logger.Info("Starting exchange rate update")
|
||||||
|
|
||||||
|
rates, err := s.fixerFetcher.FetchLatestRates(ctx, s.baseCurrency)
|
||||||
|
if err != nil {
|
||||||
|
// s.logger.Error("Failed to fetch rates", "error", err)
|
||||||
|
return fmt.Errorf("failed to fetch rates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to integer rates with precision
|
||||||
|
const precision = 6 // 1.000000
|
||||||
|
for currency, rate := range rates {
|
||||||
|
if currency == s.baseCurrency {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
intRate := domain.IntCurrencyRate{
|
||||||
|
From: s.baseCurrency,
|
||||||
|
To: currency,
|
||||||
|
Rate: rate * float64(pow10(precision)),
|
||||||
|
ValidUntil: time.Now().Add(24 * time.Hour), // Rates valid for 24 hours
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.StoreExchangeRate(ctx, intRate); err != nil {
|
||||||
|
// s.logger.Error("Failed to store rate",
|
||||||
|
// "from", s.baseCurrency,
|
||||||
|
// "to", currency,
|
||||||
|
// "error", err)
|
||||||
|
continue // Try to store other rates even if one fails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also store the inverse rate
|
||||||
|
inverseRate := domain.IntCurrencyRate{
|
||||||
|
From: currency,
|
||||||
|
To: s.baseCurrency,
|
||||||
|
Rate: (1 / rate) * float64(pow10(precision)),
|
||||||
|
ValidUntil: time.Now().Add(24 * time.Hour),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.StoreExchangeRate(ctx, inverseRate); err != nil {
|
||||||
|
// s.logger.Error("Failed to store inverse rate",
|
||||||
|
// "from", currency,
|
||||||
|
// "to", s.baseCurrency,
|
||||||
|
// "error", err)
|
||||||
|
return fmt.Errorf("Error storing exchange rates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// s.logger.Info("Exchange rates updated successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pow10(n int) int64 {
|
||||||
|
result := int64(1)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
result *= 10
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
55
internal/services/currency/worker.go
Normal file
55
internal/services/currency/worker.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
package currency
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/go-co-op/gocron"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExchangeRateWorker struct {
|
||||||
|
fetcherService *FixerFetcher
|
||||||
|
scheduler *gocron.Scheduler
|
||||||
|
logger *slog.Logger
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExchangeRateWorker(
|
||||||
|
fetcherService *FixerFetcher, logger *slog.Logger, cfg *config.Config,
|
||||||
|
) *ExchangeRateWorker {
|
||||||
|
return &ExchangeRateWorker{
|
||||||
|
fetcherService: fetcherService,
|
||||||
|
scheduler: gocron.NewScheduler(time.UTC),
|
||||||
|
logger: logger,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ExchangeRateWorker) Start(ctx context.Context) {
|
||||||
|
_, err := w.scheduler.Every(6).Hours().Do(w.RunUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run immediately on startup
|
||||||
|
go w.RunUpdate()
|
||||||
|
|
||||||
|
w.scheduler.StartAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ExchangeRateWorker) RunUpdate() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if _, err := w.fetcherService.FetchLatestRates(ctx, w.cfg.BASE_CURRENCY); err != nil {
|
||||||
|
fmt.Println("Exchange rate update failed", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ExchangeRateWorker) Stop() {
|
||||||
|
w.scheduler.Stop()
|
||||||
|
w.logger.Info("Exchange rate worker stopped")
|
||||||
|
}
|
||||||
|
|
@ -28,4 +28,5 @@ type TransferStore interface {
|
||||||
GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error)
|
GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error)
|
||||||
GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error)
|
GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error)
|
||||||
UpdateTransferVerification(ctx context.Context, id int64, verified bool) error
|
UpdateTransferVerification(ctx context.Context, id int64, verified bool) error
|
||||||
|
UpdateTransferStatus(ctx context.Context, id int64, status string) error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,17 @@ func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, veri
|
||||||
return s.transferStore.UpdateTransferVerification(ctx, id, verified)
|
return s.transferStore.UpdateTransferVerification(ctx, id, verified)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||||
|
return s.transferStore.UpdateTransferStatus(ctx, id, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||||
|
return s.transferStore.UpdateTransferStatus(ctx, id, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *Service) TransferToWallet(ctx context.Context,
|
|
||||||
senderID int64, receiverID int64,
|
func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64,
|
||||||
amount domain.Currency, paymentMethod domain.PaymentMethod,
|
amount domain.Currency, paymentMethod domain.PaymentMethod,
|
||||||
cashierID domain.ValidInt64) (domain.Transfer, error) {
|
cashierID domain.ValidInt64) (domain.Transfer, error) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
|
|
@ -34,6 +35,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
currSvc *currency.Service
|
||||||
fiber *fiber.App
|
fiber *fiber.App
|
||||||
aleaVirtualGameService alea.AleaVirtualGameService
|
aleaVirtualGameService alea.AleaVirtualGameService
|
||||||
veliVirtualGameService veli.VeliVirtualGameService
|
veliVirtualGameService veli.VeliVirtualGameService
|
||||||
|
|
@ -64,6 +66,7 @@ type App struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp(
|
func NewApp(
|
||||||
|
currSvc *currency.Service,
|
||||||
port int, validator *customvalidator.CustomValidator,
|
port int, validator *customvalidator.CustomValidator,
|
||||||
authSvc *authentication.Service,
|
authSvc *authentication.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
|
|
@ -104,6 +107,7 @@ func NewApp(
|
||||||
}))
|
}))
|
||||||
|
|
||||||
s := &App{
|
s := &App{
|
||||||
|
currSvc: currSvc,
|
||||||
fiber: app,
|
fiber: app,
|
||||||
port: port,
|
port: port,
|
||||||
authSvc: authSvc,
|
authSvc: authSvc,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
// @Param request body domain.ChapaDepositRequestPayload true "Deposit request"
|
// @Param request body domain.ChapaDepositRequestPayload true "Deposit request"
|
||||||
// @Success 200 {object} domain.ChapaDepositResponse
|
// @Success 200 {object} domain.ChapaDepositResponse
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
|
@ -65,38 +66,59 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
||||||
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
// Verify webhook signature first
|
|
||||||
// signature := c.Get("Chapa-Signature")
|
|
||||||
// if !verifySignature(signature, c.Body()) {
|
|
||||||
// return c.Status(fiber.StatusUnauthorized).JSON(ErrorResponse{
|
|
||||||
// Error: "invalid signature",
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
var payload struct {
|
chapaTransactionType := new(domain.ChapaTransactionType)
|
||||||
TxRef string `json:"tx_ref"`
|
|
||||||
Amount float64 `json:"amount"`
|
if parseTypeErr := c.BodyParser(chapaTransactionType); parseTypeErr != nil {
|
||||||
Currency string `json:"currency"`
|
return domain.UnProcessableEntityResponse(c)
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
switch chapaTransactionType.Type {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
case h.Cfg.CHAPA_TRANSFER_TYPE:
|
||||||
Error: err.Error(),
|
chapaTransferVerificationRequest := new(domain.ChapaWebHookTransfer)
|
||||||
|
|
||||||
|
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.chapaSvc.HandleVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to verify Chapa depposit",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Message: "Chapa deposit transaction verified successfully",
|
||||||
|
Data: chapaTransferVerificationRequest,
|
||||||
|
Success: true,
|
||||||
})
|
})
|
||||||
}
|
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
||||||
|
chapaPaymentVerificationRequest := new(domain.ChapaWebHookPayment)
|
||||||
|
if err := c.BodyParser(chapaPaymentVerificationRequest); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.chapaSvc.VerifyDeposit(c.Context(), payload.TxRef); err != nil {
|
err := h.chapaSvc.HandleVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
if err != nil {
|
||||||
Error: err.Error(),
|
return domain.UnExpectedErrorResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Message: "Chapa withdrawal transaction verified successfully",
|
||||||
|
Data: chapaPaymentVerificationRequest,
|
||||||
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
// Return a 400 Bad Request if the type does not match any known case
|
||||||
StatusCode: 200,
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "payment verified successfully",
|
Message: "Invalid Chapa webhook type",
|
||||||
Data: payload.TxRef,
|
Error: "Unknown transaction type",
|
||||||
Success: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +133,7 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/payments/manual/verify/{tx_ref} [get]
|
// @Router /api/v1/chapa/payments/manual/verify/{tx_ref} [get]
|
||||||
func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
||||||
txRef := c.Params("tx_ref")
|
txRef := c.Params("tx_ref")
|
||||||
if txRef == "" {
|
if txRef == "" {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -120,7 +142,7 @@ func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
verification, err := h.chapaSvc.ManualVerifyPayment(c.Context(), txRef)
|
verification, err := h.chapaSvc.ManualVerifTransaction(c.Context(), txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to verify Chapa transaction",
|
Message: "Failed to verify Chapa transaction",
|
||||||
|
|
@ -142,9 +164,9 @@ func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {array} domain.Bank
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /banks [get]
|
// @Router /api/v1/chapa/banks [get]
|
||||||
func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
||||||
banks, err := h.chapaSvc.GetSupportedBanks(c.Context())
|
banks, err := h.chapaSvc.GetSupportedBanks(c.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -161,3 +183,44 @@ func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
||||||
Data: banks,
|
Data: banks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitiateWithdrawal initiates a withdrawal request via Chapa payment gateway
|
||||||
|
// @Summary Initiate a withdrawal
|
||||||
|
// @Description Initiates a withdrawal request to transfer funds to a bank account via Chapa
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
||||||
|
// @Success 201 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
||||||
|
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
||||||
|
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse "Internal server error"
|
||||||
|
// @Router /api/v1/chapa/payments/withdraw [post]
|
||||||
|
func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
var req domain.ChapaWithdrawalRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
withdrawal, err := h.chapaSvc.InitiateWithdrawal(c.Context(), userID, req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to initiate Chapa withdrawal",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||||
|
Message: "Chapa withdrawal process initiated successfully",
|
||||||
|
StatusCode: 201,
|
||||||
|
Success: true,
|
||||||
|
Data: withdrawal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
57
internal/web_server/handlers/currency.go
Normal file
57
internal/web_server/handlers/currency.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @Summary Get supported currencies
|
||||||
|
// @Description Returns list of supported currencies
|
||||||
|
// @Tags Multi-Currency
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.Currency}
|
||||||
|
// @Router /api/v1/currencies [get]
|
||||||
|
func (h *Handler) GetSupportedCurrencies(c *fiber.Ctx) error {
|
||||||
|
currencies, err := h.currSvc.GetSupportedCurrencies(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return domain.UnExpectedErrorResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Success: true,
|
||||||
|
Message: "Supported currencies retrieved successfully",
|
||||||
|
Data: currencies,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Convert currency
|
||||||
|
// @Description Converts amount from one currency to another
|
||||||
|
// @Tags Multi-Currency
|
||||||
|
// @Produce json
|
||||||
|
// @Param from query string true "Source currency code (e.g., USD)"
|
||||||
|
// @Param to query string true "Target currency code (e.g., ETB)"
|
||||||
|
// @Param amount query number true "Amount to convert"
|
||||||
|
// @Success 200 {object} domain.Response{data=float64}
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/currencies/convert [get]
|
||||||
|
func (h *Handler) ConvertCurrency(c *fiber.Ctx) error {
|
||||||
|
from := domain.IntCurrency(c.Query("from"))
|
||||||
|
to := domain.IntCurrency(c.Query("to"))
|
||||||
|
amount := c.QueryFloat("amount", 0)
|
||||||
|
// if err != nil {
|
||||||
|
// return domain.BadRequestResponse(c)
|
||||||
|
// }
|
||||||
|
|
||||||
|
converted, err := h.currSvc.Convert(c.Context(), amount, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return domain.UnExpectedErrorResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Success: true,
|
||||||
|
Message: "Currency converted successfully",
|
||||||
|
Data: converted,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -9,14 +9,15 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
|
|
@ -29,6 +30,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
currSvc *currency.Service
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
notificationSvc *notificationservice.Service
|
notificationSvc *notificationservice.Service
|
||||||
userSvc *user.Service
|
userSvc *user.Service
|
||||||
|
|
@ -56,6 +58,7 @@ type Handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
|
currSvc *currency.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
validator *customvalidator.CustomValidator,
|
validator *customvalidator.CustomValidator,
|
||||||
|
|
@ -82,6 +85,7 @@ func New(
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
) *Handler {
|
) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
|
currSvc: currSvc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
notificationSvc: notificationSvc,
|
notificationSvc: notificationSvc,
|
||||||
reportSvc: reportSvc,
|
reportSvc: reportSvc,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
// @Param sport_id query string false "Sport ID filter"
|
// @Param sport_id query string false "Sport ID filter"
|
||||||
// @Param status query int false "Status filter (0=Pending, 1=Win, 2=Loss, 3=Half, 4=Void, 5=Error)"
|
// @Param status query int false "Status filter (0=Pending, 1=Win, 2=Loss, 3=Half, 4=Void, 5=Error)"
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Success 200 {object} report.DashboardSummary
|
// @Success 200 {object} domain.DashboardSummary
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 401 {object} domain.ErrorResponse
|
// @Failure 401 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResultRes struct {
|
type ResultRes struct {
|
||||||
ResultData json.RawMessage `json:"result_data"`
|
ResultData json.RawMessage `json:"result_data" swaggerignore:"true"`
|
||||||
Outcomes []domain.BetOutcome `json:"outcomes"`
|
Outcomes []domain.BetOutcome `json:"outcomes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
h := handlers.New(
|
h := handlers.New(
|
||||||
|
a.currSvc,
|
||||||
a.logger,
|
a.logger,
|
||||||
a.NotidicationStore,
|
a.NotidicationStore,
|
||||||
a.validator,
|
a.validator,
|
||||||
|
|
@ -199,10 +200,15 @@ a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
group.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
group.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
||||||
group.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyPayment)
|
group.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
||||||
group.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
group.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||||
|
group.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||||
group.Get("/chapa/banks", h.GetSupportedBanks)
|
group.Get("/chapa/banks", h.GetSupportedBanks)
|
||||||
|
|
||||||
|
// Currencies
|
||||||
|
group.Get("/currencies", h.GetSupportedCurrencies)
|
||||||
|
group.Get("/currencies/convert", h.ConvertCurrency)
|
||||||
|
|
||||||
//Report Routes
|
//Report Routes
|
||||||
group.Get("/reports/dashboard", h.GetDashboardReport)
|
group.Get("/reports/dashboard", h.GetDashboardReport)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user