Merge remote-tracking branch 'origin/virtual_games'
This commit is contained in:
commit
ab1d85897e
16
cmd/main.go
16
cmd/main.go
|
|
@ -60,6 +60,7 @@ import (
|
||||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/orchestration"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet/monitor"
|
||||||
|
|
@ -251,6 +252,12 @@ func main() {
|
||||||
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
|
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
|
||||||
veliCLient := veli.NewClient(cfg, walletSvc)
|
veliCLient := veli.NewClient(cfg, walletSvc)
|
||||||
veliVirtualGameService := veli.New(virtualGameSvc, vitualGameRepo, veliCLient, walletSvc, repository.NewTransferStore(store), domain.MongoDBLogger, cfg)
|
veliVirtualGameService := veli.New(virtualGameSvc, vitualGameRepo, veliCLient, walletSvc, repository.NewTransferStore(store), domain.MongoDBLogger, cfg)
|
||||||
|
orchestrationSvc := orchestration.New(
|
||||||
|
virtualGameSvc,
|
||||||
|
virtuaGamesRepo,
|
||||||
|
cfg,
|
||||||
|
veliCLient,
|
||||||
|
)
|
||||||
atlasClient := atlas.NewClient(cfg, walletSvc)
|
atlasClient := atlas.NewClient(cfg, walletSvc)
|
||||||
atlasVirtualGameService := atlas.New(virtualGameSvc, vitualGameRepo, atlasClient, walletSvc, repository.NewTransferStore(store), cfg)
|
atlasVirtualGameService := atlas.New(virtualGameSvc, vitualGameRepo, atlasClient, walletSvc, repository.NewTransferStore(store), cfg)
|
||||||
recommendationSvc := recommendation.NewService(recommendationRepo)
|
recommendationSvc := recommendation.NewService(recommendationRepo)
|
||||||
|
|
@ -301,8 +308,8 @@ func main() {
|
||||||
store,
|
store,
|
||||||
)
|
)
|
||||||
|
|
||||||
go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, veliVirtualGameService, "C:/Users/User/Desktop")
|
|
||||||
go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger)
|
go httpserver.StartEnetPulseCron(enePulseSvc, domain.MongoDBLogger)
|
||||||
|
go httpserver.SetupReportandVirtualGameCronJobs(context.Background(), reportSvc, orchestrationSvc, "C:/Users/User/Desktop")
|
||||||
go httpserver.ProcessBetCashback(context.TODO(), betSvc)
|
go httpserver.ProcessBetCashback(context.TODO(), betSvc)
|
||||||
|
|
||||||
bankRepository := repository.NewBankRepository(store)
|
bankRepository := repository.NewBankRepository(store)
|
||||||
|
|
@ -339,9 +346,9 @@ func main() {
|
||||||
fixerFertcherSvc,
|
fixerFertcherSvc,
|
||||||
)
|
)
|
||||||
|
|
||||||
exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg)
|
// exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg)
|
||||||
exchangeWorker.Start(context.Background())
|
// exchangeWorker.Start(context.Background())
|
||||||
defer exchangeWorker.Stop()
|
// defer exchangeWorker.Stop()
|
||||||
go walletMonitorSvc.Start()
|
go walletMonitorSvc.Start()
|
||||||
|
|
||||||
httpserver.StartBetAPIDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
|
httpserver.StartBetAPIDataFetchingCrons(eventSvc, *oddsSvc, resultSvc, domain.MongoDBLogger)
|
||||||
|
|
@ -369,6 +376,7 @@ func main() {
|
||||||
enetPulseSvc,
|
enetPulseSvc,
|
||||||
atlasVirtualGameService,
|
atlasVirtualGameService,
|
||||||
veliVirtualGameService,
|
veliVirtualGameService,
|
||||||
|
orchestrationSvc,
|
||||||
telebirrSvc,
|
telebirrSvc,
|
||||||
arifpaySvc,
|
arifpaySvc,
|
||||||
santimpaySvc,
|
santimpaySvc,
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,27 @@ CREATE TABLE IF NOT EXISTS virtual_game_providers (
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ
|
updated_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS virtual_game_provider_reports (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
|
||||||
|
report_date DATE NOT NULL,
|
||||||
|
total_games_played BIGINT DEFAULT 0,
|
||||||
|
total_bets NUMERIC(18,2) DEFAULT 0,
|
||||||
|
total_payouts NUMERIC(18,2) DEFAULT 0,
|
||||||
|
total_profit NUMERIC(18,2) GENERATED ALWAYS AS (total_bets - total_payouts) STORED,
|
||||||
|
total_players BIGINT DEFAULT 0,
|
||||||
|
report_type VARCHAR(50) DEFAULT 'daily',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_provider_report
|
||||||
|
ON virtual_game_provider_reports (provider_id, report_date, report_type);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS virtual_games (
|
CREATE TABLE IF NOT EXISTS virtual_games (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
game_id VARCHAR(150) NOT NULL,
|
game_id VARCHAR(150) UNIQUE NOT NULL,
|
||||||
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
|
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
category VARCHAR(100),
|
category VARCHAR(100),
|
||||||
|
|
@ -54,6 +72,25 @@ CREATE TABLE IF NOT EXISTS virtual_games (
|
||||||
updated_at TIMESTAMPTZ
|
updated_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_virtual_games_provider_game ON virtual_games (provider_id, game_id);
|
CREATE UNIQUE INDEX IF NOT EXISTS ux_virtual_games_provider_game ON virtual_games (provider_id, game_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS virtual_game_reports (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
game_id VARCHAR(150) NOT NULL REFERENCES virtual_games(game_id) ON DELETE CASCADE,
|
||||||
|
provider_id VARCHAR(100) NOT NULL REFERENCES virtual_game_providers(provider_id) ON DELETE CASCADE,
|
||||||
|
report_date DATE NOT NULL,
|
||||||
|
total_rounds BIGINT DEFAULT 0,
|
||||||
|
total_bets NUMERIC(18,2) DEFAULT 0,
|
||||||
|
total_payouts NUMERIC(18,2) DEFAULT 0,
|
||||||
|
total_profit NUMERIC(18,2) GENERATED ALWAYS AS (total_bets - total_payouts) STORED,
|
||||||
|
total_players BIGINT DEFAULT 0,
|
||||||
|
report_type VARCHAR(50) DEFAULT 'daily',
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_game_report
|
||||||
|
ON virtual_game_reports (game_id, report_date, report_type);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS wallets (
|
CREATE TABLE IF NOT EXISTS wallets (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
balance BIGINT NOT NULL DEFAULT 0,
|
balance BIGINT NOT NULL DEFAULT 0,
|
||||||
|
|
@ -234,6 +271,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer (
|
||||||
cashier_id BIGINT,
|
cashier_id BIGINT,
|
||||||
verified BOOLEAN DEFAULT false,
|
verified BOOLEAN DEFAULT false,
|
||||||
reference_number VARCHAR(255) NOT NULL,
|
reference_number VARCHAR(255) NOT NULL,
|
||||||
|
ext_reference_number VARCHAR(255),
|
||||||
session_id VARCHAR(255),
|
session_id VARCHAR(255),
|
||||||
status VARCHAR(255),
|
status VARCHAR(255),
|
||||||
payment_method VARCHAR(255),
|
payment_method VARCHAR(255),
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,8 @@ CREATE TABLE virtual_game_sessions (
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
game_id VARCHAR(50) NOT NULL,
|
game_id VARCHAR(50) NOT NULL,
|
||||||
session_token VARCHAR(255) NOT NULL UNIQUE,
|
session_token VARCHAR(255) NOT NULL UNIQUE,
|
||||||
currency VARCHAR(3) NOT NULL,
|
|
||||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE, COMPLETED, FAILED
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE virtual_game_transactions (
|
CREATE TABLE virtual_game_transactions (
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
DROP TABLE IF EXISTS enetpulse_sports;
|
|
||||||
DROP TABLE IF EXISTS enetpulse_tournament_templates;
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
CREATE TABLE IF NOT EXISTS enetpulse_sports (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
sport_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
|
||||||
name VARCHAR(255) NOT NULL, -- from API "name"
|
|
||||||
updates_count INT DEFAULT 0, -- from API "n"
|
|
||||||
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
|
||||||
status INT DEFAULT 1, -- optional status (active/inactive)
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS enetpulse_tournament_templates (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
template_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
|
||||||
name VARCHAR(255) NOT NULL, -- from API "name"
|
|
||||||
sport_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_sports(sport_id) ON DELETE CASCADE,
|
|
||||||
gender VARCHAR(20) DEFAULT 'unknown', -- from API "gender" {male, female, mixed, unknown}
|
|
||||||
updates_count INT DEFAULT 0, -- from API "n"
|
|
||||||
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
|
||||||
status INT DEFAULT 1, -- optional status (active/inactive)
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS enetpulse_tournaments (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
tournament_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
|
||||||
name VARCHAR(255) NOT NULL, -- from API "name"
|
|
||||||
|
|
||||||
-- Link to the template it belongs to:
|
|
||||||
tournament_template_fk VARCHAR(50) NOT NULL
|
|
||||||
REFERENCES enetpulse_tournament_templates(template_id) ON DELETE CASCADE,
|
|
||||||
|
|
||||||
updates_count INT DEFAULT 0, -- from API "n"
|
|
||||||
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
|
||||||
status INT DEFAULT 1, -- optional active/inactive flag
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create table for tournament stages
|
|
||||||
CREATE TABLE IF NOT EXISTS enetpulse_tournament_stages (
|
|
||||||
id BIGSERIAL PRIMARY KEY,
|
|
||||||
stage_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
|
||||||
name VARCHAR(255) NOT NULL, -- from API "name"
|
|
||||||
tournament_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_tournaments(tournament_id) ON DELETE CASCADE,
|
|
||||||
-- from API "tournamentFK"
|
|
||||||
gender VARCHAR(20) DEFAULT 'unknown', -- from API "gender" {male, female, mixed, unknown}
|
|
||||||
country_fk VARCHAR(50), -- from API "countryFK"
|
|
||||||
country_name VARCHAR(255), -- from API "country_name"
|
|
||||||
start_date TIMESTAMPTZ, -- from API "startdate"
|
|
||||||
end_date TIMESTAMPTZ, -- from API "enddate"
|
|
||||||
updates_count INT DEFAULT 0, -- from API "n"
|
|
||||||
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
|
||||||
status INT DEFAULT 1, -- optional status (active/inactive)
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Index to quickly query by tournament_fk
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_enetpulse_tournament_stages_tournament_fk
|
|
||||||
ON enetpulse_tournament_stages (tournament_fk);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,2 +1,11 @@
|
||||||
DROP TABLE IF EXISTS enetpulse_sports;
|
DROP TABLE IF EXISTS enetpulse_sports;
|
||||||
DROP TABLE IF EXISTS enetpulse_tournament_templates;
|
DROP TABLE IF EXISTS enetpulse_tournament_templates;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_tournaments;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_tournament_stages;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_fixtures;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_results;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_result_participants;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_result_referees;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_outcome_types;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_preodds;
|
||||||
|
DROP TABLE IF EXISTS enetpulse_preodds_bettingoffers;
|
||||||
|
|
@ -21,3 +21,207 @@ CREATE TABLE IF NOT EXISTS enetpulse_tournament_templates (
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ
|
updated_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_tournaments (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tournament_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
name VARCHAR(255) NOT NULL, -- from API "name"
|
||||||
|
|
||||||
|
-- Link to the template it belongs to:
|
||||||
|
tournament_template_fk VARCHAR(50) NOT NULL
|
||||||
|
REFERENCES enetpulse_tournament_templates(template_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
updates_count INT DEFAULT 0, -- from API "n"
|
||||||
|
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
||||||
|
status INT DEFAULT 1, -- optional active/inactive flag
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_tournament_stages (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
stage_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
name VARCHAR(255) NOT NULL, -- from API "name"
|
||||||
|
tournament_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_tournaments(tournament_id) ON DELETE CASCADE,
|
||||||
|
-- from API "tournamentFK"
|
||||||
|
gender VARCHAR(20) DEFAULT 'unknown', -- from API "gender" {male, female, mixed, unknown}
|
||||||
|
country_fk VARCHAR(50), -- from API "countryFK"
|
||||||
|
country_name VARCHAR(255), -- from API "country_name"
|
||||||
|
start_date TIMESTAMPTZ, -- from API "startdate"
|
||||||
|
end_date TIMESTAMPTZ, -- from API "enddate"
|
||||||
|
updates_count INT DEFAULT 0, -- from API "n"
|
||||||
|
last_updated_at TIMESTAMPTZ, -- from API "ut"
|
||||||
|
status INT DEFAULT 1, -- optional status (active/inactive)
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_enetpulse_tournament_stages_tournament_fk
|
||||||
|
ON enetpulse_tournament_stages (tournament_fk);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_fixtures (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
fixture_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
name VARCHAR(255) NOT NULL, -- fixture name (e.g. 12 de Junio-Sol de America)
|
||||||
|
|
||||||
|
sport_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_sports(sport_id) ON DELETE CASCADE,
|
||||||
|
tournament_fk VARCHAR(50), -- raw tournamentFK (optional)
|
||||||
|
tournament_template_fk VARCHAR(50) REFERENCES enetpulse_tournament_templates(template_id) ON DELETE CASCADE,
|
||||||
|
-- tournament_stage_fk VARCHAR(50) REFERENCES enetpulse_tournament_stages(stage_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- tournament_stage_name VARCHAR(255),
|
||||||
|
tournament_name VARCHAR(255),
|
||||||
|
tournament_template_name VARCHAR(255),
|
||||||
|
sport_name VARCHAR(255),
|
||||||
|
gender VARCHAR(20),
|
||||||
|
|
||||||
|
start_date TIMESTAMPTZ NOT NULL, -- startdate
|
||||||
|
status_type VARCHAR(50), -- Not started, Live, Finished
|
||||||
|
status_desc_fk VARCHAR(50), -- status_descFK
|
||||||
|
round_type_fk VARCHAR(50), -- round_typeFK
|
||||||
|
updates_count INT DEFAULT 0, -- n
|
||||||
|
last_updated_at TIMESTAMPTZ, -- ut
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_results (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
result_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
name VARCHAR(255) NOT NULL, -- event name (e.g. Brentford-Manchester City)
|
||||||
|
|
||||||
|
sport_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_sports(sport_id) ON DELETE CASCADE,
|
||||||
|
tournament_fk VARCHAR(50),
|
||||||
|
tournament_template_fk VARCHAR(50) REFERENCES enetpulse_tournament_templates(template_id) ON DELETE CASCADE,
|
||||||
|
-- tournament_stage_fk VARCHAR(50) REFERENCES enetpulse_tournament_stages(stage_id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
-- tournament_stage_name VARCHAR(255),
|
||||||
|
tournament_name VARCHAR(255),
|
||||||
|
tournament_template_name VARCHAR(255),
|
||||||
|
sport_name VARCHAR(255),
|
||||||
|
|
||||||
|
start_date TIMESTAMPTZ NOT NULL, -- startdate
|
||||||
|
status_type VARCHAR(50), -- e.g. Finished
|
||||||
|
status_desc_fk VARCHAR(50), -- status_descFK
|
||||||
|
round_type_fk VARCHAR(50), -- round_typeFK
|
||||||
|
updates_count INT DEFAULT 0, -- n
|
||||||
|
last_updated_at TIMESTAMPTZ, -- ut
|
||||||
|
|
||||||
|
-- Optional metadata (dynamic but common fields are included)
|
||||||
|
round VARCHAR(50),
|
||||||
|
live VARCHAR(10),
|
||||||
|
venue_name VARCHAR(255),
|
||||||
|
livestats_plus VARCHAR(10),
|
||||||
|
livestats_type VARCHAR(50),
|
||||||
|
commentary VARCHAR(50),
|
||||||
|
lineup_confirmed BOOLEAN,
|
||||||
|
verified BOOLEAN,
|
||||||
|
spectators INT,
|
||||||
|
|
||||||
|
-- Time-related metadata
|
||||||
|
game_started TIMESTAMPTZ,
|
||||||
|
first_half_ended TIMESTAMPTZ,
|
||||||
|
second_half_started TIMESTAMPTZ,
|
||||||
|
second_half_ended TIMESTAMPTZ,
|
||||||
|
game_ended TIMESTAMPTZ,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Event Participants (teams involved in the result)
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_result_participants (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
participant_map_id VARCHAR(50) NOT NULL UNIQUE, -- from event_participants.*.id
|
||||||
|
result_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_results(result_id) ON DELETE CASCADE,
|
||||||
|
participant_fk VARCHAR(50) NOT NULL, -- team/player FK (from API participantFK)
|
||||||
|
number INT, -- 1 or 2 (home/away indicator)
|
||||||
|
name VARCHAR(255), -- team/player name
|
||||||
|
gender VARCHAR(20),
|
||||||
|
type VARCHAR(50),
|
||||||
|
country_fk VARCHAR(50),
|
||||||
|
country_name VARCHAR(100),
|
||||||
|
|
||||||
|
-- Result details
|
||||||
|
ordinary_time VARCHAR(10),
|
||||||
|
running_score VARCHAR(10),
|
||||||
|
halftime VARCHAR(10),
|
||||||
|
final_result VARCHAR(10),
|
||||||
|
|
||||||
|
last_updated_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Referees / Match officials
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_result_referees (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
result_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_results(result_id) ON DELETE CASCADE,
|
||||||
|
referee_fk VARCHAR(50),
|
||||||
|
assistant1_referee_fk VARCHAR(50),
|
||||||
|
assistant2_referee_fk VARCHAR(50),
|
||||||
|
fourth_referee_fk VARCHAR(50),
|
||||||
|
var1_referee_fk VARCHAR(50),
|
||||||
|
var2_referee_fk VARCHAR(50),
|
||||||
|
last_updated_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_outcome_types (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
outcome_type_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
name VARCHAR(100) NOT NULL, -- e.g. "1x2"
|
||||||
|
description VARCHAR(255), -- e.g. "1x2 - 3Way"
|
||||||
|
|
||||||
|
updates_count INT DEFAULT 0, -- maps to "n"
|
||||||
|
last_updated_at TIMESTAMPTZ, -- maps to "ut"
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table for pre-match odds (main outcome entries)
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_preodds (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
preodds_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
event_fk BIGINT NOT NULL, -- maps to objectFK/event
|
||||||
|
outcome_type_fk INT, -- outcome_typeFK
|
||||||
|
outcome_scope_fk INT, -- outcome_scopeFK
|
||||||
|
outcome_subtype_fk INT, -- outcome_subtypeFK
|
||||||
|
event_participant_number INT, -- event_participant_number
|
||||||
|
iparam VARCHAR(50),
|
||||||
|
iparam2 VARCHAR(50),
|
||||||
|
dparam VARCHAR(50),
|
||||||
|
dparam2 VARCHAR(50),
|
||||||
|
sparam VARCHAR(50),
|
||||||
|
|
||||||
|
updates_count INT DEFAULT 0, -- maps to "n"
|
||||||
|
last_updated_at TIMESTAMPTZ, -- maps to "ut"
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table for nested betting offers within preodds
|
||||||
|
CREATE TABLE IF NOT EXISTS enetpulse_preodds_bettingoffers (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
bettingoffer_id VARCHAR(50) NOT NULL UNIQUE, -- from API "id"
|
||||||
|
preodds_fk VARCHAR(50) NOT NULL REFERENCES enetpulse_preodds(preodds_id) ON DELETE CASCADE,
|
||||||
|
bettingoffer_status_fk INT, -- bettingoffer_statusFK
|
||||||
|
odds_provider_fk INT, -- odds_providerFK
|
||||||
|
odds NUMERIC(10,4), -- current odds
|
||||||
|
odds_old NUMERIC(10,4), -- previous odds
|
||||||
|
active BOOLEAN, -- maps "yes"/"no" to boolean
|
||||||
|
coupon_key VARCHAR(255), -- couponKey
|
||||||
|
updates_count INT DEFAULT 0, -- maps to "n"
|
||||||
|
last_updated_at TIMESTAMPTZ, -- maps to "ut"
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@ SELECT
|
||||||
FROM enetpulse_tournament_templates
|
FROM enetpulse_tournament_templates
|
||||||
ORDER BY name;
|
ORDER BY name;
|
||||||
|
|
||||||
|
-- -- name: DeleteEnetpulseTournamentTemplateByID :exec
|
||||||
|
-- DELETE FROM enetpulse_tournament_templates WHERE template_id = $1;
|
||||||
|
|
||||||
-- name: CreateEnetpulseTournament :one
|
-- name: CreateEnetpulseTournament :one
|
||||||
INSERT INTO enetpulse_tournaments (
|
INSERT INTO enetpulse_tournaments (
|
||||||
tournament_id,
|
tournament_id,
|
||||||
|
|
@ -104,7 +107,8 @@ INSERT INTO enetpulse_tournament_stages (
|
||||||
updates_count,
|
updates_count,
|
||||||
last_updated_at,
|
last_updated_at,
|
||||||
status
|
status
|
||||||
) VALUES (
|
)
|
||||||
|
VALUES (
|
||||||
$1, -- stage_id
|
$1, -- stage_id
|
||||||
$2, -- name
|
$2, -- name
|
||||||
$3, -- tournament_fk
|
$3, -- tournament_fk
|
||||||
|
|
@ -117,6 +121,19 @@ INSERT INTO enetpulse_tournament_stages (
|
||||||
$10, -- last_updated_at
|
$10, -- last_updated_at
|
||||||
$11 -- status
|
$11 -- status
|
||||||
)
|
)
|
||||||
|
ON CONFLICT (stage_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
tournament_fk = EXCLUDED.tournament_fk,
|
||||||
|
gender = EXCLUDED.gender,
|
||||||
|
country_fk = EXCLUDED.country_fk,
|
||||||
|
country_name = EXCLUDED.country_name,
|
||||||
|
start_date = EXCLUDED.start_date,
|
||||||
|
end_date = EXCLUDED.end_date,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
status = EXCLUDED.status,
|
||||||
|
updated_at = NOW()
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetAllEnetpulseTournamentStages :many
|
-- name: GetAllEnetpulseTournamentStages :many
|
||||||
|
|
@ -130,5 +147,391 @@ FROM enetpulse_tournament_stages
|
||||||
WHERE tournament_fk = $1
|
WHERE tournament_fk = $1
|
||||||
ORDER BY created_at DESC;
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulseFixture :one
|
||||||
|
INSERT INTO enetpulse_fixtures (
|
||||||
|
fixture_id,
|
||||||
|
name,
|
||||||
|
sport_fk,
|
||||||
|
tournament_fk,
|
||||||
|
tournament_template_fk,
|
||||||
|
tournament_name,
|
||||||
|
tournament_template_name,
|
||||||
|
sport_name,
|
||||||
|
gender,
|
||||||
|
start_date,
|
||||||
|
status_type,
|
||||||
|
status_desc_fk,
|
||||||
|
round_type_fk,
|
||||||
|
updates_count,
|
||||||
|
last_updated_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (fixture_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
sport_fk = EXCLUDED.sport_fk,
|
||||||
|
tournament_fk = EXCLUDED.tournament_fk,
|
||||||
|
tournament_template_fk = EXCLUDED.tournament_template_fk,
|
||||||
|
tournament_name = EXCLUDED.tournament_name,
|
||||||
|
tournament_template_name = EXCLUDED.tournament_template_name,
|
||||||
|
sport_name = EXCLUDED.sport_name,
|
||||||
|
gender = EXCLUDED.gender,
|
||||||
|
start_date = EXCLUDED.start_date,
|
||||||
|
status_type = EXCLUDED.status_type,
|
||||||
|
status_desc_fk = EXCLUDED.status_desc_fk,
|
||||||
|
round_type_fk = EXCLUDED.round_type_fk,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulseFixtures :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_fixtures
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulseResult :one
|
||||||
|
INSERT INTO enetpulse_results (
|
||||||
|
result_id,
|
||||||
|
name,
|
||||||
|
sport_fk,
|
||||||
|
tournament_fk,
|
||||||
|
tournament_template_fk,
|
||||||
|
tournament_name,
|
||||||
|
tournament_template_name,
|
||||||
|
sport_name,
|
||||||
|
start_date,
|
||||||
|
status_type,
|
||||||
|
status_desc_fk,
|
||||||
|
round_type_fk,
|
||||||
|
updates_count,
|
||||||
|
last_updated_at,
|
||||||
|
round,
|
||||||
|
live,
|
||||||
|
venue_name,
|
||||||
|
livestats_plus,
|
||||||
|
livestats_type,
|
||||||
|
commentary,
|
||||||
|
lineup_confirmed,
|
||||||
|
verified,
|
||||||
|
spectators,
|
||||||
|
game_started,
|
||||||
|
first_half_ended,
|
||||||
|
second_half_started,
|
||||||
|
second_half_ended,
|
||||||
|
game_ended,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,
|
||||||
|
$11,$12,$13,$14,$15,$16,$17,$18,
|
||||||
|
$19,$20,$21,$22,$23,$24,$25,$26,
|
||||||
|
$27,$28,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (result_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
sport_fk = EXCLUDED.sport_fk,
|
||||||
|
tournament_fk = EXCLUDED.tournament_fk,
|
||||||
|
tournament_template_fk = EXCLUDED.tournament_template_fk,
|
||||||
|
tournament_name = EXCLUDED.tournament_name,
|
||||||
|
tournament_template_name = EXCLUDED.tournament_template_name,
|
||||||
|
sport_name = EXCLUDED.sport_name,
|
||||||
|
start_date = EXCLUDED.start_date,
|
||||||
|
status_type = EXCLUDED.status_type,
|
||||||
|
status_desc_fk = EXCLUDED.status_desc_fk,
|
||||||
|
round_type_fk = EXCLUDED.round_type_fk,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
round = EXCLUDED.round,
|
||||||
|
live = EXCLUDED.live,
|
||||||
|
venue_name = EXCLUDED.venue_name,
|
||||||
|
livestats_plus = EXCLUDED.livestats_plus,
|
||||||
|
livestats_type = EXCLUDED.livestats_type,
|
||||||
|
commentary = EXCLUDED.commentary,
|
||||||
|
lineup_confirmed = EXCLUDED.lineup_confirmed,
|
||||||
|
verified = EXCLUDED.verified,
|
||||||
|
spectators = EXCLUDED.spectators,
|
||||||
|
game_started = EXCLUDED.game_started,
|
||||||
|
first_half_ended = EXCLUDED.first_half_ended,
|
||||||
|
second_half_started = EXCLUDED.second_half_started,
|
||||||
|
second_half_ended = EXCLUDED.second_half_ended,
|
||||||
|
game_ended = EXCLUDED.game_ended,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulseResults :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_results
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulseResultParticipant :one
|
||||||
|
INSERT INTO enetpulse_result_participants (
|
||||||
|
participant_map_id,
|
||||||
|
result_fk,
|
||||||
|
participant_fk,
|
||||||
|
number,
|
||||||
|
name,
|
||||||
|
gender,
|
||||||
|
type,
|
||||||
|
country_fk,
|
||||||
|
country_name,
|
||||||
|
ordinary_time,
|
||||||
|
running_score,
|
||||||
|
halftime,
|
||||||
|
final_result,
|
||||||
|
last_updated_at,
|
||||||
|
created_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (participant_map_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
result_fk = EXCLUDED.result_fk,
|
||||||
|
participant_fk = EXCLUDED.participant_fk,
|
||||||
|
number = EXCLUDED.number,
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
gender = EXCLUDED.gender,
|
||||||
|
type = EXCLUDED.type,
|
||||||
|
country_fk = EXCLUDED.country_fk,
|
||||||
|
country_name = EXCLUDED.country_name,
|
||||||
|
ordinary_time = EXCLUDED.ordinary_time,
|
||||||
|
running_score = EXCLUDED.running_score,
|
||||||
|
halftime = EXCLUDED.halftime,
|
||||||
|
final_result = EXCLUDED.final_result,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetEnetpulseResultParticipantsByResultFK :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_result_participants
|
||||||
|
WHERE result_fk = $1
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulseResultReferee :one
|
||||||
|
INSERT INTO enetpulse_result_referees (
|
||||||
|
result_fk,
|
||||||
|
referee_fk,
|
||||||
|
assistant1_referee_fk,
|
||||||
|
assistant2_referee_fk,
|
||||||
|
fourth_referee_fk,
|
||||||
|
var1_referee_fk,
|
||||||
|
var2_referee_fk,
|
||||||
|
last_updated_at,
|
||||||
|
created_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (result_fk) DO UPDATE
|
||||||
|
SET
|
||||||
|
referee_fk = EXCLUDED.referee_fk,
|
||||||
|
assistant1_referee_fk = EXCLUDED.assistant1_referee_fk,
|
||||||
|
assistant2_referee_fk = EXCLUDED.assistant2_referee_fk,
|
||||||
|
fourth_referee_fk = EXCLUDED.fourth_referee_fk,
|
||||||
|
var1_referee_fk = EXCLUDED.var1_referee_fk,
|
||||||
|
var2_referee_fk = EXCLUDED.var2_referee_fk,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetEnetpulseResultRefereesByResultFK :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_result_referees
|
||||||
|
WHERE result_fk = $1
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulseOutcomeType :one
|
||||||
|
INSERT INTO enetpulse_outcome_types (
|
||||||
|
outcome_type_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
updates_count,
|
||||||
|
last_updated_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (outcome_type_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
description = EXCLUDED.description,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulseOutcomeTypes :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_outcome_types
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: CreateEnetpulsePreodds :one
|
||||||
|
INSERT INTO enetpulse_preodds (
|
||||||
|
preodds_id,
|
||||||
|
event_fk,
|
||||||
|
outcome_type_fk,
|
||||||
|
outcome_scope_fk,
|
||||||
|
outcome_subtype_fk,
|
||||||
|
event_participant_number,
|
||||||
|
iparam,
|
||||||
|
iparam2,
|
||||||
|
dparam,
|
||||||
|
dparam2,
|
||||||
|
sparam,
|
||||||
|
updates_count,
|
||||||
|
last_updated_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (preodds_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
event_fk = EXCLUDED.event_fk,
|
||||||
|
outcome_type_fk = EXCLUDED.outcome_type_fk,
|
||||||
|
outcome_scope_fk = EXCLUDED.outcome_scope_fk,
|
||||||
|
outcome_subtype_fk = EXCLUDED.outcome_subtype_fk,
|
||||||
|
event_participant_number = EXCLUDED.event_participant_number,
|
||||||
|
iparam = EXCLUDED.iparam,
|
||||||
|
iparam2 = EXCLUDED.iparam2,
|
||||||
|
dparam = EXCLUDED.dparam,
|
||||||
|
dparam2 = EXCLUDED.dparam2,
|
||||||
|
sparam = EXCLUDED.sparam,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulsePreodds :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_preodds
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: CreateEnetpulsePreoddsBettingOffer :one
|
||||||
|
INSERT INTO enetpulse_preodds_bettingoffers (
|
||||||
|
bettingoffer_id,
|
||||||
|
preodds_fk,
|
||||||
|
bettingoffer_status_fk,
|
||||||
|
odds_provider_fk,
|
||||||
|
odds,
|
||||||
|
odds_old,
|
||||||
|
active,
|
||||||
|
coupon_key,
|
||||||
|
updates_count,
|
||||||
|
last_updated_at,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (bettingoffer_id) DO UPDATE
|
||||||
|
SET
|
||||||
|
preodds_fk = EXCLUDED.preodds_fk,
|
||||||
|
bettingoffer_status_fk = EXCLUDED.bettingoffer_status_fk,
|
||||||
|
odds_provider_fk = EXCLUDED.odds_provider_fk,
|
||||||
|
odds = EXCLUDED.odds,
|
||||||
|
odds_old = EXCLUDED.odds_old,
|
||||||
|
active = EXCLUDED.active,
|
||||||
|
coupon_key = EXCLUDED.coupon_key,
|
||||||
|
updates_count = EXCLUDED.updates_count,
|
||||||
|
last_updated_at = EXCLUDED.last_updated_at,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulsePreoddsBettingOffers :many
|
||||||
|
SELECT *
|
||||||
|
FROM enetpulse_preodds_bettingoffers
|
||||||
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulsePreoddsWithBettingOffers :many
|
||||||
|
SELECT
|
||||||
|
p.id AS preodds_db_id,
|
||||||
|
p.preodds_id,
|
||||||
|
p.event_fk,
|
||||||
|
p.outcome_type_fk,
|
||||||
|
p.outcome_scope_fk,
|
||||||
|
p.outcome_subtype_fk,
|
||||||
|
p.event_participant_number,
|
||||||
|
p.iparam,
|
||||||
|
p.iparam2,
|
||||||
|
p.dparam,
|
||||||
|
p.dparam2,
|
||||||
|
p.sparam,
|
||||||
|
p.updates_count AS preodds_updates_count,
|
||||||
|
p.last_updated_at AS preodds_last_updated_at,
|
||||||
|
p.created_at AS preodds_created_at,
|
||||||
|
p.updated_at AS preodds_updated_at,
|
||||||
|
|
||||||
|
-- Betting offer fields
|
||||||
|
bo.id AS bettingoffer_db_id,
|
||||||
|
bo.bettingoffer_id,
|
||||||
|
bo.preodds_fk, -- ✅ ensure alias matches struct field
|
||||||
|
bo.bettingoffer_status_fk,
|
||||||
|
bo.odds_provider_fk,
|
||||||
|
bo.odds,
|
||||||
|
bo.odds_old,
|
||||||
|
bo.active,
|
||||||
|
bo.coupon_key,
|
||||||
|
bo.updates_count AS bettingoffer_updates_count,
|
||||||
|
bo.last_updated_at AS bettingoffer_last_updated_at,
|
||||||
|
bo.created_at AS bettingoffer_created_at,
|
||||||
|
bo.updated_at AS bettingoffer_updated_at
|
||||||
|
|
||||||
|
FROM enetpulse_preodds p
|
||||||
|
LEFT JOIN enetpulse_preodds_bettingoffers bo
|
||||||
|
ON bo.preodds_fk = p.preodds_id
|
||||||
|
ORDER BY p.created_at DESC, bo.created_at DESC;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: GetFixturesWithPreodds :many
|
||||||
|
SELECT
|
||||||
|
f.fixture_id AS id,
|
||||||
|
f.fixture_id AS fixture_id,
|
||||||
|
f.name AS fixture_name,
|
||||||
|
f.sport_fk,
|
||||||
|
f.tournament_fk,
|
||||||
|
f.tournament_template_fk,
|
||||||
|
f.start_date,
|
||||||
|
f.status_type,
|
||||||
|
f.status_desc_fk,
|
||||||
|
f.round_type_fk,
|
||||||
|
f.updates_count AS fixture_updates_count,
|
||||||
|
f.last_updated_at AS fixture_last_updated_at,
|
||||||
|
f.created_at AS fixture_created_at,
|
||||||
|
f.updated_at AS fixture_updated_at,
|
||||||
|
|
||||||
|
-- Preodds fields
|
||||||
|
p.id AS preodds_db_id,
|
||||||
|
p.preodds_id,
|
||||||
|
p.event_fk,
|
||||||
|
p.outcome_type_fk,
|
||||||
|
p.outcome_scope_fk,
|
||||||
|
p.outcome_subtype_fk,
|
||||||
|
p.event_participant_number,
|
||||||
|
p.iparam,
|
||||||
|
p.iparam2,
|
||||||
|
p.dparam,
|
||||||
|
p.dparam2,
|
||||||
|
p.sparam,
|
||||||
|
p.updates_count AS preodds_updates_count,
|
||||||
|
p.last_updated_at AS preodds_last_updated_at,
|
||||||
|
p.created_at AS preodds_created_at,
|
||||||
|
p.updated_at AS preodds_updated_at
|
||||||
|
|
||||||
|
FROM enetpulse_fixtures f
|
||||||
|
LEFT JOIN enetpulse_preodds p
|
||||||
|
ON p.event_fk = f.id
|
||||||
|
ORDER BY f.start_date DESC;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
ext_reference_number,
|
||||||
session_id,
|
session_id,
|
||||||
status,
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: GetAllTransfers :many
|
-- name: GetAllTransfers :many
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
|
||||||
|
|
@ -63,38 +63,38 @@ RETURNING id,
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
expires_at
|
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id,
|
RETURNING
|
||||||
|
id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at;
|
||||||
expires_at;
|
|
||||||
|
-- name: GetVirtualGameSessionByUserID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
session_token,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM virtual_game_sessions
|
||||||
|
WHERE user_id = $1;
|
||||||
|
|
||||||
-- name: GetVirtualGameSessionByToken :one
|
-- name: GetVirtualGameSessionByToken :one
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
FROM virtual_game_sessions
|
FROM virtual_game_sessions
|
||||||
WHERE session_token = $1;
|
WHERE session_token = $1;
|
||||||
-- name: UpdateVirtualGameSessionStatus :exec
|
|
||||||
UPDATE virtual_game_sessions
|
|
||||||
SET status = $2,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE id = $1;
|
|
||||||
-- name: CreateVirtualGameTransaction :one
|
-- name: CreateVirtualGameTransaction :one
|
||||||
INSERT INTO virtual_game_transactions (
|
INSERT INTO virtual_game_transactions (
|
||||||
session_id,
|
session_id,
|
||||||
|
|
@ -288,3 +288,86 @@ ORDER BY vg.created_at DESC
|
||||||
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
LIMIT sqlc.narg('limit') OFFSET sqlc.narg('offset');
|
||||||
-- name: DeleteAllVirtualGames :exec
|
-- name: DeleteAllVirtualGames :exec
|
||||||
DELETE FROM virtual_games;
|
DELETE FROM virtual_games;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: CreateVirtualGameProviderReport :one
|
||||||
|
INSERT INTO virtual_game_provider_reports (
|
||||||
|
provider_id,
|
||||||
|
report_date,
|
||||||
|
total_games_played,
|
||||||
|
total_bets,
|
||||||
|
total_payouts,
|
||||||
|
total_players,
|
||||||
|
report_type,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, COALESCE($7, 'daily'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (provider_id, report_date, report_type) DO UPDATE
|
||||||
|
SET
|
||||||
|
total_games_played = EXCLUDED.total_games_played,
|
||||||
|
total_bets = EXCLUDED.total_bets,
|
||||||
|
total_payouts = EXCLUDED.total_payouts,
|
||||||
|
total_players = EXCLUDED.total_players,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: CreateVirtualGameReport :one
|
||||||
|
INSERT INTO virtual_game_reports (
|
||||||
|
game_id,
|
||||||
|
provider_id,
|
||||||
|
report_date,
|
||||||
|
total_rounds,
|
||||||
|
total_bets,
|
||||||
|
total_payouts,
|
||||||
|
total_players,
|
||||||
|
report_type,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, COALESCE($8, 'daily'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (game_id, report_date, report_type) DO UPDATE
|
||||||
|
SET
|
||||||
|
total_rounds = EXCLUDED.total_rounds,
|
||||||
|
total_bets = EXCLUDED.total_bets,
|
||||||
|
total_payouts = EXCLUDED.total_payouts,
|
||||||
|
total_players = EXCLUDED.total_players,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetVirtualGameProviderReportByProviderAndDate :one
|
||||||
|
SELECT *
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
WHERE provider_id = $1
|
||||||
|
AND report_date = $2
|
||||||
|
AND report_type = $3;
|
||||||
|
|
||||||
|
-- name: UpdateVirtualGameProviderReportByDate :exec
|
||||||
|
UPDATE virtual_game_provider_reports
|
||||||
|
SET
|
||||||
|
total_games_played = total_games_played + $4,
|
||||||
|
total_bets = total_bets + $5,
|
||||||
|
total_payouts = total_payouts + $6,
|
||||||
|
total_players = total_players + $7,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE
|
||||||
|
provider_id = $1
|
||||||
|
AND report_date = $2
|
||||||
|
AND report_type = $3;
|
||||||
|
|
||||||
|
-- name: ListVirtualGameProviderReportsByGamesPlayedAsc :many
|
||||||
|
SELECT *
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
ORDER BY total_games_played ASC;
|
||||||
|
|
||||||
|
-- name: ListVirtualGameProviderReportsByGamesPlayedDesc :many
|
||||||
|
SELECT *
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
ORDER BY total_games_played DESC;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ services:
|
||||||
container_name: fortunebet-backend-postgres-1
|
container_name: fortunebet-backend-postgres-1
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
ports:
|
ports:
|
||||||
- 5422:5432
|
- "5422:5432"
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=secret
|
- POSTGRES_PASSWORD=secret
|
||||||
- POSTGRES_USER=root
|
- POSTGRES_USER=root
|
||||||
|
|
@ -20,12 +20,13 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./exports:/exports
|
- ./exports:/exports
|
||||||
|
|
||||||
mongo:
|
mongo:
|
||||||
container_name: fortunebet-mongo
|
container_name: fortunebet-mongo
|
||||||
image: mongo:7.0.11
|
image: mongo:7.0.11
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "27017:27017"
|
- "27025:27017"
|
||||||
environment:
|
environment:
|
||||||
MONGO_INITDB_ROOT_USERNAME: root
|
MONGO_INITDB_ROOT_USERNAME: root
|
||||||
MONGO_INITDB_ROOT_PASSWORD: secret
|
MONGO_INITDB_ROOT_PASSWORD: secret
|
||||||
|
|
@ -38,6 +39,7 @@ services:
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
image: migrate/migrate
|
image: migrate/migrate
|
||||||
volumes:
|
volumes:
|
||||||
|
|
@ -72,7 +74,7 @@ services:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: runner
|
target: runner
|
||||||
ports:
|
ports:
|
||||||
- ${PORT}:8080
|
- "${PORT}:8080"
|
||||||
environment:
|
environment:
|
||||||
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
- DB_URL=postgresql://root:secret@postgres:5432/gh?sslmode=disable
|
||||||
- MONGO_URI=mongodb://root:secret@mongo:27017
|
- MONGO_URI=mongodb://root:secret@mongo:27017
|
||||||
|
|
|
||||||
1448
docs/docs.go
1448
docs/docs.go
File diff suppressed because it is too large
Load Diff
1448
docs/swagger.json
1448
docs/swagger.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
167
gen/db/models.go
167
gen/db/models.go
|
|
@ -325,6 +325,139 @@ type DirectDeposit struct {
|
||||||
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EnetpulseFixture struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
FixtureID string `json:"fixture_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFk string `json:"sport_fk"`
|
||||||
|
TournamentFk pgtype.Text `json:"tournament_fk"`
|
||||||
|
TournamentTemplateFk pgtype.Text `json:"tournament_template_fk"`
|
||||||
|
TournamentName pgtype.Text `json:"tournament_name"`
|
||||||
|
TournamentTemplateName pgtype.Text `json:"tournament_template_name"`
|
||||||
|
SportName pgtype.Text `json:"sport_name"`
|
||||||
|
Gender pgtype.Text `json:"gender"`
|
||||||
|
StartDate pgtype.Timestamptz `json:"start_date"`
|
||||||
|
StatusType pgtype.Text `json:"status_type"`
|
||||||
|
StatusDescFk pgtype.Text `json:"status_desc_fk"`
|
||||||
|
RoundTypeFk pgtype.Text `json:"round_type_fk"`
|
||||||
|
UpdatesCount pgtype.Int4 `json:"updates_count"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseOutcomeType struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
OutcomeTypeID string `json:"outcome_type_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description pgtype.Text `json:"description"`
|
||||||
|
UpdatesCount pgtype.Int4 `json:"updates_count"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulsePreodd struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
PreoddsID string `json:"preodds_id"`
|
||||||
|
EventFk int64 `json:"event_fk"`
|
||||||
|
OutcomeTypeFk pgtype.Int4 `json:"outcome_type_fk"`
|
||||||
|
OutcomeScopeFk pgtype.Int4 `json:"outcome_scope_fk"`
|
||||||
|
OutcomeSubtypeFk pgtype.Int4 `json:"outcome_subtype_fk"`
|
||||||
|
EventParticipantNumber pgtype.Int4 `json:"event_participant_number"`
|
||||||
|
Iparam pgtype.Text `json:"iparam"`
|
||||||
|
Iparam2 pgtype.Text `json:"iparam2"`
|
||||||
|
Dparam pgtype.Text `json:"dparam"`
|
||||||
|
Dparam2 pgtype.Text `json:"dparam2"`
|
||||||
|
Sparam pgtype.Text `json:"sparam"`
|
||||||
|
UpdatesCount pgtype.Int4 `json:"updates_count"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulsePreoddsBettingoffer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
BettingofferID string `json:"bettingoffer_id"`
|
||||||
|
PreoddsFk string `json:"preodds_fk"`
|
||||||
|
BettingofferStatusFk pgtype.Int4 `json:"bettingoffer_status_fk"`
|
||||||
|
OddsProviderFk pgtype.Int4 `json:"odds_provider_fk"`
|
||||||
|
Odds pgtype.Numeric `json:"odds"`
|
||||||
|
OddsOld pgtype.Numeric `json:"odds_old"`
|
||||||
|
Active pgtype.Bool `json:"active"`
|
||||||
|
CouponKey pgtype.Text `json:"coupon_key"`
|
||||||
|
UpdatesCount pgtype.Int4 `json:"updates_count"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseResult struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ResultID string `json:"result_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFk string `json:"sport_fk"`
|
||||||
|
TournamentFk pgtype.Text `json:"tournament_fk"`
|
||||||
|
TournamentTemplateFk pgtype.Text `json:"tournament_template_fk"`
|
||||||
|
TournamentName pgtype.Text `json:"tournament_name"`
|
||||||
|
TournamentTemplateName pgtype.Text `json:"tournament_template_name"`
|
||||||
|
SportName pgtype.Text `json:"sport_name"`
|
||||||
|
StartDate pgtype.Timestamptz `json:"start_date"`
|
||||||
|
StatusType pgtype.Text `json:"status_type"`
|
||||||
|
StatusDescFk pgtype.Text `json:"status_desc_fk"`
|
||||||
|
RoundTypeFk pgtype.Text `json:"round_type_fk"`
|
||||||
|
UpdatesCount pgtype.Int4 `json:"updates_count"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
Round pgtype.Text `json:"round"`
|
||||||
|
Live pgtype.Text `json:"live"`
|
||||||
|
VenueName pgtype.Text `json:"venue_name"`
|
||||||
|
LivestatsPlus pgtype.Text `json:"livestats_plus"`
|
||||||
|
LivestatsType pgtype.Text `json:"livestats_type"`
|
||||||
|
Commentary pgtype.Text `json:"commentary"`
|
||||||
|
LineupConfirmed pgtype.Bool `json:"lineup_confirmed"`
|
||||||
|
Verified pgtype.Bool `json:"verified"`
|
||||||
|
Spectators pgtype.Int4 `json:"spectators"`
|
||||||
|
GameStarted pgtype.Timestamptz `json:"game_started"`
|
||||||
|
FirstHalfEnded pgtype.Timestamptz `json:"first_half_ended"`
|
||||||
|
SecondHalfStarted pgtype.Timestamptz `json:"second_half_started"`
|
||||||
|
SecondHalfEnded pgtype.Timestamptz `json:"second_half_ended"`
|
||||||
|
GameEnded pgtype.Timestamptz `json:"game_ended"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseResultParticipant struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ParticipantMapID string `json:"participant_map_id"`
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
ParticipantFk string `json:"participant_fk"`
|
||||||
|
Number pgtype.Int4 `json:"number"`
|
||||||
|
Name pgtype.Text `json:"name"`
|
||||||
|
Gender pgtype.Text `json:"gender"`
|
||||||
|
Type pgtype.Text `json:"type"`
|
||||||
|
CountryFk pgtype.Text `json:"country_fk"`
|
||||||
|
CountryName pgtype.Text `json:"country_name"`
|
||||||
|
OrdinaryTime pgtype.Text `json:"ordinary_time"`
|
||||||
|
RunningScore pgtype.Text `json:"running_score"`
|
||||||
|
Halftime pgtype.Text `json:"halftime"`
|
||||||
|
FinalResult pgtype.Text `json:"final_result"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseResultReferee struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
RefereeFk pgtype.Text `json:"referee_fk"`
|
||||||
|
Assistant1RefereeFk pgtype.Text `json:"assistant1_referee_fk"`
|
||||||
|
Assistant2RefereeFk pgtype.Text `json:"assistant2_referee_fk"`
|
||||||
|
FourthRefereeFk pgtype.Text `json:"fourth_referee_fk"`
|
||||||
|
Var1RefereeFk pgtype.Text `json:"var1_referee_fk"`
|
||||||
|
Var2RefereeFk pgtype.Text `json:"var2_referee_fk"`
|
||||||
|
LastUpdatedAt pgtype.Timestamptz `json:"last_updated_at"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type EnetpulseSport struct {
|
type EnetpulseSport struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SportID string `json:"sport_id"`
|
SportID string `json:"sport_id"`
|
||||||
|
|
@ -1050,16 +1183,42 @@ type VirtualGameProvider struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualGameProviderReport struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
TotalGamesPlayed pgtype.Int8 `json:"total_games_played"`
|
||||||
|
TotalBets pgtype.Numeric `json:"total_bets"`
|
||||||
|
TotalPayouts pgtype.Numeric `json:"total_payouts"`
|
||||||
|
TotalProfit pgtype.Numeric `json:"total_profit"`
|
||||||
|
TotalPlayers pgtype.Int8 `json:"total_players"`
|
||||||
|
ReportType pgtype.Text `json:"report_type"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualGameReport struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
TotalRounds pgtype.Int8 `json:"total_rounds"`
|
||||||
|
TotalBets pgtype.Numeric `json:"total_bets"`
|
||||||
|
TotalPayouts pgtype.Numeric `json:"total_payouts"`
|
||||||
|
TotalProfit pgtype.Numeric `json:"total_profit"`
|
||||||
|
TotalPlayers pgtype.Int8 `json:"total_players"`
|
||||||
|
ReportType pgtype.Text `json:"report_type"`
|
||||||
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGameSession struct {
|
type VirtualGameSession struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
GameID string `json:"game_id"`
|
GameID string `json:"game_id"`
|
||||||
SessionToken string `json:"session_token"`
|
SessionToken string `json:"session_token"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualGameTransaction struct {
|
type VirtualGameTransaction struct {
|
||||||
|
|
@ -1127,6 +1286,7 @@ type WalletTransfer struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
@ -1144,6 +1304,7 @@ type WalletTransferDetail struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,13 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
ext_reference_number,
|
||||||
session_id,
|
session_id,
|
||||||
status,
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at
|
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTransferParams struct {
|
type CreateTransferParams struct {
|
||||||
|
|
@ -38,6 +39,7 @@ type CreateTransferParams struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
@ -53,6 +55,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
arg.CashierID,
|
arg.CashierID,
|
||||||
arg.Verified,
|
arg.Verified,
|
||||||
arg.ReferenceNumber,
|
arg.ReferenceNumber,
|
||||||
|
arg.ExtReferenceNumber,
|
||||||
arg.SessionID,
|
arg.SessionID,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
arg.PaymentMethod,
|
arg.PaymentMethod,
|
||||||
|
|
@ -68,6 +71,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -78,7 +82,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllTransfers = `-- name: GetAllTransfers :many
|
const GetAllTransfers = `-- name: GetAllTransfers :many
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -101,6 +105,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -121,7 +126,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByID = `-- name: GetTransferByID :one
|
const GetTransferByID = `-- name: GetTransferByID :one
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -139,6 +144,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -152,7 +158,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByReference = `-- name: GetTransferByReference :one
|
const GetTransferByReference = `-- name: GetTransferByReference :one
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE reference_number = $1
|
WHERE reference_number = $1
|
||||||
`
|
`
|
||||||
|
|
@ -170,6 +176,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -217,7 +224,7 @@ func (q *Queries) GetTransferStats(ctx context.Context, senderWalletID pgtype.In
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE receiver_wallet_id = $1
|
WHERE receiver_wallet_id = $1
|
||||||
OR sender_wallet_id = $1
|
OR sender_wallet_id = $1
|
||||||
|
|
@ -242,6 +249,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
|
||||||
|
|
@ -281,56 +281,164 @@ func (q *Queries) CreateVirtualGameProvider(ctx context.Context, arg CreateVirtu
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CreateVirtualGameProviderReport = `-- name: CreateVirtualGameProviderReport :one
|
||||||
|
INSERT INTO virtual_game_provider_reports (
|
||||||
|
provider_id,
|
||||||
|
report_date,
|
||||||
|
total_games_played,
|
||||||
|
total_bets,
|
||||||
|
total_payouts,
|
||||||
|
total_players,
|
||||||
|
report_type,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, COALESCE($7, 'daily'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (provider_id, report_date, report_type) DO UPDATE
|
||||||
|
SET
|
||||||
|
total_games_played = EXCLUDED.total_games_played,
|
||||||
|
total_bets = EXCLUDED.total_bets,
|
||||||
|
total_payouts = EXCLUDED.total_payouts,
|
||||||
|
total_players = EXCLUDED.total_players,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING id, provider_id, report_date, total_games_played, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateVirtualGameProviderReportParams struct {
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
TotalGamesPlayed pgtype.Int8 `json:"total_games_played"`
|
||||||
|
TotalBets pgtype.Numeric `json:"total_bets"`
|
||||||
|
TotalPayouts pgtype.Numeric `json:"total_payouts"`
|
||||||
|
TotalPlayers pgtype.Int8 `json:"total_players"`
|
||||||
|
Column7 interface{} `json:"column_7"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateVirtualGameProviderReport(ctx context.Context, arg CreateVirtualGameProviderReportParams) (VirtualGameProviderReport, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateVirtualGameProviderReport,
|
||||||
|
arg.ProviderID,
|
||||||
|
arg.ReportDate,
|
||||||
|
arg.TotalGamesPlayed,
|
||||||
|
arg.TotalBets,
|
||||||
|
arg.TotalPayouts,
|
||||||
|
arg.TotalPlayers,
|
||||||
|
arg.Column7,
|
||||||
|
)
|
||||||
|
var i VirtualGameProviderReport
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProviderID,
|
||||||
|
&i.ReportDate,
|
||||||
|
&i.TotalGamesPlayed,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalPayouts,
|
||||||
|
&i.TotalProfit,
|
||||||
|
&i.TotalPlayers,
|
||||||
|
&i.ReportType,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateVirtualGameReport = `-- name: CreateVirtualGameReport :one
|
||||||
|
INSERT INTO virtual_game_reports (
|
||||||
|
game_id,
|
||||||
|
provider_id,
|
||||||
|
report_date,
|
||||||
|
total_rounds,
|
||||||
|
total_bets,
|
||||||
|
total_payouts,
|
||||||
|
total_players,
|
||||||
|
report_type,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, COALESCE($8, 'daily'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
ON CONFLICT (game_id, report_date, report_type) DO UPDATE
|
||||||
|
SET
|
||||||
|
total_rounds = EXCLUDED.total_rounds,
|
||||||
|
total_bets = EXCLUDED.total_bets,
|
||||||
|
total_payouts = EXCLUDED.total_payouts,
|
||||||
|
total_players = EXCLUDED.total_players,
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
RETURNING id, game_id, provider_id, report_date, total_rounds, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateVirtualGameReportParams struct {
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
TotalRounds pgtype.Int8 `json:"total_rounds"`
|
||||||
|
TotalBets pgtype.Numeric `json:"total_bets"`
|
||||||
|
TotalPayouts pgtype.Numeric `json:"total_payouts"`
|
||||||
|
TotalPlayers pgtype.Int8 `json:"total_players"`
|
||||||
|
Column8 interface{} `json:"column_8"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateVirtualGameReport(ctx context.Context, arg CreateVirtualGameReportParams) (VirtualGameReport, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateVirtualGameReport,
|
||||||
|
arg.GameID,
|
||||||
|
arg.ProviderID,
|
||||||
|
arg.ReportDate,
|
||||||
|
arg.TotalRounds,
|
||||||
|
arg.TotalBets,
|
||||||
|
arg.TotalPayouts,
|
||||||
|
arg.TotalPlayers,
|
||||||
|
arg.Column8,
|
||||||
|
)
|
||||||
|
var i VirtualGameReport
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.GameID,
|
||||||
|
&i.ProviderID,
|
||||||
|
&i.ReportDate,
|
||||||
|
&i.TotalRounds,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalPayouts,
|
||||||
|
&i.TotalProfit,
|
||||||
|
&i.TotalPlayers,
|
||||||
|
&i.ReportType,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
expires_at
|
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id,
|
RETURNING
|
||||||
|
id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateVirtualGameSessionParams struct {
|
type CreateVirtualGameSessionParams struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
GameID string `json:"game_id"`
|
GameID string `json:"game_id"`
|
||||||
SessionToken string `json:"session_token"`
|
SessionToken string `json:"session_token"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtualGameSessionParams) (VirtualGameSession, error) {
|
func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtualGameSessionParams) (VirtualGameSession, error) {
|
||||||
row := q.db.QueryRow(ctx, CreateVirtualGameSession,
|
row := q.db.QueryRow(ctx, CreateVirtualGameSession, arg.UserID, arg.GameID, arg.SessionToken)
|
||||||
arg.UserID,
|
|
||||||
arg.GameID,
|
|
||||||
arg.SessionToken,
|
|
||||||
arg.Currency,
|
|
||||||
arg.Status,
|
|
||||||
arg.ExpiresAt,
|
|
||||||
)
|
|
||||||
var i VirtualGameSession
|
var i VirtualGameSession
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.UserID,
|
&i.UserID,
|
||||||
&i.GameID,
|
&i.GameID,
|
||||||
&i.SessionToken,
|
&i.SessionToken,
|
||||||
&i.Currency,
|
|
||||||
&i.Status,
|
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.ExpiresAt,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -587,16 +695,46 @@ func (q *Queries) GetVirtualGameProviderByID(ctx context.Context, providerID str
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetVirtualGameProviderReportByProviderAndDate = `-- name: GetVirtualGameProviderReportByProviderAndDate :one
|
||||||
|
SELECT id, provider_id, report_date, total_games_played, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
WHERE provider_id = $1
|
||||||
|
AND report_date = $2
|
||||||
|
AND report_type = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetVirtualGameProviderReportByProviderAndDateParams struct {
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
ReportType pgtype.Text `json:"report_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetVirtualGameProviderReportByProviderAndDate(ctx context.Context, arg GetVirtualGameProviderReportByProviderAndDateParams) (VirtualGameProviderReport, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetVirtualGameProviderReportByProviderAndDate, arg.ProviderID, arg.ReportDate, arg.ReportType)
|
||||||
|
var i VirtualGameProviderReport
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProviderID,
|
||||||
|
&i.ReportDate,
|
||||||
|
&i.TotalGamesPlayed,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalPayouts,
|
||||||
|
&i.TotalProfit,
|
||||||
|
&i.TotalPlayers,
|
||||||
|
&i.ReportType,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const GetVirtualGameSessionByToken = `-- name: GetVirtualGameSessionByToken :one
|
const GetVirtualGameSessionByToken = `-- name: GetVirtualGameSessionByToken :one
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
FROM virtual_game_sessions
|
FROM virtual_game_sessions
|
||||||
WHERE session_token = $1
|
WHERE session_token = $1
|
||||||
`
|
`
|
||||||
|
|
@ -609,11 +747,34 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
||||||
&i.UserID,
|
&i.UserID,
|
||||||
&i.GameID,
|
&i.GameID,
|
||||||
&i.SessionToken,
|
&i.SessionToken,
|
||||||
&i.Currency,
|
|
||||||
&i.Status,
|
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.ExpiresAt,
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetVirtualGameSessionByUserID = `-- name: GetVirtualGameSessionByUserID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
session_token,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM virtual_game_sessions
|
||||||
|
WHERE user_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (VirtualGameSession, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetVirtualGameSessionByUserID, userID)
|
||||||
|
var i VirtualGameSession
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.GameID,
|
||||||
|
&i.SessionToken,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -745,6 +906,82 @@ func (q *Queries) ListFavoriteGames(ctx context.Context, userID int64) ([]int64,
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ListVirtualGameProviderReportsByGamesPlayedAsc = `-- name: ListVirtualGameProviderReportsByGamesPlayedAsc :many
|
||||||
|
SELECT id, provider_id, report_date, total_games_played, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
ORDER BY total_games_played ASC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListVirtualGameProviderReportsByGamesPlayedAsc(ctx context.Context) ([]VirtualGameProviderReport, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListVirtualGameProviderReportsByGamesPlayedAsc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []VirtualGameProviderReport
|
||||||
|
for rows.Next() {
|
||||||
|
var i VirtualGameProviderReport
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProviderID,
|
||||||
|
&i.ReportDate,
|
||||||
|
&i.TotalGamesPlayed,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalPayouts,
|
||||||
|
&i.TotalProfit,
|
||||||
|
&i.TotalPlayers,
|
||||||
|
&i.ReportType,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListVirtualGameProviderReportsByGamesPlayedDesc = `-- name: ListVirtualGameProviderReportsByGamesPlayedDesc :many
|
||||||
|
SELECT id, provider_id, report_date, total_games_played, total_bets, total_payouts, total_profit, total_players, report_type, created_at, updated_at
|
||||||
|
FROM virtual_game_provider_reports
|
||||||
|
ORDER BY total_games_played DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) ListVirtualGameProviderReportsByGamesPlayedDesc(ctx context.Context) ([]VirtualGameProviderReport, error) {
|
||||||
|
rows, err := q.db.Query(ctx, ListVirtualGameProviderReportsByGamesPlayedDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []VirtualGameProviderReport
|
||||||
|
for rows.Next() {
|
||||||
|
var i VirtualGameProviderReport
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.ProviderID,
|
||||||
|
&i.ReportDate,
|
||||||
|
&i.TotalGamesPlayed,
|
||||||
|
&i.TotalBets,
|
||||||
|
&i.TotalPayouts,
|
||||||
|
&i.TotalProfit,
|
||||||
|
&i.TotalPlayers,
|
||||||
|
&i.ReportType,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const ListVirtualGameProviders = `-- name: ListVirtualGameProviders :many
|
const ListVirtualGameProviders = `-- name: ListVirtualGameProviders :many
|
||||||
SELECT id,
|
SELECT id,
|
||||||
provider_id,
|
provider_id,
|
||||||
|
|
@ -845,20 +1082,40 @@ func (q *Queries) UpdateVirtualGameProviderEnabled(ctx context.Context, arg Upda
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
|
const UpdateVirtualGameProviderReportByDate = `-- name: UpdateVirtualGameProviderReportByDate :exec
|
||||||
UPDATE virtual_game_sessions
|
UPDATE virtual_game_provider_reports
|
||||||
SET status = $2,
|
SET
|
||||||
|
total_games_played = total_games_played + $4,
|
||||||
|
total_bets = total_bets + $5,
|
||||||
|
total_payouts = total_payouts + $6,
|
||||||
|
total_players = total_players + $7,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $1
|
WHERE
|
||||||
|
provider_id = $1
|
||||||
|
AND report_date = $2
|
||||||
|
AND report_type = $3
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateVirtualGameSessionStatusParams struct {
|
type UpdateVirtualGameProviderReportByDateParams struct {
|
||||||
ID int64 `json:"id"`
|
ProviderID string `json:"provider_id"`
|
||||||
Status string `json:"status"`
|
ReportDate pgtype.Date `json:"report_date"`
|
||||||
|
ReportType pgtype.Text `json:"report_type"`
|
||||||
|
TotalGamesPlayed pgtype.Int8 `json:"total_games_played"`
|
||||||
|
TotalBets pgtype.Numeric `json:"total_bets"`
|
||||||
|
TotalPayouts pgtype.Numeric `json:"total_payouts"`
|
||||||
|
TotalPlayers pgtype.Int8 `json:"total_players"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateVirtualGameSessionStatus(ctx context.Context, arg UpdateVirtualGameSessionStatusParams) error {
|
func (q *Queries) UpdateVirtualGameProviderReportByDate(ctx context.Context, arg UpdateVirtualGameProviderReportByDateParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateVirtualGameSessionStatus, arg.ID, arg.Status)
|
_, err := q.db.Exec(ctx, UpdateVirtualGameProviderReportByDate,
|
||||||
|
arg.ProviderID,
|
||||||
|
arg.ReportDate,
|
||||||
|
arg.ReportType,
|
||||||
|
arg.TotalGamesPlayed,
|
||||||
|
arg.TotalBets,
|
||||||
|
arg.TotalPayouts,
|
||||||
|
arg.TotalPlayers,
|
||||||
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
8
go.mod
8
go.mod
|
|
@ -81,19 +81,11 @@ require (
|
||||||
require github.com/twilio/twilio-go v1.26.3
|
require github.com/twilio/twilio-go v1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.10.0 // direct
|
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
|
||||||
github.com/segmentio/kafka-go v0.4.48 // direct
|
|
||||||
)
|
|
||||||
|
|
||||||
// require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f
|
// require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f
|
||||||
|
|
||||||
// require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct
|
// require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct
|
||||||
|
|
|
||||||
33
go.sum
33
go.sum
|
|
@ -1,5 +1,3 @@
|
||||||
github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f h1:UOp9At84RG8OT2Nw2TQidYWaoW1rAfTqChOJLdhYcm8=
|
|
||||||
github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f/go.mod h1:N2NQ6ad3i+oLQU+MlPci2f7mx6Mtg+wUcvzCv77KCl8=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
|
@ -12,17 +10,11 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
|
||||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
|
||||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
|
@ -31,8 +23,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
|
@ -91,7 +81,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
|
|
@ -129,15 +118,11 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
|
||||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
|
||||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
|
||||||
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
github.com/resend/resend-go/v2 v2.20.0 h1:MrIrgV0aHhwRgmcRPw33Nexn6aGJvCvG2XwfFpAMBGM=
|
||||||
github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
github.com/resend/resend-go/v2 v2.20.0/go.mod h1:3YCb8c8+pLiqhtRFXTyFwlLvfjQtluxOr9HEh2BwCkQ=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
|
@ -150,8 +135,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/segmentio/kafka-go v0.4.48 h1:9jyu9CWK4W5W+SroCe8EffbrRZVqAOkuaLd/ApID4Vs=
|
|
||||||
github.com/segmentio/kafka-go v0.4.48/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
|
@ -215,12 +198,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
|
@ -233,15 +214,11 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -257,25 +234,16 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|
||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|
@ -284,7 +252,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnetPulseConfig struct {
|
type EnetPulseConfig struct {
|
||||||
UserName string `mapstructure:"username"` // "https://api.aleaplay.com"
|
UserName string `mapstructure:"username"`
|
||||||
Token string `mapstructure:"token"` // Your operator ID with Alea
|
Token string `mapstructure:"token"`
|
||||||
|
ProviderID string `mapstructure:"provider_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AleaPlayConfig struct {
|
type AleaPlayConfig struct {
|
||||||
|
|
@ -136,6 +137,7 @@ 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_WEBHOOK_SECRET string
|
||||||
CHAPA_TRANSFER_TYPE string
|
CHAPA_TRANSFER_TYPE string
|
||||||
CHAPA_PAYMENT_TYPE string
|
CHAPA_PAYMENT_TYPE string
|
||||||
CHAPA_SECRET_KEY string
|
CHAPA_SECRET_KEY string
|
||||||
|
|
@ -144,6 +146,7 @@ type Config struct {
|
||||||
CHAPA_ENCRYPTION_KEY string
|
CHAPA_ENCRYPTION_KEY string
|
||||||
CHAPA_CALLBACK_URL string
|
CHAPA_CALLBACK_URL string
|
||||||
CHAPA_RETURN_URL string
|
CHAPA_RETURN_URL string
|
||||||
|
CHAPA_RECEIPT_URL string
|
||||||
Bet365Token string
|
Bet365Token string
|
||||||
EnetPulseConfig EnetPulseConfig
|
EnetPulseConfig EnetPulseConfig
|
||||||
PopOK domain.PopOKConfig
|
PopOK domain.PopOKConfig
|
||||||
|
|
@ -192,6 +195,7 @@ func (c *Config) loadEnv() error {
|
||||||
|
|
||||||
c.EnetPulseConfig.Token = os.Getenv("ENETPULSE_TOKEN")
|
c.EnetPulseConfig.Token = os.Getenv("ENETPULSE_TOKEN")
|
||||||
c.EnetPulseConfig.UserName = os.Getenv("ENETPULSE_USERNAME")
|
c.EnetPulseConfig.UserName = os.Getenv("ENETPULSE_USERNAME")
|
||||||
|
c.EnetPulseConfig.ProviderID = os.Getenv("ENETPULSE_PROVIDER_ID")
|
||||||
|
|
||||||
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
||||||
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
||||||
|
|
@ -260,10 +264,12 @@ func (c *Config) loadEnv() error {
|
||||||
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
|
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
|
||||||
|
|
||||||
//Chapa
|
//Chapa
|
||||||
|
c.CHAPA_WEBHOOK_SECRET = os.Getenv("CHAPA_WEBHOOK_SECRET")
|
||||||
c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY")
|
c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY")
|
||||||
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
||||||
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
||||||
c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL")
|
c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL")
|
||||||
|
c.CHAPA_RECEIPT_URL = os.Getenv("CHAPA_RECEIPT_URL")
|
||||||
if c.CHAPA_BASE_URL == "" {
|
if c.CHAPA_BASE_URL == "" {
|
||||||
c.CHAPA_BASE_URL = "https://api.chapa.co/v1"
|
c.CHAPA_BASE_URL = "https://api.chapa.co/v1"
|
||||||
}
|
}
|
||||||
|
|
@ -431,34 +437,34 @@ func (c *Config) loadEnv() error {
|
||||||
Platform: popOKPlatform,
|
Platform: popOKPlatform,
|
||||||
}
|
}
|
||||||
|
|
||||||
AtlasBaseUrl := os.Getenv("ATLAS_BASE_URL")
|
// AtlasBaseUrl := os.Getenv("ATLAS_BASE_URL")
|
||||||
if AtlasBaseUrl == "" {
|
// if AtlasBaseUrl == "" {
|
||||||
return ErrInvalidAtlasBaseUrl
|
// return ErrInvalidAtlasBaseUrl
|
||||||
}
|
// }
|
||||||
AtlasSecretKey := os.Getenv("ATLAS_SECRET_KEY")
|
// AtlasSecretKey := os.Getenv("ATLAS_SECRET_KEY")
|
||||||
if AtlasSecretKey == "" {
|
// if AtlasSecretKey == "" {
|
||||||
return ErrInvalidAtlasSecretKey
|
// return ErrInvalidAtlasSecretKey
|
||||||
}
|
// }
|
||||||
AtlasBrandID := os.Getenv("ATLAS_BRAND_ID")
|
// AtlasBrandID := os.Getenv("ATLAS_BRAND_ID")
|
||||||
if AtlasBrandID == "" {
|
// if AtlasBrandID == "" {
|
||||||
return ErrInvalidAtlasBrandID
|
// return ErrInvalidAtlasBrandID
|
||||||
}
|
// }
|
||||||
AtlasPartnerID := os.Getenv("ATLAS_PARTNER_ID")
|
// AtlasPartnerID := os.Getenv("ATLAS_PARTNER_ID")
|
||||||
if AtlasPartnerID == "" {
|
// if AtlasPartnerID == "" {
|
||||||
return ErrInvalidAtlasPartnerID
|
// return ErrInvalidAtlasPartnerID
|
||||||
}
|
// }
|
||||||
AtlasOperatorID := os.Getenv("ATLAS_OPERATOR_ID")
|
// AtlasOperatorID := os.Getenv("ATLAS_OPERATOR_ID")
|
||||||
if AtlasOperatorID == "" {
|
// if AtlasOperatorID == "" {
|
||||||
return ErrInvalidAtlasOperatorID
|
// return ErrInvalidAtlasOperatorID
|
||||||
}
|
// }
|
||||||
|
|
||||||
c.Atlas = AtlasConfig{
|
// c.Atlas = AtlasConfig{
|
||||||
BaseURL: AtlasBaseUrl,
|
// BaseURL: AtlasBaseUrl,
|
||||||
SecretKey: AtlasSecretKey,
|
// SecretKey: AtlasSecretKey,
|
||||||
CasinoID: AtlasBrandID,
|
// CasinoID: AtlasBrandID,
|
||||||
PartnerID: AtlasPartnerID,
|
// PartnerID: AtlasPartnerID,
|
||||||
OperatorID: AtlasOperatorID,
|
// OperatorID: AtlasOperatorID,
|
||||||
}
|
// }
|
||||||
|
|
||||||
betToken := os.Getenv("BET365_TOKEN")
|
betToken := os.Getenv("BET365_TOKEN")
|
||||||
if betToken == "" {
|
if betToken == "" {
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,12 @@ type WebhookRequest struct {
|
||||||
SessionID string `json:"sessionId"`
|
SessionID string `json:"sessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArifpayB2CRequest struct{
|
// type ArifpayB2CRequest struct{
|
||||||
PhoneNumber string `json:"Phonenumber"`
|
// PhoneNumber string `json:"Phonenumber"`
|
||||||
Amount float64 `json:"amount" binding:"required"`
|
// Amount float64 `json:"amount" binding:"required"`
|
||||||
CustomerEmail string `json:"customerEmail" binding:"required"`
|
// CustomerEmail string `json:"customerEmail" binding:"required"`
|
||||||
CustomerPhone string `json:"customerPhone" binding:"required"`
|
// // CustomerPhone string `json:"customerPhone" binding:"required"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ArifpayVerifyByTransactionIDRequest struct{
|
type ArifpayVerifyByTransactionIDRequest struct{
|
||||||
TransactionId string `json:"transactionId"`
|
TransactionId string `json:"transactionId"`
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ type AtlasBetResponse struct {
|
||||||
type AtlasBetWinRequest struct {
|
type AtlasBetWinRequest struct {
|
||||||
Game string `json:"game"`
|
Game string `json:"game"`
|
||||||
CasinoID string `json:"casino_id"`
|
CasinoID string `json:"casino_id"`
|
||||||
RoundID int64 `json:"round_id"`
|
RoundID string `json:"round_id"`
|
||||||
PlayerID string `json:"player_id"`
|
PlayerID string `json:"player_id"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
BetAmount float64 `json:"betAmount"`
|
BetAmount float64 `json:"betAmount"`
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const (
|
||||||
PaymentStatusFailed PaymentStatus = "failed"
|
PaymentStatusFailed PaymentStatus = "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChapaDepositRequest struct {
|
type ChapaInitDepositRequest struct {
|
||||||
Amount Currency `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|
@ -42,6 +42,8 @@ type ChapaDepositRequest struct {
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
CallbackURL string `json:"callback_url"`
|
CallbackURL string `json:"callback_url"`
|
||||||
ReturnURL string `json:"return_url"`
|
ReturnURL string `json:"return_url"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
// PhoneNumber string `json:"phone_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaDepositRequestPayload struct {
|
type ChapaDepositRequestPayload struct {
|
||||||
|
|
@ -49,9 +51,15 @@ type ChapaDepositRequestPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebhookPayload struct {
|
type ChapaWebhookPayload struct {
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"trx_ref"`
|
||||||
Amount Currency `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
// Currency string `json:"currency"`
|
||||||
|
Status PaymentStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentWebhookRequest struct {
|
||||||
|
TxRef string `json:"trx_ref"`
|
||||||
|
RefId string `json:"ref_id"`
|
||||||
Status PaymentStatus `json:"status"`
|
Status PaymentStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,11 +76,117 @@ type ChapaDepositVerification struct {
|
||||||
Currency string
|
Currency string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaVerificationResponse struct {
|
type ChapaPaymentVerificationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Data struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
|
Customization struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Logo interface{} `json:"logo"`
|
||||||
|
} `json:"customization"`
|
||||||
|
Meta interface{} `json:"meta"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransferVerificationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Mobile interface{} `json:"mobile"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
TransferMethod string `json:"transfer_method"`
|
||||||
|
Narration interface{} `json:"narration"`
|
||||||
|
ChapaTransferID string `json:"chapa_transfer_id"`
|
||||||
|
BankCode int `json:"bank_code"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
CrossPartyReference interface{} `json:"cross_party_reference"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaAllTransactionsResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
Transactions []struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
TransID *string `json:"trans_id"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
FirstName *string `json:"first_name"`
|
||||||
|
LastName *string `json:"last_name"`
|
||||||
|
Mobile *string `json:"mobile"`
|
||||||
|
} `json:"customer"`
|
||||||
|
} `json:"transactions"`
|
||||||
|
Pagination struct {
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
FirstPageURL string `json:"first_page_url"`
|
||||||
|
NextPageURL *string `json:"next_page_url"`
|
||||||
|
PrevPageURL *string `json:"prev_page_url"`
|
||||||
|
} `json:"pagination"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransactionEvent struct {
|
||||||
|
Item int64 `json:"item"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransaction struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
TransID *string `json:"trans_id"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customer ChapaCustomer `json:"customer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaCustomer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
FirstName *string `json:"first_name"`
|
||||||
|
LastName *string `json:"last_name"`
|
||||||
|
Mobile *string `json:"mobile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// type Bank struct {
|
// type Bank struct {
|
||||||
|
|
@ -93,6 +207,57 @@ type ChapaVerificationResponse struct {
|
||||||
// BankLogo string `json:"bank_logo"` // URL or base64
|
// BankLogo string `json:"bank_logo"` // URL or base64
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
type SwapResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
FromCurrency string `json:"from_currency"`
|
||||||
|
ToCurrency string `json:"to_currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
ExchangedAmount float64 `json:"exchanged_amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Rate float64 `json:"rate"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransfersListResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Meta struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
FirstPageURL string `json:"first_page_url"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
LastPageURL string `json:"last_page_url"`
|
||||||
|
NextPageURL string `json:"next_page_url"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
PrevPageURL interface{} `json:"prev_page_url"`
|
||||||
|
To int `json:"to"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Error []interface{} `json:"error"`
|
||||||
|
} `json:"meta"`
|
||||||
|
Data []struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
TransferType string `json:"transfer_type"`
|
||||||
|
ChapaReference string `json:"chapa_reference"`
|
||||||
|
BankCode int `json:"bank_code"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
BankReference interface{} `json:"bank_reference"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference interface{} `json:"reference"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
type BankResponse struct {
|
type BankResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
|
@ -157,42 +322,69 @@ type ChapaTransactionType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebHookTransfer struct {
|
type ChapaWebhookTransfer struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
Type string `json:"type"`
|
||||||
AccountName string `json:"account_name"`
|
AccountName string `json:"account_name"`
|
||||||
AccountNumber string `json:"account_number"`
|
AccountNumber string `json:"account_number"`
|
||||||
BankId string `json:"bank_id"`
|
BankID int `json:"bank_id"`
|
||||||
BankName string `json:"bank_name"`
|
BankName string `json:"bank_name"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
Type string `json:"type"`
|
Charge string `json:"charge"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
TxRef string `json:"tx_ref"`
|
|
||||||
ChapaReference string `json:"chapa_reference"`
|
ChapaReference string `json:"chapa_reference"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
BankReference string `json:"bank_reference"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebHookPayment struct {
|
type ChapaWebhookPayment struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
Email string `json:"email"`
|
Email *string `json:"email,omitempty"`
|
||||||
Mobile interface{} `json:"mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
Charge string `json:"charge"`
|
Charge string `json:"charge"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt string `json:"updated_at"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
PaymentMethod string `json:"payment_method"`
|
PaymentMethod string `json:"payment_method"`
|
||||||
Customization struct {
|
Customization ChapaWebhookCustomization `json:"customization"`
|
||||||
Title interface{} `json:"title"`
|
Meta interface{} `json:"meta"` // may vary in structure, so kept flexible
|
||||||
Description interface{} `json:"description"`
|
}
|
||||||
Logo interface{} `json:"logo"`
|
|
||||||
} `json:"customization"`
|
type ChapaWebhookCustomization struct {
|
||||||
Meta string `json:"meta"`
|
Title *string `json:"title,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Logo *string `json:"logo,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Balance struct {
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
AvailableBalance float64 `json:"available_balance"`
|
||||||
|
LedgerBalance float64 `json:"ledger_balance"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwapRequest struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaCancelResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ type TournamentStageParticipantsResponse struct {
|
||||||
type DailyEventsRequest struct {
|
type DailyEventsRequest struct {
|
||||||
SportFK int // one of these three required
|
SportFK int // one of these three required
|
||||||
TournamentTemplateFK int
|
TournamentTemplateFK int
|
||||||
TournamentStageFK int
|
// TournamentStageFK int
|
||||||
Date string // YYYY-MM-DD optional
|
Date string // YYYY-MM-DD optional
|
||||||
Live string // yes/no optional
|
Live string // yes/no optional
|
||||||
IncludeVenue string // yes/no optional
|
IncludeVenue string // yes/no optional
|
||||||
|
|
@ -147,7 +147,7 @@ type DailyEventsResponse struct {
|
||||||
type FixturesRequest struct {
|
type FixturesRequest struct {
|
||||||
SportFK int
|
SportFK int
|
||||||
TournamentTemplateFK int
|
TournamentTemplateFK int
|
||||||
TournamentStageFK int
|
// TournamentStageFK int
|
||||||
LanguageTypeFK int
|
LanguageTypeFK int
|
||||||
Date string // YYYY-MM-DD
|
Date string // YYYY-MM-DD
|
||||||
Live string // "yes" | "no"
|
Live string // "yes" | "no"
|
||||||
|
|
@ -172,7 +172,7 @@ type FixtureEvent struct {
|
||||||
type ResultsRequest struct {
|
type ResultsRequest struct {
|
||||||
SportFK int
|
SportFK int
|
||||||
TournamentTemplateFK int
|
TournamentTemplateFK int
|
||||||
TournamentStageFK int
|
// TournamentStageFK int
|
||||||
LanguageTypeFK int
|
LanguageTypeFK int
|
||||||
Date string // YYYY-MM-DD
|
Date string // YYYY-MM-DD
|
||||||
Live string // "yes" | "no"
|
Live string // "yes" | "no"
|
||||||
|
|
@ -225,7 +225,7 @@ type EventDetailsResponse struct {
|
||||||
|
|
||||||
type EventListRequest struct {
|
type EventListRequest struct {
|
||||||
TournamentFK int // optional
|
TournamentFK int // optional
|
||||||
TournamentStageFK int // optional
|
// TournamentStageFK int // optional
|
||||||
IncludeEventProperties bool // default true
|
IncludeEventProperties bool // default true
|
||||||
StatusType string // e.g. "finished", "inprogress"
|
StatusType string // e.g. "finished", "inprogress"
|
||||||
IncludeVenue bool
|
IncludeVenue bool
|
||||||
|
|
@ -250,7 +250,7 @@ type ParticipantFixturesRequest struct {
|
||||||
SportFK int
|
SportFK int
|
||||||
TournamentFK int
|
TournamentFK int
|
||||||
TournamentTemplateFK int
|
TournamentTemplateFK int
|
||||||
TournamentStageFK int
|
// TournamentStageFK int
|
||||||
Date string
|
Date string
|
||||||
Live string
|
Live string
|
||||||
Limit int
|
Limit int
|
||||||
|
|
@ -462,3 +462,308 @@ type CreateEnetpulseTournamentStage struct {
|
||||||
CountryName string `json:"country_name"` // country name from API
|
CountryName string `json:"country_name"` // country name from API
|
||||||
Status int `json:"status"` // active/inactive
|
Status int `json:"status"` // active/inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For insertion
|
||||||
|
type CreateEnetpulseFixture struct {
|
||||||
|
FixtureID string
|
||||||
|
Name string
|
||||||
|
SportFK string
|
||||||
|
TournamentFK string
|
||||||
|
TournamentTemplateFK string
|
||||||
|
TournamentStageFK string
|
||||||
|
TournamentStageName string
|
||||||
|
TournamentName string
|
||||||
|
TournamentTemplateName string
|
||||||
|
SportName string
|
||||||
|
Gender string
|
||||||
|
StartDate time.Time
|
||||||
|
StatusType string
|
||||||
|
StatusDescFK string
|
||||||
|
RoundTypeFK string
|
||||||
|
UpdatesCount int
|
||||||
|
LastUpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full domain model
|
||||||
|
type EnetpulseFixture struct {
|
||||||
|
FixtureID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFK string `json:"sportFK"`
|
||||||
|
TournamentFK string `json:"tournamentFK"`
|
||||||
|
TournamentTemplateFK string `json:"tournament_templateFK"`
|
||||||
|
// TournamentStageFK string `json:"tournament_stageFK"`
|
||||||
|
TournamentStageName string `json:"tournament_stage_name"`
|
||||||
|
TournamentName string `json:"tournament_name"`
|
||||||
|
TournamentTemplateName string `json:"tournament_template_name"`
|
||||||
|
SportName string `json:"sport_name"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
StartDate string `json:"startdate"` // ISO 8601
|
||||||
|
StatusType string `json:"status_type"`
|
||||||
|
StatusDescFK string `json:"status_descFK"`
|
||||||
|
RoundTypeFK string `json:"round_typeFK"`
|
||||||
|
UpdatesCount string `json:"n"` // convert to int
|
||||||
|
LastUpdatedAt string `json:"ut"` // parse to time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateEnetpulseResult struct {
|
||||||
|
ResultID string `json:"result_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFK string `json:"sport_fk"`
|
||||||
|
TournamentFK string `json:"tournament_fk"`
|
||||||
|
TournamentTemplateFK string `json:"tournament_template_fk"`
|
||||||
|
// TournamentStageFK string `json:"tournament_stage_fk"`
|
||||||
|
TournamentStageName string `json:"tournament_stage_name"`
|
||||||
|
TournamentName string `json:"tournament_name"`
|
||||||
|
TournamentTemplateName string `json:"tournament_template_name"`
|
||||||
|
SportName string `json:"sport_name"`
|
||||||
|
StartDate time.Time `json:"start_date"`
|
||||||
|
StatusType string `json:"status_type"`
|
||||||
|
StatusDescFK string `json:"status_desc_fk"`
|
||||||
|
RoundTypeFK string `json:"round_type_fk"`
|
||||||
|
UpdatesCount int32 `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
|
||||||
|
// Optional metadata
|
||||||
|
Round string `json:"round"`
|
||||||
|
Live string `json:"live"`
|
||||||
|
VenueName string `json:"venue_name"`
|
||||||
|
LivestatsPlus string `json:"livestats_plus"`
|
||||||
|
LivestatsType string `json:"livestats_type"`
|
||||||
|
Commentary string `json:"commentary"`
|
||||||
|
LineupConfirmed bool `json:"lineup_confirmed"`
|
||||||
|
Verified bool `json:"verified"`
|
||||||
|
Spectators int32 `json:"spectators"`
|
||||||
|
|
||||||
|
// Time-related metadata
|
||||||
|
GameStarted *time.Time `json:"game_started"`
|
||||||
|
FirstHalfEnded *time.Time `json:"first_half_ended"`
|
||||||
|
SecondHalfStarted *time.Time `json:"second_half_started"`
|
||||||
|
SecondHalfEnded *time.Time `json:"second_half_ended"`
|
||||||
|
GameEnded *time.Time `json:"game_ended"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Used for reading result records
|
||||||
|
type EnetpulseResult struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ResultID string `json:"result_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFK string `json:"sport_fk"`
|
||||||
|
TournamentFK string `json:"tournament_fk"`
|
||||||
|
TournamentTemplateFK string `json:"tournament_template_fk"`
|
||||||
|
// TournamentStageFK string `json:"tournament_stage_fk"`
|
||||||
|
TournamentStageName string `json:"tournament_stage_name"`
|
||||||
|
TournamentName string `json:"tournament_name"`
|
||||||
|
TournamentTemplateName string `json:"tournament_template_name"`
|
||||||
|
SportName string `json:"sport_name"`
|
||||||
|
StartDate time.Time `json:"start_date"`
|
||||||
|
StatusType string `json:"status_type"`
|
||||||
|
StatusDescFK string `json:"status_desc_fk"`
|
||||||
|
RoundTypeFK string `json:"round_type_fk"`
|
||||||
|
UpdatesCount int32 `json:"updates_count"`
|
||||||
|
LastUpdatedAt *time.Time `json:"last_updated_at"`
|
||||||
|
|
||||||
|
Round string `json:"round"`
|
||||||
|
Live string `json:"live"`
|
||||||
|
VenueName string `json:"venue_name"`
|
||||||
|
LivestatsPlus string `json:"livestats_plus"`
|
||||||
|
LivestatsType string `json:"livestats_type"`
|
||||||
|
Commentary string `json:"commentary"`
|
||||||
|
LineupConfirmed bool `json:"lineup_confirmed"`
|
||||||
|
Verified bool `json:"verified"`
|
||||||
|
Spectators int32 `json:"spectators"`
|
||||||
|
|
||||||
|
GameStarted *time.Time `json:"game_started"`
|
||||||
|
FirstHalfEnded *time.Time `json:"first_half_ended"`
|
||||||
|
SecondHalfStarted *time.Time `json:"second_half_started"`
|
||||||
|
SecondHalfEnded *time.Time `json:"second_half_ended"`
|
||||||
|
GameEnded *time.Time `json:"game_ended"`
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseOutcomeType struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
OutcomeTypeID string `json:"outcome_type_id"` // changed from int64 → string
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
UpdatesCount int32 `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulseOutcomeType represents the payload to create or update an outcome type.
|
||||||
|
type CreateEnetpulseOutcomeType struct {
|
||||||
|
OutcomeTypeID string `json:"outcome_type_id"` // changed from int64 → string
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
UpdatesCount int32 `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulsePreoddsTemp struct {
|
||||||
|
PreoddsID string `json:"preodds_id"`
|
||||||
|
EventFK string `json:"event_fk"`
|
||||||
|
OutcomeTypeFK string `json:"outcome_type_fk"`
|
||||||
|
OutcomeScopeFK string `json:"outcome_scope_fk"`
|
||||||
|
OutcomeSubtypeFK string `json:"outcome_subtype_fk"`
|
||||||
|
EventParticipantNumber int `json:"event_participant_number"`
|
||||||
|
IParam string `json:"iparam"`
|
||||||
|
IParam2 string `json:"iparam2"`
|
||||||
|
DParam string `json:"dparam"`
|
||||||
|
DParam2 string `json:"dparam2"`
|
||||||
|
SParam string `json:"sparam"`
|
||||||
|
UpdatesCount int `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulsePreodds is used when inserting a new preodds record
|
||||||
|
type CreateEnetpulsePreodds struct {
|
||||||
|
PreoddsID string `json:"preodds_id"`
|
||||||
|
EventFK string `json:"event_fk"`
|
||||||
|
OutcomeTypeFK string `json:"outcome_type_fk"`
|
||||||
|
OutcomeScopeFK string `json:"outcome_scope_fk"`
|
||||||
|
OutcomeSubtypeFK string `json:"outcome_subtype_fk"`
|
||||||
|
EventParticipantNumber int `json:"event_participant_number"`
|
||||||
|
IParam string `json:"iparam"`
|
||||||
|
IParam2 string `json:"iparam2"`
|
||||||
|
DParam string `json:"dparam"`
|
||||||
|
DParam2 string `json:"dparam2"`
|
||||||
|
SParam string `json:"sparam"`
|
||||||
|
UpdatesCount int `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateEnetpulsePreoddsBettingOffer struct {
|
||||||
|
BettingOfferID string
|
||||||
|
PreoddsFK string
|
||||||
|
BettingOfferStatusFK int32
|
||||||
|
OddsProviderFK int32
|
||||||
|
Odds float64
|
||||||
|
OddsOld float64
|
||||||
|
Active string
|
||||||
|
CouponKey string
|
||||||
|
UpdatesCount int
|
||||||
|
LastUpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnetpulsePreoddsBettingOffer represents the DB record of a betting offer
|
||||||
|
type EnetpulsePreoddsBettingOffer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
BettingOfferID string `json:"betting_offer_id"`
|
||||||
|
PreoddsFK string `json:"preodds_fk"`
|
||||||
|
BettingOfferStatusFK int32 `json:"betting_offer_status_fk"`
|
||||||
|
OddsProviderFK int32 `json:"odds_provider_fk"`
|
||||||
|
Odds float64 `json:"odds"`
|
||||||
|
OddsOld float64 `json:"odds_old"`
|
||||||
|
Active string `json:"active"`
|
||||||
|
CouponKey string `json:"coupon_key"`
|
||||||
|
UpdatesCount int `json:"updates_count"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseFixtureWithPreodds struct {
|
||||||
|
FixtureID string
|
||||||
|
FixtureApiID string
|
||||||
|
FixtureName string
|
||||||
|
SportFk string
|
||||||
|
TournamentFk string
|
||||||
|
TournamentTemplateFk string
|
||||||
|
TournamentStageFk string
|
||||||
|
StartDate time.Time
|
||||||
|
StatusType string
|
||||||
|
StatusDescFk string
|
||||||
|
RoundTypeFk string
|
||||||
|
UpdatesCount int32
|
||||||
|
LastUpdatedAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Preodds []EnetpulsePreodds
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulsePreodds struct {
|
||||||
|
ID int64
|
||||||
|
PreoddsID string
|
||||||
|
EventFK int64
|
||||||
|
OutcomeTypeFK int32
|
||||||
|
OutcomeScopeFK int32
|
||||||
|
OutcomeSubtypeFK int32
|
||||||
|
EventParticipantNumber int32
|
||||||
|
IParam string
|
||||||
|
IParam2 string
|
||||||
|
DParam string
|
||||||
|
DParam2 string
|
||||||
|
SParam string
|
||||||
|
UpdatesCount int32
|
||||||
|
LastUpdatedAt time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
BettingOffers []EnetpulsePreoddsBettingOffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseResultParticipant struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ParticipantMapID string `json:"participant_map_id"`
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
ParticipantFk string `json:"participant_fk"`
|
||||||
|
Number int32 `json:"number"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CountryFk string `json:"country_fk"`
|
||||||
|
CountryName string `json:"country_name"`
|
||||||
|
OrdinaryTime string `json:"ordinary_time"`
|
||||||
|
RunningScore string `json:"running_score"`
|
||||||
|
Halftime string `json:"halftime"`
|
||||||
|
FinalResult string `json:"final_result"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulseResultParticipant is the payload for inserting or updating a participant record.
|
||||||
|
type CreateEnetpulseResultParticipant struct {
|
||||||
|
ParticipantMapID string `json:"participant_map_id"`
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
ParticipantFk string `json:"participant_fk"`
|
||||||
|
Number int32 `json:"number"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CountryFk string `json:"country_fk"`
|
||||||
|
CountryName string `json:"country_name"`
|
||||||
|
OrdinaryTime string `json:"ordinary_time"`
|
||||||
|
RunningScore string `json:"running_score"`
|
||||||
|
Halftime string `json:"halftime"`
|
||||||
|
FinalResult string `json:"final_result"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnetpulseResultReferee struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
RefereeFk string `json:"referee_fk"`
|
||||||
|
Assistant1RefereeFk string `json:"assistant1_referee_fk"`
|
||||||
|
Assistant2RefereeFk string `json:"assistant2_referee_fk"`
|
||||||
|
FourthRefereeFk string `json:"fourth_referee_fk"`
|
||||||
|
Var1RefereeFk string `json:"var1_referee_fk"`
|
||||||
|
Var2RefereeFk string `json:"var2_referee_fk"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulseResultReferee is the payload for inserting or updating referee assignments.
|
||||||
|
type CreateEnetpulseResultReferee struct {
|
||||||
|
ResultFk string `json:"result_fk"`
|
||||||
|
RefereeFk string `json:"referee_fk"`
|
||||||
|
Assistant1RefereeFk string `json:"assistant1_referee_fk"`
|
||||||
|
Assistant2RefereeFk string `json:"assistant2_referee_fk"`
|
||||||
|
FourthRefereeFk string `json:"fourth_referee_fk"`
|
||||||
|
Var1RefereeFk string `json:"var1_referee_fk"`
|
||||||
|
Var2RefereeFk string `json:"var2_referee_fk"`
|
||||||
|
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ type GameStartRequest struct {
|
||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
BrandID string `json:"brandId"`
|
BrandID string `json:"brandId"`
|
||||||
UserAgent string `json:"userAgent,omitempty"`
|
// UserAgent string `json:"userAgent,omitempty"`
|
||||||
LobbyURL string `json:"lobbyUrl,omitempty"`
|
// LobbyURL string `json:"lobbyUrl,omitempty"`
|
||||||
CashierURL string `json:"cashierUrl,omitempty"`
|
// CashierURL string `json:"cashierUrl,omitempty"`
|
||||||
PlayerName string `json:"playerName,omitempty"`
|
// PlayerName string `json:"playerName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DemoGameRequest struct {
|
type DemoGameRequest struct {
|
||||||
|
|
@ -71,8 +71,8 @@ type DemoGameRequest struct {
|
||||||
DeviceType string `json:"deviceType"`
|
DeviceType string `json:"deviceType"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
BrandID string `json:"brandId"`
|
BrandID string `json:"brandId"`
|
||||||
PlayerID string `json:"playerId,omitempty"`
|
// PlayerID string `json:"playerId,omitempty"`
|
||||||
Country string `json:"country,omitempty"`
|
// Country string `json:"country,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameStartResponse struct {
|
type GameStartResponse struct {
|
||||||
|
|
|
||||||
|
|
@ -316,3 +316,61 @@ type UnifiedGame struct {
|
||||||
Status int `json:"status,omitempty"`
|
Status int `json:"status,omitempty"`
|
||||||
DemoURL string `json:"demoUrl"`
|
DemoURL string `json:"demoUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateVirtualGameProviderReport struct {
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate time.Time `json:"report_date"`
|
||||||
|
TotalGamesPlayed int64 `json:"total_games_played"`
|
||||||
|
TotalBets float64 `json:"total_bets"`
|
||||||
|
TotalPayouts float64 `json:"total_payouts"`
|
||||||
|
TotalPlayers int64 `json:"total_players"`
|
||||||
|
ReportType string `json:"report_type"` // e.g., "daily", "weekly"
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualGameProviderReport struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate time.Time `json:"report_date"`
|
||||||
|
TotalGamesPlayed int64 `json:"total_games_played"`
|
||||||
|
TotalBets float64 `json:"total_bets"`
|
||||||
|
TotalPayouts float64 `json:"total_payouts"`
|
||||||
|
TotalProfit float64 `json:"total_profit"`
|
||||||
|
TotalPlayers int64 `json:"total_players"`
|
||||||
|
ReportType string `json:"report_type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateVirtualGameReport struct {
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate time.Time `json:"report_date"`
|
||||||
|
TotalRounds int64 `json:"total_rounds"`
|
||||||
|
TotalBets float64 `json:"total_bets"`
|
||||||
|
TotalPayouts float64 `json:"total_payouts"`
|
||||||
|
TotalPlayers int64 `json:"total_players"`
|
||||||
|
ReportType string `json:"report_type"` // e.g., "daily", "weekly"
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualGameReport struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
ProviderID string `json:"provider_id"`
|
||||||
|
ReportDate time.Time `json:"report_date"`
|
||||||
|
TotalRounds int64 `json:"total_rounds"`
|
||||||
|
TotalBets float64 `json:"total_bets"`
|
||||||
|
TotalPayouts float64 `json:"total_payouts"`
|
||||||
|
TotalProfit float64 `json:"total_profit"`
|
||||||
|
TotalPlayers int64 `json:"total_players"`
|
||||||
|
ReportType string `json:"report_type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateVirtualGameProviderReportsRequest struct {
|
||||||
|
Reports []CreateVirtualGameProviderReport
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateVirtualGameReportsRequest struct {
|
||||||
|
Reports []CreateVirtualGameReport
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
|
@ -140,6 +143,295 @@ func (s *Store) GetTournamentStagesByTournamentFK(ctx context.Context, tournamen
|
||||||
return stages, nil
|
return stages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new fixture
|
||||||
|
func (s *Store) CreateEnetpulseFixture(
|
||||||
|
ctx context.Context,
|
||||||
|
fixture domain.CreateEnetpulseFixture,
|
||||||
|
) (domain.EnetpulseFixture, error) {
|
||||||
|
// Convert domain model to DB params (sqlc-generated struct or parameters)
|
||||||
|
dbFixture, err := s.queries.CreateEnetpulseFixture(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateEnetpulseFixture(fixture), // your converter
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulseFixture{}, err
|
||||||
|
}
|
||||||
|
return ConvertDBEnetpulseFixture(dbFixture), nil // convert DB row to domain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all fixtures
|
||||||
|
func (s *Store) GetAllEnetpulseFixtures(ctx context.Context) ([]domain.EnetpulseFixture, error) {
|
||||||
|
dbFixtures, err := s.queries.GetAllEnetpulseFixtures(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixtures []domain.EnetpulseFixture
|
||||||
|
for _, dbFixture := range dbFixtures {
|
||||||
|
fixtures = append(fixtures, ConvertDBEnetpulseFixture(dbFixture))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixtures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateEnetpulseResult(
|
||||||
|
ctx context.Context,
|
||||||
|
result domain.CreateEnetpulseResult,
|
||||||
|
) (domain.EnetpulseResult, error) {
|
||||||
|
dbResult, err := s.queries.CreateEnetpulseResult(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateEnetpulseResult(result),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulseResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulseResult(dbResult), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllEnetpulseResults retrieves all Enetpulse results.
|
||||||
|
func (s *Store) GetAllEnetpulseResults(ctx context.Context) ([]domain.EnetpulseResult, error) {
|
||||||
|
dbResults, err := s.queries.GetAllEnetpulseResults(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]domain.EnetpulseResult, 0, len(dbResults))
|
||||||
|
for _, dbR := range dbResults {
|
||||||
|
results = append(results, ConvertDBEnetpulseResult(dbR))
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulseOutcomeType inserts or updates an EnetPulse outcome type record.
|
||||||
|
func (s *Store) CreateEnetpulseOutcomeType(
|
||||||
|
ctx context.Context,
|
||||||
|
outcomeType domain.CreateEnetpulseOutcomeType,
|
||||||
|
) (domain.EnetpulseOutcomeType, error) {
|
||||||
|
dbOutcome, err := s.queries.CreateEnetpulseOutcomeType(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateEnetpulseOutcomeType(outcomeType),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulseOutcomeType{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulseOutcomeType(dbOutcome), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllEnetpulseOutcomeTypes retrieves all outcome types.
|
||||||
|
func (s *Store) GetAllEnetpulseOutcomeTypes(ctx context.Context) ([]domain.EnetpulseOutcomeType, error) {
|
||||||
|
dbOutcomes, err := s.queries.GetAllEnetpulseOutcomeTypes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outcomes := make([]domain.EnetpulseOutcomeType, 0, len(dbOutcomes))
|
||||||
|
for _, dbO := range dbOutcomes {
|
||||||
|
outcomes = append(outcomes, ConvertDBEnetpulseOutcomeType(dbO))
|
||||||
|
}
|
||||||
|
|
||||||
|
return outcomes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulsePreodds inserts or updates a preodds record.
|
||||||
|
func (s *Store) CreateEnetpulsePreodds(
|
||||||
|
ctx context.Context,
|
||||||
|
preodds domain.CreateEnetpulsePreodds,
|
||||||
|
) (domain.EnetpulsePreodds, error) {
|
||||||
|
|
||||||
|
// Convert domain to DB params
|
||||||
|
params, err := ConvertCreateEnetpulsePreodds(preodds)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulsePreodds{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert into DB
|
||||||
|
dbPreodds, err := s.queries.CreateEnetpulsePreodds(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulsePreodds{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulsePreodds(dbPreodds), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllEnetpulsePreodds retrieves all preodds records.
|
||||||
|
func (s *Store) GetAllEnetpulsePreodds(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
dbPreodds, err := s.queries.GetAllEnetpulsePreodds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
preodds := make([]domain.EnetpulsePreodds, 0, len(dbPreodds))
|
||||||
|
for _, dbP := range dbPreodds {
|
||||||
|
preodds = append(preodds, ConvertDBEnetpulsePreodds(dbP))
|
||||||
|
}
|
||||||
|
|
||||||
|
return preodds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEnetpulsePreoddsBettingOffer inserts or updates a betting offer
|
||||||
|
func (s *Store) CreateEnetpulsePreoddsBettingOffer(
|
||||||
|
ctx context.Context,
|
||||||
|
bettingOffer domain.CreateEnetpulsePreoddsBettingOffer,
|
||||||
|
) (domain.EnetpulsePreoddsBettingOffer, error) {
|
||||||
|
|
||||||
|
params := ConvertCreateEnetpulsePreoddsBettingOffer(bettingOffer)
|
||||||
|
|
||||||
|
dbOffer, err := s.queries.CreateEnetpulsePreoddsBettingOffer(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulsePreoddsBettingOffer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulsePreoddsBettingOffer(dbOffer), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllEnetpulsePreoddsBettingOffers retrieves all betting offers
|
||||||
|
func (s *Store) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]domain.EnetpulsePreoddsBettingOffer, error) {
|
||||||
|
dbOffers, err := s.queries.GetAllEnetpulsePreoddsBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offers := make([]domain.EnetpulsePreoddsBettingOffer, 0, len(dbOffers))
|
||||||
|
for _, dbO := range dbOffers {
|
||||||
|
offers = append(offers, ConvertDBEnetpulsePreoddsBettingOffer(dbO))
|
||||||
|
}
|
||||||
|
|
||||||
|
return offers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetAllEnetpulsePreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
rows, err := s.queries.GetAllEnetpulsePreoddsWithBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch preodds with betting offers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map for grouping betting offers under each Preodd
|
||||||
|
preoddsMap := make(map[string]*domain.EnetpulsePreodds)
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
pid := row.PreoddsID
|
||||||
|
preodd, exists := preoddsMap[pid]
|
||||||
|
if !exists {
|
||||||
|
// Create the base Preodd entry
|
||||||
|
preodd = &domain.EnetpulsePreodds{
|
||||||
|
ID: row.PreoddsDbID,
|
||||||
|
PreoddsID: row.PreoddsID,
|
||||||
|
EventFK: row.EventFk,
|
||||||
|
OutcomeTypeFK: row.OutcomeTypeFk.Int32,
|
||||||
|
OutcomeScopeFK: row.OutcomeScopeFk.Int32,
|
||||||
|
OutcomeSubtypeFK: row.OutcomeSubtypeFk.Int32,
|
||||||
|
EventParticipantNumber: row.EventParticipantNumber.Int32,
|
||||||
|
IParam: row.Iparam.String,
|
||||||
|
IParam2: row.Iparam2.String,
|
||||||
|
DParam: row.Dparam.String,
|
||||||
|
DParam2: row.Dparam2.String,
|
||||||
|
SParam: row.Sparam.String,
|
||||||
|
UpdatesCount: row.PreoddsUpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: row.PreoddsLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.PreoddsCreatedAt.Time,
|
||||||
|
UpdatedAt: row.PreoddsUpdatedAt.Time,
|
||||||
|
BettingOffers: []domain.EnetpulsePreoddsBettingOffer{},
|
||||||
|
}
|
||||||
|
preoddsMap[pid] = preodd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append BettingOffer only if exists
|
||||||
|
if row.BettingofferID.Valid && row.BettingofferID.String != "" {
|
||||||
|
offer := domain.EnetpulsePreoddsBettingOffer{
|
||||||
|
ID: row.BettingofferDbID.Int64,
|
||||||
|
BettingOfferID: row.BettingofferID.String,
|
||||||
|
BettingOfferStatusFK: row.BettingofferStatusFk.Int32,
|
||||||
|
OddsProviderFK: row.OddsProviderFk.Int32,
|
||||||
|
Odds: float64(row.Odds.Exp),
|
||||||
|
OddsOld: float64(row.OddsOld.Exp),
|
||||||
|
Active: fmt.Sprintf("%v", row.Active),
|
||||||
|
CouponKey: row.CouponKey.String,
|
||||||
|
UpdatesCount: int(row.BettingofferUpdatesCount.Int32),
|
||||||
|
LastUpdatedAt: row.BettingofferLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.BettingofferCreatedAt.Time,
|
||||||
|
UpdatedAt: row.BettingofferUpdatedAt.Time,
|
||||||
|
}
|
||||||
|
preodd.BettingOffers = append(preodd.BettingOffers, offer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice
|
||||||
|
result := make([]domain.EnetpulsePreodds, 0, len(preoddsMap))
|
||||||
|
for _, p := range preoddsMap {
|
||||||
|
result = append(result, *p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
||||||
|
dbRows, err := s.queries.GetFixturesWithPreodds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a map to group preodds by fixture
|
||||||
|
fixtureMap := make(map[string]*domain.EnetpulseFixtureWithPreodds)
|
||||||
|
|
||||||
|
for _, row := range dbRows {
|
||||||
|
// If fixture not yet in map, add it
|
||||||
|
if _, exists := fixtureMap[row.FixtureID]; !exists {
|
||||||
|
fixtureMap[row.FixtureID] = &domain.EnetpulseFixtureWithPreodds{
|
||||||
|
FixtureID: row.FixtureID,
|
||||||
|
FixtureApiID: row.FixtureID, // same alias used in query
|
||||||
|
FixtureName: row.FixtureName,
|
||||||
|
SportFk: row.SportFk,
|
||||||
|
TournamentFk: row.TournamentFk.String,
|
||||||
|
TournamentTemplateFk: row.TournamentTemplateFk.String,
|
||||||
|
// TournamentStageFk: row.TournamentStageFk.String,
|
||||||
|
StartDate: row.StartDate.Time,
|
||||||
|
StatusType: row.StatusType.String,
|
||||||
|
StatusDescFk: row.StatusDescFk.String,
|
||||||
|
RoundTypeFk: row.RoundTypeFk.String,
|
||||||
|
UpdatesCount: row.FixtureUpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: row.FixtureLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.FixtureCreatedAt.Time,
|
||||||
|
UpdatedAt: row.FixtureUpdatedAt.Time,
|
||||||
|
Preodds: []domain.EnetpulsePreodds{}, // initialize slice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add preodds only if it exists (avoid NULL rows)
|
||||||
|
if row.PreoddsDbID.Valid {
|
||||||
|
preodds := domain.EnetpulsePreodds{
|
||||||
|
ID: row.PreoddsDbID.Int64,
|
||||||
|
PreoddsID: row.PreoddsID.String,
|
||||||
|
EventFK: row.EventFk.Int64,
|
||||||
|
OutcomeTypeFK: row.OutcomeTypeFk.Int32,
|
||||||
|
OutcomeScopeFK: row.OutcomeScopeFk.Int32,
|
||||||
|
OutcomeSubtypeFK: row.OutcomeSubtypeFk.Int32,
|
||||||
|
EventParticipantNumber: row.EventParticipantNumber.Int32,
|
||||||
|
IParam: row.Iparam.String,
|
||||||
|
IParam2: row.Iparam2.String,
|
||||||
|
DParam: row.Dparam.String,
|
||||||
|
DParam2: row.Dparam2.String,
|
||||||
|
SParam: row.Sparam.String,
|
||||||
|
UpdatesCount: row.PreoddsUpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: row.PreoddsLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.PreoddsCreatedAt.Time,
|
||||||
|
UpdatedAt: row.PreoddsUpdatedAt.Time,
|
||||||
|
}
|
||||||
|
fixtureMap[row.FixtureID].Preodds = append(fixtureMap[row.FixtureID].Preodds, preodds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten the map into a slice
|
||||||
|
result := make([]domain.EnetpulseFixtureWithPreodds, 0, len(fixtureMap))
|
||||||
|
for _, f := range fixtureMap {
|
||||||
|
result = append(result, *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func ConvertCreateEnetpulseTournamentStage(stage domain.CreateEnetpulseTournamentStage) dbgen.EnetpulseTournamentStage {
|
// func ConvertCreateEnetpulseTournamentStage(stage domain.CreateEnetpulseTournamentStage) dbgen.EnetpulseTournamentStage {
|
||||||
// return dbgen.EnetpulseTournamentStage{
|
// return dbgen.EnetpulseTournamentStage{
|
||||||
// StageID: stage.StageID,
|
// StageID: stage.StageID,
|
||||||
|
|
@ -157,6 +449,54 @@ func (s *Store) GetTournamentStagesByTournamentFK(ctx context.Context, tournamen
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// ConvertCreateEnetpulseFixture converts the domain model to the SQLC params struct.
|
||||||
|
func ConvertCreateEnetpulseFixture(f domain.CreateEnetpulseFixture) dbgen.CreateEnetpulseFixtureParams {
|
||||||
|
return dbgen.CreateEnetpulseFixtureParams{
|
||||||
|
FixtureID: f.FixtureID,
|
||||||
|
Name: f.Name,
|
||||||
|
SportFk: f.SportFK,
|
||||||
|
TournamentFk: pgtype.Text{String: f.TournamentFK, Valid: f.TournamentFK != ""},
|
||||||
|
TournamentTemplateFk: pgtype.Text{String: f.TournamentTemplateFK, Valid: f.TournamentTemplateFK != ""},
|
||||||
|
// TournamentStageFk: pgtype.Text{String: f.TournamentStageFK, Valid: f.TournamentStageFK != ""},
|
||||||
|
// TournamentStageName: pgtype.Text{String: f.TournamentStageName, Valid: f.TournamentStageName != ""},
|
||||||
|
TournamentName: pgtype.Text{String: f.TournamentName, Valid: f.TournamentName != ""},
|
||||||
|
TournamentTemplateName: pgtype.Text{String: f.TournamentTemplateName, Valid: f.TournamentTemplateName != ""},
|
||||||
|
SportName: pgtype.Text{String: f.SportName, Valid: f.SportName != ""},
|
||||||
|
Gender: pgtype.Text{String: f.Gender, Valid: f.Gender != ""},
|
||||||
|
StartDate: pgtype.Timestamptz{Time: f.StartDate, Valid: !f.StartDate.IsZero()},
|
||||||
|
StatusType: pgtype.Text{String: f.StatusType, Valid: f.StatusType != ""},
|
||||||
|
StatusDescFk: pgtype.Text{String: f.StatusDescFK, Valid: f.StatusDescFK != ""},
|
||||||
|
RoundTypeFk: pgtype.Text{String: f.RoundTypeFK, Valid: f.RoundTypeFK != ""},
|
||||||
|
UpdatesCount: pgtype.Int4{Int32: int32(f.UpdatesCount), Valid: true},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: f.LastUpdatedAt, Valid: !f.LastUpdatedAt.IsZero()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertDBEnetpulseFixture converts the DB row to the domain model.
|
||||||
|
func ConvertDBEnetpulseFixture(dbF dbgen.EnetpulseFixture) domain.EnetpulseFixture {
|
||||||
|
return domain.EnetpulseFixture{
|
||||||
|
FixtureID: dbF.FixtureID,
|
||||||
|
Name: dbF.Name,
|
||||||
|
SportFK: dbF.SportFk,
|
||||||
|
TournamentFK: dbF.TournamentFk.String,
|
||||||
|
TournamentTemplateFK: dbF.TournamentTemplateFk.String,
|
||||||
|
// TournamentStageFK: dbF.TournamentStageFk.String,
|
||||||
|
// TournamentStageName: dbF.TournamentStageName.String,
|
||||||
|
TournamentName: dbF.TournamentName.String,
|
||||||
|
TournamentTemplateName: dbF.TournamentTemplateName.String,
|
||||||
|
SportName: dbF.SportName.String,
|
||||||
|
Gender: dbF.Gender.String,
|
||||||
|
StartDate: dbF.StartDate.Time.String(),
|
||||||
|
StatusType: dbF.StatusType.String,
|
||||||
|
StatusDescFK: dbF.StatusDescFk.String,
|
||||||
|
RoundTypeFK: dbF.RoundTypeFk.String,
|
||||||
|
UpdatesCount: fmt.Sprintf("%v", dbF.UpdatesCount),
|
||||||
|
LastUpdatedAt: dbF.LastUpdatedAt.Time.String(),
|
||||||
|
// CreatedAt: dbF.CreatedAt.Time,
|
||||||
|
// UpdatedAt: dbF.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ConvertCreateEnetpulseTournamentStage(stage domain.CreateEnetpulseTournamentStage) dbgen.CreateEnetpulseTournamentStageParams {
|
func ConvertCreateEnetpulseTournamentStage(stage domain.CreateEnetpulseTournamentStage) dbgen.CreateEnetpulseTournamentStageParams {
|
||||||
return dbgen.CreateEnetpulseTournamentStageParams{
|
return dbgen.CreateEnetpulseTournamentStageParams{
|
||||||
StageID: stage.StageID,
|
StageID: stage.StageID,
|
||||||
|
|
@ -321,3 +661,354 @@ func ConvertDBEnetpulseTournament(dbT dbgen.EnetpulseTournament) domain.Enetpuls
|
||||||
}(),
|
}(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConvertCreateEnetpulseResult(input domain.CreateEnetpulseResult) dbgen.CreateEnetpulseResultParams {
|
||||||
|
return dbgen.CreateEnetpulseResultParams{
|
||||||
|
ResultID: input.ResultID,
|
||||||
|
Name: input.Name,
|
||||||
|
SportFk: input.SportFK,
|
||||||
|
TournamentFk: pgtype.Text{String: input.TournamentFK, Valid: input.TournamentFK != ""},
|
||||||
|
TournamentTemplateFk: pgtype.Text{String: input.TournamentTemplateFK, Valid: input.TournamentTemplateFK != ""},
|
||||||
|
// TournamentStageFk: pgtype.Text{String: input.TournamentStageFK, Valid: input.TournamentStageFK != ""},
|
||||||
|
// TournamentStageName: pgtype.Text{String: input.TournamentStageName, Valid: input.TournamentStageName != ""},
|
||||||
|
TournamentName: pgtype.Text{String: input.TournamentName, Valid: input.TournamentName != ""},
|
||||||
|
TournamentTemplateName: pgtype.Text{String: input.TournamentTemplateName, Valid: input.TournamentTemplateName != ""},
|
||||||
|
SportName: pgtype.Text{String: input.SportName, Valid: input.SportName != ""},
|
||||||
|
StartDate: pgtype.Timestamptz{Time: input.StartDate, Valid: !input.StartDate.IsZero()},
|
||||||
|
StatusType: pgtype.Text{String: input.StatusType, Valid: input.StatusType != ""},
|
||||||
|
StatusDescFk: pgtype.Text{String: input.StatusDescFK, Valid: input.StatusDescFK != ""},
|
||||||
|
RoundTypeFk: pgtype.Text{String: input.RoundTypeFK, Valid: input.RoundTypeFK != ""},
|
||||||
|
UpdatesCount: pgtype.Int4{Int32: int32(input.UpdatesCount), Valid: true},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: input.LastUpdatedAt, Valid: !input.LastUpdatedAt.IsZero()},
|
||||||
|
Round: pgtype.Text{String: input.Round, Valid: input.Round != ""},
|
||||||
|
Live: pgtype.Text{String: input.Live, Valid: input.Live != ""},
|
||||||
|
VenueName: pgtype.Text{String: input.VenueName, Valid: input.VenueName != ""},
|
||||||
|
LivestatsPlus: pgtype.Text{String: input.LivestatsPlus, Valid: input.LivestatsPlus != ""},
|
||||||
|
LivestatsType: pgtype.Text{String: input.LivestatsType, Valid: input.LivestatsType != ""},
|
||||||
|
Commentary: pgtype.Text{String: input.Commentary, Valid: input.Commentary != ""},
|
||||||
|
LineupConfirmed: pgtype.Bool{Bool: input.LineupConfirmed, Valid: true},
|
||||||
|
Verified: pgtype.Bool{Bool: input.Verified, Valid: true},
|
||||||
|
Spectators: pgtype.Int4{Int32: int32(input.Spectators), Valid: true},
|
||||||
|
GameStarted: pgtype.Timestamptz{Time: *input.GameStarted, Valid: !input.GameStarted.IsZero()},
|
||||||
|
FirstHalfEnded: pgtype.Timestamptz{Time: *input.FirstHalfEnded, Valid: !input.FirstHalfEnded.IsZero()},
|
||||||
|
SecondHalfStarted: pgtype.Timestamptz{Time: *input.SecondHalfStarted, Valid: !input.SecondHalfStarted.IsZero()},
|
||||||
|
SecondHalfEnded: pgtype.Timestamptz{Time: *input.SecondHalfEnded, Valid: !input.SecondHalfEnded.IsZero()},
|
||||||
|
GameEnded: pgtype.Timestamptz{Time: *input.GameEnded, Valid: !input.GameEnded.IsZero()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertDBEnetpulseResult maps SQLC result → domain model
|
||||||
|
func ConvertDBEnetpulseResult(db dbgen.EnetpulseResult) domain.EnetpulseResult {
|
||||||
|
return domain.EnetpulseResult{
|
||||||
|
ID: db.ID,
|
||||||
|
ResultID: db.ResultID,
|
||||||
|
Name: db.Name,
|
||||||
|
SportFK: db.SportFk,
|
||||||
|
TournamentFK: db.TournamentFk.String,
|
||||||
|
TournamentTemplateFK: db.TournamentTemplateFk.String,
|
||||||
|
// TournamentStageFK: db.TournamentStageFk.String,
|
||||||
|
// TournamentStageName: db.TournamentStageName.String,
|
||||||
|
TournamentName: db.TournamentName.String,
|
||||||
|
TournamentTemplateName: db.TournamentTemplateName.String,
|
||||||
|
SportName: db.SportName.String,
|
||||||
|
StartDate: db.StartDate.Time,
|
||||||
|
StatusType: db.StatusType.String,
|
||||||
|
StatusDescFK: db.StatusDescFk.String,
|
||||||
|
RoundTypeFK: db.RoundTypeFk.String,
|
||||||
|
UpdatesCount: db.UpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: &db.LastUpdatedAt.Time,
|
||||||
|
Round: db.Round.String,
|
||||||
|
Live: db.Live.String,
|
||||||
|
VenueName: db.VenueName.String,
|
||||||
|
LivestatsPlus: db.LivestatsPlus.String,
|
||||||
|
LivestatsType: db.LivestatsType.String,
|
||||||
|
Commentary: db.Commentary.String,
|
||||||
|
LineupConfirmed: db.LineupConfirmed.Bool,
|
||||||
|
Verified: db.Verified.Bool,
|
||||||
|
Spectators: db.Spectators.Int32,
|
||||||
|
GameStarted: &db.GameStarted.Time,
|
||||||
|
FirstHalfEnded: &db.FirstHalfEnded.Time,
|
||||||
|
SecondHalfStarted: &db.SecondHalfStarted.Time,
|
||||||
|
SecondHalfEnded: &db.SecondHalfEnded.Time,
|
||||||
|
GameEnded: &db.GameEnded.Time,
|
||||||
|
CreatedAt: db.CreatedAt.Time,
|
||||||
|
UpdatedAt: &db.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertCreateEnetpulseOutcomeType converts the domain struct to SQLC params.
|
||||||
|
func ConvertCreateEnetpulseOutcomeType(o domain.CreateEnetpulseOutcomeType) dbgen.CreateEnetpulseOutcomeTypeParams {
|
||||||
|
return dbgen.CreateEnetpulseOutcomeTypeParams{
|
||||||
|
OutcomeTypeID: o.OutcomeTypeID,
|
||||||
|
Name: o.Name,
|
||||||
|
Description: pgtype.Text{String: o.Description, Valid: o.Description != ""}, // TODO: thiso.Description,
|
||||||
|
UpdatesCount: pgtype.Int4{Int32: int32(o.UpdatesCount), Valid: true},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: o.LastUpdatedAt, Valid: !o.LastUpdatedAt.IsZero()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertDBEnetpulseOutcomeType converts SQLC DB model to domain model.
|
||||||
|
func ConvertDBEnetpulseOutcomeType(dbO dbgen.EnetpulseOutcomeType) domain.EnetpulseOutcomeType {
|
||||||
|
return domain.EnetpulseOutcomeType{
|
||||||
|
ID: dbO.ID,
|
||||||
|
OutcomeTypeID: dbO.OutcomeTypeID,
|
||||||
|
Name: dbO.Name,
|
||||||
|
Description: dbO.Description.String,
|
||||||
|
UpdatesCount: dbO.UpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: dbO.LastUpdatedAt.Time,
|
||||||
|
CreatedAt: dbO.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbO.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateEnetpulsePreodds(p domain.CreateEnetpulsePreodds) (dbgen.CreateEnetpulsePreoddsParams, error) {
|
||||||
|
eventFK, err := strconv.ParseInt(p.EventFK, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return dbgen.CreateEnetpulsePreoddsParams{}, fmt.Errorf("invalid EventFK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outcomeTypeFK, err := strconv.ParseInt(p.OutcomeTypeFK, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return dbgen.CreateEnetpulsePreoddsParams{}, fmt.Errorf("invalid OutcomeTypeFK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outcomeScopeFK, err := strconv.ParseInt(p.OutcomeScopeFK, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return dbgen.CreateEnetpulsePreoddsParams{}, fmt.Errorf("invalid OutcomeScopeFK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outcomeSubtypeFK, err := strconv.ParseInt(p.OutcomeSubtypeFK, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return dbgen.CreateEnetpulsePreoddsParams{}, fmt.Errorf("invalid OutcomeSubtypeFK: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbgen.CreateEnetpulsePreoddsParams{
|
||||||
|
PreoddsID: p.PreoddsID,
|
||||||
|
EventFk: eventFK,
|
||||||
|
OutcomeTypeFk: pgtype.Int4{Int32: int32(outcomeTypeFK), Valid: true},
|
||||||
|
OutcomeScopeFk: pgtype.Int4{Int32: int32(outcomeScopeFK), Valid: true},
|
||||||
|
OutcomeSubtypeFk: pgtype.Int4{Int32: int32(outcomeSubtypeFK), Valid: true},
|
||||||
|
EventParticipantNumber: pgtype.Int4{Int32: int32(p.EventParticipantNumber), Valid: true},
|
||||||
|
Iparam: pgtype.Text{String: p.IParam, Valid: p.IParam != ""},
|
||||||
|
Iparam2: pgtype.Text{String: p.IParam2, Valid: p.IParam2 != ""},
|
||||||
|
Dparam: pgtype.Text{String: p.DParam, Valid: p.DParam != ""},
|
||||||
|
Dparam2: pgtype.Text{String: p.DParam2, Valid: p.DParam2 != ""},
|
||||||
|
Sparam: pgtype.Text{String: p.SParam, Valid: p.SParam != ""},
|
||||||
|
UpdatesCount: pgtype.Int4{Int32: int32(p.UpdatesCount), Valid: true},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: p.LastUpdatedAt, Valid: !p.LastUpdatedAt.IsZero()},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBEnetpulsePreodds(dbP dbgen.EnetpulsePreodd) domain.EnetpulsePreodds {
|
||||||
|
return domain.EnetpulsePreodds{
|
||||||
|
PreoddsID: dbP.PreoddsID,
|
||||||
|
EventFK: dbP.EventFk,
|
||||||
|
OutcomeTypeFK: dbP.OutcomeTypeFk.Int32,
|
||||||
|
OutcomeScopeFK: dbP.OutcomeScopeFk.Int32,
|
||||||
|
OutcomeSubtypeFK: dbP.OutcomeSubtypeFk.Int32,
|
||||||
|
EventParticipantNumber: dbP.EventParticipantNumber.Int32,
|
||||||
|
IParam: dbP.Iparam.String,
|
||||||
|
IParam2: dbP.Iparam2.String,
|
||||||
|
DParam: dbP.Dparam.String,
|
||||||
|
DParam2: dbP.Dparam2.String,
|
||||||
|
SParam: dbP.Sparam.String,
|
||||||
|
UpdatesCount: dbP.UpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: dbP.LastUpdatedAt.Time,
|
||||||
|
CreatedAt: dbP.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbP.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateEnetpulsePreoddsBettingOffer(o domain.CreateEnetpulsePreoddsBettingOffer) dbgen.CreateEnetpulsePreoddsBettingOfferParams {
|
||||||
|
// Convert float64 to int64 with scale 2
|
||||||
|
oddsInt := big.NewInt(int64(math.Round(o.Odds * 100)))
|
||||||
|
oddsOldInt := big.NewInt(int64(math.Round(o.OddsOld * 100)))
|
||||||
|
|
||||||
|
return dbgen.CreateEnetpulsePreoddsBettingOfferParams{
|
||||||
|
BettingofferID: o.BettingOfferID,
|
||||||
|
PreoddsFk: o.PreoddsFK,
|
||||||
|
BettingofferStatusFk: pgtype.Int4{Int32: o.BettingOfferStatusFK, Valid: true},
|
||||||
|
OddsProviderFk: pgtype.Int4{Int32: o.OddsProviderFK, Valid: true},
|
||||||
|
Odds: pgtype.Numeric{
|
||||||
|
Int: oddsInt,
|
||||||
|
Exp: -2, // scale 2 decimal places
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
OddsOld: pgtype.Numeric{
|
||||||
|
Int: oddsOldInt,
|
||||||
|
Exp: -2,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Active: pgtype.Bool{Bool: o.Active == "yes", Valid: true},
|
||||||
|
CouponKey: pgtype.Text{
|
||||||
|
String: o.CouponKey,
|
||||||
|
Valid: o.CouponKey != "",
|
||||||
|
},
|
||||||
|
UpdatesCount: pgtype.Int4{Int32: int32(o.UpdatesCount), Valid: true},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: o.LastUpdatedAt, Valid: !o.LastUpdatedAt.IsZero()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert DB result to domain struct
|
||||||
|
func ConvertDBEnetpulsePreoddsBettingOffer(o dbgen.EnetpulsePreoddsBettingoffer) domain.EnetpulsePreoddsBettingOffer {
|
||||||
|
var odds, oddsOld float64
|
||||||
|
if o.Odds.Valid {
|
||||||
|
odds, _ = o.Odds.Int.Float64() // Convert pgtype.Numeric to float64
|
||||||
|
}
|
||||||
|
if o.OddsOld.Valid {
|
||||||
|
oddsOld, _ = o.OddsOld.Int.Float64()
|
||||||
|
}
|
||||||
|
|
||||||
|
active := "no"
|
||||||
|
if o.Active.Valid && o.Active.Bool {
|
||||||
|
active = "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.EnetpulsePreoddsBettingOffer{
|
||||||
|
ID: o.ID,
|
||||||
|
BettingOfferID: o.BettingofferID,
|
||||||
|
PreoddsFK: o.PreoddsFk,
|
||||||
|
BettingOfferStatusFK: o.BettingofferStatusFk.Int32,
|
||||||
|
OddsProviderFK: o.OddsProviderFk.Int32,
|
||||||
|
Odds: odds,
|
||||||
|
OddsOld: oddsOld,
|
||||||
|
Active: active,
|
||||||
|
CouponKey: o.CouponKey.String,
|
||||||
|
UpdatesCount: int(o.UpdatesCount.Int32),
|
||||||
|
LastUpdatedAt: o.LastUpdatedAt.Time,
|
||||||
|
CreatedAt: o.CreatedAt.Time,
|
||||||
|
UpdatedAt: o.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateEnetpulseResultParticipant(
|
||||||
|
ctx context.Context,
|
||||||
|
participant domain.CreateEnetpulseResultParticipant,
|
||||||
|
) (domain.EnetpulseResultParticipant, error) {
|
||||||
|
dbParticipant, err := s.queries.CreateEnetpulseResultParticipant(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateEnetpulseResultParticipant(participant),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulseResultParticipant{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulseResultParticipant(dbParticipant), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetEnetpulseResultParticipantsByResultFK(
|
||||||
|
ctx context.Context,
|
||||||
|
resultFK string,
|
||||||
|
) ([]domain.EnetpulseResultParticipant, error) {
|
||||||
|
dbParticipants, err := s.queries.GetEnetpulseResultParticipantsByResultFK(ctx, resultFK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
participants := make([]domain.EnetpulseResultParticipant, 0, len(dbParticipants))
|
||||||
|
for _, dbp := range dbParticipants {
|
||||||
|
participants = append(participants, ConvertDBEnetpulseResultParticipant(dbp))
|
||||||
|
}
|
||||||
|
|
||||||
|
return participants, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateEnetpulseResultReferee(
|
||||||
|
ctx context.Context,
|
||||||
|
referee domain.CreateEnetpulseResultReferee,
|
||||||
|
) (domain.EnetpulseResultReferee, error) {
|
||||||
|
dbReferee, err := s.queries.CreateEnetpulseResultReferee(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateEnetpulseResultReferee(referee),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.EnetpulseResultReferee{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBEnetpulseResultReferee(dbReferee), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetEnetpulseResultRefereesByResultFK(
|
||||||
|
ctx context.Context,
|
||||||
|
resultFK string,
|
||||||
|
) ([]domain.EnetpulseResultReferee, error) {
|
||||||
|
dbReferees, err := s.queries.GetEnetpulseResultRefereesByResultFK(ctx, resultFK)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
referees := make([]domain.EnetpulseResultReferee, 0, len(dbReferees))
|
||||||
|
for _, dbr := range dbReferees {
|
||||||
|
referees = append(referees, ConvertDBEnetpulseResultReferee(dbr))
|
||||||
|
}
|
||||||
|
|
||||||
|
return referees, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateEnetpulseResultParticipant(p domain.CreateEnetpulseResultParticipant) dbgen.CreateEnetpulseResultParticipantParams {
|
||||||
|
return dbgen.CreateEnetpulseResultParticipantParams{
|
||||||
|
ParticipantMapID: p.ParticipantMapID,
|
||||||
|
ResultFk: p.ResultFk,
|
||||||
|
ParticipantFk: p.ParticipantFk,
|
||||||
|
Number: pgtype.Int4{Int32: p.Number},
|
||||||
|
Name: pgtype.Text{String: p.Name},
|
||||||
|
Gender: pgtype.Text{String: p.Gender},
|
||||||
|
Type: pgtype.Text{String: p.Type},
|
||||||
|
CountryFk: pgtype.Text{String: p.CountryFk},
|
||||||
|
CountryName: pgtype.Text{String: p.CountryName},
|
||||||
|
OrdinaryTime: pgtype.Text{String: p.OrdinaryTime},
|
||||||
|
RunningScore: pgtype.Text{String: p.RunningScore},
|
||||||
|
Halftime: pgtype.Text{String: p.Halftime},
|
||||||
|
FinalResult: pgtype.Text{String: p.FinalResult},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: p.LastUpdatedAt, Valid: !p.LastUpdatedAt.IsZero()},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBEnetpulseResultParticipant(p dbgen.EnetpulseResultParticipant) domain.EnetpulseResultParticipant {
|
||||||
|
return domain.EnetpulseResultParticipant{
|
||||||
|
ID: p.ID,
|
||||||
|
ParticipantMapID: p.ParticipantMapID,
|
||||||
|
ResultFk: p.ResultFk,
|
||||||
|
ParticipantFk: p.ParticipantFk,
|
||||||
|
Number: p.Number.Int32,
|
||||||
|
Name: p.Name.String,
|
||||||
|
Gender: p.Gender.String,
|
||||||
|
Type: p.Type.String,
|
||||||
|
CountryFk: p.CountryFk.String,
|
||||||
|
CountryName: p.CountryName.String,
|
||||||
|
OrdinaryTime: p.OrdinaryTime.String,
|
||||||
|
RunningScore: p.RunningScore.String,
|
||||||
|
Halftime: p.Halftime.String,
|
||||||
|
FinalResult: p.FinalResult.String,
|
||||||
|
LastUpdatedAt: p.LastUpdatedAt.Time,
|
||||||
|
CreatedAt: p.CreatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateEnetpulseResultReferee(r domain.CreateEnetpulseResultReferee) dbgen.CreateEnetpulseResultRefereeParams {
|
||||||
|
return dbgen.CreateEnetpulseResultRefereeParams{
|
||||||
|
ResultFk: r.ResultFk,
|
||||||
|
RefereeFk: pgtype.Text{String: r.RefereeFk},
|
||||||
|
Assistant1RefereeFk: pgtype.Text{String: r.Assistant1RefereeFk},
|
||||||
|
Assistant2RefereeFk: pgtype.Text{String: r.Assistant2RefereeFk},
|
||||||
|
FourthRefereeFk: pgtype.Text{String: r.FourthRefereeFk},
|
||||||
|
Var1RefereeFk: pgtype.Text{String: r.Var1RefereeFk},
|
||||||
|
Var2RefereeFk: pgtype.Text{String: r.Var2RefereeFk},
|
||||||
|
LastUpdatedAt: pgtype.Timestamptz{Time: r.LastUpdatedAt},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBEnetpulseResultReferee(r dbgen.EnetpulseResultReferee) domain.EnetpulseResultReferee {
|
||||||
|
return domain.EnetpulseResultReferee{
|
||||||
|
ID: r.ID,
|
||||||
|
ResultFk: r.ResultFk,
|
||||||
|
RefereeFk: r.RefereeFk.String,
|
||||||
|
Assistant1RefereeFk: r.Assistant1RefereeFk.String,
|
||||||
|
Assistant2RefereeFk: r.Assistant2RefereeFk.String,
|
||||||
|
FourthRefereeFk: r.FourthRefereeFk.String,
|
||||||
|
Var1RefereeFk: r.Var1RefereeFk.String,
|
||||||
|
Var2RefereeFk: r.Var2RefereeFk.String,
|
||||||
|
LastUpdatedAt: r.LastUpdatedAt.Time,
|
||||||
|
CreatedAt: r.CreatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -19,8 +21,9 @@ type VirtualGameRepository interface {
|
||||||
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
|
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
|
||||||
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error)
|
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error)
|
||||||
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
||||||
|
GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (*domain.VirtualGameSession, error)
|
||||||
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
||||||
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
// UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
||||||
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
||||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||||
|
|
@ -36,6 +39,12 @@ type VirtualGameRepository interface {
|
||||||
CreateVirtualGame(ctx context.Context, arg dbgen.CreateVirtualGameParams) (dbgen.VirtualGame, error)
|
CreateVirtualGame(ctx context.Context, arg dbgen.CreateVirtualGameParams) (dbgen.VirtualGame, error)
|
||||||
ListAllVirtualGames(ctx context.Context, arg dbgen.GetAllVirtualGamesParams) ([]dbgen.GetAllVirtualGamesRow, error)
|
ListAllVirtualGames(ctx context.Context, arg dbgen.GetAllVirtualGamesParams) ([]dbgen.GetAllVirtualGamesRow, error)
|
||||||
RemoveAllVirtualGames(ctx context.Context) error
|
RemoveAllVirtualGames(ctx context.Context) error
|
||||||
|
CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error)
|
||||||
|
CreateVirtualGameReport(ctx context.Context, report domain.CreateVirtualGameReport) (domain.VirtualGameReport, error)
|
||||||
|
GetVirtualGameProviderReportByProviderAndDate(ctx context.Context, providerID string, createdAt time.Time, reportType string) (domain.VirtualGameProviderReport, error)
|
||||||
|
UpdateVirtualGameProviderReportByDate(ctx context.Context, providerID string, reportDate time.Time, reportType string, totalGamesPlayed int64, totalBets float64, totalPayouts float64, totalPlayers int64) error
|
||||||
|
ListVirtualGameProviderReportsByGamesPlayedAsc(ctx context.Context) ([]domain.VirtualGameProviderReport, error)
|
||||||
|
ListVirtualGameProviderReportsByGamesPlayedDesc(ctx context.Context) ([]domain.VirtualGameProviderReport, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualGameRepo struct {
|
type VirtualGameRepo struct {
|
||||||
|
|
@ -158,14 +167,33 @@ func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session
|
||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
GameID: session.GameID,
|
GameID: session.GameID,
|
||||||
SessionToken: session.SessionToken,
|
SessionToken: session.SessionToken,
|
||||||
Currency: session.Currency,
|
// Currency: session.Currency,
|
||||||
Status: session.Status,
|
// Status: session.Status,
|
||||||
ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
// ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
||||||
}
|
}
|
||||||
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (*domain.VirtualGameSession, error) {
|
||||||
|
dbSession, err := r.store.queries.GetVirtualGameSessionByUserID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.VirtualGameSession{
|
||||||
|
ID: dbSession.ID,
|
||||||
|
UserID: dbSession.UserID,
|
||||||
|
GameID: dbSession.GameID,
|
||||||
|
SessionToken: dbSession.SessionToken,
|
||||||
|
CreatedAt: dbSession.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error) {
|
func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error) {
|
||||||
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -179,20 +207,20 @@ func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, toke
|
||||||
UserID: dbSession.UserID,
|
UserID: dbSession.UserID,
|
||||||
GameID: dbSession.GameID,
|
GameID: dbSession.GameID,
|
||||||
SessionToken: dbSession.SessionToken,
|
SessionToken: dbSession.SessionToken,
|
||||||
Currency: dbSession.Currency,
|
// Currency: dbSession.Currency,
|
||||||
Status: dbSession.Status,
|
// Status: dbSession.Status,
|
||||||
CreatedAt: dbSession.CreatedAt.Time,
|
CreatedAt: dbSession.CreatedAt.Time,
|
||||||
UpdatedAt: dbSession.UpdatedAt.Time,
|
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||||
ExpiresAt: dbSession.ExpiresAt.Time,
|
// ExpiresAt: dbSession.ExpiresAt.Time,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
// func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
||||||
return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
// return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
||||||
ID: id,
|
// ID: id,
|
||||||
Status: status,
|
// Status: status,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
||||||
params := dbgen.CreateVirtualGameTransactionParams{
|
params := dbgen.CreateVirtualGameTransactionParams{
|
||||||
|
|
@ -314,3 +342,183 @@ func (r *VirtualGameRepo) ListAllVirtualGames(ctx context.Context, arg dbgen.Get
|
||||||
func (r *VirtualGameRepo) RemoveAllVirtualGames(ctx context.Context) error {
|
func (r *VirtualGameRepo) RemoveAllVirtualGames(ctx context.Context) error {
|
||||||
return r.store.queries.DeleteAllVirtualGames(ctx)
|
return r.store.queries.DeleteAllVirtualGames(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) CreateVirtualGameProviderReport(
|
||||||
|
ctx context.Context,
|
||||||
|
report domain.CreateVirtualGameProviderReport,
|
||||||
|
) (domain.VirtualGameProviderReport, error) {
|
||||||
|
dbReport, err := r.store.queries.CreateVirtualGameProviderReport(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateVirtualGameProviderReport(report),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.VirtualGameProviderReport{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBVirtualGameProviderReport(dbReport), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) CreateVirtualGameReport(
|
||||||
|
ctx context.Context,
|
||||||
|
report domain.CreateVirtualGameReport,
|
||||||
|
) (domain.VirtualGameReport, error) {
|
||||||
|
dbReport, err := r.store.queries.CreateVirtualGameReport(
|
||||||
|
ctx,
|
||||||
|
ConvertCreateVirtualGameReport(report),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.VirtualGameReport{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBVirtualGameReport(dbReport), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) GetVirtualGameProviderReportByProviderAndDate(
|
||||||
|
ctx context.Context,
|
||||||
|
providerID string,
|
||||||
|
reportDate time.Time,
|
||||||
|
reportType string,
|
||||||
|
) (domain.VirtualGameProviderReport, error) {
|
||||||
|
arg := dbgen.GetVirtualGameProviderReportByProviderAndDateParams{
|
||||||
|
ProviderID: providerID,
|
||||||
|
ReportDate: pgtype.Date{Time: reportDate, Valid: true},
|
||||||
|
ReportType: pgtype.Text{String: reportType, Valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
dbReport, err := r.store.queries.GetVirtualGameProviderReportByProviderAndDate(ctx, arg)
|
||||||
|
if err != nil {
|
||||||
|
return domain.VirtualGameProviderReport{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertDBVirtualGameProviderReport(dbReport), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx context.Context,
|
||||||
|
providerID string,
|
||||||
|
reportDate time.Time,
|
||||||
|
reportType string,
|
||||||
|
totalGamesPlayed int64,
|
||||||
|
totalBets float64,
|
||||||
|
totalPayouts float64,
|
||||||
|
totalPlayers int64,
|
||||||
|
) error {
|
||||||
|
arg := dbgen.UpdateVirtualGameProviderReportByDateParams{
|
||||||
|
ProviderID: providerID,
|
||||||
|
ReportDate: pgtype.Date{Time: reportDate, Valid: true},
|
||||||
|
ReportType: pgtype.Text{String: reportType, Valid: true},
|
||||||
|
TotalGamesPlayed: pgtype.Int8{Int64: totalGamesPlayed, Valid: true},
|
||||||
|
TotalBets: pgtype.Numeric{},
|
||||||
|
TotalPayouts: pgtype.Numeric{},
|
||||||
|
TotalPlayers: pgtype.Int8{Int64: totalPlayers, Valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safely convert float64 → pgtype.Numeric
|
||||||
|
if err := arg.TotalBets.Scan(totalBets); err != nil {
|
||||||
|
return fmt.Errorf("failed to set total_bets: %w", err)
|
||||||
|
}
|
||||||
|
if err := arg.TotalPayouts.Scan(totalPayouts); err != nil {
|
||||||
|
return fmt.Errorf("failed to set total_payouts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.store.queries.UpdateVirtualGameProviderReportByDate(ctx, arg); err != nil {
|
||||||
|
return fmt.Errorf("failed to update provider report for %s: %w", providerID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) ListVirtualGameProviderReportsByGamesPlayedAsc(
|
||||||
|
ctx context.Context,
|
||||||
|
) ([]domain.VirtualGameProviderReport, error) {
|
||||||
|
dbReports, err := r.store.queries.ListVirtualGameProviderReportsByGamesPlayedAsc(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reports := make([]domain.VirtualGameProviderReport, len(dbReports))
|
||||||
|
for i, r := range dbReports {
|
||||||
|
reports[i] = ConvertDBVirtualGameProviderReport(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) ListVirtualGameProviderReportsByGamesPlayedDesc(
|
||||||
|
ctx context.Context,
|
||||||
|
) ([]domain.VirtualGameProviderReport, error) {
|
||||||
|
dbReports, err := r.store.queries.ListVirtualGameProviderReportsByGamesPlayedDesc(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reports := make([]domain.VirtualGameProviderReport, len(dbReports))
|
||||||
|
for i, r := range dbReports {
|
||||||
|
reports[i] = ConvertDBVirtualGameProviderReport(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateVirtualGameProviderReport(r domain.CreateVirtualGameProviderReport) dbgen.CreateVirtualGameProviderReportParams {
|
||||||
|
// var totalBets, totalPayouts pgtype.Numeric
|
||||||
|
|
||||||
|
// _ = r.TotalBets.
|
||||||
|
// _ = totalPayouts.Set(r.TotalPayouts)
|
||||||
|
|
||||||
|
return dbgen.CreateVirtualGameProviderReportParams{
|
||||||
|
ProviderID: r.ProviderID,
|
||||||
|
ReportDate: pgtype.Date{Time: r.ReportDate, Valid: true},
|
||||||
|
TotalGamesPlayed: pgtype.Int8{Int64: r.TotalGamesPlayed, Valid: true},
|
||||||
|
TotalBets: pgtype.Numeric{Exp: int32(r.TotalBets)},
|
||||||
|
TotalPayouts: pgtype.Numeric{Exp: int32(r.TotalPayouts)},
|
||||||
|
TotalPlayers: pgtype.Int8{Int64: r.TotalPlayers, Valid: true},
|
||||||
|
Column7: pgtype.Text{String: r.ReportType, Valid: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBVirtualGameProviderReport(db dbgen.VirtualGameProviderReport) domain.VirtualGameProviderReport {
|
||||||
|
return domain.VirtualGameProviderReport{
|
||||||
|
ID: db.ID,
|
||||||
|
ProviderID: db.ProviderID,
|
||||||
|
ReportDate: db.ReportDate.Time,
|
||||||
|
TotalGamesPlayed: db.TotalGamesPlayed.Int64,
|
||||||
|
TotalBets: float64(db.TotalBets.Exp),
|
||||||
|
TotalPayouts: float64(db.TotalPayouts.Exp),
|
||||||
|
TotalProfit: float64(db.TotalProfit.Exp),
|
||||||
|
TotalPlayers: db.TotalPlayers.Int64,
|
||||||
|
ReportType: db.ReportType.String,
|
||||||
|
CreatedAt: db.CreatedAt.Time,
|
||||||
|
UpdatedAt: db.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertCreateVirtualGameReport(r domain.CreateVirtualGameReport) dbgen.CreateVirtualGameReportParams {
|
||||||
|
return dbgen.CreateVirtualGameReportParams{
|
||||||
|
GameID: r.GameID,
|
||||||
|
ProviderID: r.ProviderID,
|
||||||
|
ReportDate: pgtype.Date{Time: r.ReportDate},
|
||||||
|
TotalRounds: pgtype.Int8{Int64: r.TotalRounds},
|
||||||
|
TotalBets: pgtype.Numeric{Exp: int32(r.TotalBets)},
|
||||||
|
TotalPayouts: pgtype.Numeric{Exp: int32(r.TotalPayouts)},
|
||||||
|
TotalPlayers: pgtype.Int8{Int64: r.TotalPlayers},
|
||||||
|
Column8: r.ReportType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertDBVirtualGameReport(db dbgen.VirtualGameReport) domain.VirtualGameReport {
|
||||||
|
return domain.VirtualGameReport{
|
||||||
|
ID: db.ID,
|
||||||
|
GameID: db.GameID,
|
||||||
|
ProviderID: db.ProviderID,
|
||||||
|
ReportDate: db.ReportDate.Time,
|
||||||
|
TotalRounds: db.TotalRounds.Int64,
|
||||||
|
TotalBets: float64(db.TotalBets.Exp),
|
||||||
|
TotalPayouts: float64(db.TotalPayouts.Exp),
|
||||||
|
TotalProfit: float64(db.TotalProfit.Exp),
|
||||||
|
TotalPlayers: db.TotalPlayers.Int64,
|
||||||
|
ReportType: db.ReportType.String,
|
||||||
|
CreatedAt: db.CreatedAt.Time,
|
||||||
|
UpdatedAt: db.UpdatedAt.Time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
|
|
@ -32,7 +33,7 @@ func NewArifpayService(cfg *config.Config, transferStore ports.TransferStore, wa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientRequest, isDeposit bool) (map[string]any, error) {
|
func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientRequest, isDeposit bool, userId int64) (map[string]any, error) {
|
||||||
// Generate unique nonce
|
// Generate unique nonce
|
||||||
nonce := uuid.NewString()
|
nonce := uuid.NewString()
|
||||||
|
|
||||||
|
|
@ -130,6 +131,10 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
||||||
ReferenceNumber: nonce,
|
ReferenceNumber: nonce,
|
||||||
SessionID: fmt.Sprintf("%v", data["sessionId"]),
|
SessionID: fmt.Sprintf("%v", data["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
||||||
|
|
@ -139,7 +144,7 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID string) (*domain.CancelCheckoutSessionResponse, error) {
|
func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID string) (any, error) {
|
||||||
// Build the cancel URL
|
// Build the cancel URL
|
||||||
url := fmt.Sprintf("%s/api/sandbox/checkout/session/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
|
url := fmt.Sprintf("%s/api/sandbox/checkout/session/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
|
||||||
|
|
||||||
|
|
@ -177,17 +182,19 @@ func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID st
|
||||||
return nil, fmt.Errorf("failed to unmarshal cancel response: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal cancel response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cancelResp, nil
|
return cancelResp.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRequest, userId int64, isDepost bool) error {
|
func (s *ArifpayService) ProcessWebhook(ctx context.Context, req domain.WebhookRequest, isDeposit bool) error {
|
||||||
// 1. Get transfer by SessionID
|
// 1. Get transfer by SessionID
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Transaction.TransactionID)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
userId := transfer.DepositorID.Value
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +204,7 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update transfer status
|
// 2. Update transfer status
|
||||||
newStatus := req.Transaction.TransactionStatus
|
newStatus := strings.ToLower(req.Transaction.TransactionStatus)
|
||||||
// if req.Transaction.TransactionStatus != "" {
|
// if req.Transaction.TransactionStatus != "" {
|
||||||
// newStatus = req.Transaction.TransactionStatus
|
// newStatus = req.Transaction.TransactionStatus
|
||||||
// }
|
// }
|
||||||
|
|
@ -213,10 +220,10 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. If SUCCESS -> update customer wallet balance
|
// 3. If SUCCESS -> update customer wallet balance
|
||||||
if (newStatus == "SUCCESS" && isDepost) || (newStatus == "FAILED" && !isDepost) {
|
if (newStatus == "success" && isDeposit) || (newStatus == "failed" && !isDeposit) {
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
||||||
ReferenceNumber: domain.ValidString{
|
ReferenceNumber: domain.ValidString{
|
||||||
Value: req.Transaction.TransactionID,
|
Value: req.Nonce,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
BankNumber: domain.ValidString{
|
BankNumber: domain.ValidString{
|
||||||
|
|
@ -232,35 +239,94 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
// Step 1: Create Session
|
// Step 1: Create Session
|
||||||
|
|
||||||
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user wallets: %w", err)
|
||||||
|
}
|
||||||
|
// if len(userWallets) == 0 {
|
||||||
|
// return fmt.Errorf("no wallet found for user %d", userId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
referenceNum := uuid.NewString()
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
CustomerEmail: req.CustomerEmail,
|
CustomerEmail: req.CustomerEmail,
|
||||||
CustomerPhone: req.CustomerPhone,
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to create session: %w", err)
|
return fmt.Errorf("failed to create session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionRespData := sessionResp["data"].(map[string]any)
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
// Step 2: Execute Transfer
|
||||||
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
reqBody := map[string]any{
|
reqBody := map[string]any{
|
||||||
"Sessionid": sessionResp["sessionId"],
|
"Sessionid": sessionRespData["sessionId"],
|
||||||
"Phonenumber": req.PhoneNumber,
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
payload, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to marshal transfer request: %w", err)
|
return fmt.Errorf("failed to marshal transfer request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to build transfer request: %w", err)
|
return fmt.Errorf("failed to build transfer request: %w", err)
|
||||||
}
|
}
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
@ -268,11 +334,35 @@ func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req dom
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to execute transfer request: %w", err)
|
return fmt.Errorf("failed to execute transfer request: %w", err)
|
||||||
}
|
}
|
||||||
defer transferResp.Body.Close()
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
return fmt.Errorf("transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
return fmt.Errorf("transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
@ -283,109 +373,33 @@ func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req dom
|
||||||
Verified: false,
|
Verified: false,
|
||||||
Type: domain.WITHDRAW, // B2C = payout
|
Type: domain.WITHDRAW, // B2C = payout
|
||||||
ReferenceNumber: referenceNum,
|
ReferenceNumber: referenceNum,
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
SessionID: fmt.Sprintf("%v", sessionRespData["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
return fmt.Errorf("failed to store transfer: %w", err)
|
return fmt.Errorf("failed to store transfer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Deduct from wallet
|
// Step 4: Deduct from wallet
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get user wallets: %w", err)
|
|
||||||
}
|
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("no wallet found for user %d", userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
|
||||||
ctx,
|
|
||||||
userWallets[0].ID,
|
|
||||||
domain.Currency(req.Amount),
|
|
||||||
domain.ValidInt64{},
|
|
||||||
domain.TRANSFER_ARIFPAY,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
// Step 1: Create Session
|
// Step 1: Deduct from user wallet first
|
||||||
referenceNum := uuid.NewString()
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
|
||||||
Amount: req.Amount,
|
|
||||||
CustomerEmail: req.CustomerEmail,
|
|
||||||
CustomerPhone: req.CustomerPhone,
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cbebirr: failed to create session: %w", err)
|
return fmt.Errorf("cbebirr: failed to get user wallet: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
|
||||||
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"Sessionid": sessionResp["sessionId"],
|
|
||||||
"Phonenumber": req.PhoneNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to marshal transfer request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to build transfer request: %w", err)
|
|
||||||
}
|
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
|
||||||
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to execute transfer request: %w", err)
|
|
||||||
}
|
|
||||||
defer transferResp.Body.Close()
|
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
|
||||||
return fmt.Errorf("cbebirr: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Store transfer in DB
|
|
||||||
transfer := domain.CreateTransfer{
|
|
||||||
Amount: domain.Currency(req.Amount),
|
|
||||||
Verified: false,
|
|
||||||
Type: domain.WITHDRAW, // B2C = payout
|
|
||||||
ReferenceNumber: referenceNum,
|
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
|
||||||
Status: string(domain.PaymentStatusPending),
|
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
|
||||||
}
|
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to store transfer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Deduct from user wallet
|
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to get user wallets: %w", err)
|
|
||||||
}
|
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("cbebirr: no wallet found for user %d", userId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
ctx,
|
ctx,
|
||||||
userWallets[0].ID,
|
userWallet.RegularID,
|
||||||
domain.Currency(req.Amount),
|
domain.Currency(req.Amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_ARIFPAY,
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
|
@ -395,55 +409,68 @@ func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.A
|
||||||
return fmt.Errorf("cbebirr: failed to deduct from wallet: %w", err)
|
return fmt.Errorf("cbebirr: failed to deduct from wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
|
||||||
// Step 1: Create Session
|
|
||||||
referenceNum := uuid.NewString()
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
|
// Step 2: Create Session
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
CustomerEmail: req.CustomerEmail,
|
CustomerEmail: req.CustomerEmail,
|
||||||
CustomerPhone: req.CustomerPhone,
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to create session: %w", err)
|
// refund wallet if session creation fails
|
||||||
|
_, refundErr := s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if refundErr != nil {
|
||||||
|
return fmt.Errorf("cbebirr: refund failed after session creation error: %v", refundErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cbebirr: failed to create session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
// Step 3: Execute Transfer
|
||||||
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
reqBody := map[string]any{
|
reqBody := map[string]any{
|
||||||
"Sessionid": sessionResp["sessionId"],
|
"Sessionid": sessionResp["sessionId"],
|
||||||
"Phonenumber": req.PhoneNumber,
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
payload, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to marshal transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to marshal transfer request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to build transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to build transfer request: %w", err)
|
||||||
}
|
}
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to execute transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to execute transfer request: %w", err)
|
||||||
}
|
}
|
||||||
defer transferResp.Body.Close()
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
return fmt.Errorf("Mpesa: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Store transfer in DB
|
// Step 4: Store transfer in DB
|
||||||
transfer := domain.CreateTransfer{
|
transfer := domain.CreateTransfer{
|
||||||
Amount: domain.Currency(req.Amount),
|
Amount: domain.Currency(req.Amount),
|
||||||
Verified: false,
|
Verified: false,
|
||||||
|
|
@ -452,30 +479,116 @@ func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
}
|
CashierID: domain.ValidInt64{
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
Value: userId,
|
||||||
return fmt.Errorf("Mpesa: failed to store transfer: %w", err)
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Deduct from user wallet
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
return fmt.Errorf("cbebirr: failed to store transfer: %w", err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Mpesa: failed to get user wallets: %w", err)
|
|
||||||
}
|
}
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("Mpesa: no wallet found for user %d", userId)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
|
// Step 1: Deduct from user wallet first
|
||||||
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mpesa: failed to get user wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
ctx,
|
ctx,
|
||||||
userWallets[0].ID,
|
userWallet.RegularID,
|
||||||
domain.Currency(req.Amount),
|
domain.Currency(req.Amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_ARIFPAY,
|
domain.TRANSFER_ARIFPAY,
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to deduct from wallet: %w", err)
|
return fmt.Errorf("mpesa: failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
|
// Step 2: Create Session
|
||||||
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
|
Amount: req.Amount,
|
||||||
|
CustomerEmail: req.CustomerEmail,
|
||||||
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
|
if err != nil {
|
||||||
|
// Refund wallet if session creation fails
|
||||||
|
_, refundErr := s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if refundErr != nil {
|
||||||
|
return fmt.Errorf("mpesa: refund failed after session creation error: %v", refundErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("mpesa: failed to create session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Execute Transfer
|
||||||
|
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
|
reqBody := map[string]any{
|
||||||
|
"Sessionid": sessionResp["sessionId"],
|
||||||
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to marshal transfer request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to build transfer request: %w", err)
|
||||||
|
}
|
||||||
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
|
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
||||||
|
|
||||||
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to execute transfer request: %w", err)
|
||||||
|
}
|
||||||
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Store transfer in DB
|
||||||
|
transfer := domain.CreateTransfer{
|
||||||
|
Amount: domain.Currency(req.Amount),
|
||||||
|
Verified: false,
|
||||||
|
Type: domain.WITHDRAW, // B2C = payout
|
||||||
|
ReferenceNumber: referenceNum,
|
||||||
|
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
||||||
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
|
return fmt.Errorf("mpesa: failed to store transfer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -933,6 +933,7 @@ func (s *Service) GetBetOutcomeByBetID(ctx context.Context, UserID int64) ([]dom
|
||||||
func (s *Service) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) {
|
func (s *Service) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) {
|
||||||
return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter)
|
return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||||
return s.betStore.GetBetOutcomeByEventID(ctx, eventID, is_filtered)
|
return s.betStore.GetBetOutcomeByEventID(ctx, eventID, is_filtered)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,17 @@ func NewClient(baseURL, secretKey string) *Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) {
|
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error) {
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"amount": fmt.Sprintf("%.2f", float64(req.Amount)),
|
"amount": fmt.Sprintf("%.2f", float64(req.Amount)),
|
||||||
"currency": req.Currency,
|
"currency": req.Currency,
|
||||||
// "email": req.Email,
|
"email": req.Email,
|
||||||
"first_name": req.FirstName,
|
"first_name": req.FirstName,
|
||||||
"last_name": req.LastName,
|
"last_name": req.LastName,
|
||||||
"tx_ref": req.TxRef,
|
"tx_ref": req.TxRef,
|
||||||
"callback_url": req.CallbackURL,
|
// "callback_url": req.CallbackURL,
|
||||||
"return_url": req.ReturnURL,
|
"return_url": req.ReturnURL,
|
||||||
|
"phone_number": req.PhoneNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
||||||
|
|
@ -69,9 +70,9 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
// if resp.StatusCode != http.StatusOK {
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
// return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
}
|
// }
|
||||||
|
|
||||||
var response struct {
|
var response struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
@ -130,15 +131,16 @@ func (c *Client) VerifyPayment(ctx context.Context, reference string) (domain.Ch
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaPaymentVerificationResponse, error) {
|
||||||
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -147,35 +149,27 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var response struct {
|
var verification domain.ChapaPaymentVerificationResponse
|
||||||
Status string `json:"status"`
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
Amount float64 `json:"amount"`
|
|
||||||
Currency string `json:"currency"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var status domain.PaymentStatus
|
// Normalize payment status for internal use
|
||||||
switch response.Status {
|
// switch strings.ToLower(verification.Data.Status) {
|
||||||
case "success":
|
// case "success":
|
||||||
status = domain.PaymentStatusCompleted
|
// verification.Status = string(domain.PaymentStatusCompleted)
|
||||||
default:
|
// default:
|
||||||
status = domain.PaymentStatusFailed
|
// verification.Status = string(domain.PaymentStatusFailed)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return &verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaTransferVerificationResponse, error) {
|
||||||
Status: string(status),
|
|
||||||
Amount: response.Amount,
|
|
||||||
Currency: response.Currency,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
|
||||||
url := fmt.Sprintf("%s/transfers/verify/%s", c.baseURL, txRef)
|
url := fmt.Sprintf("%s/transfers/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
|
@ -213,14 +207,77 @@ func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domai
|
||||||
status = domain.PaymentStatusFailed
|
status = domain.PaymentStatusFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
return &domain.ChapaTransferVerificationResponse{
|
||||||
Status: string(status),
|
Status: string(status),
|
||||||
Amount: response.Amount,
|
// Amount: response.Amount,
|
||||||
Currency: response.Currency,
|
// Currency: response.Currency,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (c *Client) GetAllTransactions(ctx context.Context) (domain.ChapaAllTransactionsResponse, error) {
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/transactions", nil)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response domain.ChapaAllTransactionsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetTransactionEvents(ctx context.Context, refId string) ([]domain.ChapaTransactionEvent, error) {
|
||||||
|
url := fmt.Sprintf("%s/transaction/events/%s", c.baseURL, refId)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data []domain.ChapaTransactionEvent `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.BankData, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
|
@ -243,9 +300,9 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var banks []domain.Bank
|
var banks []domain.BankData
|
||||||
for _, bankData := range bankResponse.Data {
|
for _, bankData := range bankResponse.Data {
|
||||||
bank := domain.Bank{
|
bank := domain.BankData{
|
||||||
ID: bankData.ID,
|
ID: bankData.ID,
|
||||||
Slug: bankData.Slug,
|
Slug: bankData.Slug,
|
||||||
Swift: bankData.Swift,
|
Swift: bankData.Swift,
|
||||||
|
|
@ -267,7 +324,7 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return banks, nil
|
return banks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
func (c *Client) InitializeTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
||||||
endpoint := c.baseURL + "/transfers"
|
endpoint := c.baseURL + "/transfers"
|
||||||
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
||||||
|
|
||||||
|
|
@ -304,7 +361,7 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
||||||
return response.Status == string(domain.WithdrawalStatusSuccessful), nil
|
return response.Status == string(domain.WithdrawalStatusSuccessful), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaTransferVerificationResponse, error) {
|
||||||
base, err := url.Parse(c.baseURL)
|
base, err := url.Parse(c.baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid base URL: %w", err)
|
return nil, fmt.Errorf("invalid base URL: %w", err)
|
||||||
|
|
@ -328,7 +385,7 @@ func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.
|
||||||
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var verification domain.ChapaVerificationResponse
|
var verification domain.ChapaTransferVerificationResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -336,6 +393,62 @@ func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.
|
||||||
return &verification, nil
|
return &verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) CancelTransaction(ctx context.Context, txRef string) (domain.ChapaCancelResponse, error) {
|
||||||
|
// Construct URL for the cancel transaction endpoint
|
||||||
|
url := fmt.Sprintf("%s/transaction/cancel/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
|
// Create HTTP request with context
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPut, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set authorization header
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Execute the HTTP request
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Handle non-OK responses
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode successful response
|
||||||
|
var response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return mapped domain response
|
||||||
|
return domain.ChapaCancelResponse{
|
||||||
|
Message: response.Message,
|
||||||
|
Status: response.Status,
|
||||||
|
TxRef: response.Data.TxRef,
|
||||||
|
Amount: response.Data.Amount,
|
||||||
|
Currency: response.Data.Currency,
|
||||||
|
CreatedAt: response.Data.CreatedAt,
|
||||||
|
UpdatedAt: response.Data.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) setHeaders(req *http.Request) {
|
func (c *Client) setHeaders(req *http.Request) {
|
||||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,14 @@ import (
|
||||||
// }
|
// }
|
||||||
|
|
||||||
type ChapaStore interface {
|
type ChapaStore interface {
|
||||||
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
|
InitializePayment(request domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error)
|
||||||
// VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
|
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaTransferVerificationResponse, 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)
|
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||||
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
|
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebhookTransfer) error
|
||||||
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
|
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebhookPayment) error
|
||||||
|
GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error)
|
||||||
|
GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
||||||
|
GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error)
|
||||||
|
InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
package chapa
|
package chapa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -40,6 +47,31 @@ func NewService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) VerifyWebhookSignature(ctx context.Context, payload []byte, chapaSignature, xChapaSignature string) (bool, error) {
|
||||||
|
secret := s.cfg.CHAPA_WEBHOOK_SECRET // or os.Getenv("CHAPA_SECRET_KEY")
|
||||||
|
if secret == "" {
|
||||||
|
return false, fmt.Errorf("missing Chapa secret key in configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute expected signature using HMAC SHA256
|
||||||
|
h := hmac.New(sha256.New, []byte(secret))
|
||||||
|
h.Write(payload)
|
||||||
|
expected := hex.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
|
// Check either header
|
||||||
|
if chapaSignature == expected || xChapaSignature == expected {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally log for debugging
|
||||||
|
var pretty map[string]interface{}
|
||||||
|
_ = json.Unmarshal(payload, &pretty)
|
||||||
|
fmt.Printf("[Webhook Verification Failed]\nExpected: %s\nGot chapa-signature: %s\nGot x-chapa-signature: %s\nPayload: %+v\n",
|
||||||
|
expected, chapaSignature, xChapaSignature, pretty)
|
||||||
|
|
||||||
|
return false, fmt.Errorf("invalid webhook signature")
|
||||||
|
}
|
||||||
|
|
||||||
// InitiateDeposit starts a new deposit process
|
// InitiateDeposit starts a new deposit process
|
||||||
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
|
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
|
||||||
// Validate amount
|
// Validate amount
|
||||||
|
|
@ -53,22 +85,22 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
return "", fmt.Errorf("failed to get user: %w", err)
|
return "", fmt.Errorf("failed to get user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var senderWallet domain.Wallet
|
// var senderWallet domain.Wallet
|
||||||
|
|
||||||
// Generate unique reference
|
// Generate unique reference
|
||||||
// reference := uuid.New().String()
|
// reference := uuid.New().String()
|
||||||
reference := fmt.Sprintf("chapa-deposit-%d-%s", userID, uuid.New().String())
|
reference := fmt.Sprintf("chapa-deposit-%d-%s", userID, uuid.New().String())
|
||||||
|
|
||||||
senderWallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
senderWallet, err := s.walletStore.GetCustomerWallet(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get sender wallets: %w", err)
|
return "", fmt.Errorf("failed to get sender wallet: %w", err)
|
||||||
}
|
|
||||||
for _, wallet := range senderWallets {
|
|
||||||
if wallet.IsWithdraw {
|
|
||||||
senderWallet = wallet
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// for _, wallet := range senderWallets {
|
||||||
|
// if wallet.IsTransferable {
|
||||||
|
// senderWallet = wallet
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Check if payment with this reference already exists
|
// Check if payment with this reference already exists
|
||||||
// if transfer, err := s.transferStore.GetTransferByReference(ctx, reference); err == nil {
|
// if transfer, err := s.transferStore.GetTransferByReference(ctx, reference); err == nil {
|
||||||
|
|
@ -85,13 +117,20 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
ReferenceNumber: reference,
|
ReferenceNumber: reference,
|
||||||
// ReceiverWalletID: 1,
|
// ReceiverWalletID: 1,
|
||||||
SenderWalletID: domain.ValidInt64{
|
SenderWalletID: domain.ValidInt64{
|
||||||
Value: senderWallet.ID,
|
Value: senderWallet.RegularID,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Verified: false,
|
Verified: false,
|
||||||
|
Status: string(domain.STATUS_PENDING),
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := domain.ChapaDepositRequest{
|
userPhoneNum := user.PhoneNumber[len(user.PhoneNumber)-9:]
|
||||||
|
|
||||||
|
if len(user.PhoneNumber) >= 9 {
|
||||||
|
userPhoneNum = "0" + userPhoneNum
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := domain.ChapaInitDepositRequest{
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Currency: "ETB",
|
Currency: "ETB",
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
|
|
@ -100,6 +139,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
TxRef: reference,
|
TxRef: reference,
|
||||||
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
||||||
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
||||||
|
PhoneNumber: userPhoneNum,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize payment with Chapa
|
// Initialize payment with Chapa
|
||||||
|
|
@ -124,170 +164,9 @@ 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) {
|
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, req domain.ChapaWebhookPayment) error {
|
||||||
// Parse and validate amount
|
|
||||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
|
||||||
if err != nil || amount <= 0 {
|
|
||||||
return nil, domain.ErrInvalidWithdrawalAmount
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user's wallet
|
|
||||||
wallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user wallets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var withdrawWallet domain.Wallet
|
|
||||||
for _, wallet := range wallets {
|
|
||||||
if wallet.IsWithdraw {
|
|
||||||
withdrawWallet = wallet
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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{
|
|
||||||
Message: fmt.Sprintf("Withdrawing %d into wallet using chapa. Reference Number %s", amount, reference),
|
|
||||||
Amount: domain.Currency(amount),
|
|
||||||
Type: domain.WITHDRAW,
|
|
||||||
SenderWalletID: domain.ValidInt64{
|
|
||||||
Value: withdrawWallet.ID,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
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 {
|
|
||||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
|
||||||
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !success {
|
|
||||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
|
||||||
return nil, errors.New("chapa rejected the transfer request")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) 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) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
|
||||||
// Lookup transfer by reference
|
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("transfer not found for reference %s: %w", txRef, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if transfer.Verified {
|
|
||||||
return &domain.ChapaVerificationResponse{
|
|
||||||
Status: string(domain.PaymentStatusCompleted),
|
|
||||||
Amount: float64(transfer.Amount) / 100,
|
|
||||||
Currency: "ETB",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate sender wallet
|
|
||||||
if !transfer.SenderWalletID.Valid {
|
|
||||||
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var verification *domain.ChapaVerificationResponse
|
|
||||||
|
|
||||||
// Decide verification method based on type
|
|
||||||
switch strings.ToLower(string(transfer.Type)) {
|
|
||||||
case "deposit":
|
|
||||||
// Use Chapa Payment Verification
|
|
||||||
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
|
||||||
// Mark verified
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark deposit transfer as verified: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit wallet
|
|
||||||
_, err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID.Value,
|
|
||||||
transfer.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{},
|
|
||||||
fmt.Sprintf("Added %v to wallet using Chapa", transfer.Amount.Float32()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "withdraw":
|
|
||||||
// Use Chapa Transfer Verification
|
|
||||||
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
|
||||||
// Mark verified (withdraw doesn't affect balance)
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark withdrawal transfer as verified: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported transfer type: %s", transfer.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error {
|
|
||||||
// Find payment by reference
|
// Find payment by reference
|
||||||
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.Reference)
|
payment, err := s.transferStore.GetTransferByReference(ctx, req.TxRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ErrPaymentNotFound
|
return domain.ErrPaymentNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -309,15 +188,19 @@ func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domai
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// If payment is completed, credit user's wallet
|
// If payment is completed, credit user's wallet
|
||||||
if transfer.Status == string(domain.PaymentStatusSuccessful) {
|
if req.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to update is payment verified value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, payment.ID, string(domain.DepositStatusCompleted)); err != nil {
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
||||||
ReferenceNumber: domain.ValidString{
|
ReferenceNumber: domain.ValidString{
|
||||||
Value: transfer.Reference,
|
Value: req.TxRef,
|
||||||
},
|
},
|
||||||
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
||||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
|
|
@ -327,9 +210,209 @@ func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domai
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error {
|
func (s *Service) CancelDeposit(ctx context.Context, userID int64, txRef string) (domain.ChapaCancelResponse, error) {
|
||||||
|
// Validate input
|
||||||
|
if txRef == "" {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("transaction reference is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve user to verify ownership / context (optional but good practice)
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to get user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nAttempting to cancel Chapa transaction: %s for user %s (%d)\n\n", txRef, user.Email, userID)
|
||||||
|
|
||||||
|
// Call Chapa API to cancel transaction
|
||||||
|
cancelResp, err := s.chapaClient.CancelTransaction(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to cancel transaction via Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update transfer/payment status locally
|
||||||
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
// Log but do not block cancellation if remote succeeded
|
||||||
|
fmt.Printf("Warning: unable to find local transfer for txRef %s: %v\n", txRef, err)
|
||||||
|
} else {
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.STATUS_CANCELLED)); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to update transfer status for txRef %s: %v\n", txRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, false); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to update transfer status for txRef %s: %v\n", txRef, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nChapa cancellation response: %+v\n\n", cancelResp)
|
||||||
|
|
||||||
|
return cancelResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAllTransactions(ctx context.Context) ([]domain.ChapaTransaction, error) {
|
||||||
|
// Call Chapa API to get all transactions
|
||||||
|
resp, err := s.chapaClient.GetAllTransactions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch transactions from Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Status != "success" {
|
||||||
|
return nil, fmt.Errorf("chapa API returned non-success status: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]domain.ChapaTransaction, 0, len(resp.Data.Transactions))
|
||||||
|
|
||||||
|
// Map API transactions to domain transactions
|
||||||
|
for _, t := range resp.Data.Transactions {
|
||||||
|
tx := domain.ChapaTransaction{
|
||||||
|
Status: t.Status,
|
||||||
|
RefID: t.RefID,
|
||||||
|
Type: t.Type,
|
||||||
|
CreatedAt: t.CreatedAt,
|
||||||
|
Currency: t.Currency,
|
||||||
|
Amount: t.Amount,
|
||||||
|
Charge: t.Charge,
|
||||||
|
TransID: t.TransID,
|
||||||
|
PaymentMethod: t.PaymentMethod,
|
||||||
|
Customer: domain.ChapaCustomer{
|
||||||
|
ID: t.Customer.ID,
|
||||||
|
Email: t.Customer.Email,
|
||||||
|
FirstName: t.Customer.FirstName,
|
||||||
|
LastName: t.Customer.LastName,
|
||||||
|
Mobile: t.Customer.Mobile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
transactions = append(transactions, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchTransactionEvents(ctx context.Context, refID string) ([]domain.ChapaTransactionEvent, error) {
|
||||||
|
if refID == "" {
|
||||||
|
return nil, fmt.Errorf("transaction reference ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Chapa client to fetch transaction events
|
||||||
|
events, err := s.chapaClient.GetTransactionEvents(ctx, refID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch transaction events from Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Transform or filter events if needed
|
||||||
|
transformedEvents := make([]domain.ChapaTransactionEvent, 0, len(events))
|
||||||
|
for _, e := range events {
|
||||||
|
transformedEvents = append(transformedEvents, domain.ChapaTransactionEvent{
|
||||||
|
Item: e.Item,
|
||||||
|
Message: e.Message,
|
||||||
|
Type: e.Type,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedEvents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||||
|
// Parse and validate amount
|
||||||
|
amount, err := strconv.ParseFloat(req.Amount, 64)
|
||||||
|
if err != nil || amount <= 0 {
|
||||||
|
return nil, domain.ErrInvalidWithdrawalAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's wallet
|
||||||
|
wallet, err := s.walletStore.GetCustomerWallet(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get user wallets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// var withdrawWallet domain.Wallet
|
||||||
|
// for _, wallet := range wallets {
|
||||||
|
// if wallet.IsWithdraw {
|
||||||
|
// withdrawWallet = wallet
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if withdrawWallet.ID == 0 {
|
||||||
|
// return nil, errors.New("withdrawal wallet not found")
|
||||||
|
// }
|
||||||
|
// Check balance
|
||||||
|
if float64(wallet.RegularBalance) < float64(amount) {
|
||||||
|
return nil, domain.ErrInsufficientBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique reference
|
||||||
|
reference := uuid.New().String()
|
||||||
|
|
||||||
|
createTransfer := domain.CreateTransfer{
|
||||||
|
Message: fmt.Sprintf("Withdrawing %f into wallet using chapa. Reference Number %s", amount, reference),
|
||||||
|
Amount: domain.Currency(amount),
|
||||||
|
Type: domain.WITHDRAW,
|
||||||
|
SenderWalletID: domain.ValidInt64{
|
||||||
|
Value: wallet.RegularID,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
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("%f", amount),
|
||||||
|
Currency: req.Currency,
|
||||||
|
Reference: reference,
|
||||||
|
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
newBalance := float64(wallet.RegularBalance) - float64(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success, err := s.chapaClient.InitializeTransfer(ctx, transferReq)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
newBalance := float64(wallet.RegularBalance) + float64(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
newBalance := float64(wallet.RegularBalance) + float64(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
return nil, errors.New("chapa rejected the transfer request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
return &transfer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, req domain.ChapaWebhookTransfer) error {
|
||||||
// Find payment by reference
|
// Find payment by reference
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ErrPaymentNotFound
|
return domain.ErrPaymentNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -350,7 +433,7 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
||||||
// verified = true
|
// verified = true
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if payment.Status == string(domain.PaymentStatusSuccessful) {
|
if req.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
} // If payment is completed, credit user's walle
|
} // If payment is completed, credit user's walle
|
||||||
|
|
@ -365,3 +448,274 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPaymentReceiptURL(refId string) (string, error) {
|
||||||
|
if refId == "" {
|
||||||
|
return "", fmt.Errorf("reference ID cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
receiptURL := s.cfg.CHAPA_RECEIPT_URL + refId
|
||||||
|
return receiptURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (any, error) {
|
||||||
|
// Lookup transfer by reference
|
||||||
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transfer not found for reference %s: %w", txRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already verified, just return a completed response
|
||||||
|
if transfer.Verified {
|
||||||
|
return map[string]any{}, errors.New("transfer already verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sender wallet
|
||||||
|
if !transfer.SenderWalletID.Valid {
|
||||||
|
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var verification any
|
||||||
|
|
||||||
|
switch strings.ToLower(string(transfer.Type)) {
|
||||||
|
case string(domain.DEPOSIT):
|
||||||
|
// Verify Chapa payment
|
||||||
|
verification, err := s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(verification.Data.Status) == "success" ||
|
||||||
|
verification.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
|
||||||
|
// Credit wallet
|
||||||
|
_, err := s.walletStore.AddToWallet(ctx,
|
||||||
|
transfer.SenderWalletID.Value,
|
||||||
|
transfer.Amount,
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_CHAPA,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
fmt.Sprintf("Added %.2f ETB to wallet using Chapa", transfer.Amount.Float32()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark verified in DB
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to mark deposit transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.DepositStatusCompleted)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update deposit transfer status: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case string(domain.WITHDRAW):
|
||||||
|
// Verify Chapa transfer
|
||||||
|
verification, err := s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(verification.Data.Status) == "success" ||
|
||||||
|
verification.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
|
||||||
|
// Deduct wallet
|
||||||
|
_, err := s.walletStore.DeductFromWallet(ctx,
|
||||||
|
transfer.SenderWalletID.Value,
|
||||||
|
transfer.Amount,
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_CHAPA,
|
||||||
|
fmt.Sprintf("Deducted %.2f ETB from wallet using Chapa", transfer.Amount.Float32()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark verified in DB
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to mark withdraw transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusCompleted)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update withdraw transfer status: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported transfer type: %s", transfer.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return verification, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.BankData, 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) GetAllTransfers(ctx context.Context) (*domain.ChapaTransfersListResponse, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.cfg.CHAPA_BASE_URL+"/transfers", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
|
||||||
|
resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch transfers: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result domain.ChapaTransfersListResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the decoded result directly; no intermediate dynamic map needed
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error) {
|
||||||
|
URL := s.cfg.CHAPA_BASE_URL + "/balances"
|
||||||
|
if currencyCode != "" {
|
||||||
|
URL = fmt.Sprintf("%s/%s", URL, strings.ToLower(currencyCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create balance request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
|
||||||
|
resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute balance request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data []domain.Balance `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode balance response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) SwapCurrency(ctx context.Context, reqBody domain.SwapRequest) (*domain.SwapResponse, error) {
|
||||||
|
URL := s.cfg.CHAPA_BASE_URL + "/swap"
|
||||||
|
|
||||||
|
// Normalize currency codes
|
||||||
|
reqBody.From = strings.ToUpper(reqBody.From)
|
||||||
|
reqBody.To = strings.ToUpper(reqBody.To)
|
||||||
|
|
||||||
|
// Marshal request body
|
||||||
|
body, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal swap payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, URL, bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create swap request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
|
||||||
|
// Execute request
|
||||||
|
resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute swap request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Handle unexpected status
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode response
|
||||||
|
var result domain.SwapResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode swap response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (s *Service) InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error) {
|
||||||
|
// if amount < 1 {
|
||||||
|
// return nil, fmt.Errorf("amount must be at least 1 USD")
|
||||||
|
// }
|
||||||
|
// if strings.ToUpper(from) != "USD" || strings.ToUpper(to) != "ETB" {
|
||||||
|
// return nil, fmt.Errorf("only USD to ETB swap is supported")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// payload := domain.SwapRequest{
|
||||||
|
// Amount: amount,
|
||||||
|
// From: strings.ToUpper(from),
|
||||||
|
// To: strings.ToUpper(to),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // payload := map[string]any{
|
||||||
|
// // "amount": amount,
|
||||||
|
// // "from": strings.ToUpper(from),
|
||||||
|
// // "to": strings.ToUpper(to),
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// body, err := json.Marshal(payload)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to encode swap payload: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.chapa.co/v1/swap", bytes.NewBuffer(body))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to create swap request: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
// req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to execute swap request: %w", err)
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
|
// if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||||
|
// bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
// return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var result struct {
|
||||||
|
// Message string `json:"message"`
|
||||||
|
// Status string `json:"status"`
|
||||||
|
// Data domain.SwapResponse `json:"data"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to decode swap response: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return &result.Data, nil
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,5 @@ type EnetPulseService interface {
|
||||||
FetchTournamentParticipants(ctx context.Context, tournamentID string) error
|
FetchTournamentParticipants(ctx context.Context, tournamentID string) error
|
||||||
FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, error)
|
FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, error)
|
||||||
FetchCountryFlag(ctx context.Context, countryFK int64) (*domain.ImageResponse, error)
|
FetchCountryFlag(ctx context.Context, countryFK int64) (*domain.ImageResponse, error)
|
||||||
|
GetAllPreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
|
@ -142,6 +143,10 @@ func (s *Service) FetchAndStoreTournamentTemplates(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sport := range sports {
|
for _, sport := range sports {
|
||||||
|
|
||||||
|
if sport.SportID != "1" {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
// 2️⃣ Compose URL for each sport using its Enetpulse sportFK
|
// 2️⃣ Compose URL for each sport using its Enetpulse sportFK
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"http://eapi.enetpulse.com/tournament_template/list/?sportFK=%s&username=%s&token=%s",
|
"http://eapi.enetpulse.com/tournament_template/list/?sportFK=%s&username=%s&token=%s",
|
||||||
|
|
@ -232,9 +237,11 @@ func (s *Service) FetchAndStoreTournamentTemplates(ctx context.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("✅ Successfully fetched and stored all tournament templates")
|
// fmt.Println("✅ Successfully fetched and stored all tournament templates")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,7 +367,7 @@ func (s *Service) FetchAndStoreTournamentStages(ctx context.Context) error {
|
||||||
for _, t := range tournaments {
|
for _, t := range tournaments {
|
||||||
// Compose URL for each tournament
|
// Compose URL for each tournament
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"http://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s",
|
"https://eapi.enetpulse.com/tournament_stage/list/?language_typeFK=3&tz=Europe/Sofia&tournamentFK=%s&username=%s&token=%s",
|
||||||
t.TournamentID,
|
t.TournamentID,
|
||||||
s.cfg.EnetPulseConfig.UserName,
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
s.cfg.EnetPulseConfig.Token,
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
|
@ -452,6 +459,691 @@ func (s *Service) GetAllTournamentStages(ctx context.Context) ([]domain.Enetpuls
|
||||||
return stages, nil
|
return stages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAndStoreFixtures(ctx context.Context, date string) error {
|
||||||
|
// 1️⃣ Fetch all sports from DB
|
||||||
|
sports, err := s.store.GetAllEnetpulseSports(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch sports from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define API fixture struct
|
||||||
|
type Fixture struct {
|
||||||
|
FixtureID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFK string `json:"sportFK"`
|
||||||
|
TournamentFK string `json:"tournamentFK"`
|
||||||
|
TournamentTemplateFK string `json:"tournament_templateFK"`
|
||||||
|
TournamentStageFK string `json:"tournament_stageFK"`
|
||||||
|
TournamentStageName string `json:"tournament_stage_name"`
|
||||||
|
TournamentName string `json:"tournament_name"`
|
||||||
|
TournamentTemplateName string `json:"tournament_template_name"`
|
||||||
|
SportName string `json:"sport_name"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
StartDate string `json:"startdate"` // ISO 8601
|
||||||
|
StatusType string `json:"status_type"`
|
||||||
|
StatusDescFK string `json:"status_descFK"`
|
||||||
|
RoundTypeFK string `json:"round_typeFK"`
|
||||||
|
UpdatesCount string `json:"n"` // convert to int
|
||||||
|
LastUpdatedAt string `json:"ut"` // parse to time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Loop through each sport
|
||||||
|
for _, sport := range sports {
|
||||||
|
if sport.SportID != "1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"https://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s&sportFK=%s&language_typeFK=3&date=%s",
|
||||||
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
sport.SportID,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("creating request for sport %s: %v\n", sport.SportID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("requesting fixtures for sport %s: %v\n", sport.SportID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
fmt.Printf("failed to fetch fixtures for sport %s (status %d): %s\n",
|
||||||
|
sport.SportID, resp.StatusCode, string(body))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Decode API response
|
||||||
|
var fixturesResp struct {
|
||||||
|
Events map[string]Fixture `json:"events"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil {
|
||||||
|
fmt.Printf("decoding fixtures for sport %s: %v\n", sport.SportID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4️⃣ Iterate and upsert fixtures
|
||||||
|
for _, fx := range fixturesResp.Events {
|
||||||
|
// Parse StartDate and LastUpdatedAt
|
||||||
|
startDate, err := time.Parse(time.RFC3339, fx.StartDate)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("invalid startDate for fixture %s: %v\n", fx.FixtureID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastUpdated, err := time.Parse(time.RFC3339, fx.LastUpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("invalid lastUpdatedAt for fixture %s: %v\n", fx.FixtureID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert UpdatesCount
|
||||||
|
updatesCount, err := strconv.Atoi(fx.UpdatesCount)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("invalid updatesCount for fixture %s: %v\n", fx.FixtureID, err)
|
||||||
|
updatesCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fixture := domain.CreateEnetpulseFixture{
|
||||||
|
FixtureID: fx.FixtureID,
|
||||||
|
Name: fx.Name,
|
||||||
|
SportFK: fx.SportFK,
|
||||||
|
TournamentFK: fx.TournamentFK,
|
||||||
|
TournamentTemplateFK: fx.TournamentTemplateFK,
|
||||||
|
TournamentStageFK: fx.TournamentStageFK,
|
||||||
|
TournamentStageName: fx.TournamentStageName,
|
||||||
|
TournamentName: fx.TournamentName,
|
||||||
|
TournamentTemplateName: fx.TournamentTemplateName,
|
||||||
|
SportName: fx.SportName,
|
||||||
|
Gender: fx.Gender,
|
||||||
|
StartDate: startDate,
|
||||||
|
StatusType: fx.StatusType,
|
||||||
|
StatusDescFK: fx.StatusDescFK,
|
||||||
|
RoundTypeFK: fx.RoundTypeFK,
|
||||||
|
UpdatesCount: updatesCount,
|
||||||
|
LastUpdatedAt: lastUpdated,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5️⃣ Save fixture using UPSERT repository method
|
||||||
|
if _, err := s.store.CreateEnetpulseFixture(ctx, fixture); err != nil {
|
||||||
|
fmt.Printf("failed upserting fixture %s: %v\n", fx.FixtureID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Successfully fetched and stored fixtures for sport %s\n", sport.SportID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllFixtures(ctx context.Context) ([]domain.EnetpulseFixture, error) {
|
||||||
|
// 1️⃣ Fetch all from store
|
||||||
|
fixtures, err := s.store.GetAllEnetpulseFixtures(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch fixtures from DB: %w", err)
|
||||||
|
}
|
||||||
|
return fixtures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAndStoreResults(ctx context.Context) error {
|
||||||
|
sports, err := s.store.GetAllEnetpulseSports(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch sports from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sport := range sports {
|
||||||
|
if sport.SportID != "1" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
today := time.Now().Format("2006-01-02")
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"http://eapi.enetpulse.com/event/results/?sportFK=%s&date=%s&username=%s&token=%s",
|
||||||
|
sport.SportID,
|
||||||
|
today,
|
||||||
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
)
|
||||||
|
fmt.Println("Fetching results:", url)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating results request for sport %s: %w", sport.SportID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("requesting results for sport %s: %w", sport.SportID, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("failed to fetch results for sport %s (status %d): %s",
|
||||||
|
sport.SportID, resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Events []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SportFK string `json:"sportFK"`
|
||||||
|
TournamentFK string `json:"tournamentFK"`
|
||||||
|
TournamentTemplateFK string `json:"tournament_templateFK"`
|
||||||
|
TournamentStageFK string `json:"tournament_stageFK"`
|
||||||
|
TournamentStageName string `json:"tournament_stage_name"`
|
||||||
|
TournamentName string `json:"tournament_name"`
|
||||||
|
TournamentTemplateName string `json:"tournament_template_name"`
|
||||||
|
SportName string `json:"sport_name"`
|
||||||
|
StartDate string `json:"startdate"`
|
||||||
|
StatusType string `json:"status_type"`
|
||||||
|
StatusDescFK string `json:"status_descFK"`
|
||||||
|
RoundTypeFK string `json:"round_typeFK"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
|
||||||
|
Property map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
} `json:"property"`
|
||||||
|
|
||||||
|
EventParticipants map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
ParticipantFK string `json:"participantFK"`
|
||||||
|
EventFK string `json:"eventFK"`
|
||||||
|
|
||||||
|
Result map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
EventParticipantsFK string `json:"event_participantsFK"`
|
||||||
|
ResultTypeFK string `json:"result_typeFK"`
|
||||||
|
ResultCode string `json:"result_code"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
} `json:"result"`
|
||||||
|
|
||||||
|
Participant struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CountryFK string `json:"countryFK"`
|
||||||
|
CountryName string `json:"country_name"`
|
||||||
|
} `json:"participant"`
|
||||||
|
} `json:"event_participants"`
|
||||||
|
} `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
if err := json.Unmarshal(bodyBytes, &data); err != nil {
|
||||||
|
return fmt.Errorf("decoding results failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range data.Events {
|
||||||
|
// 1️⃣ Create result record
|
||||||
|
lastUpdatedAt, _ := time.Parse(time.RFC3339, event.UT)
|
||||||
|
startDate, _ := time.Parse(time.RFC3339, event.StartDate)
|
||||||
|
|
||||||
|
createResult := domain.CreateEnetpulseResult{
|
||||||
|
ResultID: event.ID,
|
||||||
|
Name: event.Name,
|
||||||
|
SportFK: event.SportFK,
|
||||||
|
TournamentFK: event.TournamentFK,
|
||||||
|
TournamentTemplateFK: event.TournamentTemplateFK,
|
||||||
|
TournamentStageName: event.TournamentStageName,
|
||||||
|
TournamentName: event.TournamentName,
|
||||||
|
TournamentTemplateName: event.TournamentTemplateName,
|
||||||
|
SportName: event.SportName,
|
||||||
|
StartDate: startDate,
|
||||||
|
StatusType: event.StatusType,
|
||||||
|
StatusDescFK: event.StatusDescFK,
|
||||||
|
RoundTypeFK: event.RoundTypeFK,
|
||||||
|
LastUpdatedAt: lastUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.store.CreateEnetpulseResult(ctx, createResult); err != nil {
|
||||||
|
fmt.Printf("❌ failed to store result %s: %v\n", event.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Create referees (type == "ref:participant")
|
||||||
|
for _, prop := range event.Property {
|
||||||
|
if strings.HasPrefix(prop.Type, "ref:participant") {
|
||||||
|
refCreatedAt, _ := time.Parse(time.RFC3339, prop.UT)
|
||||||
|
ref := domain.CreateEnetpulseResultReferee{
|
||||||
|
ResultFk: event.ID,
|
||||||
|
RefereeFk: prop.Value,
|
||||||
|
LastUpdatedAt: refCreatedAt,
|
||||||
|
}
|
||||||
|
if _, err := s.store.CreateEnetpulseResultReferee(ctx, ref); err != nil {
|
||||||
|
fmt.Printf("⚠️ failed to create referee %s: %v\n", prop.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Create participants + their results
|
||||||
|
for _, ep := range event.EventParticipants {
|
||||||
|
p := domain.CreateEnetpulseResultParticipant{
|
||||||
|
ParticipantMapID: ep.ID,
|
||||||
|
ResultFk: ep.EventFK,
|
||||||
|
ParticipantFk: ep.ParticipantFK,
|
||||||
|
Name: ep.Participant.Name,
|
||||||
|
CountryFk: ep.Participant.CountryFK,
|
||||||
|
CountryName: ep.Participant.CountryName,
|
||||||
|
}
|
||||||
|
if _, err := s.store.CreateEnetpulseResultParticipant(ctx, p); err != nil {
|
||||||
|
fmt.Printf("⚠️ failed to create participant %s: %v\n", ep.Participant.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break // stop after the first sport (football)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("✅ Successfully fetched and stored EnetPulse results + participants + referees")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllResults(ctx context.Context) ([]domain.EnetpulseResult, error) {
|
||||||
|
results, err := s.store.GetAllEnetpulseResults(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch results from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✅ Retrieved %d results from DB\n", len(results))
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAndStoreOutcomeTypes fetches outcome types from EnetPulse API and stores them in the DB.
|
||||||
|
func (s *Service) FetchAndStoreOutcomeTypes(ctx context.Context) error {
|
||||||
|
// 1️⃣ Compose EnetPulse API URL
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"http://eapi.enetpulse.com/static/outcome_type/?language_typeFK=3&username=%s&token=%s",
|
||||||
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2️⃣ Create HTTP request
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create outcome types request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Execute request
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to call EnetPulse outcome_type API: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("unexpected status %d fetching outcome types: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4️⃣ Decode JSON response
|
||||||
|
var outcomeResp struct {
|
||||||
|
OutcomeTypes map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
} `json:"outcome_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&outcomeResp); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode outcome types JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5️⃣ Iterate and store each outcome type
|
||||||
|
for _, ot := range outcomeResp.OutcomeTypes {
|
||||||
|
updatesCount := 0
|
||||||
|
if ot.N != "" {
|
||||||
|
if n, err := strconv.Atoi(ot.N); err == nil {
|
||||||
|
updatesCount = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdatedAt, err := time.Parse(time.RFC3339, ot.UT)
|
||||||
|
if err != nil {
|
||||||
|
lastUpdatedAt = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
createOutcome := domain.CreateEnetpulseOutcomeType{
|
||||||
|
OutcomeTypeID: ot.ID,
|
||||||
|
Name: ot.Name,
|
||||||
|
Description: ot.Description,
|
||||||
|
UpdatesCount: int32(updatesCount),
|
||||||
|
LastUpdatedAt: lastUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6️⃣ Save to DB (upsert)
|
||||||
|
if _, err := s.store.CreateEnetpulseOutcomeType(ctx, createOutcome); err != nil {
|
||||||
|
// Optionally log the failure, continue to next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// s.logger.Info("✅ Successfully fetched and stored all EnetPulse outcome types")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOutcomeTypes retrieves all stored outcome types from the DB.
|
||||||
|
func (s *Service) GetAllOutcomeTypes(ctx context.Context) ([]domain.EnetpulseOutcomeType, error) {
|
||||||
|
outcomes, err := s.store.GetAllEnetpulseOutcomeTypes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch outcome types from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// s.logger.Info("✅ Fetched outcome types from DB", zap.Int("count", len(outcomes)))
|
||||||
|
return outcomes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
|
// 1️⃣ Fetch all fixtures
|
||||||
|
fixtures, err := s.store.GetAllEnetpulseFixtures(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch fixtures: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Fetch all outcome types
|
||||||
|
outcomeTypes, err := s.store.GetAllEnetpulseOutcomeTypes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch outcome types: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Loop through each fixture
|
||||||
|
for _, fixture := range fixtures {
|
||||||
|
// 4️⃣ Loop through each outcome type
|
||||||
|
for _, outcome := range outcomeTypes {
|
||||||
|
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s",
|
||||||
|
fixture.FixtureID,
|
||||||
|
s.cfg.EnetPulseConfig.ProviderID,
|
||||||
|
outcome.OutcomeTypeID,
|
||||||
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct adjusted exactly to match JSON structure
|
||||||
|
var preoddsResp struct {
|
||||||
|
Preodds map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
OutcomeTypeFK string `json:"outcome_typeFK"`
|
||||||
|
OutcomeScopeFK string `json:"outcome_scopeFK"`
|
||||||
|
OutcomeSubtypeFK string `json:"outcome_subtypeFK"`
|
||||||
|
EventParticipantNumber string `json:"event_participant_number"`
|
||||||
|
Iparam string `json:"iparam"`
|
||||||
|
Iparam2 string `json:"iparam2"`
|
||||||
|
Dparam string `json:"dparam"`
|
||||||
|
Dparam2 string `json:"dparam2"`
|
||||||
|
Sparam string `json:"sparam"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
|
||||||
|
PreoddsBettingOffers map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
BettingOfferStatusFK string `json:"bettingoffer_statusFK"`
|
||||||
|
OddsProviderFK string `json:"odds_providerFK"`
|
||||||
|
Odds string `json:"odds"`
|
||||||
|
OddsOld string `json:"odds_old"`
|
||||||
|
Active string `json:"active"`
|
||||||
|
CouponKey string `json:"couponKey"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
} `json:"preodds_bettingoffers"`
|
||||||
|
} `json:"preodds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&preoddsResp); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range preoddsResp.Preodds {
|
||||||
|
// Convert numeric/string fields safely
|
||||||
|
updatesCount, _ := strconv.Atoi(defaultIfEmpty(p.N, "0"))
|
||||||
|
eventParticipantNumber, _ := strconv.Atoi(defaultIfEmpty(p.EventParticipantNumber, "0"))
|
||||||
|
lastUpdatedAt := parseTimeOrNow(p.UT)
|
||||||
|
|
||||||
|
createPreodds := domain.CreateEnetpulsePreodds{
|
||||||
|
PreoddsID: p.ID,
|
||||||
|
EventFK: fixture.FixtureID,
|
||||||
|
OutcomeTypeFK: p.OutcomeTypeFK,
|
||||||
|
OutcomeScopeFK: p.OutcomeScopeFK,
|
||||||
|
OutcomeSubtypeFK: p.OutcomeSubtypeFK,
|
||||||
|
EventParticipantNumber: eventParticipantNumber,
|
||||||
|
IParam: p.Iparam,
|
||||||
|
IParam2: p.Iparam2,
|
||||||
|
DParam: p.Dparam,
|
||||||
|
DParam2: p.Dparam2,
|
||||||
|
SParam: p.Sparam,
|
||||||
|
UpdatesCount: updatesCount,
|
||||||
|
LastUpdatedAt: lastUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store preodds in DB
|
||||||
|
_, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5️⃣ Loop through betting offers map
|
||||||
|
for _, o := range p.PreoddsBettingOffers {
|
||||||
|
bettingUpdates, _ := strconv.Atoi(defaultIfEmpty(o.N, "0"))
|
||||||
|
bettingLastUpdatedAt := parseTimeOrNow(o.UT)
|
||||||
|
|
||||||
|
odds, _ := strconv.ParseFloat(defaultIfEmpty(o.Odds, "0"), 64)
|
||||||
|
oddsOld, _ := strconv.ParseFloat(defaultIfEmpty(o.OddsOld, "0"), 64)
|
||||||
|
bettingOfferStatusFK, _ := strconv.Atoi(defaultIfEmpty(o.BettingOfferStatusFK, "0"))
|
||||||
|
oddsProviderFK, _ := strconv.Atoi(defaultIfEmpty(o.OddsProviderFK, "0"))
|
||||||
|
|
||||||
|
createOffer := domain.CreateEnetpulsePreoddsBettingOffer{
|
||||||
|
BettingOfferID: o.ID,
|
||||||
|
PreoddsFK: createPreodds.PreoddsID,
|
||||||
|
BettingOfferStatusFK: int32(bettingOfferStatusFK),
|
||||||
|
OddsProviderFK: int32(oddsProviderFK),
|
||||||
|
Odds: odds,
|
||||||
|
OddsOld: oddsOld,
|
||||||
|
Active: o.Active,
|
||||||
|
CouponKey: o.CouponKey,
|
||||||
|
UpdatesCount: bettingUpdates,
|
||||||
|
LastUpdatedAt: bettingLastUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility helpers
|
||||||
|
func defaultIfEmpty(val, def string) string {
|
||||||
|
if val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimeOrNow(t string) time.Time {
|
||||||
|
parsed, err := time.Parse(time.RFC3339, t)
|
||||||
|
if err != nil {
|
||||||
|
return time.Now().UTC()
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// helper function to parse string to int32 safely
|
||||||
|
func ParseStringToInt32(s string) int32 {
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i, _ := strconv.Atoi(s)
|
||||||
|
return int32(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllPreodds(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
preodds, err := s.store.GetAllEnetpulsePreodds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch preodds from DB: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n\nFetched Preodds are:%v\n\n", preodds)
|
||||||
|
return preodds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAndStoreBettingOffers fetches betting offers from EnetPulse API and stores them in the DB.
|
||||||
|
func (s *Service) StoreBettingOffers(ctx context.Context, preoddsID string, oddsProviderIDs []int32) error {
|
||||||
|
// 1️⃣ Compose API URL
|
||||||
|
providers := make([]string, len(oddsProviderIDs))
|
||||||
|
for i, p := range oddsProviderIDs {
|
||||||
|
providers[i] = strconv.Itoa(int(p))
|
||||||
|
}
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"http://eapi.enetpulse.com/preodds_bettingoffer/?preoddsFK=%s&odds_providerFK=%s&username=%s&token=%s",
|
||||||
|
preoddsID,
|
||||||
|
strings.Join(providers, ","),
|
||||||
|
s.cfg.EnetPulseConfig.UserName,
|
||||||
|
s.cfg.EnetPulseConfig.Token,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2️⃣ Create HTTP request
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create betting offer request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Execute request
|
||||||
|
resp, err := s.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to call EnetPulse betting offer API: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("unexpected status %d fetching betting offers: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4️⃣ Decode JSON response
|
||||||
|
var offerResp struct {
|
||||||
|
BettingOffers map[string]struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
PreoddsFK string `json:"preodds_fk"`
|
||||||
|
BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"`
|
||||||
|
OddsProviderFK int32 `json:"odds_provider_fk"`
|
||||||
|
Odds float64 `json:"odds"`
|
||||||
|
OddsOld float64 `json:"odds_old"`
|
||||||
|
Active string `json:"active"`
|
||||||
|
CouponKey string `json:"coupon_key"`
|
||||||
|
N string `json:"n"`
|
||||||
|
UT string `json:"ut"`
|
||||||
|
} `json:"bettingoffer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&offerResp); err != nil {
|
||||||
|
return fmt.Errorf("failed to decode betting offers JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5️⃣ Iterate and store each betting offer
|
||||||
|
for _, o := range offerResp.BettingOffers {
|
||||||
|
updatesCount := 0
|
||||||
|
if o.N != "" {
|
||||||
|
if n, err := strconv.Atoi(o.N); err == nil {
|
||||||
|
updatesCount = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdatedAt, err := time.Parse(time.RFC3339, o.UT)
|
||||||
|
if err != nil {
|
||||||
|
lastUpdatedAt = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
createOffer := domain.CreateEnetpulsePreoddsBettingOffer{
|
||||||
|
BettingOfferID: o.ID,
|
||||||
|
PreoddsFK: preoddsID,
|
||||||
|
BettingOfferStatusFK: o.BettingOfferStatusFK,
|
||||||
|
OddsProviderFK: o.OddsProviderFK,
|
||||||
|
Odds: o.Odds,
|
||||||
|
OddsOld: o.OddsOld,
|
||||||
|
Active: o.Active,
|
||||||
|
CouponKey: o.CouponKey,
|
||||||
|
UpdatesCount: int(updatesCount),
|
||||||
|
LastUpdatedAt: lastUpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6️⃣ Save to DB
|
||||||
|
if _, err := s.store.CreateEnetpulsePreoddsBettingOffer(ctx, createOffer); err != nil {
|
||||||
|
// optionally log the failure and continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllBettingOffers retrieves all stored betting offers from the DB.
|
||||||
|
func (s *Service) GetAllBettingOffers(ctx context.Context) ([]domain.EnetpulsePreoddsBettingOffer, error) {
|
||||||
|
offers, err := s.store.GetAllEnetpulsePreoddsBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch betting offers from DB: %w", err)
|
||||||
|
}
|
||||||
|
return offers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllPreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
preodds, err := s.store.GetAllEnetpulsePreoddsWithBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch preodds with betting offers from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return preodds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
||||||
|
// 1️⃣ Fetch fixtures and their associated preodds from the repository
|
||||||
|
fixtures, err := s.store.GetFixturesWithPreodds(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch fixtures with preodds from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixtures, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) FetchTournamentTemplates(ctx context.Context) (*domain.TournamentTemplatesResponse, error) {
|
func (s *Service) FetchTournamentTemplates(ctx context.Context) (*domain.TournamentTemplatesResponse, error) {
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"http://eapi.enetpulse.com/tournamenttemplate/list/?username=%s&token=%s",
|
"http://eapi.enetpulse.com/tournamenttemplate/list/?username=%s&token=%s",
|
||||||
|
|
@ -701,9 +1393,9 @@ func (s *Service) FetchDailyEvents(ctx context.Context, req domain.DailyEventsRe
|
||||||
if req.TournamentTemplateFK != 0 {
|
if req.TournamentTemplateFK != 0 {
|
||||||
query += fmt.Sprintf("&tournament_templateFK=%d", req.TournamentTemplateFK)
|
query += fmt.Sprintf("&tournament_templateFK=%d", req.TournamentTemplateFK)
|
||||||
}
|
}
|
||||||
if req.TournamentStageFK != 0 {
|
// if req.TournamentStageFK != 0 {
|
||||||
query += fmt.Sprintf("&tournament_stageFK=%d", req.TournamentStageFK)
|
// query += fmt.Sprintf("&tournament_stageFK=%d", req.TournamentStageFK)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Optionals
|
// Optionals
|
||||||
if req.Date != "" {
|
if req.Date != "" {
|
||||||
|
|
@ -757,73 +1449,6 @@ func (s *Service) FetchDailyEvents(ctx context.Context, req domain.DailyEventsRe
|
||||||
return &dailyResp, nil
|
return &dailyResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) FetchFixtures(ctx context.Context, params domain.FixturesRequest) (*domain.FixturesResponse, error) {
|
|
||||||
// Build base URL
|
|
||||||
url := fmt.Sprintf("http://eapi.enetpulse.com/event/fixtures/?username=%s&token=%s",
|
|
||||||
s.cfg.EnetPulseConfig.UserName, s.cfg.EnetPulseConfig.Token)
|
|
||||||
|
|
||||||
// Required filter: one of sportFK | tournament_templateFK | tournament_stageFK
|
|
||||||
if params.SportFK != 0 {
|
|
||||||
url += fmt.Sprintf("&sportFK=%d", params.SportFK)
|
|
||||||
}
|
|
||||||
if params.TournamentTemplateFK != 0 {
|
|
||||||
url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK)
|
|
||||||
}
|
|
||||||
if params.TournamentStageFK != 0 {
|
|
||||||
url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional filters
|
|
||||||
if params.LanguageTypeFK != 0 {
|
|
||||||
url += fmt.Sprintf("&language_typeFK=%d", params.LanguageTypeFK)
|
|
||||||
} else {
|
|
||||||
url += "&language_typeFK=3" // default to English
|
|
||||||
}
|
|
||||||
if params.Date != "" {
|
|
||||||
url += fmt.Sprintf("&date=%s", params.Date)
|
|
||||||
}
|
|
||||||
if params.Live != "" {
|
|
||||||
url += fmt.Sprintf("&live=%s", params.Live)
|
|
||||||
}
|
|
||||||
if params.IncludeVenue {
|
|
||||||
url += "&includeVenue=yes"
|
|
||||||
}
|
|
||||||
if !params.IncludeEventProperties {
|
|
||||||
url += "&includeEventProperties=no"
|
|
||||||
}
|
|
||||||
if params.IncludeCountryCodes {
|
|
||||||
url += "&includeCountryCodes=yes"
|
|
||||||
}
|
|
||||||
if params.IncludeFirstLastName {
|
|
||||||
url += "&includeFirstLastName=yes"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make request
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating fixtures request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("requesting fixtures: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
body, _ := io.ReadAll(resp.Body)
|
|
||||||
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode response
|
|
||||||
var fixturesResp domain.FixturesResponse
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&fixturesResp); err != nil {
|
|
||||||
return nil, fmt.Errorf("decoding fixtures response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &fixturesResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) FetchResults(ctx context.Context, params domain.ResultsRequest) (*domain.ResultsResponse, error) {
|
func (s *Service) FetchResults(ctx context.Context, params domain.ResultsRequest) (*domain.ResultsResponse, error) {
|
||||||
// Build base URL
|
// Build base URL
|
||||||
url := fmt.Sprintf("http://eapi.enetpulse.com/event/results/?username=%s&token=%s",
|
url := fmt.Sprintf("http://eapi.enetpulse.com/event/results/?username=%s&token=%s",
|
||||||
|
|
@ -836,9 +1461,9 @@ func (s *Service) FetchResults(ctx context.Context, params domain.ResultsRequest
|
||||||
if params.TournamentTemplateFK != 0 {
|
if params.TournamentTemplateFK != 0 {
|
||||||
url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK)
|
url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK)
|
||||||
}
|
}
|
||||||
if params.TournamentStageFK != 0 {
|
// if params.TournamentStageFK != 0 {
|
||||||
url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
// url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Optional filters
|
// Optional filters
|
||||||
if params.LanguageTypeFK != 0 {
|
if params.LanguageTypeFK != 0 {
|
||||||
|
|
@ -974,7 +1599,7 @@ func (s *Service) FetchEventDetails(ctx context.Context, params domain.EventDeta
|
||||||
|
|
||||||
func (s *Service) FetchEventList(ctx context.Context, params domain.EventListRequest) (*domain.EventListResponse, error) {
|
func (s *Service) FetchEventList(ctx context.Context, params domain.EventListRequest) (*domain.EventListResponse, error) {
|
||||||
// You must provide either TournamentFK or TournamentStageFK
|
// You must provide either TournamentFK or TournamentStageFK
|
||||||
if params.TournamentFK == 0 && params.TournamentStageFK == 0 {
|
if params.TournamentFK == 0 {
|
||||||
return nil, fmt.Errorf("either TournamentFK or TournamentStageFK is required")
|
return nil, fmt.Errorf("either TournamentFK or TournamentStageFK is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -986,9 +1611,9 @@ func (s *Service) FetchEventList(ctx context.Context, params domain.EventListReq
|
||||||
if params.TournamentFK != 0 {
|
if params.TournamentFK != 0 {
|
||||||
url += fmt.Sprintf("&tournamentFK=%d", params.TournamentFK)
|
url += fmt.Sprintf("&tournamentFK=%d", params.TournamentFK)
|
||||||
}
|
}
|
||||||
if params.TournamentStageFK != 0 {
|
// if params.TournamentStageFK != 0 {
|
||||||
url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
// url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Optional parameters
|
// Optional parameters
|
||||||
if !params.IncludeEventProperties {
|
if !params.IncludeEventProperties {
|
||||||
|
|
@ -1059,9 +1684,9 @@ func (s *Service) FetchParticipantFixtures(ctx context.Context, params domain.Pa
|
||||||
if params.TournamentTemplateFK != 0 {
|
if params.TournamentTemplateFK != 0 {
|
||||||
url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK)
|
url += fmt.Sprintf("&tournament_templateFK=%d", params.TournamentTemplateFK)
|
||||||
}
|
}
|
||||||
if params.TournamentStageFK != 0 {
|
// if params.TournamentStageFK != 0 {
|
||||||
url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
// url += fmt.Sprintf("&tournament_stageFK=%d", params.TournamentStageFK)
|
||||||
}
|
// }
|
||||||
if params.Date != "" {
|
if params.Date != "" {
|
||||||
url += "&date=" + params.Date
|
url += "&date=" + params.Date
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,9 @@ func (s *ServiceImpl) ProcessBet365Odds(ctx context.Context) error {
|
||||||
Value: domain.STATUS_PENDING,
|
Value: domain.STATUS_PENDING,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Source: domain.ValidEventSource{
|
// Source: domain.ValidEventSource{
|
||||||
Value: domain.EVENT_SOURCE_BET365,
|
// Value: domain.EVENT_SOURCE_BET365,
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error(
|
s.mongoLogger.Error(
|
||||||
|
|
|
||||||
|
|
@ -237,10 +237,10 @@ func (s *Service) FetchB365ResultAndUpdateBets(ctx context.Context) error {
|
||||||
Value: time.Now(),
|
Value: time.Now(),
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Source: domain.ValidEventSource{
|
// Source: domain.ValidEventSource{
|
||||||
Value: domain.EVENT_SOURCE_BET365,
|
// Value: domain.EVENT_SOURCE_BET365,
|
||||||
Valid: true,
|
// Valid: true,
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -463,10 +463,10 @@ func (s *Service) CheckAndUpdateExpiredB365Events(ctx context.Context) (int64, e
|
||||||
Value: time.Now(),
|
Value: time.Now(),
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Source: domain.ValidEventSource{
|
// Source: domain.ValidEventSource{
|
||||||
Value: domain.EVENT_SOURCE_BET365,
|
// Value: domain.EVENT_SOURCE_BET365,
|
||||||
Valid: true,
|
// Valid: true,
|
||||||
},
|
// },
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.mongoLogger.Error(
|
s.mongoLogger.Error(
|
||||||
|
|
@ -685,7 +685,7 @@ func (s *Service) GetBet365ResultForEvent(ctx context.Context, b365EventID strin
|
||||||
zap.String("b365EventID", b365EventID),
|
zap.String("b365EventID", b365EventID),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
return json.RawMessage{}, nil, fmt.Errorf("invalid API response for event %d", b365EventID)
|
return json.RawMessage{}, nil, fmt.Errorf("invalid API response for event %s", b365EventID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonResp domain.CommonResultResponse
|
var commonResp domain.CommonResultResponse
|
||||||
|
|
|
||||||
|
|
@ -135,16 +135,16 @@ func (s *SantimPayService) ProcessCallback(ctx context.Context, payload domain.S
|
||||||
return fmt.Errorf("invalid ThirdPartyId '%s': %w", payload.ThirdPartyId, err)
|
return fmt.Errorf("invalid ThirdPartyId '%s': %w", payload.ThirdPartyId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get wallets for user %d: %w", userID, err)
|
return fmt.Errorf("failed to get wallets for customer %d: %w", userID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally, credit user wallet
|
// Optionally, credit user wallet
|
||||||
if transfer.Type == domain.DEPOSIT {
|
if transfer.Type == domain.DEPOSIT {
|
||||||
if _, err := s.walletSvc.AddToWallet(
|
if _, err := s.walletSvc.AddToWallet(
|
||||||
ctx,
|
ctx,
|
||||||
wallets[0].ID,
|
wallet.RegularID,
|
||||||
domain.Currency(amount),
|
domain.Currency(amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_SANTIMPAY,
|
domain.TRANSFER_SANTIMPAY,
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,13 @@ func (s *AleaPlayService) HandleCallback(ctx context.Context, callback *domain.A
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update session status using the proper repository method
|
// Update session status using the proper repository method
|
||||||
if callback.Type == "SESSION_END" {
|
// if callback.Type == "SESSION_END" {
|
||||||
if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
// if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
||||||
s.logger.Error("failed to update session status",
|
// s.logger.Error("failed to update session status",
|
||||||
"sessionID", session.ID,
|
// "sessionID", session.ID,
|
||||||
"error", err)
|
// "error", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ func (c *Client) generateHash(body []byte, timestamp string) string {
|
||||||
func (c *Client) post(ctx context.Context, path string, body map[string]any, result any) error {
|
func (c *Client) post(ctx context.Context, path string, body map[string]any, result any) error {
|
||||||
// Add timestamp first
|
// Add timestamp first
|
||||||
timestamp := nowTimestamp()
|
timestamp := nowTimestamp()
|
||||||
body["timestamp"] = timestamp
|
|
||||||
|
|
||||||
// Marshal without hash first
|
// Marshal without hash first
|
||||||
tmp, _ := json.Marshal(body)
|
tmp, _ := json.Marshal(body)
|
||||||
|
|
@ -61,12 +60,14 @@ func (c *Client) post(ctx context.Context, path string, body map[string]any, res
|
||||||
hash := c.generateHash(tmp, timestamp)
|
hash := c.generateHash(tmp, timestamp)
|
||||||
body["hash"] = hash
|
body["hash"] = hash
|
||||||
|
|
||||||
|
body["timestamp"] = timestamp
|
||||||
|
|
||||||
fmt.Printf("atlasPost: %v \n", body)
|
fmt.Printf("atlasPost: %v \n", body)
|
||||||
// Marshal final body
|
// Marshal final body
|
||||||
data, _ := json.Marshal(body)
|
data, _ := json.Marshal(body)
|
||||||
|
|
||||||
req, _ := http.NewRequestWithContext(ctx, "POST", c.BaseURL+path, bytes.NewReader(data))
|
req, _ := http.NewRequestWithContext(ctx, "POST", c.BaseURL+path, bytes.NewReader(data))
|
||||||
req.Header.Set("Content-Type", "text/javascript")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
fmt.Println("Request URL:", c.BaseURL+path)
|
fmt.Println("Request URL:", c.BaseURL+path)
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.AtlasBetRequest) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Deduct amount from wallet (record transaction)
|
// 6. Deduct amount from wallet (record transaction)
|
||||||
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.ID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -158,13 +158,13 @@ func (s *Service) ProcessBetWin(ctx context.Context, req domain.AtlasBetWinReque
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Deduct amount from wallet (record transaction)
|
// 6. Deduct amount from wallet (record transaction)
|
||||||
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.ID, domain.Currency(req.BetAmount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.BetAmount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.WinAmount > 0 {
|
if req.WinAmount > 0 {
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.ID, domain.Currency(req.WinAmount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.WinAmount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +173,7 @@ func (s *Service) ProcessBetWin(ctx context.Context, req domain.AtlasBetWinReque
|
||||||
// 8. Build response
|
// 8. Build response
|
||||||
res := &domain.AtlasBetWinResponse{
|
res := &domain.AtlasBetWinResponse{
|
||||||
PlayerID: req.PlayerID,
|
PlayerID: req.PlayerID,
|
||||||
Balance: float64(wallet.RegularBalance) - req.BetAmount + req.WinAmount,
|
Balance: float64(wallet.RegularBalance),
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
|
@ -197,7 +197,7 @@ func (s *Service) ProcessRoundResult(ctx context.Context, req domain.RoundResult
|
||||||
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.ID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +229,7 @@ func (s *Service) ProcessRollBack(ctx context.Context, req domain.RollbackReques
|
||||||
return nil, fmt.Errorf("failed to fetch transfer for reference %s: %w", req.BetTransactionID, err)
|
return nil, fmt.Errorf("failed to fetch transfer for reference %s: %w", req.BetTransactionID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.ID, domain.Currency(transfer.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+float64(transfer.Amount)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +284,7 @@ func (s *Service) ProcessFreeSpinResult(ctx context.Context, req domain.FreeSpin
|
||||||
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.ID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +313,7 @@ func (s *Service) ProcessJackPot(ctx context.Context, req domain.JackpotRequest)
|
||||||
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.ID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package virtualgameservice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Remove a provider by provider_id
|
|
||||||
func (s *service) RemoveProvider(ctx context.Context, providerID string) error {
|
|
||||||
return s.repo.DeleteVirtualGameProvider(ctx, providerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch provider by provider_id
|
|
||||||
func (s *service) GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) {
|
|
||||||
return s.repo.GetVirtualGameProviderByID(ctx, providerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List providers with pagination
|
|
||||||
func (s *service) ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error) {
|
|
||||||
providers, err := s.repo.ListVirtualGameProviders(ctx, limit, offset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
total, err := s.repo.CountVirtualGameProviders(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert []dbgen.VirtualGameProvider to []domain.VirtualGameProvider
|
|
||||||
domainProviders := make([]domain.VirtualGameProvider, len(providers))
|
|
||||||
for i, p := range providers {
|
|
||||||
var logoDark *string
|
|
||||||
if p.LogoDark.Valid {
|
|
||||||
logoDark = &p.LogoDark.String
|
|
||||||
}
|
|
||||||
|
|
||||||
var logoLight *string
|
|
||||||
if p.LogoLight.Valid {
|
|
||||||
logoLight = &p.LogoLight.String
|
|
||||||
}
|
|
||||||
|
|
||||||
domainProviders[i] = domain.VirtualGameProvider{
|
|
||||||
ProviderID: p.ProviderID,
|
|
||||||
ProviderName: p.ProviderName,
|
|
||||||
Enabled: p.Enabled,
|
|
||||||
LogoDark: logoDark,
|
|
||||||
LogoLight: logoLight,
|
|
||||||
CreatedAt: p.CreatedAt.Time,
|
|
||||||
UpdatedAt: &p.UpdatedAt.Time,
|
|
||||||
// Add other fields as needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainProviders, total, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable/Disable a provider
|
|
||||||
func (s *service) SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error) {
|
|
||||||
provider, err := s.repo.UpdateVirtualGameProviderEnabled(ctx, providerID, enabled)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to update provider enabled status", "provider_id", providerID, "enabled", enabled, "error", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
provider.UpdatedAt.Time = now
|
|
||||||
|
|
||||||
domainProvider := &domain.VirtualGameProvider{
|
|
||||||
ProviderID: provider.ProviderID,
|
|
||||||
ProviderName: provider.ProviderName,
|
|
||||||
Enabled: provider.Enabled,
|
|
||||||
CreatedAt: provider.CreatedAt.Time,
|
|
||||||
UpdatedAt: &provider.UpdatedAt.Time,
|
|
||||||
// Add other fields as needed
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainProvider, nil
|
|
||||||
}
|
|
||||||
14
internal/services/virtualGame/orchestration/port.go
Normal file
14
internal/services/virtualGame/orchestration/port.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package orchestration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrchestrationService interface {
|
||||||
|
FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error)
|
||||||
|
GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error)
|
||||||
|
AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
|
||||||
|
}
|
||||||
526
internal/services/virtualGame/orchestration/service.go
Normal file
526
internal/services/virtualGame/orchestration/service.go
Normal file
|
|
@ -0,0 +1,526 @@
|
||||||
|
package orchestration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
|
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||||
|
repo repository.VirtualGameRepository
|
||||||
|
cfg *config.Config
|
||||||
|
client *veli.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(virtualGameSvc virtualgameservice.VirtualGameService, repo repository.VirtualGameRepository, cfg *config.Config, client *veli.Client) *Service {
|
||||||
|
return &Service{
|
||||||
|
virtualGameSvc: virtualGameSvc,
|
||||||
|
repo: repo,
|
||||||
|
cfg: cfg,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
|
||||||
|
|
||||||
|
// logger := s.mongoLogger.With(zap.String("service", "AddProviders"), zap.Any("ProviderRequest", req))
|
||||||
|
|
||||||
|
// 0. Remove all existing providers first
|
||||||
|
if err := s.repo.DeleteAllVirtualGameProviders(ctx); err != nil {
|
||||||
|
// logger.Error("failed to delete all virtual game providers", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to clear existing providers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Prepare signature parameters
|
||||||
|
sigParams := map[string]any{
|
||||||
|
"brandId": req.BrandID,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional fields
|
||||||
|
sigParams["extraData"] = fmt.Sprintf("%t", req.ExtraData) // false is still included
|
||||||
|
if req.Size > 0 {
|
||||||
|
sigParams["size"] = fmt.Sprintf("%d", req.Size)
|
||||||
|
} else {
|
||||||
|
sigParams["size"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page > 0 {
|
||||||
|
sigParams["page"] = fmt.Sprintf("%d", req.Page)
|
||||||
|
} else {
|
||||||
|
sigParams["page"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Call external API
|
||||||
|
var res domain.ProviderResponse
|
||||||
|
if err := s.client.Post(ctx, "/game-lists/public/providers", req, sigParams, &res); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch providers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Loop through fetched providers and insert into DB
|
||||||
|
for _, p := range res.Items {
|
||||||
|
createParams := dbgen.CreateVirtualGameProviderParams{
|
||||||
|
ProviderID: p.ProviderID,
|
||||||
|
ProviderName: p.ProviderName,
|
||||||
|
LogoDark: pgtype.Text{String: p.LogoForDark, Valid: p.LogoForDark != ""},
|
||||||
|
LogoLight: pgtype.Text{String: p.LogoForLight, Valid: p.LogoForLight != ""},
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.CreateVirtualGameProvider(ctx, createParams); err != nil {
|
||||||
|
// logger.Error("failed to add provider", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to add provider %s: %w", p.ProviderID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Always add "popok" provider manually
|
||||||
|
popokParams := dbgen.CreateVirtualGameProviderParams{
|
||||||
|
ProviderID: "popok",
|
||||||
|
ProviderName: "Popok Gaming",
|
||||||
|
LogoDark: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-dark.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
|
||||||
|
LogoLight: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-light.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
atlasParams := dbgen.CreateVirtualGameProviderParams{
|
||||||
|
ProviderID: "atlas",
|
||||||
|
ProviderName: "Atlas Gaming",
|
||||||
|
LogoDark: pgtype.Text{String: "/static/logos/atlas-dark.png", Valid: true}, // adjust as needed
|
||||||
|
LogoLight: pgtype.Text{String: "/static/logos/atlas-light.png", Valid: true}, // adjust as needed
|
||||||
|
Enabled: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.CreateVirtualGameProvider(ctx, popokParams); err != nil {
|
||||||
|
// logger.Error("failed to add popok provider", zap.Any("popokParams", popokParams), zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to add popok provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.repo.CreateVirtualGameProvider(ctx, atlasParams); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add atlas provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally also append it to the response for consistency
|
||||||
|
// res.Items = append(res.Items, domain.VirtualGameProvider{
|
||||||
|
// ProviderID: uuid.New().String(),
|
||||||
|
// ProviderName: "Popok Gaming",
|
||||||
|
// LogoForDark: "/static/logos/popok-dark.png",
|
||||||
|
// LogoForLight: "/static/logos/popok-light.png",
|
||||||
|
// })
|
||||||
|
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) {
|
||||||
|
// Build params for repo call
|
||||||
|
// logger := s.mongoLogger.With(zap.String("service", "GetAllVirtualGames"), zap.Any("params", params))
|
||||||
|
rows, err := s.repo.ListAllVirtualGames(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("[GetAllVirtualGames] Failed to fetch virtual games", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to fetch virtual games: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allGames []domain.UnifiedGame
|
||||||
|
for _, r := range rows {
|
||||||
|
// --- Convert nullable Rtp to *float64 ---
|
||||||
|
var rtpPtr *float64
|
||||||
|
if r.Rtp.Valid {
|
||||||
|
rtpFloat, err := r.Rtp.Float64Value()
|
||||||
|
if err == nil {
|
||||||
|
rtpPtr = new(float64)
|
||||||
|
*rtpPtr = rtpFloat.Float64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var betsFloat64 []float64
|
||||||
|
for _, bet := range r.Bets {
|
||||||
|
if bet.Valid {
|
||||||
|
betFloat, err := bet.Float64Value()
|
||||||
|
if err == nil {
|
||||||
|
betsFloat64 = append(betsFloat64, betFloat.Float64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allGames = append(allGames, domain.UnifiedGame{
|
||||||
|
GameID: r.GameID,
|
||||||
|
ProviderID: r.ProviderID,
|
||||||
|
Provider: r.ProviderName,
|
||||||
|
Name: r.Name,
|
||||||
|
Category: r.Category.String,
|
||||||
|
DeviceType: r.DeviceType.String,
|
||||||
|
Volatility: r.Volatility.String,
|
||||||
|
RTP: rtpPtr,
|
||||||
|
HasDemo: r.HasDemo.Bool,
|
||||||
|
HasFreeBets: r.HasFreeBets.Bool,
|
||||||
|
Bets: betsFloat64,
|
||||||
|
Thumbnail: r.Thumbnail.String,
|
||||||
|
Status: int(r.Status.Int32), // nullable status
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return allGames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) {
|
||||||
|
// logger := s.mongoLogger.With(
|
||||||
|
// zap.String("service", "FetchAndStoreAllVirtualGames"),
|
||||||
|
// zap.Any("ProviderRequest", req),
|
||||||
|
// )
|
||||||
|
|
||||||
|
// This is necessary since the provider is a foreign key
|
||||||
|
_, err := s.AddProviders(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add providers to database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var allGames []domain.UnifiedGame
|
||||||
|
|
||||||
|
// --- 1. Existing providers (Veli Games) ---
|
||||||
|
providersRes, err := s.veliVirtualGameSvc.GetProviders(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("Failed to fetch provider", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to fetch providers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. Fetch games for each provider (Veli Games) ---
|
||||||
|
for _, p := range providersRes.Items {
|
||||||
|
games, err := s.veliVirtualGameSvc.GetGames(ctx, domain.GameListRequest{
|
||||||
|
BrandID: s.cfg.VeliGames.BrandID,
|
||||||
|
ProviderID: p.ProviderID,
|
||||||
|
Page: req.Page,
|
||||||
|
Size: req.Size,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to get veli games", zap.String("ProviderID", p.ProviderID), zap.Error(err))
|
||||||
|
continue // skip failing provider but continue others
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range games {
|
||||||
|
unified := domain.UnifiedGame{
|
||||||
|
GameID: g.GameID,
|
||||||
|
ProviderID: g.ProviderID,
|
||||||
|
Provider: p.ProviderName,
|
||||||
|
Name: g.Name,
|
||||||
|
Category: g.Category,
|
||||||
|
DeviceType: g.DeviceType,
|
||||||
|
HasDemo: g.HasDemoMode,
|
||||||
|
HasFreeBets: g.HasFreeBets,
|
||||||
|
}
|
||||||
|
allGames = append(allGames, unified)
|
||||||
|
|
||||||
|
// Save to DB
|
||||||
|
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
||||||
|
GameID: g.GameID,
|
||||||
|
ProviderID: g.ProviderID,
|
||||||
|
Name: g.Name,
|
||||||
|
Category: pgtype.Text{
|
||||||
|
String: g.Category,
|
||||||
|
Valid: g.Category != "",
|
||||||
|
},
|
||||||
|
DeviceType: pgtype.Text{
|
||||||
|
String: g.DeviceType,
|
||||||
|
Valid: g.DeviceType != "",
|
||||||
|
},
|
||||||
|
HasDemo: pgtype.Bool{
|
||||||
|
Bool: g.HasDemoMode,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
HasFreeBets: pgtype.Bool{
|
||||||
|
Bool: g.HasFreeBets,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to create virtual game", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Fetch Atlas-V games ---
|
||||||
|
atlasGames, err := s.veliVirtualGameSvc.GetAtlasVGames(ctx)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to fetch Atlas-V games", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
for _, g := range atlasGames {
|
||||||
|
unified := domain.UnifiedGame{
|
||||||
|
GameID: g.GameID,
|
||||||
|
ProviderID: "atlasv",
|
||||||
|
Provider: "Atlas-V Gaming", // "Atlas-V"
|
||||||
|
Name: g.Name,
|
||||||
|
Category: g.Category, // using Type as Category
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
HasDemo: true,
|
||||||
|
DemoURL: g.DemoURL,
|
||||||
|
}
|
||||||
|
allGames = append(allGames, unified)
|
||||||
|
|
||||||
|
// Save to DB
|
||||||
|
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
||||||
|
GameID: g.GameID,
|
||||||
|
ProviderID: "atlasv",
|
||||||
|
Name: g.Name,
|
||||||
|
Category: pgtype.Text{
|
||||||
|
String: g.Category,
|
||||||
|
Valid: g.Category != "",
|
||||||
|
},
|
||||||
|
Thumbnail: pgtype.Text{
|
||||||
|
String: g.Thumbnail,
|
||||||
|
Valid: g.Thumbnail != "",
|
||||||
|
},
|
||||||
|
HasDemo: pgtype.Bool{
|
||||||
|
Bool: g.HasDemoMode,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to create Atlas-V virtual game", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. Handle PopOK separately ---
|
||||||
|
popokGames, err := s.virtualGameSvc.ListGames(ctx, currency)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to fetch PopOk games", zap.Error(err))
|
||||||
|
return nil, fmt.Errorf("failed to fetch PopOK games: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range popokGames {
|
||||||
|
unified := domain.UnifiedGame{
|
||||||
|
GameID: fmt.Sprintf("%d", g.ID),
|
||||||
|
ProviderID: "popok",
|
||||||
|
Provider: "PopOK",
|
||||||
|
Name: g.GameName,
|
||||||
|
Category: "Crash",
|
||||||
|
Bets: g.Bets,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Status: g.Status,
|
||||||
|
}
|
||||||
|
allGames = append(allGames, unified)
|
||||||
|
|
||||||
|
// Convert []float64 to []pgtype.Numeric
|
||||||
|
var betsNumeric []pgtype.Numeric
|
||||||
|
for _, bet := range g.Bets {
|
||||||
|
var num pgtype.Numeric
|
||||||
|
_ = num.Scan(bet)
|
||||||
|
betsNumeric = append(betsNumeric, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save to DB
|
||||||
|
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
||||||
|
GameID: fmt.Sprintf("%d", g.ID),
|
||||||
|
ProviderID: "popok",
|
||||||
|
Name: g.GameName,
|
||||||
|
Bets: betsNumeric,
|
||||||
|
Thumbnail: pgtype.Text{
|
||||||
|
String: g.Thumbnail,
|
||||||
|
Valid: g.Thumbnail != "",
|
||||||
|
},
|
||||||
|
Status: pgtype.Int4{
|
||||||
|
Int32: int32(g.Status),
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
HasDemo: pgtype.Bool{
|
||||||
|
Bool: true,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
Category: pgtype.Text{
|
||||||
|
String: "Crash",
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to create PopOK virtual game", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allGames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RemoveProvider(ctx context.Context, providerID string) error {
|
||||||
|
return s.repo.DeleteVirtualGameProvider(ctx, providerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch provider by provider_id
|
||||||
|
func (s *Service) GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) {
|
||||||
|
return s.repo.GetVirtualGameProviderByID(ctx, providerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List providers with pagination
|
||||||
|
func (s *Service) ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error) {
|
||||||
|
providers, err := s.repo.ListVirtualGameProviders(ctx, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := s.repo.CountVirtualGameProviders(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert []dbgen.VirtualGameProvider to []domain.VirtualGameProvider
|
||||||
|
domainProviders := make([]domain.VirtualGameProvider, len(providers))
|
||||||
|
for i, p := range providers {
|
||||||
|
var logoDark *string
|
||||||
|
if p.LogoDark.Valid {
|
||||||
|
logoDark = &p.LogoDark.String
|
||||||
|
}
|
||||||
|
|
||||||
|
var logoLight *string
|
||||||
|
if p.LogoLight.Valid {
|
||||||
|
logoLight = &p.LogoLight.String
|
||||||
|
}
|
||||||
|
|
||||||
|
domainProviders[i] = domain.VirtualGameProvider{
|
||||||
|
ProviderID: p.ProviderID,
|
||||||
|
ProviderName: p.ProviderName,
|
||||||
|
Enabled: p.Enabled,
|
||||||
|
LogoDark: logoDark,
|
||||||
|
LogoLight: logoLight,
|
||||||
|
CreatedAt: p.CreatedAt.Time,
|
||||||
|
UpdatedAt: &p.UpdatedAt.Time,
|
||||||
|
// Add other fields as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return domainProviders, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/Disable a provider
|
||||||
|
func (s *Service) SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error) {
|
||||||
|
provider, err := s.repo.UpdateVirtualGameProviderEnabled(ctx, providerID, enabled)
|
||||||
|
if err != nil {
|
||||||
|
//s.logger.Error("Failed to update provider enabled status", "provider_id", providerID, "enabled", enabled, "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
provider.UpdatedAt.Time = now
|
||||||
|
|
||||||
|
domainProvider := &domain.VirtualGameProvider{
|
||||||
|
ProviderID: provider.ProviderID,
|
||||||
|
ProviderName: provider.ProviderName,
|
||||||
|
Enabled: provider.Enabled,
|
||||||
|
CreatedAt: provider.CreatedAt.Time,
|
||||||
|
UpdatedAt: &provider.UpdatedAt.Time,
|
||||||
|
// Add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
return domainProvider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error) {
|
||||||
|
// Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameProviderReport"), zap.Any("Report", report))
|
||||||
|
|
||||||
|
// Call repository to create the report
|
||||||
|
created, err := s.repo.CreateVirtualGameProviderReport(ctx, report)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to create provider report", zap.Error(err), zap.Any("report", report))
|
||||||
|
return domain.VirtualGameProviderReport{}, fmt.Errorf("failed to create provider report for provider %s: %w", report.ProviderID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the created report
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateVirtualGameReport(ctx context.Context, report domain.CreateVirtualGameReport) (domain.VirtualGameReport, error) {
|
||||||
|
// Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameReport"), zap.Any("Report", report))
|
||||||
|
|
||||||
|
// Call repository to create the report
|
||||||
|
created, err := s.repo.CreateVirtualGameReport(ctx, report)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to create game report", zap.Error(err), zap.Any("report", report))
|
||||||
|
return domain.VirtualGameReport{}, fmt.Errorf("failed to create game report for game %s: %w", report.GameID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the created report
|
||||||
|
return created, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetVirtualGameProviderReportByProviderAndDate(
|
||||||
|
ctx context.Context,
|
||||||
|
providerID string,
|
||||||
|
reportDate time.Time,
|
||||||
|
reportType string,
|
||||||
|
) (domain.VirtualGameProviderReport, error) {
|
||||||
|
// Example logger if needed
|
||||||
|
// logger := s.mongoLogger.With(zap.String("service", "GetVirtualGameProviderReportByProviderAndDate"),
|
||||||
|
// zap.String("provider_id", providerID),
|
||||||
|
// zap.Time("report_date", reportDate),
|
||||||
|
// zap.String("report_type", reportType),
|
||||||
|
// )
|
||||||
|
|
||||||
|
report, err := s.repo.GetVirtualGameProviderReportByProviderAndDate(ctx, providerID, reportDate, reportType)
|
||||||
|
if err != nil {
|
||||||
|
// logger.Error("failed to retrieve virtual game provider report", zap.Error(err))
|
||||||
|
return domain.VirtualGameProviderReport{}, fmt.Errorf(
|
||||||
|
"failed to retrieve provider report for provider %s on %s (%s): %w",
|
||||||
|
providerID, reportDate.Format("2006-01-02"), reportType, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx context.Context,
|
||||||
|
providerID string,
|
||||||
|
reportDate time.Time,
|
||||||
|
reportType string,
|
||||||
|
totalGamesPlayed int64,
|
||||||
|
totalBets float64,
|
||||||
|
totalPayouts float64,
|
||||||
|
totalPlayers int64,
|
||||||
|
) error {
|
||||||
|
// Optionally log or trace the update
|
||||||
|
// Example: s.mongoLogger.Info("Updating virtual game provider report",
|
||||||
|
// zap.String("provider_id", providerID),
|
||||||
|
// zap.Time("report_date", reportDate),
|
||||||
|
// zap.String("report_type", reportType),
|
||||||
|
// )
|
||||||
|
|
||||||
|
err := s.repo.UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx,
|
||||||
|
providerID,
|
||||||
|
reportDate,
|
||||||
|
reportType,
|
||||||
|
totalGamesPlayed,
|
||||||
|
totalBets,
|
||||||
|
totalPayouts,
|
||||||
|
totalPlayers,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update provider report for provider %s on %s (%s): %w",
|
||||||
|
providerID,
|
||||||
|
reportDate.Format("2006-01-02"),
|
||||||
|
reportType,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListVirtualGameProviderReportsByGamesPlayedAsc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) {
|
||||||
|
reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedAsc(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch virtual game provider reports ascending: %w", err)
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVirtualGameProviderReportsByGamesPlayedDesc fetches all reports sorted by total_games_played descending.
|
||||||
|
func (s *Service) ListVirtualGameProviderReportsByGamesPlayedDesc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) {
|
||||||
|
reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedDesc(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch virtual game provider reports descending: %w", err)
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
@ -3,16 +3,15 @@ package virtualgameservice
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VirtualGameService interface {
|
type VirtualGameService interface {
|
||||||
// AddProvider(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
|
// AddProvider(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error)
|
||||||
RemoveProvider(ctx context.Context, providerID string) error
|
// RemoveProvider(ctx context.Context, providerID string) error
|
||||||
GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error)
|
// GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error)
|
||||||
ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error)
|
// ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error)
|
||||||
SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error)
|
// SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error)
|
||||||
|
|
||||||
GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error)
|
GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error)
|
||||||
HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error
|
HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error
|
||||||
|
|
|
||||||
|
|
@ -224,16 +224,16 @@ func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfo
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
if err != nil || len(wallets) == 0 {
|
if err != nil {
|
||||||
s.logger.Error("No wallets found for user", "userID", claims.UserID)
|
s.logger.Error("No wallets found for user", "userID", claims.UserID)
|
||||||
return nil, fmt.Errorf("no wallet found")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.PopOKPlayerInfoResponse{
|
return &domain.PopOKPlayerInfoResponse{
|
||||||
Country: "ET",
|
Country: "ET",
|
||||||
Currency: claims.Currency,
|
Currency: claims.Currency,
|
||||||
Balance: float64(wallets[0].Balance), // Convert cents to currency
|
Balance: float64(wallet.RegularBalance), // Convert cents to currency
|
||||||
PlayerID: fmt.Sprintf("%d", claims.UserID),
|
PlayerID: fmt.Sprintf("%d", claims.UserID),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -246,17 +246,17 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert amount to cents (assuming wallet uses cents)
|
// Convert amount to cents (assuming wallet uses cents)
|
||||||
amountCents := int64(req.Amount)
|
// amount := int64(req.Amount)
|
||||||
|
|
||||||
// Deduct from wallet
|
// Deduct from wallet
|
||||||
|
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
|
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
|
||||||
}
|
}
|
||||||
_, err = s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents),
|
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID, domain.Currency(req.Amount),
|
||||||
domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||||
fmt.Sprintf("Deducted %v amount from wallet by system while placing virtual game bet", amountCents))
|
fmt.Sprintf("Deducted %v amount from wallet by system while placing virtual game bet", req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("insufficient balance")
|
return nil, fmt.Errorf("insufficient balance")
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +268,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
||||||
Provider: string(domain.PROVIDER_POPOK),
|
Provider: string(domain.PROVIDER_POPOK),
|
||||||
GameID: req.GameID,
|
GameID: req.GameID,
|
||||||
TransactionType: "BET",
|
TransactionType: "BET",
|
||||||
Amount: amountCents, // Negative for bets
|
Amount: int64(req.Amount), // Negative for bets
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
ExternalTransactionID: req.TransactionID,
|
ExternalTransactionID: req.TransactionID,
|
||||||
Status: "COMPLETED",
|
Status: "COMPLETED",
|
||||||
|
|
@ -283,7 +283,7 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
|
||||||
return &domain.PopOKBetResponse{
|
return &domain.PopOKBetResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID
|
||||||
Balance: float64(userWallets[0].Balance),
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,10 +319,15 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Convert amount to cents
|
// 3. Convert amount to cents
|
||||||
amountCents := int64(req.Amount)
|
// amountCents := int64(req.Amount)
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read user wallets")
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Credit to wallet
|
// 4. Credit to wallet
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{},
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{},
|
||||||
domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||||
fmt.Sprintf("Added %v to wallet for winning PopOkBet", req.Amount),
|
fmt.Sprintf("Added %v to wallet for winning PopOkBet", req.Amount),
|
||||||
)
|
)
|
||||||
|
|
@ -331,10 +336,10 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
return nil, fmt.Errorf("wallet credit failed")
|
return nil, fmt.Errorf("wallet credit failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return &domain.PopOKWinResponse{}, fmt.Errorf("Failed to read user wallets")
|
// return &domain.PopOKWinResponse{}, fmt.Errorf("Failed to read user wallets")
|
||||||
}
|
// }
|
||||||
// 5. Create transaction record
|
// 5. Create transaction record
|
||||||
tx := &domain.VirtualGameTransaction{
|
tx := &domain.VirtualGameTransaction{
|
||||||
UserID: claims.UserID,
|
UserID: claims.UserID,
|
||||||
|
|
@ -342,7 +347,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
Provider: string(domain.PROVIDER_POPOK),
|
Provider: string(domain.PROVIDER_POPOK),
|
||||||
GameID: req.GameID,
|
GameID: req.GameID,
|
||||||
TransactionType: "WIN",
|
TransactionType: "WIN",
|
||||||
Amount: amountCents,
|
Amount: int64(req.Amount),
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
ExternalTransactionID: req.TransactionID,
|
ExternalTransactionID: req.TransactionID,
|
||||||
Status: "COMPLETED",
|
Status: "COMPLETED",
|
||||||
|
|
@ -354,12 +359,12 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
|
||||||
return nil, fmt.Errorf("transaction recording failed")
|
return nil, fmt.Errorf("transaction recording failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n\n Win balance is:%v\n\n", float64(userWallets[0].Balance))
|
fmt.Printf("\n\n Win balance is:%v\n\n", float64(wallet.RegularBalance))
|
||||||
|
|
||||||
return &domain.PopOKWinResponse{
|
return &domain.PopOKWinResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
||||||
Balance: float64(userWallets[0].Balance),
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -371,6 +376,11 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read user wallets")
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Check for duplicate tournament win transaction
|
// 2. Check for duplicate tournament win transaction
|
||||||
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -379,15 +389,15 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
|
||||||
}
|
}
|
||||||
if existingTx != nil && existingTx.TransactionType == "TOURNAMENT_WIN" {
|
if existingTx != nil && existingTx.TransactionType == "TOURNAMENT_WIN" {
|
||||||
s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID)
|
s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID)
|
||||||
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
balance := 0.0
|
// balance := 0.0
|
||||||
if len(wallets) > 0 {
|
// if len(wallets) > 0 {
|
||||||
balance = float64(wallets[0].Balance)
|
// balance = float64(wallets[0].Balance)
|
||||||
}
|
// }
|
||||||
return &domain.PopOKWinResponse{
|
return &domain.PopOKWinResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
||||||
Balance: balance,
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,7 +405,7 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
|
||||||
amountCents := int64(req.Amount)
|
amountCents := int64(req.Amount)
|
||||||
|
|
||||||
// 4. Credit user wallet
|
// 4. Credit user wallet
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||||
fmt.Sprintf("Added %v to wallet for winning Popok Tournament", req.Amount))
|
fmt.Sprintf("Added %v to wallet for winning Popok Tournament", req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err)
|
s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err)
|
||||||
|
|
@ -419,15 +429,15 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Fetch updated balance
|
// 6. Fetch updated balance
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to get wallet balance")
|
// return nil, fmt.Errorf("Failed to get wallet balance")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return &domain.PopOKWinResponse{
|
return &domain.PopOKWinResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
||||||
Balance: float64(wallets[0].Balance),
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,6 +448,11 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
|
||||||
return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read user wallets")
|
||||||
|
}
|
||||||
|
|
||||||
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to check existing promo transaction", "error", err)
|
s.logger.Error("Failed to check existing promo transaction", "error", err)
|
||||||
|
|
@ -445,20 +460,20 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
|
||||||
}
|
}
|
||||||
if existingTx != nil && existingTx.TransactionType == "PROMO_WIN" {
|
if existingTx != nil && existingTx.TransactionType == "PROMO_WIN" {
|
||||||
s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID)
|
s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID)
|
||||||
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
balance := 0.0
|
// balance := 0.0
|
||||||
if len(wallets) > 0 {
|
// if len(wallets) > 0 {
|
||||||
balance = float64(wallets[0].Balance)
|
// balance = float64(wallets[0].Balance)
|
||||||
}
|
// }
|
||||||
return &domain.PopOKWinResponse{
|
return &domain.PopOKWinResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
|
||||||
Balance: balance,
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
amountCents := int64(req.Amount * 100)
|
// amountCents := int64(req.Amount * 100)
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents), domain.ValidInt64{},
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{},
|
||||||
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet for winning PopOk Promo Win", req.Amount))
|
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet for winning PopOk Promo Win", req.Amount))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err)
|
s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err)
|
||||||
|
|
@ -468,7 +483,7 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
|
||||||
tx := &domain.VirtualGameTransaction{
|
tx := &domain.VirtualGameTransaction{
|
||||||
UserID: claims.UserID,
|
UserID: claims.UserID,
|
||||||
TransactionType: "PROMO_WIN",
|
TransactionType: "PROMO_WIN",
|
||||||
Amount: amountCents,
|
Amount: int64(wallet.RegularBalance),
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
ExternalTransactionID: req.TransactionID,
|
ExternalTransactionID: req.TransactionID,
|
||||||
Status: "COMPLETED",
|
Status: "COMPLETED",
|
||||||
|
|
@ -480,15 +495,15 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
|
||||||
return nil, fmt.Errorf("transaction recording failed")
|
return nil, fmt.Errorf("transaction recording failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read wallets")
|
// return nil, fmt.Errorf("failed to read wallets")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return &domain.PopOKWinResponse{
|
return &domain.PopOKWinResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
|
||||||
Balance: float64(wallets[0].Balance),
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -535,6 +550,11 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
|
||||||
// return nil, fmt.Errorf("invalid token")
|
// return nil, fmt.Errorf("invalid token")
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read user wallets")
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Find the original bet transaction
|
// 2. Find the original bet transaction
|
||||||
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -551,21 +571,21 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
|
||||||
// 4. Check if already cancelled
|
// 4. Check if already cancelled
|
||||||
if originalBet.Status == "CANCELLED" {
|
if originalBet.Status == "CANCELLED" {
|
||||||
s.logger.Warn("Transaction already cancelled", "transactionID", req.TransactionID)
|
s.logger.Warn("Transaction already cancelled", "transactionID", req.TransactionID)
|
||||||
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
balance := 0.0
|
// balance := 0.0
|
||||||
if len(wallets) > 0 {
|
// if len(wallets) > 0 {
|
||||||
balance = float64(wallets[0].Balance)
|
// balance = float64(wallets[0].Balance)
|
||||||
}
|
// }
|
||||||
return &domain.PopOKCancelResponse{
|
return &domain.PopOKCancelResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", originalBet.ID),
|
ExternalTrxID: fmt.Sprintf("%v", originalBet.ID),
|
||||||
Balance: balance,
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Refund the bet amount (absolute value since bet amount is negative)
|
// 5. Refund the bet amount (absolute value since bet amount is negative)
|
||||||
refundAmount := -originalBet.Amount
|
refundAmount := -originalBet.Amount
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||||
fmt.Sprintf("Added %v to wallet as refund for cancelling PopOk bet", refundAmount),
|
fmt.Sprintf("Added %v to wallet as refund for cancelling PopOk bet", refundAmount),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -573,10 +593,10 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
|
||||||
return nil, fmt.Errorf("refund failed")
|
return nil, fmt.Errorf("refund failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
// userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return &domain.PopOKCancelResponse{}, fmt.Errorf("Failed to read user wallets")
|
// return &domain.PopOKCancelResponse{}, fmt.Errorf("Failed to read user wallets")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 6. Mark original bet as cancelled and create cancel record
|
// 6. Mark original bet as cancelled and create cancel record
|
||||||
cancelTx := &domain.VirtualGameTransaction{
|
cancelTx := &domain.VirtualGameTransaction{
|
||||||
|
|
@ -615,7 +635,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
|
||||||
return &domain.PopOKCancelResponse{
|
return &domain.PopOKCancelResponse{
|
||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID),
|
ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID),
|
||||||
Balance: float64(userWallets[0].Balance),
|
Balance: float64(wallet.RegularBalance),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -657,10 +677,6 @@ func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
|
||||||
return expected == callback.Signature
|
return expected == callback.Signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
|
||||||
// return s.repo.GetGameCounts(ctx, filter)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) {
|
func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) {
|
||||||
now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss
|
now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ func (c *Client) generateSignature(params map[string]any) (string, error) {
|
||||||
|
|
||||||
|
|
||||||
// POST helper
|
// POST helper
|
||||||
func (c *Client) post(ctx context.Context, path string, body any, sigParams map[string]any, result any) error {
|
func (c *Client) Post(ctx context.Context, path string, body any, sigParams map[string]any, result any) error {
|
||||||
data, _ := json.Marshal(body)
|
data, _ := json.Marshal(body)
|
||||||
sig, err := c.generateSignature(sigParams)
|
sig, err := c.generateSignature(sigParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,326 +0,0 @@
|
||||||
package veli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Service) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) {
|
|
||||||
|
|
||||||
logger := s.mongoLogger.With(zap.String("service", "AddProviders"), zap.Any("ProviderRequest", req))
|
|
||||||
|
|
||||||
// 0. Remove all existing providers first
|
|
||||||
if err := s.repo.DeleteAllVirtualGameProviders(ctx); err != nil {
|
|
||||||
logger.Error("failed to delete all virtual game providers", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to clear existing providers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Prepare signature parameters
|
|
||||||
sigParams := map[string]any{
|
|
||||||
"brandId": req.BrandID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional fields
|
|
||||||
sigParams["extraData"] = fmt.Sprintf("%t", req.ExtraData) // false is still included
|
|
||||||
if req.Size > 0 {
|
|
||||||
sigParams["size"] = fmt.Sprintf("%d", req.Size)
|
|
||||||
} else {
|
|
||||||
sigParams["size"] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Page > 0 {
|
|
||||||
sigParams["page"] = fmt.Sprintf("%d", req.Page)
|
|
||||||
} else {
|
|
||||||
sigParams["page"] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Call external API
|
|
||||||
var res domain.ProviderResponse
|
|
||||||
if err := s.client.post(ctx, "/game-lists/public/providers", req, sigParams, &res); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch providers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Loop through fetched providers and insert into DB
|
|
||||||
for _, p := range res.Items {
|
|
||||||
createParams := dbgen.CreateVirtualGameProviderParams{
|
|
||||||
ProviderID: p.ProviderID,
|
|
||||||
ProviderName: p.ProviderName,
|
|
||||||
LogoDark: pgtype.Text{String: p.LogoForDark, Valid: p.LogoForDark != ""},
|
|
||||||
LogoLight: pgtype.Text{String: p.LogoForLight, Valid: p.LogoForLight != ""},
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.repo.CreateVirtualGameProvider(ctx, createParams); err != nil {
|
|
||||||
logger.Error("failed to add provider", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to add provider %s: %w", p.ProviderID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Always add "popok" provider manually
|
|
||||||
popokParams := dbgen.CreateVirtualGameProviderParams{
|
|
||||||
ProviderID: "popok",
|
|
||||||
ProviderName: "Popok Gaming",
|
|
||||||
LogoDark: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-dark.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
|
|
||||||
LogoLight: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-light.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
atlasParams := dbgen.CreateVirtualGameProviderParams{
|
|
||||||
ProviderID: "atlas",
|
|
||||||
ProviderName: "Atlas Gaming",
|
|
||||||
LogoDark: pgtype.Text{String: "/static/logos/atlas-dark.png", Valid: true}, // adjust as needed
|
|
||||||
LogoLight: pgtype.Text{String: "/static/logos/atlas-light.png", Valid: true}, // adjust as needed
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.repo.CreateVirtualGameProvider(ctx, popokParams); err != nil {
|
|
||||||
logger.Error("failed to add popok provider", zap.Any("popokParams", popokParams), zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to add popok provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.repo.CreateVirtualGameProvider(ctx, atlasParams); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to add atlas provider: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally also append it to the response for consistency
|
|
||||||
// res.Items = append(res.Items, domain.VirtualGameProvider{
|
|
||||||
// ProviderID: uuid.New().String(),
|
|
||||||
// ProviderName: "Popok Gaming",
|
|
||||||
// LogoForDark: "/static/logos/popok-dark.png",
|
|
||||||
// LogoForLight: "/static/logos/popok-light.png",
|
|
||||||
// })
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) {
|
|
||||||
// Build params for repo call
|
|
||||||
logger := s.mongoLogger.With(zap.String("service", "GetAllVirtualGames"), zap.Any("params", params))
|
|
||||||
rows, err := s.repo.ListAllVirtualGames(ctx, params)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("[GetAllVirtualGames] Failed to fetch virtual games", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to fetch virtual games: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var allGames []domain.UnifiedGame
|
|
||||||
for _, r := range rows {
|
|
||||||
// --- Convert nullable Rtp to *float64 ---
|
|
||||||
var rtpPtr *float64
|
|
||||||
if r.Rtp.Valid {
|
|
||||||
rtpFloat, err := r.Rtp.Float64Value()
|
|
||||||
if err == nil {
|
|
||||||
rtpPtr = new(float64)
|
|
||||||
*rtpPtr = rtpFloat.Float64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var betsFloat64 []float64
|
|
||||||
for _, bet := range r.Bets {
|
|
||||||
if bet.Valid {
|
|
||||||
betFloat, err := bet.Float64Value()
|
|
||||||
if err == nil {
|
|
||||||
betsFloat64 = append(betsFloat64, betFloat.Float64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allGames = append(allGames, domain.UnifiedGame{
|
|
||||||
GameID: r.GameID,
|
|
||||||
ProviderID: r.ProviderID,
|
|
||||||
Provider: r.ProviderName,
|
|
||||||
Name: r.Name,
|
|
||||||
Category: r.Category.String,
|
|
||||||
DeviceType: r.DeviceType.String,
|
|
||||||
Volatility: r.Volatility.String,
|
|
||||||
RTP: rtpPtr,
|
|
||||||
HasDemo: r.HasDemo.Bool,
|
|
||||||
HasFreeBets: r.HasFreeBets.Bool,
|
|
||||||
Bets: betsFloat64,
|
|
||||||
Thumbnail: r.Thumbnail.String,
|
|
||||||
Status: int(r.Status.Int32), // nullable status
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return allGames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) {
|
|
||||||
logger := s.mongoLogger.With(
|
|
||||||
zap.String("service", "FetchAndStoreAllVirtualGames"),
|
|
||||||
zap.Any("ProviderRequest", req),
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is necessary since the provider is a foreign key
|
|
||||||
_, err := s.AddProviders(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to add providers to database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var allGames []domain.UnifiedGame
|
|
||||||
|
|
||||||
// --- 1. Existing providers (Veli Games) ---
|
|
||||||
providersRes, err := s.GetProviders(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Failed to fetch provider", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to fetch providers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 2. Fetch games for each provider (Veli Games) ---
|
|
||||||
for _, p := range providersRes.Items {
|
|
||||||
games, err := s.GetGames(ctx, domain.GameListRequest{
|
|
||||||
BrandID: s.cfg.VeliGames.BrandID,
|
|
||||||
ProviderID: p.ProviderID,
|
|
||||||
Page: req.Page,
|
|
||||||
Size: req.Size,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to get veli games", zap.String("ProviderID", p.ProviderID), zap.Error(err))
|
|
||||||
continue // skip failing provider but continue others
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range games {
|
|
||||||
unified := domain.UnifiedGame{
|
|
||||||
GameID: g.GameID,
|
|
||||||
ProviderID: g.ProviderID,
|
|
||||||
Provider: p.ProviderName,
|
|
||||||
Name: g.Name,
|
|
||||||
Category: g.Category,
|
|
||||||
DeviceType: g.DeviceType,
|
|
||||||
HasDemo: g.HasDemoMode,
|
|
||||||
HasFreeBets: g.HasFreeBets,
|
|
||||||
}
|
|
||||||
allGames = append(allGames, unified)
|
|
||||||
|
|
||||||
// Save to DB
|
|
||||||
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
|
||||||
GameID: g.GameID,
|
|
||||||
ProviderID: g.ProviderID,
|
|
||||||
Name: g.Name,
|
|
||||||
Category: pgtype.Text{
|
|
||||||
String: g.Category,
|
|
||||||
Valid: g.Category != "",
|
|
||||||
},
|
|
||||||
DeviceType: pgtype.Text{
|
|
||||||
String: g.DeviceType,
|
|
||||||
Valid: g.DeviceType != "",
|
|
||||||
},
|
|
||||||
HasDemo: pgtype.Bool{
|
|
||||||
Bool: g.HasDemoMode,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
HasFreeBets: pgtype.Bool{
|
|
||||||
Bool: g.HasFreeBets,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to create virtual game", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 3. Fetch Atlas-V games ---
|
|
||||||
atlasGames, err := s.GetAtlasVGames(ctx)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to fetch Atlas-V games", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
for _, g := range atlasGames {
|
|
||||||
unified := domain.UnifiedGame{
|
|
||||||
GameID: g.GameID,
|
|
||||||
ProviderID: "atlasv",
|
|
||||||
Provider: "Atlas-V Gaming", // "Atlas-V"
|
|
||||||
Name: g.Name,
|
|
||||||
Category: g.Category, // using Type as Category
|
|
||||||
Thumbnail: g.Thumbnail,
|
|
||||||
HasDemo: true,
|
|
||||||
DemoURL: g.DemoURL,
|
|
||||||
}
|
|
||||||
allGames = append(allGames, unified)
|
|
||||||
|
|
||||||
// Save to DB
|
|
||||||
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
|
||||||
GameID: g.GameID,
|
|
||||||
ProviderID: "atlasv",
|
|
||||||
Name: g.Name,
|
|
||||||
Category: pgtype.Text{
|
|
||||||
String: g.Category,
|
|
||||||
Valid: g.Category != "",
|
|
||||||
},
|
|
||||||
Thumbnail: pgtype.Text{
|
|
||||||
String: g.Thumbnail,
|
|
||||||
Valid: g.Thumbnail != "",
|
|
||||||
},
|
|
||||||
HasDemo: pgtype.Bool{
|
|
||||||
Bool: g.HasDemoMode,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to create Atlas-V virtual game", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 4. Handle PopOK separately ---
|
|
||||||
popokGames, err := s.virtualGameSvc.ListGames(ctx, currency)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to fetch PopOk games", zap.Error(err))
|
|
||||||
return nil, fmt.Errorf("failed to fetch PopOK games: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, g := range popokGames {
|
|
||||||
unified := domain.UnifiedGame{
|
|
||||||
GameID: fmt.Sprintf("%d", g.ID),
|
|
||||||
ProviderID: "popok",
|
|
||||||
Provider: "PopOK",
|
|
||||||
Name: g.GameName,
|
|
||||||
Category: "Crash",
|
|
||||||
Bets: g.Bets,
|
|
||||||
Thumbnail: g.Thumbnail,
|
|
||||||
Status: g.Status,
|
|
||||||
}
|
|
||||||
allGames = append(allGames, unified)
|
|
||||||
|
|
||||||
// Convert []float64 to []pgtype.Numeric
|
|
||||||
var betsNumeric []pgtype.Numeric
|
|
||||||
for _, bet := range g.Bets {
|
|
||||||
var num pgtype.Numeric
|
|
||||||
_ = num.Scan(bet)
|
|
||||||
betsNumeric = append(betsNumeric, num)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to DB
|
|
||||||
_, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{
|
|
||||||
GameID: fmt.Sprintf("%d", g.ID),
|
|
||||||
ProviderID: "popok",
|
|
||||||
Name: g.GameName,
|
|
||||||
Bets: betsNumeric,
|
|
||||||
Thumbnail: pgtype.Text{
|
|
||||||
String: g.Thumbnail,
|
|
||||||
Valid: g.Thumbnail != "",
|
|
||||||
},
|
|
||||||
Status: pgtype.Int4{
|
|
||||||
Int32: int32(g.Status),
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
HasDemo: pgtype.Bool{
|
|
||||||
Bool: true,
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
Category: pgtype.Text{
|
|
||||||
String: "Crash",
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to create PopOK virtual game", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allGames, nil
|
|
||||||
}
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -112,7 +113,7 @@ func (s *Service) GetProviders(ctx context.Context, req domain.ProviderRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res domain.ProviderResponse
|
var res domain.ProviderResponse
|
||||||
err := s.client.post(ctx, "/game-lists/public/providers", req, sigParams, &res)
|
err := s.client.Post(ctx, "/game-lists/public/providers", req, sigParams, &res)
|
||||||
return &res, err
|
return &res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +141,7 @@ func (s *Service) GetGames(ctx context.Context, req domain.GameListRequest) ([]d
|
||||||
var res struct {
|
var res struct {
|
||||||
Items []domain.GameEntity `json:"items"`
|
Items []domain.GameEntity `json:"items"`
|
||||||
}
|
}
|
||||||
if err := s.client.post(ctx, "/game-lists/public/games", req, sigParams, &res); err != nil {
|
if err := s.client.Post(ctx, "/game-lists/public/games", req, sigParams, &res); err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch games for provider %s: %w", req.ProviderID, err)
|
return nil, fmt.Errorf("failed to fetch games for provider %s: %w", req.ProviderID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,17 +169,32 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
|
||||||
"playerId": req.PlayerID,
|
"playerId": req.PlayerID,
|
||||||
"currency": req.Currency,
|
"currency": req.Currency,
|
||||||
"deviceType": req.DeviceType,
|
"deviceType": req.DeviceType,
|
||||||
"country": "US",
|
"country": req.Country,
|
||||||
"ip": req.IP,
|
"ip": req.IP,
|
||||||
"brandId": req.BrandID,
|
"brandId": req.BrandID,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Call external API
|
// 3. Call external API
|
||||||
var res domain.GameStartResponse
|
var res domain.GameStartResponse
|
||||||
if err := s.client.post(ctx, "/unified-api/public/start-game", req, sigParams, &res); err != nil {
|
if err := s.client.Post(ctx, "/unified-api/public/start-game", req, sigParams, &res); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err)
|
return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid PlayerID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &domain.VirtualGameSession{
|
||||||
|
UserID: playerIDInt64,
|
||||||
|
GameID: req.GameID,
|
||||||
|
SessionToken: uuid.NewString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create virtual game session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,7 +222,7 @@ func (s *Service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest)
|
||||||
|
|
||||||
// 3. Call external API
|
// 3. Call external API
|
||||||
var res domain.GameStartResponse
|
var res domain.GameStartResponse
|
||||||
if err := s.client.post(ctx, "/unified-api/public/start-demo-game", req, sigParams, &res); err != nil {
|
if err := s.client.Post(ctx, "/unified-api/public/start-demo-game", req, sigParams, &res); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start demo game with provider %s: %w", req.ProviderID, err)
|
return nil, fmt.Errorf("failed to start demo game with provider %s: %w", req.ProviderID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,23 +235,24 @@ func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid PlayerID: %w", err)
|
return nil, fmt.Errorf("invalid PlayerID: %w", err)
|
||||||
}
|
}
|
||||||
playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
// playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to get real balance: %w", err)
|
||||||
|
// }
|
||||||
|
// if len(playerWallets) == 0 {
|
||||||
|
// return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallet found for player %s", req.PlayerID)
|
||||||
|
// }
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get real balance: %w", err)
|
return nil, fmt.Errorf("failed to read user wallets")
|
||||||
}
|
|
||||||
if len(playerWallets) == 0 {
|
|
||||||
return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallet found for player %s", req.PlayerID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
realBalance := playerWallets[0].Balance
|
// realBalance := playerWallets[0].Balance
|
||||||
|
|
||||||
// Retrieve bonus balance if applicable
|
// Retrieve bonus balance if applicable
|
||||||
var bonusBalance float64
|
// var bonusBalance float64
|
||||||
if len(playerWallets) > 1 {
|
// bonusBalance := float64(wallet.StaticBalance)
|
||||||
bonusBalance = float64(playerWallets[1].Balance)
|
|
||||||
} else {
|
|
||||||
bonusBalance = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the response
|
// Build the response
|
||||||
res := &domain.BalanceResponse{
|
res := &domain.BalanceResponse{
|
||||||
|
|
@ -244,19 +261,19 @@ func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
}{
|
}{
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
Amount: float64(realBalance),
|
Amount: float64(wallet.RegularBalance),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if bonusBalance > 0 {
|
// if bonusBalance > 0 {
|
||||||
res.Bonus = &struct {
|
// res.Bonus = &struct {
|
||||||
Currency string `json:"currency"`
|
// Currency string `json:"currency"`
|
||||||
Amount float64 `json:"amount"`
|
// Amount float64 `json:"amount"`
|
||||||
}{
|
// }{
|
||||||
Currency: req.Currency,
|
// Currency: req.Currency,
|
||||||
Amount: bonusBalance,
|
// Amount: bonusBalance,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
@ -281,91 +298,64 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// --- 3. Get player wallets ---
|
// --- 3. Get player wallets ---
|
||||||
playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
// playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to get real balance: %w", err)
|
||||||
|
// }
|
||||||
|
// if len(playerWallets) == 0 {
|
||||||
|
// return nil, fmt.Errorf("no wallets found for player %s", req.PlayerID)
|
||||||
|
// }
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get real balance: %w", err)
|
return nil, fmt.Errorf("failed to read user wallets")
|
||||||
}
|
|
||||||
if len(playerWallets) == 0 {
|
|
||||||
return nil, fmt.Errorf("no wallets found for player %s", req.PlayerID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
realWallet := playerWallets[0]
|
// realWallet := playerWallets[0]
|
||||||
realBalance := float64(realWallet.Balance)
|
// realBalance := float64(realWallet.Balance)
|
||||||
|
|
||||||
var bonusBalance float64
|
// var bonusBalance float64
|
||||||
if len(playerWallets) > 1 {
|
// if len(playerWallets) > 1 {
|
||||||
bonusBalance = float64(playerWallets[1].Balance)
|
// bonusBalance = float64(playerWallets[1].Balance)
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
bonusBalance := float64(wallet.StaticBalance)
|
||||||
|
|
||||||
// --- 4. Check sufficient balance ---
|
// --- 4. Check sufficient balance ---
|
||||||
totalBalance := realBalance + bonusBalance
|
// totalBalance := float64(wallet.RegularBalance) + bonusBalance
|
||||||
if totalBalance < req.Amount.Amount {
|
if float64(wallet.RegularBalance) < req.Amount.Amount {
|
||||||
return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
|
return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 5. Deduct funds (bonus first, then real) ---
|
// --- 5. Deduct funds (bonus first, then real) ---
|
||||||
remaining := req.Amount.Amount
|
remaining := req.Amount.Amount
|
||||||
var usedBonus, usedReal float64
|
// var usedBonus, usedReal float64
|
||||||
|
|
||||||
if bonusBalance > 0 {
|
if remaining > float64(wallet.RegularBalance) {
|
||||||
if bonusBalance >= remaining {
|
|
||||||
// fully cover from bonus
|
|
||||||
usedBonus = remaining
|
|
||||||
bonusBalance -= remaining
|
|
||||||
remaining = 0
|
|
||||||
} else {
|
|
||||||
// partially cover from bonus
|
|
||||||
usedBonus = bonusBalance
|
|
||||||
remaining -= bonusBalance
|
|
||||||
bonusBalance = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if remaining > 0 {
|
|
||||||
if realBalance >= remaining {
|
|
||||||
usedReal = remaining
|
|
||||||
realBalance -= remaining
|
|
||||||
remaining = 0
|
|
||||||
} else {
|
|
||||||
// should never happen because of totalBalance check
|
|
||||||
return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
|
return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// --- 6. Persist wallet deductions ---
|
// --- 6. Persist wallet deductions ---
|
||||||
if usedBonus > 0 && len(playerWallets) > 1 {
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(ctx, playerWallets[1].ID,
|
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID,
|
||||||
domain.Currency(usedBonus),
|
domain.Currency(req.Amount.Amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_DIRECT,
|
domain.TRANSFER_DIRECT,
|
||||||
fmt.Sprintf("Deduct bonus %.2f for bet %s", usedBonus, req.TransactionID),
|
fmt.Sprintf("Deduct amount %.2f for bet %s", req.Amount.Amount, req.TransactionID),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("bonus deduction failed: %w", err)
|
return nil, fmt.Errorf("bonus deduction failed: %w", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if usedReal > 0 {
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(ctx, realWallet.ID,
|
|
||||||
domain.Currency(usedReal),
|
|
||||||
domain.ValidInt64{},
|
|
||||||
domain.TRANSFER_DIRECT,
|
|
||||||
fmt.Sprintf("Deduct real %.2f for bet %s", usedReal, req.TransactionID),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("real deduction failed: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 7. Build response ---
|
// --- 7. Build response ---
|
||||||
res := &domain.BetResponse{
|
res := &domain.BetResponse{
|
||||||
Real: domain.BalanceDetail{
|
Real: domain.BalanceDetail{
|
||||||
Currency: "ETB",
|
Currency: "ETB",
|
||||||
Amount: realBalance,
|
Amount: float64(wallet.RegularBalance),
|
||||||
},
|
},
|
||||||
WalletTransactionID: req.TransactionID,
|
WalletTransactionID: req.TransactionID,
|
||||||
UsedRealAmount: usedReal,
|
UsedRealAmount: req.Amount.Amount,
|
||||||
UsedBonusAmount: usedBonus,
|
UsedBonusAmount: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if bonusBalance > 0 {
|
if bonusBalance > 0 {
|
||||||
|
|
@ -386,21 +376,19 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Get player wallets ---
|
// --- 2. Get player wallets ---
|
||||||
playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get wallets: %w", err)
|
return nil, fmt.Errorf("failed to read user wallets")
|
||||||
}
|
|
||||||
if len(playerWallets) == 0 {
|
|
||||||
return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallets for player %s", req.PlayerID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
realWallet := playerWallets[0]
|
// realWallet := playerWallets[0]
|
||||||
realBalance := float64(realWallet.Balance)
|
realBalance := float64(wallet.RegularBalance)
|
||||||
|
|
||||||
var bonusBalance float64
|
// var bonusBalance float64
|
||||||
if len(playerWallets) > 1 {
|
// if len(playerWallets) > 1 {
|
||||||
bonusBalance = float64(playerWallets[1].Balance)
|
// bonusBalance = float64(playerWallets[1].Balance)
|
||||||
}
|
// }
|
||||||
|
bonusBalance := float64(wallet.StaticBalance)
|
||||||
|
|
||||||
// --- 3. Apply winnings (for now, everything goes to real wallet) ---
|
// --- 3. Apply winnings (for now, everything goes to real wallet) ---
|
||||||
winAmount := req.Amount.Amount
|
winAmount := req.Amount.Amount
|
||||||
|
|
@ -412,7 +400,7 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(
|
_, err = s.walletSvc.AddToWallet(
|
||||||
ctx,
|
ctx,
|
||||||
realWallet.ID,
|
wallet.RegularID,
|
||||||
domain.Currency(winAmount),
|
domain.Currency(winAmount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_DIRECT,
|
domain.TRANSFER_DIRECT,
|
||||||
|
|
@ -423,18 +411,18 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
|
||||||
return nil, fmt.Errorf("failed to credit real wallet: %w", err)
|
return nil, fmt.Errorf("failed to credit real wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 4. Reload balances after credit ---
|
// // --- 4. Reload balances after credit ---
|
||||||
updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
// updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, fmt.Errorf("failed to reload balances: %w", err)
|
// return nil, fmt.Errorf("failed to reload balances: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
updatedReal := updatedWallets[0]
|
// updatedReal := updatedWallets[0]
|
||||||
realBalance = float64(updatedReal.Balance)
|
// realBalance = float64(wallet.RegularBalance)
|
||||||
|
|
||||||
if len(updatedWallets) > 1 {
|
// if len(updatedWallets) > 1 {
|
||||||
bonusBalance = float64(updatedWallets[1].Balance)
|
// bonusBalance = float64(updatedWallets[1].Balance)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// --- 5. Build response ---
|
// --- 5. Build response ---
|
||||||
res := &domain.WinResponse{
|
res := &domain.WinResponse{
|
||||||
|
|
@ -465,21 +453,18 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Get player wallets ---
|
// --- 2. Get player wallets ---
|
||||||
playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get wallets: %w", err)
|
return nil, fmt.Errorf("failed to read user wallets")
|
||||||
}
|
|
||||||
if len(playerWallets) == 0 {
|
|
||||||
return nil, fmt.Errorf("no wallets for player %s", req.PlayerID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
realWallet := playerWallets[0]
|
// realWallet := playerWallets[0]
|
||||||
realBalance := float64(realWallet.Balance)
|
realBalance := float64(wallet.RegularBalance)
|
||||||
|
|
||||||
var bonusBalance float64
|
// var bonusBalance float64
|
||||||
if len(playerWallets) > 1 {
|
// if len(playerWallets) > 1 {
|
||||||
bonusBalance = float64(playerWallets[1].Balance)
|
bonusBalance := float64(wallet.StaticBalance)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// --- 3. Determine refund amount based on IsAdjustment ---
|
// --- 3. Determine refund amount based on IsAdjustment ---
|
||||||
var refundAmount float64
|
var refundAmount float64
|
||||||
|
|
@ -503,7 +488,7 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
|
||||||
|
|
||||||
_, err = s.walletSvc.AddToWallet(
|
_, err = s.walletSvc.AddToWallet(
|
||||||
ctx,
|
ctx,
|
||||||
realWallet.ID,
|
wallet.RegularID,
|
||||||
domain.Currency(refundAmount),
|
domain.Currency(refundAmount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_DIRECT,
|
domain.TRANSFER_DIRECT,
|
||||||
|
|
@ -521,23 +506,23 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 5. Reload balances after refund ---
|
// --- 5. Reload balances after refund ---
|
||||||
updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
// updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, fmt.Errorf("failed to reload balances: %w", err)
|
// return nil, fmt.Errorf("failed to reload balances: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
updatedReal := updatedWallets[0]
|
// updatedReal := updatedWallets[0]
|
||||||
realBalance = float64(updatedReal.Balance)
|
// realBalance = float64(wallet.RegularBalance)
|
||||||
|
|
||||||
if len(updatedWallets) > 1 {
|
// if len(updatedWallets) > 1 {
|
||||||
bonusBalance = float64(updatedWallets[1].Balance)
|
// bonusBalance = float64(updatedWallets[1].Balance)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// --- 6. Build response ---
|
// --- 6. Build response ---
|
||||||
res := &domain.CancelResponse{
|
res := &domain.CancelResponse{
|
||||||
WalletTransactionID: req.TransactionID,
|
WalletTransactionID: req.TransactionID,
|
||||||
Real: domain.BalanceDetail{
|
Real: domain.BalanceDetail{
|
||||||
Currency: "ETB",
|
Currency: req.AdjustmentRefund.Currency,
|
||||||
Amount: realBalance,
|
Amount: realBalance,
|
||||||
},
|
},
|
||||||
UsedRealAmount: usedReal,
|
UsedRealAmount: usedReal,
|
||||||
|
|
@ -546,7 +531,7 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
|
||||||
|
|
||||||
if bonusBalance > 0 {
|
if bonusBalance > 0 {
|
||||||
res.Bonus = &domain.BalanceDetail{
|
res.Bonus = &domain.BalanceDetail{
|
||||||
Currency: "ETB",
|
Currency: req.AdjustmentRefund.Currency,
|
||||||
Amount: bonusBalance,
|
Amount: bonusBalance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -554,12 +539,6 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example helper to fetch original bet
|
|
||||||
// func (s *Service) getOriginalBet(ctx context.Context, transactionID string) (*domain.BetRecord, error) {
|
|
||||||
// // TODO: implement actual lookup
|
|
||||||
// return &domain.BetRecord{Amount: 50}, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (s *Service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) {
|
func (s *Service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) {
|
||||||
// --- Signature Params (flattened strings for signing) ---
|
// --- Signature Params (flattened strings for signing) ---
|
||||||
sigParams := map[string]any{
|
sigParams := map[string]any{
|
||||||
|
|
@ -599,7 +578,7 @@ func (s *Service) GetGamingActivity(ctx context.Context, req domain.GamingActivi
|
||||||
|
|
||||||
// --- Actual API Call ---
|
// --- Actual API Call ---
|
||||||
var res domain.GamingActivityResponse
|
var res domain.GamingActivityResponse
|
||||||
err := s.client.post(ctx, "/report-api/public/gaming-activity", req, sigParams, &res)
|
err := s.client.Post(ctx, "/report-api/public/gaming-activity", req, sigParams, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -640,7 +619,7 @@ func (s *Service) GetHugeWins(ctx context.Context, req domain.HugeWinsRequest) (
|
||||||
|
|
||||||
// --- Actual API Call ---
|
// --- Actual API Call ---
|
||||||
var res domain.HugeWinsResponse
|
var res domain.HugeWinsResponse
|
||||||
err := s.client.post(ctx, "/report-api/public/gaming-activity/huge-wins", req, sigParams, &res)
|
err := s.client.Post(ctx, "/report-api/public/gaming-activity/huge-wins", req, sigParams, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -663,7 +642,7 @@ func (s *Service) GetCreditBalances(ctx context.Context, brandID string) ([]doma
|
||||||
Credits []domain.CreditBalance `json:"credits"`
|
Credits []domain.CreditBalance `json:"credits"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.client.post(ctx, "/report-api/public/credit/balances", body, nil, &res); err != nil {
|
if err := s.client.Post(ctx, "/report-api/public/credit/balances", body, nil, &res); err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch credit balances: %w", err)
|
return nil, fmt.Errorf("failed to fetch credit balances: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/orchestration"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
|
|
@ -49,7 +50,8 @@ import (
|
||||||
type App struct {
|
type App struct {
|
||||||
enetPulseSvc *enetpulse.Service
|
enetPulseSvc *enetpulse.Service
|
||||||
atlasVirtualGameService atlas.AtlasVirtualGameService
|
atlasVirtualGameService atlas.AtlasVirtualGameService
|
||||||
veliVirtualGameService veli.VeliVirtualGameService
|
veliVirtualGameService *veli.Service
|
||||||
|
orchestrationSvc *orchestration.Service
|
||||||
telebirrSvc *telebirr.TelebirrService
|
telebirrSvc *telebirr.TelebirrService
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
santimpaySvc *santimpay.SantimPayService
|
santimpaySvc *santimpay.SantimPayService
|
||||||
|
|
@ -92,7 +94,8 @@ type App struct {
|
||||||
func NewApp(
|
func NewApp(
|
||||||
enetPulseSvc *enetpulse.Service,
|
enetPulseSvc *enetpulse.Service,
|
||||||
atlasVirtualGameService atlas.AtlasVirtualGameService,
|
atlasVirtualGameService atlas.AtlasVirtualGameService,
|
||||||
veliVirtualGameService veli.VeliVirtualGameService,
|
veliVirtualGameService *veli.Service,
|
||||||
|
orchestrationSvc *orchestration.Service,
|
||||||
telebirrSvc *telebirr.TelebirrService,
|
telebirrSvc *telebirr.TelebirrService,
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
santimpaySvc *santimpay.SantimPayService,
|
santimpaySvc *santimpay.SantimPayService,
|
||||||
|
|
@ -149,6 +152,7 @@ func NewApp(
|
||||||
enetPulseSvc: enetPulseSvc,
|
enetPulseSvc: enetPulseSvc,
|
||||||
atlasVirtualGameService: atlasVirtualGameService,
|
atlasVirtualGameService: atlasVirtualGameService,
|
||||||
veliVirtualGameService: veliVirtualGameService,
|
veliVirtualGameService: veliVirtualGameService,
|
||||||
|
orchestrationSvc: orchestrationSvc,
|
||||||
telebirrSvc: telebirrSvc,
|
telebirrSvc: telebirrSvc,
|
||||||
arifpaySvc: arifpaySvc,
|
arifpaySvc: arifpaySvc,
|
||||||
santimpaySvc: santimpaySvc,
|
santimpaySvc: santimpaySvc,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
resultsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/stats"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/stats"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/orchestration"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
@ -347,21 +347,18 @@ func StartReportCrons(reportService report.ReportService, mongoLogger *zap.Logge
|
||||||
func SetupReportandVirtualGameCronJobs(
|
func SetupReportandVirtualGameCronJobs(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
reportService report.ReportService,
|
reportService report.ReportService,
|
||||||
virtualGameService *veli.Service, // inject your virtual game service
|
virtualGameOrchestrationService *orchestration.Service,
|
||||||
outputDir string,
|
outputDir string,
|
||||||
) {
|
) {
|
||||||
c := cron.New(cron.WithSeconds()) // use WithSeconds for testing
|
c := cron.New(cron.WithSeconds()) // WithSeconds for testing, remove in prod
|
||||||
|
|
||||||
schedule := []struct {
|
schedule := []struct {
|
||||||
spec string
|
spec string
|
||||||
period string
|
period string
|
||||||
}{
|
}{
|
||||||
// {
|
// { spec: "*/60 * * * * *", period: "test" }, // every 60 seconds for testing
|
||||||
// spec: "*/60 * * * * *", // Every 1 minute for testing
|
|
||||||
// period: "test",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
spec: "0 0 0 * * *", // Daily at midnight
|
spec: "0 0 0 * * *", // daily at midnight
|
||||||
period: "daily",
|
period: "daily",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -370,7 +367,7 @@ func SetupReportandVirtualGameCronJobs(
|
||||||
period := job.period
|
period := job.period
|
||||||
|
|
||||||
if _, err := c.AddFunc(job.spec, func() {
|
if _, err := c.AddFunc(job.spec, func() {
|
||||||
log.Printf("[%s] Running virtual game fetch & store job...", period)
|
log.Printf("[%s] Running virtual game & provider report job...", period)
|
||||||
|
|
||||||
brandID := os.Getenv("VELI_BRAND_ID")
|
brandID := os.Getenv("VELI_BRAND_ID")
|
||||||
if brandID == "" {
|
if brandID == "" {
|
||||||
|
|
@ -378,6 +375,7 @@ func SetupReportandVirtualGameCronJobs(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 1. Fetch and store all virtual games
|
||||||
req := domain.ProviderRequest{
|
req := domain.ProviderRequest{
|
||||||
BrandID: brandID,
|
BrandID: brandID,
|
||||||
ExtraData: true,
|
ExtraData: true,
|
||||||
|
|
@ -385,7 +383,7 @@ func SetupReportandVirtualGameCronJobs(
|
||||||
Page: 1,
|
Page: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
allGames, err := virtualGameService.FetchAndStoreAllVirtualGames(ctx, req, "ETB")
|
allGames, err := virtualGameOrchestrationService.FetchAndStoreAllVirtualGames(ctx, req, "ETB")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] Error fetching/storing virtual games: %v", period, err)
|
log.Printf("[%s] Error fetching/storing virtual games: %v", period, err)
|
||||||
return
|
return
|
||||||
|
|
@ -393,19 +391,42 @@ func SetupReportandVirtualGameCronJobs(
|
||||||
|
|
||||||
log.Printf("[%s] Successfully fetched & stored %d virtual games", period, len(allGames))
|
log.Printf("[%s] Successfully fetched & stored %d virtual games", period, len(allGames))
|
||||||
|
|
||||||
// --- Generate reports only for daily runs ---
|
// Step 2. Fetch all providers
|
||||||
// if period == "daily" {
|
providers, total, err := virtualGameOrchestrationService.ListProviders(ctx, 1000, 0)
|
||||||
// now := time.Now()
|
if err != nil {
|
||||||
// from := time.Date(now.Year(), now.Month(), now.Day()-1, 0, 0, 0, 0, now.Location())
|
log.Printf("[%s] Failed to list providers: %v", period, err)
|
||||||
// to := time.Date(now.Year(), now.Month(), now.Day()-1, 23, 59, 59, 0, now.Location())
|
return
|
||||||
|
} else if total == 0 {
|
||||||
|
log.Printf("[%s] No providers found, skipping report generation", period)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[%s] Found %d total providers", period, total)
|
||||||
|
|
||||||
|
// Step 3. Create provider-level daily report entries
|
||||||
|
reportDate := time.Now().UTC().Truncate(24 * time.Hour)
|
||||||
|
for _, p := range providers {
|
||||||
|
createReq := domain.CreateVirtualGameProviderReport{
|
||||||
|
ProviderID: p.ProviderID,
|
||||||
|
ReportDate: reportDate,
|
||||||
|
TotalGamesPlayed: 0,
|
||||||
|
TotalBets: 0,
|
||||||
|
TotalPayouts: 0,
|
||||||
|
TotalPlayers: 0,
|
||||||
|
ReportType: period, // "daily"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := virtualGameOrchestrationService.CreateVirtualGameProviderReport(ctx, createReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[%s] Failed to create report for provider %s: %v", period, p.ProviderID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[%s] Created daily report row for provider: %s", period, p.ProviderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[%s] Daily provider reports created successfully", period)
|
||||||
|
|
||||||
// log.Printf("Running daily report for period %s -> %s", from.Format(time.RFC3339), to.Format(time.RFC3339))
|
|
||||||
// if err := reportService.GenerateReport(ctx, from, to); err != nil {
|
|
||||||
// log.Printf("Error generating daily report: %v", err)
|
|
||||||
// } else {
|
|
||||||
// log.Printf("Successfully generated daily report")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
|
log.Fatalf("Failed to schedule %s cron job: %v", period, err)
|
||||||
}
|
}
|
||||||
|
|
@ -453,60 +474,89 @@ func StartEnetPulseCron(enetPulseSvc *enetpulse.Service, mongoLogger *zap.Logger
|
||||||
task func()
|
task func()
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
spec: "0 0,10,20,30,40,50 * * * *", // Every 10 minutes
|
spec: "0 0 */2 * * *", // Every 2 hours
|
||||||
task: func() {
|
task: func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// 1️⃣ Sports
|
||||||
mongoLogger.Info("Began fetching and storing sports cron task")
|
mongoLogger.Info("Began fetching and storing sports cron task")
|
||||||
if err := enetPulseSvc.FetchAndStoreSports(context.Background()); err != nil {
|
if err := enetPulseSvc.FetchAndStoreSports(ctx); err != nil {
|
||||||
mongoLogger.Error("Failed to fetch and store sports",
|
mongoLogger.Error("Failed to fetch and store sports", zap.Error(err))
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
mongoLogger.Info("Completed fetching and storing sports without errors")
|
mongoLogger.Info("\n\n✅ Completed fetching and storing sports\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Tournament Templates
|
||||||
mongoLogger.Info("Began fetching and storing tournament templates cron task")
|
mongoLogger.Info("Began fetching and storing tournament templates cron task")
|
||||||
if err := enetPulseSvc.FetchAndStoreTournamentTemplates(context.Background()); err != nil {
|
if err := enetPulseSvc.FetchAndStoreTournamentTemplates(ctx); err != nil {
|
||||||
mongoLogger.Error("Failed to fetch and store tournament templates",
|
mongoLogger.Error("Failed to fetch and store tournament templates", zap.Error(err))
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
mongoLogger.Info("Completed fetching and storing tournament templates without errors")
|
mongoLogger.Info("\n\n✅ Completed fetching and storing tournament templates\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3️⃣ Tournaments
|
||||||
mongoLogger.Info("Began fetching and storing tournaments cron task")
|
mongoLogger.Info("Began fetching and storing tournaments cron task")
|
||||||
if err := enetPulseSvc.FetchAndStoreTournaments(context.Background()); err != nil {
|
if err := enetPulseSvc.FetchAndStoreTournaments(ctx); err != nil {
|
||||||
mongoLogger.Error("Failed to fetch and store tournaments",
|
mongoLogger.Error("Failed to fetch and store tournaments", zap.Error(err))
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
mongoLogger.Info("Completed fetching and storing tournaments without errors")
|
mongoLogger.Info("\n\n✅ Completed fetching and storing tournaments\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
mongoLogger.Info("Began fetching and storing tournament stages cron task")
|
// 4️⃣ Tournament Stages
|
||||||
if err := enetPulseSvc.FetchAndStoreTournamentStages(context.Background()); err != nil {
|
// mongoLogger.Info("Began fetching and storing tournament stages cron task")
|
||||||
mongoLogger.Error("Failed to fetch and store tournament stages",
|
// if err := enetPulseSvc.FetchAndStoreTournamentStages(ctx); err != nil {
|
||||||
zap.Error(err),
|
// mongoLogger.Error("Failed to fetch and store tournament stages", zap.Error(err))
|
||||||
)
|
// } else {
|
||||||
|
// mongoLogger.Info("✅ \n\nCompleted fetching and storing tournament stages\n\n")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 5️⃣ Fixtures
|
||||||
|
mongoLogger.Info("Began fetching and storing fixtures cron task")
|
||||||
|
today := time.Now().Format("2006-01-02")
|
||||||
|
if err := enetPulseSvc.FetchAndStoreFixtures(ctx, today); err != nil {
|
||||||
|
mongoLogger.Error("Failed to fetch and store fixtures", zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
mongoLogger.Info("Completed fetching and storing tournament stages without errors")
|
mongoLogger.Info("\n\n✅ Completed fetching and storing fixtures\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6️⃣ Results
|
||||||
|
// mongoLogger.Info("Began fetching and storing results cron task")
|
||||||
|
// if err := enetPulseSvc.FetchAndStoreResults(ctx); err != nil {
|
||||||
|
// mongoLogger.Error("Failed to fetch and store results", zap.Error(err))
|
||||||
|
// } else {
|
||||||
|
// mongoLogger.Info("\n\n✅ Completed fetching and storing results\n\n")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 7 Outcome Types
|
||||||
|
mongoLogger.Info("Began fetching and storing outcome_types cron task")
|
||||||
|
if err := enetPulseSvc.FetchAndStoreOutcomeTypes(ctx); err != nil {
|
||||||
|
mongoLogger.Error("Failed to fetch and store outcome_types", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
mongoLogger.Info("\n\n✅ Completed fetching and storing outcome_types\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8 Preodds
|
||||||
|
mongoLogger.Info("Began fetching and storing preodds cron task")
|
||||||
|
if err := enetPulseSvc.FetchAndStorePreodds(ctx); err != nil {
|
||||||
|
mongoLogger.Error("Failed to fetch and store preodds", zap.Error(err))
|
||||||
|
} else {
|
||||||
|
mongoLogger.Info("\n\n✅ Completed fetching and storing preodds\n\n")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, job := range schedule {
|
for _, job := range schedule {
|
||||||
// Run the task immediately at startup
|
// Run immediately at startup
|
||||||
job.task()
|
job.task()
|
||||||
|
|
||||||
// Schedule the task
|
// Schedule the task
|
||||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||||
mongoLogger.Error("Failed to schedule EnetPulse cron job",
|
mongoLogger.Error("Failed to schedule EnetPulse cron job", zap.Error(err))
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
log.Println("EnetPulse cron jobs started for sports, tournament templates, tournaments, and tournament stages")
|
log.Println("EnetPulse cron jobs started for sports, tournament templates, tournaments, tournament stages, fixtures, and results")
|
||||||
mongoLogger.Info("EnetPulse cron jobs started for sports, tournament templates, tournaments, and tournament stages")
|
mongoLogger.Info("EnetPulse cron jobs started for sports, tournament templates, tournaments, tournament stages, fixtures, and results")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ import (
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/arifpay/checkout [post]
|
// @Router /api/v1/arifpay/checkout [post]
|
||||||
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
userId, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
|
Error: "missing user id",
|
||||||
|
Message: "Unauthorized",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var req domain.CheckoutSessionClientRequest
|
var req domain.CheckoutSessionClientRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -26,7 +35,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := h.arifpaySvc.CreateCheckoutSession(req, true)
|
data, err := h.arifpaySvc.CreateCheckoutSession(req, true, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -53,7 +62,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
// @Success 200 {object} domain.Response
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/arifpay/checkout/{sessionId}/cancel [post]
|
// @Router /api/v1/arifpay/checkout/cancel/{sessionId} [post]
|
||||||
func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
|
func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
sessionID := c.Params("sessionId")
|
sessionID := c.Params("sessionId")
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
|
|
@ -103,15 +112,15 @@ func (h *Handler) HandleArifpayC2BWebhook(c *fiber.Ctx) error {
|
||||||
// 🚨 Decide how to get userId:
|
// 🚨 Decide how to get userId:
|
||||||
// If you get it from auth context/middleware, extract it here.
|
// If you get it from auth context/middleware, extract it here.
|
||||||
// For now, let's assume userId comes from your auth claims:
|
// For now, let's assume userId comes from your auth claims:
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
// userId, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
Error: "missing user id",
|
// Error: "missing user id",
|
||||||
Message: "Unauthorized",
|
// Message: "Unauthorized",
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, true)
|
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -150,15 +159,15 @@ func (h *Handler) HandleArifpayB2CWebhook(c *fiber.Ctx) error {
|
||||||
// 🚨 Decide how to get userId:
|
// 🚨 Decide how to get userId:
|
||||||
// If you get it from auth context/middleware, extract it here.
|
// If you get it from auth context/middleware, extract it here.
|
||||||
// For now, let's assume userId comes from your auth claims:
|
// For now, let's assume userId comes from your auth claims:
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
// userId, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
Error: "missing user id",
|
// Error: "missing user id",
|
||||||
Message: "Unauthorized",
|
// Message: "Unauthorized",
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, false)
|
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -253,7 +262,7 @@ func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
|
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
|
||||||
// @Param request body domain.ArifpayB2CRequest true "Transfer request payload"
|
// @Param request body domain.CheckoutSessionClientRequest true "Transfer request payload"
|
||||||
// @Success 200 {object} map[string]string "message: transfer executed successfully"
|
// @Success 200 {object} map[string]string "message: transfer executed successfully"
|
||||||
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
|
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
|
||||||
// @Failure 500 {object} map[string]string "error: internal server error"
|
// @Failure 500 {object} map[string]string "error: internal server error"
|
||||||
|
|
@ -275,7 +284,7 @@ func (h *Handler) ExecuteArifpayB2CTransfer(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var req domain.ArifpayB2CRequest
|
var req domain.CheckoutSessionClientRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to process your withdrawal request",
|
Message: "Failed to process your withdrawal request",
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAtlasVGames godoc
|
// GetAtlasVGames godoc
|
||||||
|
|
@ -52,7 +54,7 @@ func (h *Handler) GetAtlasVGames(c *fiber.Ctx) error {
|
||||||
// @Failure 502 {object} domain.ErrorResponse
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/atlas/init-game [post]
|
// @Router /api/v1/atlas/init-game [post]
|
||||||
func (h *Handler) InitAtlasGame(c *fiber.Ctx) error {
|
func (h *Handler) InitAtlasGame(c *fiber.Ctx) error {
|
||||||
// Retrieve user ID from context
|
// 1️⃣ Retrieve user ID from context
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
userId, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -61,6 +63,7 @@ func (h *Handler) InitAtlasGame(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Parse request body
|
||||||
var req domain.AtlasGameInitRequest
|
var req domain.AtlasGameInitRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -69,30 +72,53 @@ func (h *Handler) InitAtlasGame(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach user ID to request
|
// 3️⃣ Attach user ID to request
|
||||||
req.PlayerID = fmt.Sprintf("%d", userId)
|
req.PlayerID = fmt.Sprintf("%d", userId)
|
||||||
|
|
||||||
// Default language if not provided
|
// 4️⃣ Set defaults if not provided
|
||||||
if req.Language == "" {
|
if req.Language == "" {
|
||||||
req.Language = "en"
|
req.Language = "en"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default currency if not provided
|
|
||||||
if req.Currency == "" {
|
if req.Currency == "" {
|
||||||
req.Currency = "USD"
|
req.Currency = "USD"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the service
|
// 5️⃣ Call the Atlas service
|
||||||
res, err := h.atlasVirtualGameSvc.InitGame(context.Background(), req)
|
res, err := h.atlasVirtualGameSvc.InitGame(context.Background(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("InitAtlasGame error:", err)
|
log.Println("InitAtlasGame error:", err)
|
||||||
|
|
||||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to initialize Atlas game",
|
Message: "Failed to initialize Atlas game",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6️⃣ Update provider report: increment total_games_played
|
||||||
|
go func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
reportDate := time.Now().Truncate(24 * time.Hour)
|
||||||
|
reportType := "daily"
|
||||||
|
providerID := "atlas" // all Atlas games belong to this provider
|
||||||
|
|
||||||
|
err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx,
|
||||||
|
providerID,
|
||||||
|
reportDate,
|
||||||
|
reportType,
|
||||||
|
1, // increment total_games_played by 1
|
||||||
|
0, // total_bets (no change)
|
||||||
|
0, // total_payouts (no change)
|
||||||
|
1, // total_players (no change)
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to update total_games_played for Atlas game",
|
||||||
|
zap.String("provider_id", providerID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 7️⃣ Return response to user
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Game initialized successfully",
|
Message: "Game initialized successfully",
|
||||||
Data: res,
|
Data: res,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
@ -39,7 +41,7 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
amount := domain.Currency(req.Amount * 100)
|
amount := domain.Currency(req.Amount)
|
||||||
|
|
||||||
fmt.Println("We are here init Chapa payment")
|
fmt.Println("We are here init Chapa payment")
|
||||||
|
|
||||||
|
|
@ -51,40 +53,6 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// get static wallet of user
|
|
||||||
// wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
|
|
||||||
// if err != nil {
|
|
||||||
// return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
||||||
// Error: err.Error(),
|
|
||||||
// Message: "Failed to initiate Chapa deposit",
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var multiplier float32 = 1
|
|
||||||
// bonusMultiplier, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
|
||||||
// if err == nil {
|
|
||||||
// multiplier = bonusMultiplier[0].Multiplier
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var balanceCap int64 = 0
|
|
||||||
// bonusBalanceCap, err := h.bonusSvc.GetBonusBalanceCap(c.Context())
|
|
||||||
// if err == nil {
|
|
||||||
// balanceCap = bonusBalanceCap[0].BalanceCap
|
|
||||||
// }
|
|
||||||
|
|
||||||
// capedBalanceAmount := domain.Currency((math.Min(req.Amount, float64(balanceCap)) * float64(multiplier)) * 100)
|
|
||||||
|
|
||||||
// _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, capedBalanceAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
|
||||||
// fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", capedBalanceAmount, multiplier),
|
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// h.logger.Error("Failed to add bonus to static wallet", "walletID", wallet.StaticID, "user id", userID, "error", err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err := h.bonusSvc.ProcessWelcomeBonus(c.Context(), domain.ToCurrency(float32(req.Amount)), 0, userID); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Chapa deposit process initiated successfully",
|
Message: "Chapa deposit process initiated successfully",
|
||||||
Data: checkoutURL,
|
Data: checkoutURL,
|
||||||
|
|
@ -95,69 +63,180 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// WebhookCallback godoc
|
// WebhookCallback godoc
|
||||||
// @Summary Chapa payment webhook callback (used by Chapa)
|
// @Summary Chapa payment webhook callback (used by Chapa)
|
||||||
// @Description Handles payment notifications from Chapa
|
// @Description Handles payment and transfer notifications from Chapa
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body domain.ChapaWebhookPayload true "Webhook payload"
|
// @Param request body domain.ChapaWebhookPayment true "Webhook payload"
|
||||||
// @Success 200 {object} map[string]interface{}
|
// @Success 200 {object} domain.Response
|
||||||
// @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/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 {
|
||||||
|
body := c.Body()
|
||||||
|
|
||||||
chapaTransactionType := new(domain.ChapaTransactionType)
|
// Retrieve signature headers
|
||||||
|
chapaSignature := c.Get("chapa-signature")
|
||||||
|
xChapaSignature := c.Get("x-chapa-signature")
|
||||||
|
|
||||||
if parseTypeErr := c.BodyParser(chapaTransactionType); parseTypeErr != nil {
|
// Verify webhook signature
|
||||||
|
valid, err := h.chapaSvc.VerifyWebhookSignature(c.Context(), body, chapaSignature, xChapaSignature)
|
||||||
|
if err != nil || !valid {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid Chapa webhook signature",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing as transfer webhook first
|
||||||
|
var transfer domain.ChapaWebhookTransfer
|
||||||
|
if err := json.Unmarshal(body, &transfer); err == nil &&
|
||||||
|
strings.EqualFold(transfer.Type, "payout") {
|
||||||
|
|
||||||
|
if err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), transfer); err != nil {
|
||||||
|
return domain.UnExpectedErrorResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Message: "Chapa withdrawal webhook processed successfully",
|
||||||
|
// Data: transfer,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try as payment webhook
|
||||||
|
var payment domain.ChapaWebhookPayment
|
||||||
|
if err := json.Unmarshal(body, &payment); err != nil {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch chapaTransactionType.Type {
|
if err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), payment); err != nil {
|
||||||
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
|
||||||
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{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to verify Chapa depposit",
|
Message: "Failed to verify Chapa deposit",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Message: "Chapa deposit transaction verified successfully",
|
Message: "Chapa deposit webhook processed successfully",
|
||||||
Data: chapaTransferVerificationRequest,
|
// Data: payment,
|
||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
case h.Cfg.CHAPA_TRANSFER_TYPE:
|
|
||||||
chapaPaymentVerificationRequest := new(domain.ChapaWebHookPayment)
|
|
||||||
if err := c.BodyParser(chapaPaymentVerificationRequest); err != nil {
|
|
||||||
return domain.UnProcessableEntityResponse(c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.chapaSvc.HandleVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
// CancelDeposit godoc
|
||||||
|
// @Summary Cancel a Chapa deposit transaction
|
||||||
|
// @Description Cancels an active Chapa transaction using its transaction reference
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param tx_ref path string true "Transaction Reference"
|
||||||
|
// @Success 200 {object} domain.ChapaCancelResponse
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transaction/cancel/{tx_ref} [put]
|
||||||
|
func (h *Handler) CancelDeposit(c *fiber.Ctx) error {
|
||||||
|
// Get user ID from context (set by your auth middleware)
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: "invalid user ID",
|
||||||
|
Message: "User ID is required to cancel a deposit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tx_ref from URL path
|
||||||
|
txRef := c.Params("tx_ref")
|
||||||
|
if txRef == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: "missing transaction reference",
|
||||||
|
Message: "Transaction reference is required in the path",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nReceived request to cancel Chapa transaction: %s (User ID: %d)\n\n", txRef, userID)
|
||||||
|
|
||||||
|
// Call the service layer to cancel deposit
|
||||||
|
cancelResp, err := h.chapaSvc.CancelDeposit(c.Context(), userID, txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.UnExpectedErrorResponse(c)
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to cancel Chapa deposit",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transaction cancelled successfully",
|
||||||
|
Data: cancelResp,
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Message: "Chapa withdrawal transaction verified successfully",
|
|
||||||
Data: chapaPaymentVerificationRequest,
|
|
||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a 400 Bad Request if the type does not match any known case
|
// FetchAllTransactions godoc
|
||||||
|
// @Summary Get all Chapa transactions
|
||||||
|
// @Description Retrieves all transactions from Chapa payment gateway
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {array} domain.ChapaTransaction
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transactions [get]
|
||||||
|
func (h *Handler) FetchAllTransactions(c *fiber.Ctx) error {
|
||||||
|
transactions, err := h.chapaSvc.FetchAllTransactions(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to fetch Chapa transactions",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transactions retrieved successfully",
|
||||||
|
Data: transactions,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionEvents godoc
|
||||||
|
// @Summary Fetch transaction events
|
||||||
|
// @Description Retrieve the timeline of events for a specific Chapa transaction
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param ref_id path string true "Transaction Reference"
|
||||||
|
// @Success 200 {array} domain.ChapaTransactionEvent
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transaction/events/{ref_id} [get]
|
||||||
|
func (h *Handler) GetTransactionEvents(c *fiber.Ctx) error {
|
||||||
|
refID := c.Params("ref_id")
|
||||||
|
if refID == "" {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "Invalid Chapa webhook type",
|
Message: "Failed to fetch transaction events",
|
||||||
Error: "Unknown transaction type",
|
Error: "Transaction reference is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := h.chapaSvc.FetchTransactionEvents(c.Context(), refID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch transaction events",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Transaction events fetched successfully",
|
||||||
|
Data: events,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,10 +247,10 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param tx_ref path string true "Transaction Reference"
|
// @Param tx_ref path string true "Transaction Reference"
|
||||||
// @Success 200 {object} domain.ChapaVerificationResponse
|
// @Success 200 {object} domain.Response
|
||||||
// @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/transaction/manual/verify/{tx_ref} [get]
|
||||||
func (h *Handler) ManualVerifyTransaction(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 == "" {
|
||||||
|
|
@ -189,11 +268,11 @@ func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.ChapaVerificationResponse{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Status: string(verification.Status),
|
Message: "Chapa transaction verified successfully",
|
||||||
Amount: verification.Amount,
|
Data: verification,
|
||||||
Currency: verification.Currency,
|
StatusCode: 200,
|
||||||
TxRef: txRef,
|
Success: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +310,7 @@ func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
||||||
// @Success 201 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
// @Success 200 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
||||||
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
||||||
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
||||||
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
||||||
|
|
@ -256,10 +335,151 @@ func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Chapa withdrawal process initiated successfully",
|
Message: "Chapa withdrawal process initiated successfully",
|
||||||
StatusCode: 201,
|
StatusCode: 200,
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: withdrawal,
|
Data: withdrawal,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPaymentReceipt godoc
|
||||||
|
// @Summary Get Chapa Payment Receipt URL
|
||||||
|
// @Description Retrieve the Chapa payment receipt URL using the reference ID
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param chapa_ref path string true "Chapa Reference ID"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/payments/receipt/{chapa_ref} [get]
|
||||||
|
func (h *Handler) GetPaymentReceipt(c *fiber.Ctx) error {
|
||||||
|
chapaRef := c.Params("chapa_ref")
|
||||||
|
if chapaRef == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to get Chapa payment receipt",
|
||||||
|
Error: "Chapa reference ID is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
receiptURL, err := h.chapaSvc.GetPaymentReceiptURL(chapaRef)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to get Chapa payment receipt",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Payment receipt URL generated successfully",
|
||||||
|
Data: receiptURL,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTransfers godoc
|
||||||
|
// @Summary Get all Chapa transfers
|
||||||
|
// @Description Retrieve all transfer records from Chapa
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transfers [get]
|
||||||
|
func (h *Handler) GetAllTransfers(c *fiber.Ctx) error {
|
||||||
|
transfers, err := h.chapaSvc.GetAllTransfers(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch Chapa transfers",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transfers retrieved successfully",
|
||||||
|
Data: transfers,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountBalance godoc
|
||||||
|
// @Summary Get Chapa account balance
|
||||||
|
// @Description Retrieve Chapa account balance, optionally filtered by currency code (e.g., ETB, USD)
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param currency_code query string false "Currency code (optional)"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/balances [get]
|
||||||
|
func (h *Handler) GetAccountBalance(c *fiber.Ctx) error {
|
||||||
|
currencyCode := c.Query("currency_code", "")
|
||||||
|
|
||||||
|
balances, err := h.chapaSvc.GetAccountBalance(c.Context(), currencyCode)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch Chapa account balance",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa account balances retrieved successfully",
|
||||||
|
Data: balances,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapCurrency godoc
|
||||||
|
// @Summary Swap currency using Chapa API
|
||||||
|
// @Description Convert an amount from one currency to another using Chapa's currency swap API
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body domain.SwapRequest true "Swap request payload"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/swap [post]
|
||||||
|
func (h *Handler) SwapCurrency(c *fiber.Ctx) error {
|
||||||
|
var reqBody domain.SwapRequest
|
||||||
|
|
||||||
|
// Parse request body
|
||||||
|
if err := c.BodyParser(&reqBody); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid request payload",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
if reqBody.From == "" || reqBody.To == "" || reqBody.Amount <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Missing or invalid swap parameters",
|
||||||
|
Error: "from, to, and amount are required fields",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call service
|
||||||
|
resp, err := h.chapaSvc.SwapCurrency(c.Context(), reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to perform currency swap",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success response
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Currency swapped successfully",
|
||||||
|
Data: resp,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,67 +9,10 @@ import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPreMatchOdds godoc
|
|
||||||
// @Summary Get pre-match odds for an event
|
|
||||||
// @Description Fetches pre-match odds from EnetPulse for a given event
|
|
||||||
// @Tags EnetPulse - PreMatch
|
|
||||||
// @Accept json
|
|
||||||
// @Produce json
|
|
||||||
// @Param objectFK query int true "Event ID"
|
|
||||||
// @Param oddsProviderFK query []int false "Odds provider IDs (comma separated)"
|
|
||||||
// @Param outcomeTypeFK query int false "Outcome type ID"
|
|
||||||
// @Param outcomeScopeFK query int false "Outcome scope ID"
|
|
||||||
// @Param outcomeSubtypeFK query int false "Outcome subtype ID"
|
|
||||||
// @Param limit query int false "Limit results"
|
|
||||||
// @Param offset query int false "Offset results"
|
|
||||||
// @Param languageTypeFK query int false "Language type ID"
|
|
||||||
// @Success 200 {object} domain.Response{data=domain.PreMatchOddsResponse}
|
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
|
||||||
// @Failure 502 {object} domain.ErrorResponse
|
|
||||||
// @Router /api/v1/odds/pre-match [get]
|
|
||||||
func (h *Handler) GetPreMatchOdds(c *fiber.Ctx) error {
|
|
||||||
// Parse query parameters
|
|
||||||
objectFK := c.QueryInt("objectFK")
|
|
||||||
if objectFK == 0 {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
||||||
Message: "Event ID (objectFK) is required",
|
|
||||||
Error: "missing or invalid objectFK",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
params := domain.PreMatchOddsRequest{
|
|
||||||
ObjectFK: int64(objectFK),
|
|
||||||
OddsProviderFK: intSliceToInt64Slice(parseIntSlice(c.Query("oddsProviderFK"))), // convert []int to []int64
|
|
||||||
OutcomeTypeFK: int64(c.QueryInt("outcomeTypeFK")),
|
|
||||||
OutcomeScopeFK: int64(c.QueryInt("outcomeScopeFK")),
|
|
||||||
OutcomeSubtypeFK: int64(c.QueryInt("outcomeSubtypeFK")),
|
|
||||||
Limit: c.QueryInt("limit"),
|
|
||||||
Offset: c.QueryInt("offset"),
|
|
||||||
LanguageTypeFK: int64(c.QueryInt("languageTypeFK")),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call service
|
|
||||||
res, err := h.enetPulseSvc.FetchPreMatchOdds(c.Context(), params)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("FetchPreMatchOdds error:", err)
|
|
||||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
|
||||||
Message: "Failed to fetch pre-match odds",
|
|
||||||
Error: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
||||||
Message: "Pre-match odds fetched successfully",
|
|
||||||
Data: res,
|
|
||||||
StatusCode: fiber.StatusOK,
|
|
||||||
Success: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllSports godoc
|
// GetAllSports godoc
|
||||||
// @Summary Get all sports
|
// @Summary Get all sports
|
||||||
// @Description Fetches all sports stored in the database
|
// @Description Fetches all sports stored in the database
|
||||||
// @Tags EnetPulse - Sports
|
// @Tags EnetPulse
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseSport}
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseSport}
|
||||||
|
|
@ -97,7 +40,7 @@ func (h *Handler) GetAllSports(c *fiber.Ctx) error {
|
||||||
// GetAllTournamentTemplates godoc
|
// GetAllTournamentTemplates godoc
|
||||||
// @Summary Get all tournament templates
|
// @Summary Get all tournament templates
|
||||||
// @Description Fetches all tournament templates stored in the database
|
// @Description Fetches all tournament templates stored in the database
|
||||||
// @Tags EnetPulse - Tournament Templates
|
// @Tags EnetPulse
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournamentTemplate}
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournamentTemplate}
|
||||||
|
|
@ -125,7 +68,7 @@ func (h *Handler) GetAllTournamentTemplates(c *fiber.Ctx) error {
|
||||||
// GetAllTournaments godoc
|
// GetAllTournaments godoc
|
||||||
// @Summary Get all tournaments
|
// @Summary Get all tournaments
|
||||||
// @Description Fetches all tournaments stored in the database
|
// @Description Fetches all tournaments stored in the database
|
||||||
// @Tags EnetPulse - Tournaments
|
// @Tags EnetPulse
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournament}
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournament}
|
||||||
|
|
@ -153,7 +96,7 @@ func (h *Handler) GetAllTournaments(c *fiber.Ctx) error {
|
||||||
// GetAllTournamentStages godoc
|
// GetAllTournamentStages godoc
|
||||||
// @Summary Get all tournament stages
|
// @Summary Get all tournament stages
|
||||||
// @Description Fetches all tournament stages stored in the database
|
// @Description Fetches all tournament stages stored in the database
|
||||||
// @Tags EnetPulse - Tournament Stages
|
// @Tags EnetPulse
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournamentStage}
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseTournamentStage}
|
||||||
|
|
@ -178,8 +121,177 @@ func (h *Handler) GetAllTournamentStages(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFixturesByDate godoc
|
||||||
|
// @Summary Get all stored fixtures
|
||||||
|
// @Description Fetches all fixtures stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseFixture}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/fixtures [get]
|
||||||
|
func (h *Handler) GetFixturesByDate(c *fiber.Ctx) error {
|
||||||
|
// Call service to get all fixtures from DB
|
||||||
|
fixtures, err := h.enetPulseSvc.GetAllFixtures(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllFixtures error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch fixtures from database",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Fixtures fetched successfully",
|
||||||
|
Data: fixtures,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllResults godoc
|
||||||
|
// @Summary Get all results
|
||||||
|
// @Description Fetches all EnetPulse match results stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseResult}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/results [get]
|
||||||
|
func (h *Handler) GetAllResults(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
results, err := h.enetPulseSvc.GetAllResults(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllResults error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse results",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse results fetched successfully",
|
||||||
|
Data: results,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPreodds godoc
|
||||||
|
// @Summary Get all preodds
|
||||||
|
// @Description Fetches all EnetPulse pre-match odds stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulsePreodds}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/preodds [get]
|
||||||
|
func (h *Handler) GetAllPreodds(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
preodds, err := h.enetPulseSvc.GetAllPreodds(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllPreodds error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse preodds",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse preodds fetched successfully",
|
||||||
|
Data: preodds,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllBettingOffers godoc
|
||||||
|
// @Summary Get all betting offers
|
||||||
|
// @Description Fetches all EnetPulse preodds betting offers stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulsePreoddsBettingOffer}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/betting-offers [get]
|
||||||
|
func (h *Handler) GetAllBettingOffers(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
offers, err := h.enetPulseSvc.GetAllBettingOffers(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllBettingOffers error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse betting offers",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse betting offers fetched successfully",
|
||||||
|
Data: offers,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPreoddsWithBettingOffers godoc
|
||||||
|
// @Summary Get all preodds with betting offers
|
||||||
|
// @Description Fetches all EnetPulse pre-match odds along with their associated betting offers stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulsePreodds}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/preodds-with-offers [get]
|
||||||
|
func (h *Handler) GetAllPreoddsWithBettingOffers(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
preodds, err := h.enetPulseSvc.GetAllPreoddsWithBettingOffers(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllPreoddsWithBettingOffers error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse preodds with betting offers",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse preodds with betting offers fetched successfully",
|
||||||
|
Data: preodds,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFixturesWithPreodds godoc
|
||||||
|
// @Summary Get fixtures with preodds
|
||||||
|
// @Description Fetches all EnetPulse fixtures along with their associated pre-match odds
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulseFixtureWithPreodds}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/fixtures/preodds [get]
|
||||||
|
func (h *Handler) GetFixturesWithPreodds(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
fixtures, err := h.enetPulseSvc.GetFixturesWithPreodds(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetFixturesWithPreodds error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse fixtures with preodds",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return success response
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse fixtures with preodds fetched successfully",
|
||||||
|
Data: fixtures,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: parse comma-separated string into []int
|
// Helper: parse comma-separated string into []int
|
||||||
func parseIntSlice(input string) []int {
|
func ParseIntSlice(input string) []int {
|
||||||
if input == "" {
|
if input == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +306,7 @@ func parseIntSlice(input string) []int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: convert []int to []int64
|
// Helper: convert []int to []int64
|
||||||
func intSliceToInt64Slice(input []int) []int64 {
|
func IntSliceToInt64Slice(input []int) []int64 {
|
||||||
if input == nil {
|
if input == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import (
|
||||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
alea "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/Alea"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/atlas"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/orchestration"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
|
|
@ -42,6 +43,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
orchestrationSvc *orchestration.Service
|
||||||
enetPulseSvc *enetpulse.Service
|
enetPulseSvc *enetpulse.Service
|
||||||
telebirrSvc *telebirr.TelebirrService
|
telebirrSvc *telebirr.TelebirrService
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
|
|
@ -69,7 +71,7 @@ type Handler struct {
|
||||||
leagueSvc *league.Service
|
leagueSvc *league.Service
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
veliVirtualGameSvc *veli.Service
|
||||||
atlasVirtualGameSvc atlas.AtlasVirtualGameService
|
atlasVirtualGameSvc atlas.AtlasVirtualGameService
|
||||||
recommendationSvc recommendation.RecommendationService
|
recommendationSvc recommendation.RecommendationService
|
||||||
authSvc *authentication.Service
|
authSvc *authentication.Service
|
||||||
|
|
@ -82,6 +84,7 @@ type Handler struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
|
orchestrationSvc *orchestration.Service,
|
||||||
enetPulseSvc *enetpulse.Service,
|
enetPulseSvc *enetpulse.Service,
|
||||||
telebirrSvc *telebirr.TelebirrService,
|
telebirrSvc *telebirr.TelebirrService,
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
|
|
@ -101,7 +104,7 @@ func New(
|
||||||
bonusSvc *bonus.Service,
|
bonusSvc *bonus.Service,
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService,
|
aleaVirtualGameSvc alea.AleaVirtualGameService,
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService,
|
veliVirtualGameSvc *veli.Service,
|
||||||
atlasVirtualGameSvc atlas.AtlasVirtualGameService,
|
atlasVirtualGameSvc atlas.AtlasVirtualGameService,
|
||||||
recommendationSvc recommendation.RecommendationService,
|
recommendationSvc recommendation.RecommendationService,
|
||||||
userSvc *user.Service,
|
userSvc *user.Service,
|
||||||
|
|
@ -121,6 +124,7 @@ func New(
|
||||||
mongoLoggerSvc *zap.Logger,
|
mongoLoggerSvc *zap.Logger,
|
||||||
) *Handler {
|
) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
|
orchestrationSvc: orchestrationSvc,
|
||||||
enetPulseSvc: enetPulseSvc,
|
enetPulseSvc: enetPulseSvc,
|
||||||
telebirrSvc: telebirrSvc,
|
telebirrSvc: telebirrSvc,
|
||||||
arifpaySvc: arifpaySvc,
|
arifpaySvc: arifpaySvc,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"time"
|
||||||
|
|
||||||
|
// "fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -120,14 +122,6 @@ func (h *Handler) GetGamesByProvider(c *fiber.Ctx) error {
|
||||||
// @Failure 502 {object} domain.ErrorResponse
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/veli/start-game [post]
|
// @Router /api/v1/veli/start-game [post]
|
||||||
func (h *Handler) StartGame(c *fiber.Ctx) error {
|
func (h *Handler) StartGame(c *fiber.Ctx) error {
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
|
||||||
if !ok {
|
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
|
||||||
Error: "missing user id",
|
|
||||||
Message: "Unauthorized",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var req domain.GameStartRequest
|
var req domain.GameStartRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -136,25 +130,24 @@ func (h *Handler) StartGame(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// There needs to be a way to generate a sessionID
|
|
||||||
|
|
||||||
// Attach user ID to request
|
|
||||||
req.PlayerID = fmt.Sprintf("%d", userId)
|
|
||||||
|
|
||||||
// Default brand if not provided
|
// Default brand if not provided
|
||||||
if req.BrandID == "" {
|
if req.BrandID == "" {
|
||||||
req.BrandID = h.Cfg.VeliGames.BrandID
|
req.BrandID = h.Cfg.VeliGames.BrandID
|
||||||
}
|
}
|
||||||
|
|
||||||
req.IP = c.IP()
|
useId := c.Locals("user_id")
|
||||||
|
|
||||||
|
req.IP = c.IP()
|
||||||
|
req.PlayerID = useId.(string)
|
||||||
|
|
||||||
|
// 1️⃣ Call StartGame service
|
||||||
res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req)
|
res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]StartGame",
|
h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]StartGame",
|
||||||
zap.Any("request", req),
|
zap.Any("request", req),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
// Handle provider disabled case specifically
|
|
||||||
if strings.Contains(err.Error(), "is disabled") {
|
if strings.Contains(err.Error(), "is disabled") {
|
||||||
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
||||||
Message: "Provider is disabled",
|
Message: "Provider is disabled",
|
||||||
|
|
@ -162,13 +155,39 @@ func (h *Handler) StartGame(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for other errors
|
|
||||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to start game",
|
Message: "Failed to start game",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2️⃣ Game started successfully → Update total_games_played
|
||||||
|
go func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
reportDate := time.Now().Truncate(24 * time.Hour)
|
||||||
|
reportType := "daily"
|
||||||
|
|
||||||
|
// Increment total_games_played by 1
|
||||||
|
err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx,
|
||||||
|
req.ProviderID,
|
||||||
|
reportDate,
|
||||||
|
reportType,
|
||||||
|
1, // increment total_games_played by 1
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to update total_games_played",
|
||||||
|
zap.String("provider_id", req.ProviderID),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 3️⃣ Return response to user
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Game started successfully",
|
Message: "Game started successfully",
|
||||||
Data: res,
|
Data: res,
|
||||||
|
|
@ -260,15 +279,13 @@ func (h *Handler) GetBalance(c *fiber.Ctx) error {
|
||||||
func (h *Handler) PlaceBet(c *fiber.Ctx) error {
|
func (h *Handler) PlaceBet(c *fiber.Ctx) error {
|
||||||
var req domain.BetRequest
|
var req domain.BetRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "Invalid request body",
|
Message: "Invalid request body",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signature check optional here
|
// 1️⃣ Process the bet with the external provider
|
||||||
|
|
||||||
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
||||||
|
|
@ -278,10 +295,42 @@ func (h *Handler) PlaceBet(c *fiber.Ctx) error {
|
||||||
Message: "Failed to process bet",
|
Message: "Failed to process bet",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
// return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(res)
|
// 2️⃣ If bet successful → update total_bets in the report
|
||||||
|
go func() {
|
||||||
|
ctx := context.Background()
|
||||||
|
reportDate := time.Now().Truncate(24 * time.Hour)
|
||||||
|
reportType := "daily"
|
||||||
|
|
||||||
|
// Increment total_bets by the bet amount
|
||||||
|
err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate(
|
||||||
|
ctx,
|
||||||
|
req.ProviderID,
|
||||||
|
reportDate,
|
||||||
|
reportType,
|
||||||
|
0, // total_games_played (no change)
|
||||||
|
req.Amount.Amount, // add this bet to total_bets
|
||||||
|
0, // total_payouts (no change)
|
||||||
|
0, // total_players (no change)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
h.InternalServerErrorLogger().Error("Failed to update total_bets after bet",
|
||||||
|
zap.String("provider_id", req.ProviderID),
|
||||||
|
zap.Float64("bet_amount", req.Amount.Amount),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 3️⃣ Return success response
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Bet processed successfully",
|
||||||
|
Data: res,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) RegisterWin(c *fiber.Ctx) error {
|
func (h *Handler) RegisterWin(c *fiber.Ctx) error {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,54 @@ type launchVirtualGameRes struct {
|
||||||
LaunchURL string `json:"launch_url"`
|
LaunchURL string `json:"launch_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListVirtualGameProviderReportsAscHandler
|
||||||
|
// @Summary List virtual game provider reports (ascending)
|
||||||
|
// @Description Retrieves all virtual game provider reports sorted by total_games_played in ascending order
|
||||||
|
// @Tags VirtualGames - Orchestration
|
||||||
|
// @Success 200 {array} domain.VirtualGameProviderReport
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/orchestrator/virtual-game/provider-reports/asc [get]
|
||||||
|
func (h *Handler) ListVirtualGameProviderReportsAscHandler(c *fiber.Ctx) error {
|
||||||
|
reports, err := h.orchestrationSvc.ListVirtualGameProviderReportsByGamesPlayedAsc(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch virtual game provider reports ascending",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Virtual game provider reports retrieved successfully",
|
||||||
|
Data: reports,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListVirtualGameProviderReportsDescHandler
|
||||||
|
// @Summary List virtual game provider reports (descending)
|
||||||
|
// @Description Retrieves all virtual game provider reports sorted by total_games_played in descending order
|
||||||
|
// @Tags VirtualGames - Orchestration
|
||||||
|
// @Success 200 {array} domain.VirtualGameProviderReport
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/orchestrator/virtual-game/provider-reports/desc [get]
|
||||||
|
func (h *Handler) ListVirtualGameProviderReportsDescHandler(c *fiber.Ctx) error {
|
||||||
|
reports, err := h.orchestrationSvc.ListVirtualGameProviderReportsByGamesPlayedDesc(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch virtual game provider reports descending",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Virtual game provider reports retrieved successfully",
|
||||||
|
Data: reports,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ListVirtualGames godoc
|
// ListVirtualGames godoc
|
||||||
// @Summary List all virtual games
|
// @Summary List all virtual games
|
||||||
// @Description Returns all virtual games with optional filters (category, search, pagination)
|
// @Description Returns all virtual games with optional filters (category, search, pagination)
|
||||||
|
|
@ -77,7 +125,7 @@ func (h *Handler) ListVirtualGames(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Call service method ---
|
// --- Call service method ---
|
||||||
games, err := h.veliVirtualGameSvc.GetAllVirtualGames(c.Context(), params)
|
games, err := h.orchestrationSvc.GetAllVirtualGames(c.Context(), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ListVirtualGames error:", err)
|
log.Println("ListVirtualGames error:", err)
|
||||||
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -105,7 +153,7 @@ func (h *Handler) ListVirtualGames(c *fiber.Ctx) error {
|
||||||
// @Router /api/v1/virtual-game/providers/{provider_id} [delete]
|
// @Router /api/v1/virtual-game/providers/{provider_id} [delete]
|
||||||
func (h *Handler) RemoveProvider(c *fiber.Ctx) error {
|
func (h *Handler) RemoveProvider(c *fiber.Ctx) error {
|
||||||
providerID := c.Params("providerID")
|
providerID := c.Params("providerID")
|
||||||
if err := h.virtualGameSvc.RemoveProvider(c.Context(), providerID); err != nil {
|
if err := h.orchestrationSvc.RemoveProvider(c.Context(), providerID); err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove provider")
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not remove provider")
|
||||||
}
|
}
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
|
@ -122,7 +170,7 @@ func (h *Handler) RemoveProvider(c *fiber.Ctx) error {
|
||||||
// @Router /api/v1/virtual-game/providers/{provider_id} [get]
|
// @Router /api/v1/virtual-game/providers/{provider_id} [get]
|
||||||
func (h *Handler) GetProviderByID(c *fiber.Ctx) error {
|
func (h *Handler) GetProviderByID(c *fiber.Ctx) error {
|
||||||
providerID := c.Params("providerID")
|
providerID := c.Params("providerID")
|
||||||
provider, err := h.virtualGameSvc.GetProviderByID(c.Context(), providerID)
|
provider, err := h.orchestrationSvc.GetProviderByID(c.Context(), providerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch provider")
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not fetch provider")
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +191,7 @@ func (h *Handler) ListProviders(c *fiber.Ctx) error {
|
||||||
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
limit, _ := strconv.Atoi(c.Query("limit", "20"))
|
||||||
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
offset, _ := strconv.Atoi(c.Query("offset", "0"))
|
||||||
|
|
||||||
providers, total, err := h.virtualGameSvc.ListProviders(c.Context(), int32(limit), int32(offset))
|
providers, total, err := h.orchestrationSvc.ListProviders(c.Context(), int32(limit), int32(offset))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not list providers")
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not list providers")
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +216,7 @@ func (h *Handler) SetProviderEnabled(c *fiber.Ctx) error {
|
||||||
providerID := c.Params("providerID")
|
providerID := c.Params("providerID")
|
||||||
enabled, _ := strconv.ParseBool(c.Query("enabled", "true"))
|
enabled, _ := strconv.ParseBool(c.Query("enabled", "true"))
|
||||||
|
|
||||||
provider, err := h.virtualGameSvc.SetProviderEnabled(c.Context(), providerID, enabled)
|
provider, err := h.orchestrationSvc.SetProviderEnabled(c.Context(), providerID, enabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusInternalServerError, "Could not update provider status")
|
return fiber.NewError(fiber.StatusInternalServerError, "Could not update provider status")
|
||||||
}
|
}
|
||||||
|
|
@ -264,6 +312,14 @@ func (h *Handler) HandlePlayerInfo(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
||||||
|
// userID := c.Locals("user_id")
|
||||||
|
// fmt.Printf("\n\nBet User ID is%v\n\n",userID)
|
||||||
|
// if userID == "" {
|
||||||
|
// return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
// Message: "Failed to process Bet request",
|
||||||
|
// Error: "Invalid user identification",
|
||||||
|
// })
|
||||||
|
// }
|
||||||
// Read the raw body
|
// Read the raw body
|
||||||
body := c.Body()
|
body := c.Body()
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
|
|
@ -292,6 +348,8 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
||||||
|
|
||||||
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
if errors.Is(err, veli.ErrDuplicateTransaction) {
|
||||||
|
|
@ -316,6 +374,8 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
||||||
|
|
||||||
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &req)
|
resp, err := h.virtualGameSvc.ProcessBet(c.Context(), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := fiber.StatusInternalServerError
|
code := fiber.StatusInternalServerError
|
||||||
|
|
@ -341,6 +401,8 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// req.PlayerID = fmt.Sprintf("%v", userID)
|
||||||
|
|
||||||
resp, err := h.atlasVirtualGameSvc.ProcessBet(c.Context(), req)
|
resp, err := h.atlasVirtualGameSvc.ProcessBet(c.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// code := fiber.StatusInternalServerError
|
// code := fiber.StatusInternalServerError
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
h := handlers.New(
|
h := handlers.New(
|
||||||
|
a.orchestrationSvc,
|
||||||
a.enetPulseSvc,
|
a.enetPulseSvc,
|
||||||
a.telebirrSvc,
|
a.telebirrSvc,
|
||||||
a.arifpaySvc,
|
a.arifpaySvc,
|
||||||
|
|
@ -150,9 +151,9 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
//Arifpay
|
//Arifpay
|
||||||
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
|
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
|
||||||
groupV1.Post("/arifpay/checkout/cancel/:session_id", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
groupV1.Post("/arifpay/checkout/cancel/:sessionId", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
||||||
groupV1.Post("/api/v1/arifpay/c2b-webhook", a.authMiddleware, h.HandleArifpayC2BWebhook)
|
groupV1.Post("/api/v1/arifpay/c2b-webhook", h.HandleArifpayC2BWebhook)
|
||||||
groupV1.Post("/api/v1/arifpay/b2c-webhook", a.authMiddleware, h.HandleArifpayB2CWebhook)
|
groupV1.Post("/api/v1/arifpay/b2c-webhook", h.HandleArifpayB2CWebhook)
|
||||||
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
|
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
|
||||||
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
||||||
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
||||||
|
|
@ -275,7 +276,7 @@ func (a *App) initAppRoutes() {
|
||||||
tenant.Delete("/odds/market-settings", a.authMiddleware, a.CompanyOnly, h.DeleteAllCompanyMarketSettings)
|
tenant.Delete("/odds/market-settings", a.authMiddleware, a.CompanyOnly, h.DeleteAllCompanyMarketSettings)
|
||||||
tenant.Delete("/odds/market-settings/:id", a.authMiddleware, a.CompanyOnly, h.DeleteCompanyMarketSettings)
|
tenant.Delete("/odds/market-settings/:id", a.authMiddleware, a.CompanyOnly, h.DeleteCompanyMarketSettings)
|
||||||
|
|
||||||
groupV1.Get("/events", a.authMiddleware, h.GetAllEvents)
|
groupV1.Get("/events", h.GetAllEvents)
|
||||||
groupV1.Get("/events/:id", a.authMiddleware, h.GetEventByID)
|
groupV1.Get("/events/:id", a.authMiddleware, h.GetEventByID)
|
||||||
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
|
groupV1.Delete("/events/:id", a.authMiddleware, a.SuperAdminOnly, h.SetEventStatusToRemoved)
|
||||||
groupV1.Patch("/events/:id/is_monitored", a.authMiddleware, a.SuperAdminOnly, h.SetEventIsMonitored)
|
groupV1.Patch("/events/:id/is_monitored", a.authMiddleware, a.SuperAdminOnly, h.SetEventIsMonitored)
|
||||||
|
|
@ -290,11 +291,16 @@ func (a *App) initAppRoutes() {
|
||||||
tenant.Get("/events/:id/bets", a.authMiddleware, a.CompanyOnly, h.GetTenantBetsByEventID)
|
tenant.Get("/events/:id/bets", a.authMiddleware, a.CompanyOnly, h.GetTenantBetsByEventID)
|
||||||
|
|
||||||
//EnetPulse
|
//EnetPulse
|
||||||
groupV1.Get("/odds/pre-match", h.GetPreMatchOdds)
|
// groupV1.Get("/odds/pre-match", h.GetPreMatchOdds)
|
||||||
groupV1.Get("/sports", h.GetAllSports)
|
groupV1.Get("/sports", h.GetAllSports)
|
||||||
groupV1.Get("/tournament_templates", h.GetAllTournamentTemplates)
|
groupV1.Get("/tournament_templates", h.GetAllTournamentTemplates)
|
||||||
groupV1.Get("/tournaments", h.GetAllTournamentTemplates)
|
groupV1.Get("/tournaments", h.GetAllTournaments)
|
||||||
groupV1.Get("/tournament_stages", h.GetAllTournamentStages)
|
groupV1.Get("/tournament_stages", h.GetAllTournamentStages)
|
||||||
|
groupV1.Get("/fixtures", h.GetFixturesByDate)
|
||||||
|
groupV1.Get("/results", h.GetAllResults)
|
||||||
|
groupV1.Get("/preodds", h.GetAllPreoddsWithBettingOffers)
|
||||||
|
groupV1.Get("/bettingoffers", h.GetAllBettingOffers)
|
||||||
|
groupV1.Get("/fixtures/preodds", h.GetFixturesWithPreodds)
|
||||||
|
|
||||||
// Leagues
|
// Leagues
|
||||||
groupV1.Get("/leagues", a.authMiddleware, a.SuperAdminOnly, h.GetAllLeagues)
|
groupV1.Get("/leagues", a.authMiddleware, a.SuperAdminOnly, h.GetAllLeagues)
|
||||||
|
|
@ -381,10 +387,17 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
||||||
groupV1.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
groupV1.Get("/chapa/transaction/manual/verify/:tx_ref", a.authMiddleware, h.ManualVerifyTransaction)
|
||||||
|
groupV1.Put("/chapa/transaction/cancel/:tx_ref", a.authMiddleware, h.CancelDeposit)
|
||||||
|
groupV1.Get("/chapa/transactions", a.authMiddleware, h.FetchAllTransactions)
|
||||||
|
groupV1.Get("/chapa/transaction/events/:ref_id", a.authMiddleware, h.GetTransactionEvents)
|
||||||
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||||
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||||
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
||||||
|
groupV1.Get("/chapa/payments/receipt/:chapa_ref", a.authMiddleware, h.GetPaymentReceipt)
|
||||||
|
groupV1.Get("/chapa/transfers", a.authMiddleware, h.GetAllTransfers)
|
||||||
|
groupV1.Get("/chapa/balance", a.authMiddleware, h.GetAccountBalance)
|
||||||
|
groupV1.Post("/chapa/swap", a.authMiddleware, h.SwapCurrency)
|
||||||
|
|
||||||
// Currencies
|
// Currencies
|
||||||
groupV1.Get("/currencies", h.GetSupportedCurrencies)
|
groupV1.Get("/currencies", h.GetSupportedCurrencies)
|
||||||
|
|
@ -414,7 +427,7 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Post("/veli/credit-balances", a.authMiddleware, h.GetCreditBalances)
|
groupV1.Post("/veli/credit-balances", a.authMiddleware, h.GetCreditBalances)
|
||||||
|
|
||||||
//Atlas Virtual Game Routes
|
//Atlas Virtual Game Routes
|
||||||
groupV1.Get("/atlas/games", a.authMiddleware, h.InitAtlasGame)
|
groupV1.Get("/atlas/games", h.GetAtlasVGames)
|
||||||
groupV1.Post("/atlas/init-game", a.authMiddleware, h.InitAtlasGame)
|
groupV1.Post("/atlas/init-game", a.authMiddleware, h.InitAtlasGame)
|
||||||
a.fiber.Post("/account", h.AtlasGetUserDataCallback)
|
a.fiber.Post("/account", h.AtlasGetUserDataCallback)
|
||||||
a.fiber.Post("/betwin", h.HandleAtlasBetWin)
|
a.fiber.Post("/betwin", h.HandleAtlasBetWin)
|
||||||
|
|
@ -469,6 +482,8 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
|
groupV1.Delete("/virtual-game/favorites/:gameID", a.authMiddleware, h.RemoveFavorite)
|
||||||
groupV1.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
|
groupV1.Get("/virtual-game/favorites", a.authMiddleware, h.ListFavorites)
|
||||||
|
|
||||||
|
groupV1.Get("/orchestrator/virtual-game/provider-reports/asc", a.OnlyAdminAndAbove, h.ListVirtualGameProviderReportsAscHandler)
|
||||||
|
groupV1.Get("/orchestrator/virtual-game/provider-reports/desc", a.OnlyAdminAndAbove, h.ListVirtualGameProviderReportsDescHandler)
|
||||||
groupV1.Delete("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.RemoveProvider)
|
groupV1.Delete("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.RemoveProvider)
|
||||||
groupV1.Get("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.GetProviderByID)
|
groupV1.Get("/virtual-game/orchestrator/providers/:provideID", a.authMiddleware, h.GetProviderByID)
|
||||||
groupV1.Get("/virtual-game/orchestrator/games", h.ListVirtualGames)
|
groupV1.Get("/virtual-game/orchestrator/games", h.ListVirtualGames)
|
||||||
|
|
|
||||||
20
makefile
20
makefile
|
|
@ -46,45 +46,45 @@ postgres:
|
||||||
.PHONY: backup
|
.PHONY: backup
|
||||||
backup:
|
backup:
|
||||||
@mkdir -p backup
|
@mkdir -p backup
|
||||||
@docker exec -t fortunebet-backend-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
|
@docker exec -t fortunebet-postgres-1 pg_dump -U root --data-only --exclude-table=schema_migrations gh | gzip > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql.gz
|
||||||
|
|
||||||
restore:
|
restore:
|
||||||
@echo "Restoring latest backup..."
|
@echo "Restoring latest backup..."
|
||||||
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
|
@latest_file=$$(ls -t backup/dump_*.sql.gz | head -n 1); \
|
||||||
echo "Restoring from $$latest_file"; \
|
echo "Restoring from $$latest_file"; \
|
||||||
gunzip -c $$latest_file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
|
gunzip -c $$latest_file | docker exec -i fortunebet-postgres-1 psql -U root -d gh
|
||||||
restore_file:
|
restore_file:
|
||||||
@echo "Restoring latest backup..."
|
@echo "Restoring latest backup..."
|
||||||
gunzip -c $(file) | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh
|
gunzip -c $(file) | docker exec -i fortunebet-postgres-1 psql -U root -d gh
|
||||||
|
|
||||||
.PHONY: seed_data
|
.PHONY: seed_data
|
||||||
seed_data:
|
seed_data:
|
||||||
|
|
||||||
@echo "Waiting for PostgreSQL to be ready..."
|
@echo "Waiting for PostgreSQL to be ready..."
|
||||||
@until docker exec fortunebet-backend-postgres-1 pg_isready -U root -d gh; do \
|
@until docker exec fortunebet-postgres-1 pg_isready -U root -d gh; do \
|
||||||
echo "PostgreSQL is not ready yet..."; \
|
echo "PostgreSQL is not ready yet..."; \
|
||||||
sleep 1; \
|
sleep 1; \
|
||||||
done
|
done
|
||||||
@for file in db/data/*.sql; do \
|
@for file in db/data/*.sql; do \
|
||||||
echo "Seeding $$file..."; \
|
echo "Seeding $$file..."; \
|
||||||
cat $$file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; \
|
cat $$file | docker exec -i fortunebet-postgres-1 psql -U root -d gh; \
|
||||||
done
|
done
|
||||||
.PHONY: seed_dev_data
|
.PHONY: seed_dev_data
|
||||||
seed_dev_data:
|
seed_dev_data:
|
||||||
@echo "Waiting for PostgreSQL to be ready..."
|
@echo "Waiting for PostgreSQL to be ready..."
|
||||||
@until docker exec fortunebet-backend-postgres-1 pg_isready -U root -d gh; do \
|
@until docker exec fortunebet-postgres-1 pg_isready -U root -d gh; do \
|
||||||
echo "PostgreSQL is not ready yet..."; \
|
echo "PostgreSQL is not ready yet..."; \
|
||||||
sleep 1; \
|
sleep 1; \
|
||||||
done
|
done
|
||||||
cat db/scripts/fix_autoincrement_desync.sql | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh;
|
cat db/scripts/fix_autoincrement_desync.sql | docker exec -i fortunebet-postgres-1 psql -U root -d gh;
|
||||||
@for file in db/dev_data/*.sql; do \
|
@for file in db/dev_data/*.sql; do \
|
||||||
if [ -f "$$file" ]; then \
|
if [ -f "$$file" ]; then \
|
||||||
echo "Seeding $$file..."; \
|
echo "Seeding $$file..."; \
|
||||||
cat $$file | docker exec -i fortunebet-backend-postgres-1 psql -U root -d gh; \
|
cat $$file | docker exec -i fortunebet-postgres-1 psql -U root -d gh; \
|
||||||
fi \
|
fi \
|
||||||
done
|
done
|
||||||
postgres_log:
|
postgres_log:
|
||||||
docker logs fortunebet-backend-postgres-1
|
docker logs fortunebet-postgres-1
|
||||||
.PHONY: swagger
|
.PHONY: swagger
|
||||||
swagger:
|
swagger:
|
||||||
@swag init -g cmd/main.go
|
@swag init -g cmd/main.go
|
||||||
|
|
@ -94,7 +94,7 @@ logs:
|
||||||
db-up: | logs
|
db-up: | logs
|
||||||
@mkdir -p logs
|
@mkdir -p logs
|
||||||
@docker compose up -d postgres migrate mongo
|
@docker compose up -d postgres migrate mongo
|
||||||
@docker logs fortunebet-backend-postgres-1 > logs/postgres.log 2>&1 &
|
@docker logs fortunebet-postgres-1 > logs/postgres.log 2>&1 &
|
||||||
.PHONY: db-down
|
.PHONY: db-down
|
||||||
db-down:
|
db-down:
|
||||||
@docker compose down -v
|
@docker compose down -v
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user