Merge branch 'main' into ticket-bet
This commit is contained in:
commit
73c1db14c1
16
cmd/main.go
16
cmd/main.go
|
|
@ -20,8 +20,10 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
|
|
@ -85,6 +87,7 @@ func main() {
|
||||||
transactionSvc := transaction.NewService(store)
|
transactionSvc := transaction.NewService(store)
|
||||||
branchSvc := branch.NewService(store)
|
branchSvc := branch.NewService(store)
|
||||||
companySvc := company.NewService(store)
|
companySvc := company.NewService(store)
|
||||||
|
leagueSvc := league.New(store)
|
||||||
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger)
|
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger)
|
||||||
resultSvc := result.NewService(store, cfg, logger, *betSvc, oddsSvc, eventSvc)
|
resultSvc := result.NewService(store, cfg, logger, *betSvc, oddsSvc, eventSvc)
|
||||||
notificationRepo := repository.NewNotificationRepository(store)
|
notificationRepo := repository.NewNotificationRepository(store)
|
||||||
|
|
@ -108,6 +111,17 @@ func main() {
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
recommendationSvc := recommendation.NewService(recommendationRepo)
|
recommendationSvc := recommendation.NewService(recommendationRepo)
|
||||||
|
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
|
chapaSvc := chapa.NewService(
|
||||||
|
transaction.TransactionStore(store),
|
||||||
|
wallet.WalletStore(store),
|
||||||
|
user.UserStore(store),
|
||||||
|
referalSvc,
|
||||||
|
branch.BranchStore(store),
|
||||||
|
chapaClient,
|
||||||
|
store,
|
||||||
|
)
|
||||||
|
|
||||||
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
httpserver.StartDataFetchingCrons(eventSvc, oddsSvc, resultSvc)
|
||||||
httpserver.StartTicketCrons(*ticketSvc)
|
httpserver.StartTicketCrons(*ticketSvc)
|
||||||
|
|
@ -116,7 +130,7 @@ func main() {
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
}, userSvc,
|
}, userSvc,
|
||||||
ticketSvc, betSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg)
|
ticketSvc, betSvc, chapaSvc, walletSvc, transactionSvc, branchSvc, companySvc, notificationSvc, oddsSvc, eventSvc, leagueSvc, referalSvc, virtualGameSvc, aleaService, veliService, recommendationSvc, resultSvc, cfg)
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
|
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -75,4 +75,5 @@ DROP TABLE IF EXISTS supported_operations;
|
||||||
DROP TABLE IF EXISTS refresh_tokens;
|
DROP TABLE IF EXISTS refresh_tokens;
|
||||||
DROP TABLE IF EXISTS otps;
|
DROP TABLE IF EXISTS otps;
|
||||||
DROP TABLE IF EXISTS odds;
|
DROP TABLE IF EXISTS odds;
|
||||||
DROP TABLE IF EXISTS events;
|
DROP TABLE IF EXISTS events;
|
||||||
|
DROP TABLE IF EXISTS leagues;
|
||||||
|
|
@ -184,15 +184,15 @@ CREATE TABLE IF NOT EXISTS branch_cashiers (
|
||||||
);
|
);
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
sport_id TEXT,
|
sport_id INT,
|
||||||
match_name TEXT,
|
match_name TEXT,
|
||||||
home_team TEXT,
|
home_team TEXT,
|
||||||
away_team TEXT,
|
away_team TEXT,
|
||||||
home_team_id TEXT,
|
home_team_id INT,
|
||||||
away_team_id TEXT,
|
away_team_id INT,
|
||||||
home_kit_image TEXT,
|
home_kit_image TEXT,
|
||||||
away_kit_image TEXT,
|
away_kit_image TEXT,
|
||||||
league_id TEXT,
|
league_id INT,
|
||||||
league_name TEXT,
|
league_name TEXT,
|
||||||
league_cc TEXT,
|
league_cc TEXT,
|
||||||
start_time TIMESTAMP,
|
start_time TIMESTAMP,
|
||||||
|
|
@ -233,6 +233,20 @@ CREATE TABLE companies (
|
||||||
admin_id BIGINT NOT NULL,
|
admin_id BIGINT NOT NULL,
|
||||||
wallet_id BIGINT NOT NULL
|
wallet_id BIGINT NOT NULL
|
||||||
);
|
);
|
||||||
|
CREATE TABLE leagues (
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
country_code TEXT,
|
||||||
|
bet365_id INT,
|
||||||
|
is_active BOOLEAN DEFAULT true
|
||||||
|
);
|
||||||
|
CREATE TABLE teams (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
team_name TEXT NOT NULL,
|
||||||
|
country TEXT,
|
||||||
|
bet365_id INT,
|
||||||
|
logo_url TEXT
|
||||||
|
);
|
||||||
-- Views
|
-- Views
|
||||||
CREATE VIEW companies_details AS
|
CREATE VIEW companies_details AS
|
||||||
SELECT companies.*,
|
SELECT companies.*,
|
||||||
|
|
@ -297,6 +311,7 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
|
||||||
ALTER TABLE branch_cashiers
|
ALTER TABLE branch_cashiers
|
||||||
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE companies
|
ALTER TABLE companies
|
||||||
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
||||||
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
||||||
|
|
|
||||||
48
db/query/leagues.sql
Normal file
48
db/query/leagues.sql
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
-- name: InsertLeague :exec
|
||||||
|
INSERT INTO leagues (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5
|
||||||
|
)
|
||||||
|
ON CONFLICT (id) DO UPDATE
|
||||||
|
SET name = EXCLUDED.name,
|
||||||
|
country_code = EXCLUDED.country_code,
|
||||||
|
bet365_id = EXCLUDED.bet365_id,
|
||||||
|
is_active = EXCLUDED.is_active;
|
||||||
|
-- name: GetSupportedLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
FROM leagues
|
||||||
|
WHERE is_active = true;
|
||||||
|
-- name: GetAllLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
FROM leagues;
|
||||||
|
-- name: CheckLeagueSupport :one
|
||||||
|
SELECT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM leagues
|
||||||
|
WHERE id = $1
|
||||||
|
AND is_active = true
|
||||||
|
);
|
||||||
|
-- name: UpdateLeague :exec
|
||||||
|
UPDATE leagues
|
||||||
|
SET name = $1,
|
||||||
|
country_code = $2,
|
||||||
|
bet365_id = $3,
|
||||||
|
is_active = $4
|
||||||
|
WHERE id = $5;
|
||||||
|
-- name: SetLeagueActive :exec
|
||||||
|
UPDATE leagues
|
||||||
|
SET is_active = true
|
||||||
|
WHERE id = $1;
|
||||||
476
docs/docs.go
476
docs/docs.go
|
|
@ -306,7 +306,6 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/banks": {
|
"/api/v1/chapa/banks": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Fetch all supported banks from Chapa",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -316,20 +315,50 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Get list of banks",
|
"summary": "fetches chapa supported banks",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse"
|
"$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/callback": {
|
"/api/v1/chapa/payments/deposit": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
"description": "Deposits money into user wallet from user account using Chapa",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -339,31 +368,48 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Receive Chapa webhook",
|
"summary": "Deposit money into user wallet using Chapa",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Webhook Payload (dynamic)",
|
"description": "Deposit request payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object"
|
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "ok",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/domain.ChapaPaymentUrlResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/initialize": {
|
"/api/v1/chapa/payments/verify": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a payment through Chapa",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -373,15 +419,15 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Initialize a payment transaction",
|
"summary": "Verifies Chapa webhook transaction",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Payment initialization request",
|
"description": "Webhook Payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -389,47 +435,15 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
"/api/v1/chapa/payments/withdraw": {
|
||||||
"get": {
|
|
||||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Chapa"
|
|
||||||
],
|
|
||||||
"summary": "Verify a payment transaction",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Transaction Reference",
|
|
||||||
"name": "tx_ref",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/domain.VerifyTransactionResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/chapa/transfers": {
|
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a transfer request via Chapa",
|
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -439,55 +453,59 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Create a money transfer",
|
"summary": "Withdraw using Chapa",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Transfer request body",
|
"description": "Chapa Withdraw Request",
|
||||||
"name": "payload",
|
"name": "request",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.TransferRequest"
|
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "Withdrawal requested successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"400": {
|
||||||
}
|
"description": "Invalid request",
|
||||||
},
|
|
||||||
"/api/v1/chapa/transfers/verify/{transfer_ref}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Check the status of a money transfer via reference",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Chapa"
|
|
||||||
],
|
|
||||||
"summary": "Verify a transfer",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Transfer Reference",
|
|
||||||
"name": "transfer_ref",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.VerifyTransferResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4460,6 +4478,46 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaDepositRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"payment_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponseWrapper": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ChapaSupportedBank": {
|
"domain.ChapaSupportedBank": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4513,17 +4571,56 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ChapaSupportedBanksResponse": {
|
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {},
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaTransactionType": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWithdrawRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"bank_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"beneficiary_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"wallet_id": {
|
||||||
|
"description": "add this",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -4579,76 +4676,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.CreateTransferResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransferData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"checkout_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"callback_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"first_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"return_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.InitPaymentData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"description": "e.g., \"Payment initialized\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"description": "\"success\"",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.Odd": {
|
"domain.Odd": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4879,6 +4906,21 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.Response": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -4961,86 +5003,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.TransactionData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"account_number": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bank_code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reason": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"recipient_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferVerificationData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"account_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bank_code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.UpcomingEvent": {
|
"domain.UpcomingEvent": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -5147,34 +5109,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.VerifyTransactionResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransactionData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VerifyTransferResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransferVerificationData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VirtualGame": {
|
"domain.VirtualGame": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -298,7 +298,6 @@
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/banks": {
|
"/api/v1/chapa/banks": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Fetch all supported banks from Chapa",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -308,20 +307,50 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Get list of banks",
|
"summary": "fetches chapa supported banks",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse"
|
"$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/callback": {
|
"/api/v1/chapa/payments/deposit": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
"description": "Deposits money into user wallet from user account using Chapa",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -331,31 +360,48 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Receive Chapa webhook",
|
"summary": "Deposit money into user wallet using Chapa",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Webhook Payload (dynamic)",
|
"description": "Deposit request payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object"
|
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "ok",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/domain.ChapaPaymentUrlResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/initialize": {
|
"/api/v1/chapa/payments/verify": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a payment through Chapa",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -365,15 +411,15 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Initialize a payment transaction",
|
"summary": "Verifies Chapa webhook transaction",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Payment initialization request",
|
"description": "Webhook Payload",
|
||||||
"name": "payload",
|
"name": "payload",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -381,47 +427,15 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
"/api/v1/chapa/payments/withdraw": {
|
||||||
"get": {
|
|
||||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Chapa"
|
|
||||||
],
|
|
||||||
"summary": "Verify a payment transaction",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Transaction Reference",
|
|
||||||
"name": "tx_ref",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/domain.VerifyTransactionResponse"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/v1/chapa/transfers": {
|
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a transfer request via Chapa",
|
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -431,55 +445,59 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Create a money transfer",
|
"summary": "Withdraw using Chapa",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Transfer request body",
|
"description": "Chapa Withdraw Request",
|
||||||
"name": "payload",
|
"name": "request",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.TransferRequest"
|
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "Withdrawal requested successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"400": {
|
||||||
}
|
"description": "Invalid request",
|
||||||
},
|
|
||||||
"/api/v1/chapa/transfers/verify/{transfer_ref}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Check the status of a money transfer via reference",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Chapa"
|
|
||||||
],
|
|
||||||
"summary": "Verify a transfer",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Transfer Reference",
|
|
||||||
"name": "transfer_ref",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.VerifyTransferResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4452,6 +4470,46 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaDepositRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"payment_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponseWrapper": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ChapaSupportedBank": {
|
"domain.ChapaSupportedBank": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4505,17 +4563,56 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ChapaSupportedBanksResponse": {
|
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {},
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"message": {
|
"message": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaTransactionType": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWithdrawRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"bank_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"beneficiary_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"wallet_id": {
|
||||||
|
"description": "add this",
|
||||||
|
"type": "integer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -4571,76 +4668,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.CreateTransferResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransferData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"checkout_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"callback_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"first_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"last_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"return_url": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.InitPaymentResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.InitPaymentData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"description": "e.g., \"Payment initialized\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"description": "\"success\"",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.Odd": {
|
"domain.Odd": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4871,6 +4898,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.Response": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -4953,86 +4995,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.TransactionData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"tx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"account_number": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bank_code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reason": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"recipient_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.TransferVerificationData": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"account_name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bank_code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"reference": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.UpcomingEvent": {
|
"domain.UpcomingEvent": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -5139,34 +5101,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.VerifyTransactionResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransactionData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VerifyTransferResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"$ref": "#/definitions/domain.TransferVerificationData"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VirtualGame": {
|
"domain.VirtualGame": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,32 @@ definitions:
|
||||||
example: 2
|
example: 2
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
domain.ChapaDepositRequest:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
branch_id:
|
||||||
|
type: integer
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaPaymentUrlResponse:
|
||||||
|
properties:
|
||||||
|
payment_url:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaPaymentUrlResponseWrapper:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
domain.ChapaSupportedBank:
|
domain.ChapaSupportedBank:
|
||||||
properties:
|
properties:
|
||||||
acct_length:
|
acct_length:
|
||||||
|
|
@ -159,14 +185,40 @@ definitions:
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
domain.ChapaSupportedBanksResponse:
|
domain.ChapaSupportedBanksResponseWrapper:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data: {}
|
||||||
items:
|
|
||||||
$ref: '#/definitions/domain.ChapaSupportedBank'
|
|
||||||
type: array
|
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
domain.ChapaTransactionType:
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaWithdrawRequest:
|
||||||
|
properties:
|
||||||
|
account_name:
|
||||||
|
type: string
|
||||||
|
account_number:
|
||||||
|
type: string
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
bank_code:
|
||||||
|
type: string
|
||||||
|
beneficiary_name:
|
||||||
|
type: string
|
||||||
|
branch_id:
|
||||||
|
type: integer
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
wallet_id:
|
||||||
|
description: add this
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
domain.CreateBetOutcomeReq:
|
domain.CreateBetOutcomeReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -203,52 +255,6 @@ definitions:
|
||||||
- $ref: '#/definitions/domain.OutcomeStatus'
|
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||||
example: 1
|
example: 1
|
||||||
type: object
|
type: object
|
||||||
domain.CreateTransferResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/domain.TransferData'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.InitPaymentData:
|
|
||||||
properties:
|
|
||||||
checkout_url:
|
|
||||||
type: string
|
|
||||||
tx_ref:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.InitPaymentRequest:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: string
|
|
||||||
callback_url:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
first_name:
|
|
||||||
type: string
|
|
||||||
last_name:
|
|
||||||
type: string
|
|
||||||
return_url:
|
|
||||||
type: string
|
|
||||||
tx_ref:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.InitPaymentResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/domain.InitPaymentData'
|
|
||||||
message:
|
|
||||||
description: e.g., "Payment initialized"
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
description: '"success"'
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.Odd:
|
domain.Odd:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
|
|
@ -408,6 +414,16 @@ definitions:
|
||||||
totalRewardEarned:
|
totalRewardEarned:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
domain.Response:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
domain.Role:
|
domain.Role:
|
||||||
enum:
|
enum:
|
||||||
- super_admin
|
- super_admin
|
||||||
|
|
@ -468,58 +484,6 @@ definitions:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
domain.TransactionData:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
tx_ref:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.TransferData:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
reference:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.TransferRequest:
|
|
||||||
properties:
|
|
||||||
account_number:
|
|
||||||
type: string
|
|
||||||
amount:
|
|
||||||
type: string
|
|
||||||
bank_code:
|
|
||||||
type: string
|
|
||||||
currency:
|
|
||||||
type: string
|
|
||||||
reason:
|
|
||||||
type: string
|
|
||||||
recipient_name:
|
|
||||||
type: string
|
|
||||||
reference:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.TransferVerificationData:
|
|
||||||
properties:
|
|
||||||
account_name:
|
|
||||||
type: string
|
|
||||||
bank_code:
|
|
||||||
type: string
|
|
||||||
reference:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.UpcomingEvent:
|
domain.UpcomingEvent:
|
||||||
properties:
|
properties:
|
||||||
awayKitImage:
|
awayKitImage:
|
||||||
|
|
@ -598,24 +562,6 @@ definitions:
|
||||||
description: Veli's user identifier
|
description: Veli's user identifier
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
domain.VerifyTransactionResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/domain.TransactionData'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.VerifyTransferResponse:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
$ref: '#/definitions/domain.TransferVerificationData'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.VirtualGame:
|
domain.VirtualGame:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
|
|
@ -1691,123 +1637,133 @@ paths:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Fetch all supported banks from Chapa
|
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ChapaSupportedBanksResponse'
|
$ref: '#/definitions/domain.ChapaSupportedBanksResponseWrapper'
|
||||||
summary: Get list of banks
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"422":
|
||||||
|
description: Unprocessable Entity
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
summary: fetches chapa supported banks
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
/api/v1/chapa/payments/callback:
|
/api/v1/chapa/payments/deposit:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Endpoint to receive webhook payloads from Chapa
|
description: Deposits money into user wallet from user account using Chapa
|
||||||
parameters:
|
parameters:
|
||||||
- description: Webhook Payload (dynamic)
|
- description: Deposit request payload
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: object
|
$ref: '#/definitions/domain.ChapaDepositRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: ok
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/definitions/domain.ChapaPaymentUrlResponseWrapper'
|
||||||
summary: Receive Chapa webhook
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"422":
|
||||||
|
description: Validation error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
summary: Deposit money into user wallet using Chapa
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
/api/v1/chapa/payments/initialize:
|
/api/v1/chapa/payments/verify:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Initiate a payment through Chapa
|
|
||||||
parameters:
|
parameters:
|
||||||
- description: Payment initialization request
|
- description: Webhook Payload
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: payload
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.InitPaymentRequest'
|
$ref: '#/definitions/domain.ChapaTransactionType'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.InitPaymentResponse'
|
$ref: '#/definitions/domain.Response'
|
||||||
summary: Initialize a payment transaction
|
summary: Verifies Chapa webhook transaction
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
/api/v1/chapa/payments/verify/{tx_ref}:
|
/api/v1/chapa/payments/withdraw:
|
||||||
get:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Verify the transaction status from Chapa using tx_ref
|
|
||||||
parameters:
|
|
||||||
- description: Transaction Reference
|
|
||||||
in: path
|
|
||||||
name: tx_ref
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.VerifyTransactionResponse'
|
|
||||||
summary: Verify a payment transaction
|
|
||||||
tags:
|
|
||||||
- Chapa
|
|
||||||
/api/v1/chapa/transfers:
|
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Initiate a transfer request via Chapa
|
description: Initiates a withdrawal transaction using Chapa for the authenticated
|
||||||
|
user.
|
||||||
parameters:
|
parameters:
|
||||||
- description: Transfer request body
|
- description: Chapa Withdraw Request
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.TransferRequest'
|
$ref: '#/definitions/domain.ChapaWithdrawRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: Withdrawal requested successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.CreateTransferResponse'
|
allOf:
|
||||||
summary: Create a money transfer
|
- $ref: '#/definitions/domain.Response'
|
||||||
tags:
|
- properties:
|
||||||
- Chapa
|
data:
|
||||||
/api/v1/chapa/transfers/verify/{transfer_ref}:
|
type: string
|
||||||
get:
|
type: object
|
||||||
consumes:
|
"400":
|
||||||
- application/json
|
description: Invalid request
|
||||||
description: Check the status of a money transfer via reference
|
|
||||||
parameters:
|
|
||||||
- description: Transfer Reference
|
|
||||||
in: path
|
|
||||||
name: transfer_ref
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.VerifyTransferResponse'
|
$ref: '#/definitions/domain.Response'
|
||||||
summary: Verify a transfer
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"422":
|
||||||
|
description: Unprocessable Entity
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
summary: Withdraw using Chapa
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
/api/v1/virtual-games/recommendations/{userID}:
|
/api/v1/virtual-games/recommendations/{userID}:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.29.0
|
||||||
// source: cashier.sql
|
// source: cashier.sql
|
||||||
|
|
||||||
package dbgen
|
package dbgen
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,15 @@ ORDER BY start_time ASC
|
||||||
|
|
||||||
type GetAllUpcomingEventsRow struct {
|
type GetAllUpcomingEventsRow struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -132,15 +132,15 @@ ORDER BY start_time ASC
|
||||||
|
|
||||||
type GetExpiredUpcomingEventsRow struct {
|
type GetExpiredUpcomingEventsRow struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -230,8 +230,8 @@ LIMIT $6 OFFSET $5
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetPaginatedUpcomingEventsParams struct {
|
type GetPaginatedUpcomingEventsParams struct {
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||||
Offset pgtype.Int4 `json:"offset"`
|
Offset pgtype.Int4 `json:"offset"`
|
||||||
|
|
@ -240,15 +240,15 @@ type GetPaginatedUpcomingEventsParams struct {
|
||||||
|
|
||||||
type GetPaginatedUpcomingEventsRow struct {
|
type GetPaginatedUpcomingEventsRow struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -327,8 +327,8 @@ WHERE is_live = false
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTotalEventsParams struct {
|
type GetTotalEventsParams struct {
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||||
}
|
}
|
||||||
|
|
@ -372,15 +372,15 @@ LIMIT 1
|
||||||
|
|
||||||
type GetUpcomingByIDRow struct {
|
type GetUpcomingByIDRow struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -488,15 +488,15 @@ SET sport_id = EXCLUDED.sport_id,
|
||||||
|
|
||||||
type InsertEventParams struct {
|
type InsertEventParams struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -595,15 +595,15 @@ SET sport_id = EXCLUDED.sport_id,
|
||||||
|
|
||||||
type InsertUpcomingEventParams struct {
|
type InsertUpcomingEventParams struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
|
||||||
174
gen/db/leagues.sql.go
Normal file
174
gen/db/leagues.sql.go
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: leagues.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CheckLeagueSupport = `-- name: CheckLeagueSupport :one
|
||||||
|
SELECT EXISTS(
|
||||||
|
SELECT 1
|
||||||
|
FROM leagues
|
||||||
|
WHERE id = $1
|
||||||
|
AND is_active = true
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) CheckLeagueSupport(ctx context.Context, id int64) (bool, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CheckLeagueSupport, id)
|
||||||
|
var exists bool
|
||||||
|
err := row.Scan(&exists)
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetAllLeagues = `-- name: GetAllLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
FROM leagues
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetAllLeagues(ctx context.Context) ([]League, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllLeagues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []League
|
||||||
|
for rows.Next() {
|
||||||
|
var i League
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.CountryCode,
|
||||||
|
&i.Bet365ID,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetSupportedLeagues = `-- name: GetSupportedLeagues :many
|
||||||
|
SELECT id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
FROM leagues
|
||||||
|
WHERE is_active = true
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetSupportedLeagues(ctx context.Context) ([]League, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetSupportedLeagues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []League
|
||||||
|
for rows.Next() {
|
||||||
|
var i League
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.CountryCode,
|
||||||
|
&i.Bet365ID,
|
||||||
|
&i.IsActive,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const InsertLeague = `-- name: InsertLeague :exec
|
||||||
|
INSERT INTO leagues (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
country_code,
|
||||||
|
bet365_id,
|
||||||
|
is_active
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5
|
||||||
|
)
|
||||||
|
ON CONFLICT (id) DO UPDATE
|
||||||
|
SET name = EXCLUDED.name,
|
||||||
|
country_code = EXCLUDED.country_code,
|
||||||
|
bet365_id = EXCLUDED.bet365_id,
|
||||||
|
is_active = EXCLUDED.is_active
|
||||||
|
`
|
||||||
|
|
||||||
|
type InsertLeagueParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) InsertLeague(ctx context.Context, arg InsertLeagueParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, InsertLeague,
|
||||||
|
arg.ID,
|
||||||
|
arg.Name,
|
||||||
|
arg.CountryCode,
|
||||||
|
arg.Bet365ID,
|
||||||
|
arg.IsActive,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const SetLeagueActive = `-- name: SetLeagueActive :exec
|
||||||
|
UPDATE leagues
|
||||||
|
SET is_active = true
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) SetLeagueActive(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, SetLeagueActive, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateLeague = `-- name: UpdateLeague :exec
|
||||||
|
UPDATE leagues
|
||||||
|
SET name = $1,
|
||||||
|
country_code = $2,
|
||||||
|
bet365_id = $3,
|
||||||
|
is_active = $4
|
||||||
|
WHERE id = $5
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateLeagueParams struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateLeague(ctx context.Context, arg UpdateLeagueParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, UpdateLeague,
|
||||||
|
arg.Name,
|
||||||
|
arg.CountryCode,
|
||||||
|
arg.Bet365ID,
|
||||||
|
arg.IsActive,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -178,15 +178,15 @@ type CustomerWallet struct {
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Text `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
MatchName pgtype.Text `json:"match_name"`
|
MatchName pgtype.Text `json:"match_name"`
|
||||||
HomeTeam pgtype.Text `json:"home_team"`
|
HomeTeam pgtype.Text `json:"home_team"`
|
||||||
AwayTeam pgtype.Text `json:"away_team"`
|
AwayTeam pgtype.Text `json:"away_team"`
|
||||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||||
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
HomeKitImage pgtype.Text `json:"home_kit_image"`
|
||||||
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
AwayKitImage pgtype.Text `json:"away_kit_image"`
|
||||||
LeagueID pgtype.Text `json:"league_id"`
|
LeagueID pgtype.Int4 `json:"league_id"`
|
||||||
LeagueName pgtype.Text `json:"league_name"`
|
LeagueName pgtype.Text `json:"league_name"`
|
||||||
LeagueCc pgtype.Text `json:"league_cc"`
|
LeagueCc pgtype.Text `json:"league_cc"`
|
||||||
StartTime pgtype.Timestamp `json:"start_time"`
|
StartTime pgtype.Timestamp `json:"start_time"`
|
||||||
|
|
@ -201,6 +201,14 @@ type Event struct {
|
||||||
Source pgtype.Text `json:"source"`
|
Source pgtype.Text `json:"source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type League struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
CountryCode pgtype.Text `json:"country_code"`
|
||||||
|
Bet365ID pgtype.Int4 `json:"bet365_id"`
|
||||||
|
IsActive pgtype.Bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
RecipientID int64 `json:"recipient_id"`
|
RecipientID int64 `json:"recipient_id"`
|
||||||
|
|
|
||||||
18
go.mod
18
go.mod
|
|
@ -9,28 +9,28 @@ require (
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
github.com/gofiber/fiber/v2 v2.52.6
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/jackc/pgx/v5 v5.7.4
|
github.com/jackc/pgx/v5 v5.7.4
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/shopspring/decimal v1.4.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
// github.com/stretchr/testify v1.10.0
|
||||||
github.com/swaggo/fiber-swagger v1.3.0
|
github.com/swaggo/fiber-swagger v1.3.0
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
|
github.com/valyala/fasthttp v1.59.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/gorilla/websocket v1.5.3 // indirect
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
// github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
// github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
|
|
@ -38,7 +38,6 @@ require (
|
||||||
github.com/go-openapi/swag v0.23.1 // indirect
|
github.com/go-openapi/swag v0.23.1 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
|
@ -50,12 +49,13 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasthttp v1.59.0
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
golang.org/x/sync v0.12.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -112,12 +112,16 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
||||||
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/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
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=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
|
||||||
|
|
@ -289,3 +289,8 @@ func (c *Config) loadEnv() error {
|
||||||
c.Bet365Token = betToken
|
c.Bet365Token = betToken
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaConfig struct {
|
||||||
|
ChapaPaymentType string `mapstructure:"chapa_payment_type"`
|
||||||
|
ChapaTransferType string `mapstructure:"chapa_transfer_type"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ChapaSecret string
|
ChapaSecret string
|
||||||
|
|
@ -8,14 +11,14 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type InitPaymentRequest struct {
|
type InitPaymentRequest struct {
|
||||||
Amount string `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransferRequest struct {
|
type TransferRequest struct {
|
||||||
|
|
@ -105,3 +108,121 @@ type VerifyTransferResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data TransferVerificationData `json:"data"`
|
Data TransferVerificationData `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaTransactionType struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWebHookTransfer struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
BankId string `json:"bank_id"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
ChapaReference string `json:"chapa_reference"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWebHookPayment struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Mobile interface{} `json:"mobile"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customization struct {
|
||||||
|
Title interface{} `json:"title"`
|
||||||
|
Description interface{} `json:"description"`
|
||||||
|
Logo interface{} `json:"logo"`
|
||||||
|
} `json:"customization"`
|
||||||
|
Meta string `json:"meta"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaWithdrawRequest struct {
|
||||||
|
WalletID int64 `json:"wallet_id"` // add this
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BeneficiaryName string `json:"beneficiary_name"`
|
||||||
|
BankCode string `json:"bank_code"`
|
||||||
|
BranchID int64 `json:"branch_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransferPayload struct {
|
||||||
|
AccountName string
|
||||||
|
AccountNumber string
|
||||||
|
Amount string
|
||||||
|
Currency string
|
||||||
|
BeneficiaryName string
|
||||||
|
TxRef string
|
||||||
|
Reference string
|
||||||
|
BankCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaDepositRequest struct {
|
||||||
|
Amount Currency `json:"amount"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BranchID int64 `json:"branch_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ChapaDepositRequest) Validate() error {
|
||||||
|
if r.Amount <= 0 {
|
||||||
|
return errors.New("amount must be greater than zero")
|
||||||
|
}
|
||||||
|
if r.Currency == "" {
|
||||||
|
return errors.New("currency is required")
|
||||||
|
}
|
||||||
|
if r.PhoneNumber == "" {
|
||||||
|
return errors.New("phone number is required")
|
||||||
|
}
|
||||||
|
if r.BranchID == 0 {
|
||||||
|
return errors.New("branch ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcceptChapaPaymentRequest struct {
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
CallbackUrl string `json:"callback_url"`
|
||||||
|
ReturnUrl string `json:"return_url"`
|
||||||
|
CustomizationTitle string `json:"customization[title]"`
|
||||||
|
CustomizationDescription string `json:"customization[description]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentUrlResponse struct {
|
||||||
|
PaymentURL string `json:"payment_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentUrlResponseWrapper struct {
|
||||||
|
Data ChapaPaymentUrlResponse `json:"data"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaSupportedBanksResponseWrapper struct {
|
||||||
|
Data []ChapaSupportedBank `json:"data"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,10 @@ type ValidInt struct {
|
||||||
Value int
|
Value int
|
||||||
Valid bool
|
Valid bool
|
||||||
}
|
}
|
||||||
|
type ValidInt32 struct {
|
||||||
|
Value int32
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
type ValidString struct {
|
type ValidString struct {
|
||||||
Value string
|
Value string
|
||||||
|
|
@ -48,6 +52,18 @@ func (m Currency) String() string {
|
||||||
return fmt.Sprintf("$%.2f", x)
|
return fmt.Sprintf("$%.2f", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponseWDataFactory[T any] struct {
|
||||||
|
Data T `json:"data"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
|
|
||||||
func CalculateWinnings(amount Currency, totalOdds float32) Currency {
|
func CalculateWinnings(amount Currency, totalOdds float32) Currency {
|
||||||
|
|
||||||
vat := amount.Float32() * 0.15
|
vat := amount.Float32() * 0.15
|
||||||
|
|
|
||||||
|
|
@ -37,15 +37,15 @@ const (
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string
|
ID string
|
||||||
SportID string
|
SportID int32
|
||||||
MatchName string
|
MatchName string
|
||||||
HomeTeam string
|
HomeTeam string
|
||||||
AwayTeam string
|
AwayTeam string
|
||||||
HomeTeamID string
|
HomeTeamID int32
|
||||||
AwayTeamID string
|
AwayTeamID int32
|
||||||
HomeKitImage string
|
HomeKitImage string
|
||||||
AwayKitImage string
|
AwayKitImage string
|
||||||
LeagueID string
|
LeagueID int32
|
||||||
LeagueName string
|
LeagueName string
|
||||||
LeagueCC string
|
LeagueCC string
|
||||||
StartTime string
|
StartTime string
|
||||||
|
|
@ -87,15 +87,15 @@ type BetResult struct {
|
||||||
|
|
||||||
type UpcomingEvent struct {
|
type UpcomingEvent struct {
|
||||||
ID string // Event ID
|
ID string // Event ID
|
||||||
SportID string // Sport ID
|
SportID int32 // Sport ID
|
||||||
MatchName string // Match or event name
|
MatchName string // Match or event name
|
||||||
HomeTeam string // Home team name (if available)
|
HomeTeam string // Home team name (if available)
|
||||||
AwayTeam string // Away team name (can be empty/null)
|
AwayTeam string // Away team name (can be empty/null)
|
||||||
HomeTeamID string // Home team ID
|
HomeTeamID int32 // Home team ID
|
||||||
AwayTeamID string // Away team ID (can be empty/null)
|
AwayTeamID int32 // Away team ID (can be empty/null)
|
||||||
HomeKitImage string // Kit or image for home team (optional)
|
HomeKitImage string // Kit or image for home team (optional)
|
||||||
AwayKitImage string // Kit or image for away team (optional)
|
AwayKitImage string // Kit or image for away team (optional)
|
||||||
LeagueID string // League ID
|
LeagueID int32 // League ID
|
||||||
LeagueName string // League name
|
LeagueName string // League name
|
||||||
LeagueCC string // League country code
|
LeagueCC string // League country code
|
||||||
StartTime time.Time // Converted from "time" field in UNIX format
|
StartTime time.Time // Converted from "time" field in UNIX format
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
// TODO Will make this dynamic by moving into the database
|
type League struct {
|
||||||
|
ID int64
|
||||||
var SupportedLeagues = []int64{
|
Name string
|
||||||
// Football
|
CountryCode string
|
||||||
10041282, //Premier League
|
Bet365ID int32
|
||||||
10083364, //La Liga
|
IsActive bool
|
||||||
10041095, //German Bundesliga
|
|
||||||
10041100, //Ligue 1
|
|
||||||
10041809, //UEFA Champions League
|
|
||||||
10041957, //UEFA Europa League
|
|
||||||
10079560, //UEFA Conference League
|
|
||||||
10047168, // US MLS
|
|
||||||
10044469, // Ethiopian Premier League
|
|
||||||
10050282, //UEFA Nations League
|
|
||||||
10044685, //FIFA Club World Cup
|
|
||||||
10082328, //Kings League World Cup
|
|
||||||
10081269, //CONCACAF Champions Cup
|
|
||||||
10040162, //Asia - World Cup Qualifying
|
|
||||||
10067624, //South America - World Cup Qualifying
|
|
||||||
10067913, // Europe - World Cup Qualifying
|
|
||||||
10067624, // South America - World Cup Qualifying
|
|
||||||
|
|
||||||
10043156, //England FA Cup
|
|
||||||
10042103, //France Cup
|
|
||||||
10041088, //Premier League 2
|
|
||||||
10084250, //Turkiye Super League
|
|
||||||
10041187, //Kenya Super League
|
|
||||||
10041315, //Italian Serie A
|
|
||||||
10041391, //Netherlands Eredivisie
|
|
||||||
10036538, //Spain Segunda
|
|
||||||
10041058, //Denmark Superligaen
|
|
||||||
10077480, //Women’s International
|
|
||||||
10046936, // USA NPSL
|
|
||||||
10085159, //Baller League UK
|
|
||||||
10040601, //Argentina Cup
|
|
||||||
10037440, //Brazil Serie A
|
|
||||||
10043205, //Copa Sudamericana
|
|
||||||
|
|
||||||
10037327, //Austria Landesliga
|
|
||||||
10082020, //USA USL League One Cup
|
|
||||||
10037075, //International Match
|
|
||||||
10046648, //Kenya Cup
|
|
||||||
10040485, //Kenya Super League
|
|
||||||
10041369, //Norway Eliteserien
|
|
||||||
|
|
||||||
// Basketball
|
|
||||||
173998768, //NBA
|
|
||||||
10041830, //NBA
|
|
||||||
10049984, //WNBA
|
|
||||||
10037165, //German Bundesliga
|
|
||||||
10036608, //Italian Lega 1
|
|
||||||
10040795, //EuroLeague
|
|
||||||
10084178, //Kenya Premier League
|
|
||||||
10043548, //International Women
|
|
||||||
|
|
||||||
// Ice Hockey
|
|
||||||
10037477, //NHL
|
|
||||||
10037447, //AHL
|
|
||||||
10074238, // AIHL
|
|
||||||
10069385, //IIHF World Championship
|
|
||||||
|
|
||||||
// Cricket
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -15,10 +14,11 @@ type Market struct {
|
||||||
MarketName string
|
MarketName string
|
||||||
MarketID string
|
MarketID string
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
Odds []json.RawMessage
|
Odds []map[string]interface{}
|
||||||
Name string
|
Name string
|
||||||
Handicap string
|
Handicap string
|
||||||
OddsVal float64
|
OddsVal float64
|
||||||
|
Source string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Odd struct {
|
type Odd struct {
|
||||||
|
|
|
||||||
53
internal/domain/responses.go
Normal file
53
internal/domain/responses.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UnProcessableEntityResponse(c *fiber.Ctx) error {
|
||||||
|
return c.Status(fiber.StatusUnprocessableEntity).JSON(Response{
|
||||||
|
Message: "failed to parse request body",
|
||||||
|
StatusCode: fiber.StatusUnprocessableEntity,
|
||||||
|
Success: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FiberErrorResponse(c *fiber.Ctx, err error) error {
|
||||||
|
var statusCode int
|
||||||
|
var message string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, fiber.ErrNotFound):
|
||||||
|
statusCode = fiber.StatusNotFound
|
||||||
|
message = "resource not found"
|
||||||
|
|
||||||
|
case errors.Is(err, fiber.ErrBadRequest):
|
||||||
|
statusCode = fiber.StatusBadRequest
|
||||||
|
message = "bad request"
|
||||||
|
|
||||||
|
case errors.Is(err, fiber.ErrUnauthorized):
|
||||||
|
statusCode = fiber.StatusUnauthorized
|
||||||
|
message = "unauthorized"
|
||||||
|
|
||||||
|
case errors.Is(err, fiber.ErrForbidden):
|
||||||
|
statusCode = fiber.StatusForbidden
|
||||||
|
message = "forbidden"
|
||||||
|
|
||||||
|
case errors.Is(err, fiber.ErrConflict):
|
||||||
|
statusCode = fiber.StatusConflict
|
||||||
|
message = "conflict occurred"
|
||||||
|
|
||||||
|
default:
|
||||||
|
statusCode = fiber.StatusInternalServerError
|
||||||
|
message = "unexpected server error"
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(statusCode).JSON(fiber.Map{
|
||||||
|
"success": false,
|
||||||
|
"status_code": statusCode,
|
||||||
|
"error": message,
|
||||||
|
"details": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ type BaseResultResponse struct {
|
||||||
Results []json.RawMessage `json:"results"`
|
Results []json.RawMessage `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type League struct {
|
type LeagueRes struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CC string `json:"cc"`
|
CC string `json:"cc"`
|
||||||
|
|
@ -39,14 +39,14 @@ type CommonResultResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type FootballResultResponse struct {
|
type FootballResultResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID string `json:"sport_id"`
|
SportID string `json:"sport_id"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
TimeStatus string `json:"time_status"`
|
TimeStatus string `json:"time_status"`
|
||||||
League League `json:"league"`
|
League LeagueRes `json:"league"`
|
||||||
Home Team `json:"home"`
|
Home Team `json:"home"`
|
||||||
Away Team `json:"away"`
|
Away Team `json:"away"`
|
||||||
SS string `json:"ss"`
|
SS string `json:"ss"`
|
||||||
Scores struct {
|
Scores struct {
|
||||||
FirstHalf Score `json:"1"`
|
FirstHalf Score `json:"1"`
|
||||||
SecondHalf Score `json:"2"`
|
SecondHalf Score `json:"2"`
|
||||||
|
|
@ -78,14 +78,14 @@ type FootballResultResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasketballResultResponse struct {
|
type BasketballResultResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID string `json:"sport_id"`
|
SportID string `json:"sport_id"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
TimeStatus string `json:"time_status"`
|
TimeStatus string `json:"time_status"`
|
||||||
League League `json:"league"`
|
League LeagueRes `json:"league"`
|
||||||
Home Team `json:"home"`
|
Home Team `json:"home"`
|
||||||
Away Team `json:"away"`
|
Away Team `json:"away"`
|
||||||
SS string `json:"ss"`
|
SS string `json:"ss"`
|
||||||
Scores struct {
|
Scores struct {
|
||||||
FirstQuarter Score `json:"1"`
|
FirstQuarter Score `json:"1"`
|
||||||
SecondQuarter Score `json:"2"`
|
SecondQuarter Score `json:"2"`
|
||||||
|
|
@ -125,14 +125,14 @@ type BasketballResultResponse struct {
|
||||||
Bet365ID string `json:"bet365_id"`
|
Bet365ID string `json:"bet365_id"`
|
||||||
}
|
}
|
||||||
type IceHockeyResultResponse struct {
|
type IceHockeyResultResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID string `json:"sport_id"`
|
SportID string `json:"sport_id"`
|
||||||
Time string `json:"time"`
|
Time string `json:"time"`
|
||||||
TimeStatus string `json:"time_status"`
|
TimeStatus string `json:"time_status"`
|
||||||
League League `json:"league"`
|
League LeagueRes `json:"league"`
|
||||||
Home Team `json:"home"`
|
Home Team `json:"home"`
|
||||||
Away Team `json:"away"`
|
Away Team `json:"away"`
|
||||||
SS string `json:"ss"`
|
SS string `json:"ss"`
|
||||||
Scores struct {
|
Scores struct {
|
||||||
FirstPeriod Score `json:"1"`
|
FirstPeriod Score `json:"1"`
|
||||||
SecondPeriod Score `json:"2"`
|
SecondPeriod Score `json:"2"`
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ type CreateTransaction struct {
|
||||||
PaymentOption PaymentOption
|
PaymentOption PaymentOption
|
||||||
FullName string
|
FullName string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
// Payment Details for bank
|
|
||||||
BankCode string
|
BankCode string
|
||||||
BeneficiaryName string
|
BeneficiaryName string
|
||||||
AccountName string
|
AccountName string
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,24 @@ var Environment = map[string]string{
|
||||||
|
|
||||||
func NewLogger(env string, lvl slog.Level) *slog.Logger {
|
func NewLogger(env string, lvl slog.Level) *slog.Logger {
|
||||||
var logHandler slog.Handler
|
var logHandler slog.Handler
|
||||||
|
|
||||||
|
err := os.MkdirAll("logs", os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to create log directory: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile("logs/app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to open log file: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
switch env {
|
switch env {
|
||||||
case "development":
|
case "development":
|
||||||
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
logHandler = slog.NewTextHandler(file, &slog.HandlerOptions{
|
||||||
Level: lvl,
|
Level: lvl,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
logHandler = slog.NewJSONHandler(file, &slog.HandlerOptions{
|
||||||
Level: lvl,
|
Level: lvl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,15 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
|
||||||
|
|
||||||
return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{
|
return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
SportID: pgtype.Text{String: e.SportID, Valid: true},
|
SportID: pgtype.Int4{Int32: e.SportID, Valid: true},
|
||||||
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
|
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
|
||||||
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
||||||
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
||||||
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
|
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
|
||||||
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
|
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
|
||||||
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
|
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
|
||||||
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
|
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
|
||||||
LeagueID: pgtype.Text{String: e.LeagueID, Valid: true},
|
LeagueID: pgtype.Int4{Int32: e.LeagueID, Valid: true},
|
||||||
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
|
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
|
||||||
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
|
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
|
||||||
StartTime: pgtype.Timestamp{Time: parsedTime, Valid: true},
|
StartTime: pgtype.Timestamp{Time: parsedTime, Valid: true},
|
||||||
|
|
@ -46,15 +46,15 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
|
||||||
func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
|
func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
|
||||||
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
|
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
SportID: pgtype.Text{String: e.SportID, Valid: true},
|
SportID: pgtype.Int4{Int32: e.SportID, Valid: true},
|
||||||
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
|
MatchName: pgtype.Text{String: e.MatchName, Valid: true},
|
||||||
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
||||||
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
||||||
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
|
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
|
||||||
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
|
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
|
||||||
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
|
HomeKitImage: pgtype.Text{String: e.HomeKitImage, Valid: true},
|
||||||
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
|
AwayKitImage: pgtype.Text{String: e.AwayKitImage, Valid: true},
|
||||||
LeagueID: pgtype.Text{String: e.LeagueID, Valid: true},
|
LeagueID: pgtype.Int4{Int32: e.LeagueID, Valid: true},
|
||||||
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
|
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
|
||||||
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
|
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
|
||||||
StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true},
|
StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true},
|
||||||
|
|
@ -75,15 +75,15 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
|
||||||
for i, e := range events {
|
for i, e := range events {
|
||||||
upcomingEvents[i] = domain.UpcomingEvent{
|
upcomingEvents[i] = domain.UpcomingEvent{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
SportID: e.SportID.String,
|
SportID: e.SportID.Int32,
|
||||||
MatchName: e.MatchName.String,
|
MatchName: e.MatchName.String,
|
||||||
HomeTeam: e.HomeTeam.String,
|
HomeTeam: e.HomeTeam.String,
|
||||||
AwayTeam: e.AwayTeam.String,
|
AwayTeam: e.AwayTeam.String,
|
||||||
HomeTeamID: e.HomeTeamID.String,
|
HomeTeamID: e.HomeTeamID.Int32,
|
||||||
AwayTeamID: e.AwayTeamID.String,
|
AwayTeamID: e.AwayTeamID.Int32,
|
||||||
HomeKitImage: e.HomeKitImage.String,
|
HomeKitImage: e.HomeKitImage.String,
|
||||||
AwayKitImage: e.AwayKitImage.String,
|
AwayKitImage: e.AwayKitImage.String,
|
||||||
LeagueID: e.LeagueID.String,
|
LeagueID: e.LeagueID.Int32,
|
||||||
LeagueName: e.LeagueName.String,
|
LeagueName: e.LeagueName.String,
|
||||||
LeagueCC: e.LeagueCc.String,
|
LeagueCC: e.LeagueCc.String,
|
||||||
StartTime: e.StartTime.Time.UTC(),
|
StartTime: e.StartTime.Time.UTC(),
|
||||||
|
|
@ -106,15 +106,15 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
|
||||||
for i, e := range events {
|
for i, e := range events {
|
||||||
upcomingEvents[i] = domain.UpcomingEvent{
|
upcomingEvents[i] = domain.UpcomingEvent{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
SportID: e.SportID.String,
|
SportID: e.SportID.Int32,
|
||||||
MatchName: e.MatchName.String,
|
MatchName: e.MatchName.String,
|
||||||
HomeTeam: e.HomeTeam.String,
|
HomeTeam: e.HomeTeam.String,
|
||||||
AwayTeam: e.AwayTeam.String,
|
AwayTeam: e.AwayTeam.String,
|
||||||
HomeTeamID: e.HomeTeamID.String,
|
HomeTeamID: e.HomeTeamID.Int32,
|
||||||
AwayTeamID: e.AwayTeamID.String,
|
AwayTeamID: e.AwayTeamID.Int32,
|
||||||
HomeKitImage: e.HomeKitImage.String,
|
HomeKitImage: e.HomeKitImage.String,
|
||||||
AwayKitImage: e.AwayKitImage.String,
|
AwayKitImage: e.AwayKitImage.String,
|
||||||
LeagueID: e.LeagueID.String,
|
LeagueID: e.LeagueID.Int32,
|
||||||
LeagueName: e.LeagueName.String,
|
LeagueName: e.LeagueName.String,
|
||||||
LeagueCC: e.LeagueCc.String,
|
LeagueCC: e.LeagueCc.String,
|
||||||
StartTime: e.StartTime.Time.UTC(),
|
StartTime: e.StartTime.Time.UTC(),
|
||||||
|
|
@ -160,15 +160,15 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
||||||
for i, e := range events {
|
for i, e := range events {
|
||||||
upcomingEvents[i] = domain.UpcomingEvent{
|
upcomingEvents[i] = domain.UpcomingEvent{
|
||||||
ID: e.ID,
|
ID: e.ID,
|
||||||
SportID: e.SportID.String,
|
SportID: e.SportID.Int32,
|
||||||
MatchName: e.MatchName.String,
|
MatchName: e.MatchName.String,
|
||||||
HomeTeam: e.HomeTeam.String,
|
HomeTeam: e.HomeTeam.String,
|
||||||
AwayTeam: e.AwayTeam.String,
|
AwayTeam: e.AwayTeam.String,
|
||||||
HomeTeamID: e.HomeTeamID.String,
|
HomeTeamID: e.HomeTeamID.Int32,
|
||||||
AwayTeamID: e.AwayTeamID.String,
|
AwayTeamID: e.AwayTeamID.Int32,
|
||||||
HomeKitImage: e.HomeKitImage.String,
|
HomeKitImage: e.HomeKitImage.String,
|
||||||
AwayKitImage: e.AwayKitImage.String,
|
AwayKitImage: e.AwayKitImage.String,
|
||||||
LeagueID: e.LeagueID.String,
|
LeagueID: e.LeagueID.Int32,
|
||||||
LeagueName: e.LeagueName.String,
|
LeagueName: e.LeagueName.String,
|
||||||
LeagueCC: e.LeagueCc.String,
|
LeagueCC: e.LeagueCc.String,
|
||||||
StartTime: e.StartTime.Time.UTC(),
|
StartTime: e.StartTime.Time.UTC(),
|
||||||
|
|
@ -208,15 +208,15 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
|
||||||
|
|
||||||
return domain.UpcomingEvent{
|
return domain.UpcomingEvent{
|
||||||
ID: event.ID,
|
ID: event.ID,
|
||||||
SportID: event.SportID.String,
|
SportID: event.SportID.Int32,
|
||||||
MatchName: event.MatchName.String,
|
MatchName: event.MatchName.String,
|
||||||
HomeTeam: event.HomeTeam.String,
|
HomeTeam: event.HomeTeam.String,
|
||||||
AwayTeam: event.AwayTeam.String,
|
AwayTeam: event.AwayTeam.String,
|
||||||
HomeTeamID: event.HomeTeamID.String,
|
HomeTeamID: event.HomeTeamID.Int32,
|
||||||
AwayTeamID: event.AwayTeamID.String,
|
AwayTeamID: event.AwayTeamID.Int32,
|
||||||
HomeKitImage: event.HomeKitImage.String,
|
HomeKitImage: event.HomeKitImage.String,
|
||||||
AwayKitImage: event.AwayKitImage.String,
|
AwayKitImage: event.AwayKitImage.String,
|
||||||
LeagueID: event.LeagueID.String,
|
LeagueID: event.LeagueID.Int32,
|
||||||
LeagueName: event.LeagueName.String,
|
LeagueName: event.LeagueName.String,
|
||||||
LeagueCC: event.LeagueCc.String,
|
LeagueCC: event.LeagueCc.String,
|
||||||
StartTime: event.StartTime.Time.UTC(),
|
StartTime: event.StartTime.Time.UTC(),
|
||||||
|
|
|
||||||
75
internal/repository/league.go
Normal file
75
internal/repository/league.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Store) SaveLeague(ctx context.Context, l domain.League) error {
|
||||||
|
return s.queries.InsertLeague(ctx, dbgen.InsertLeagueParams{
|
||||||
|
ID: l.ID,
|
||||||
|
Name: l.Name,
|
||||||
|
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
|
||||||
|
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
|
||||||
|
IsActive: pgtype.Bool{Bool: l.IsActive, Valid: true},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetSupportedLeagues(ctx context.Context) ([]domain.League, error) {
|
||||||
|
leagues, err := s.queries.GetSupportedLeagues(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedLeagues := make([]domain.League, len(leagues))
|
||||||
|
for i, league := range leagues {
|
||||||
|
supportedLeagues[i] = domain.League{
|
||||||
|
ID: league.ID,
|
||||||
|
Name: league.Name,
|
||||||
|
CountryCode: league.CountryCode.String,
|
||||||
|
Bet365ID: league.Bet365ID.Int32,
|
||||||
|
IsActive: league.IsActive.Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supportedLeagues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetAllLeagues(ctx context.Context) ([]domain.League, error) {
|
||||||
|
l, err := s.queries.GetAllLeagues(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leagues := make([]domain.League, len(l))
|
||||||
|
for i, league := range l {
|
||||||
|
leagues[i] = domain.League{
|
||||||
|
ID: league.ID,
|
||||||
|
Name: league.Name,
|
||||||
|
CountryCode: league.CountryCode.String,
|
||||||
|
Bet365ID: league.Bet365ID.Int32,
|
||||||
|
IsActive: league.IsActive.Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return leagues, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CheckLeagueSupport(ctx context.Context, leagueID int64) (bool, error) {
|
||||||
|
return s.queries.CheckLeagueSupport(ctx, leagueID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) SetLeagueActive(ctx context.Context, leagueId int64) error {
|
||||||
|
return s.queries.SetLeagueActive(ctx, leagueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: update based on id, no need for the entire league (same as the set active one)
|
||||||
|
func (s *Store) SetLeagueInActive(ctx context.Context, l domain.League) error {
|
||||||
|
return s.queries.UpdateLeague(ctx, dbgen.UpdateLeagueParams{
|
||||||
|
Name: l.Name,
|
||||||
|
CountryCode: pgtype.Text{String: l.CountryCode, Valid: true},
|
||||||
|
Bet365ID: pgtype.Int4{Int32: l.Bet365ID, Valid: true},
|
||||||
|
IsActive: pgtype.Bool{Bool: false, Valid: true},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -17,15 +17,19 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, raw := range m.Odds {
|
for _, item := range m.Odds {
|
||||||
var item map[string]interface{}
|
var name string
|
||||||
if err := json.Unmarshal(raw, &item); err != nil {
|
var oddsVal float64
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := getString(item["name"])
|
if m.Source == "bwin" {
|
||||||
|
nameValue := getMap(item["name"])
|
||||||
|
name = getString(nameValue["value"])
|
||||||
|
oddsVal = getFloat(item["odds"])
|
||||||
|
} else {
|
||||||
|
name = getString(item["name"])
|
||||||
|
oddsVal = getConvertedFloat(item["odds"])
|
||||||
|
}
|
||||||
handicap := getString(item["handicap"])
|
handicap := getString(item["handicap"])
|
||||||
oddsVal := getFloat(item["odds"])
|
|
||||||
|
|
||||||
rawOddsBytes, _ := json.Marshal(m.Odds)
|
rawOddsBytes, _ := json.Marshal(m.Odds)
|
||||||
|
|
||||||
|
|
@ -43,7 +47,7 @@ func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
|
||||||
Category: pgtype.Text{Valid: false},
|
Category: pgtype.Text{Valid: false},
|
||||||
RawOdds: rawOddsBytes,
|
RawOdds: rawOddsBytes,
|
||||||
IsActive: pgtype.Bool{Bool: true, Valid: true},
|
IsActive: pgtype.Bool{Bool: true, Valid: true},
|
||||||
Source: pgtype.Text{String: "b365api", Valid: true},
|
Source: pgtype.Text{String: m.Source, Valid: true},
|
||||||
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
FetchedAt: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,23 +89,6 @@ func writeFailedMarketLog(m domain.Market, err error) error {
|
||||||
return writeErr
|
return writeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func getString(v interface{}) string {
|
|
||||||
if s, ok := v.(string); ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFloat(v interface{}) float64 {
|
|
||||||
if s, ok := v.(string); ok {
|
|
||||||
f, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err == nil {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
|
||||||
odds, err := s.queries.GetPrematchOdds(ctx)
|
odds, err := s.queries.GetPrematchOdds(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -286,3 +273,34 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
|
||||||
|
|
||||||
return domainOdds, nil
|
return domainOdds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getString(v interface{}) string {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConvertedFloat(v interface{}) float64 {
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err == nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFloat(v interface{}) float64 {
|
||||||
|
if n, ok := v.(float64); ok {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMap(v interface{}) map[string]interface{} {
|
||||||
|
if m, ok := v.(map[string]interface{}); ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -39,3 +40,12 @@ func OpenDB(url string) (*pgxpool.Pool, func(), error) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) BeginTx(ctx context.Context) (*dbgen.Queries, pgx.Tx, error) {
|
||||||
|
tx, err := s.conn.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
q := s.queries.WithTx(tx)
|
||||||
|
return q, tx, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,11 @@ func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketI
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.CreateBetOutcome{}, err
|
return domain.CreateBetOutcome{}, err
|
||||||
}
|
}
|
||||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return domain.CreateBetOutcome{}, err
|
|
||||||
}
|
|
||||||
newOutcome := domain.CreateBetOutcome{
|
newOutcome := domain.CreateBetOutcome{
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
OddID: oddID,
|
OddID: oddID,
|
||||||
MarketID: marketID,
|
MarketID: marketID,
|
||||||
SportID: sportID,
|
SportID: int64(event.SportID),
|
||||||
HomeTeamName: event.HomeTeam,
|
HomeTeamName: event.HomeTeam,
|
||||||
AwayTeamName: event.AwayTeam,
|
AwayTeamName: event.AwayTeam,
|
||||||
MarketName: odds.MarketName,
|
MarketName: odds.MarketName,
|
||||||
|
|
@ -287,7 +283,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportID, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
|
func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, sportID int32, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) {
|
||||||
|
|
||||||
var newOdds []domain.CreateBetOutcome
|
var newOdds []domain.CreateBetOutcome
|
||||||
var totalOdds float32 = 1
|
var totalOdds float32 = 1
|
||||||
|
|
@ -337,11 +333,6 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
s.logger.Error("Failed to parse odd", "error", err)
|
s.logger.Error("Failed to parse odd", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sportID, err := strconv.ParseInt(sportID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to get sport id", "error", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
eventID, err := strconv.ParseInt(eventID, 10, 64)
|
eventID, err := strconv.ParseInt(eventID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get event id", "error", err)
|
s.logger.Error("Failed to get event id", "error", err)
|
||||||
|
|
@ -365,7 +356,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
OddID: oddID,
|
OddID: oddID,
|
||||||
MarketID: marketID,
|
MarketID: marketID,
|
||||||
SportID: sportID,
|
SportID: int64(sportID),
|
||||||
HomeTeamName: HomeTeam,
|
HomeTeamName: HomeTeam,
|
||||||
AwayTeamName: AwayTeam,
|
AwayTeamName: AwayTeam,
|
||||||
MarketName: marketName,
|
MarketName: marketName,
|
||||||
|
|
@ -388,7 +379,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
||||||
return newOdds, totalOdds, nil
|
return newOdds, totalOdds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidString, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
|
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
|
||||||
|
|
||||||
// Get a unexpired event id
|
// Get a unexpired event id
|
||||||
|
|
||||||
|
|
|
||||||
126
internal/services/chapa/client.go
Normal file
126
internal/services/chapa/client.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package chapa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChapaClient interface {
|
||||||
|
IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error)
|
||||||
|
InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error)
|
||||||
|
FetchBanks() ([]domain.ChapaSupportedBank, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
BaseURL string
|
||||||
|
SecretKey string
|
||||||
|
HTTPClient *http.Client
|
||||||
|
UserAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(baseURL, secretKey string) *Client {
|
||||||
|
return &Client{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
UserAgent: "FortuneBet/1.0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error) {
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to serialize payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+"/transfers", bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to create HTTP 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 false, fmt.Errorf("chapa HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("chapa error: status %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// service/chapa_service.go
|
||||||
|
func (c *Client) InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error) {
|
||||||
|
payloadBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to serialize payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+"/transaction/initialize", bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create HTTP 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 "", fmt.Errorf("chapa HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return "", fmt.Errorf("chapa error: status %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Data struct {
|
||||||
|
CheckoutURL string `json:"checkout_url"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &response); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse chapa response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.CheckoutURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FetchBanks() ([]domain.ChapaSupportedBank, error) {
|
||||||
|
req, _ := http.NewRequest("GET", c.BaseURL+"/banks", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.SecretKey)
|
||||||
|
fmt.Printf("\n\nbase URL is: %s\n\n", c.BaseURL)
|
||||||
|
|
||||||
|
res, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data []domain.ChapaSupportedBank `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nclient fetched banks: %+v\n\n", resp.Data)
|
||||||
|
|
||||||
|
return resp.Data, nil
|
||||||
|
}
|
||||||
15
internal/services/chapa/port.go
Normal file
15
internal/services/chapa/port.go
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package chapa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChapaPort interface {
|
||||||
|
HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error
|
||||||
|
HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error
|
||||||
|
WithdrawUsingChapa(ctx context.Context, userID int64, req domain.ChapaWithdrawRequest) error
|
||||||
|
DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error)
|
||||||
|
GetSupportedBanks() ([]domain.ChapaSupportedBank, error)
|
||||||
|
}
|
||||||
351
internal/services/chapa/service.go
Normal file
351
internal/services/chapa/service.go
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
package chapa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
// "log/slog"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
transactionStore transaction.TransactionStore
|
||||||
|
walletStore wallet.WalletStore
|
||||||
|
userStore user.UserStore
|
||||||
|
referralStore referralservice.ReferralStore
|
||||||
|
branchStore branch.BranchStore
|
||||||
|
chapaClient ChapaClient
|
||||||
|
config *config.Config
|
||||||
|
// logger *slog.Logger
|
||||||
|
store *repository.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(
|
||||||
|
txStore transaction.TransactionStore,
|
||||||
|
walletStore wallet.WalletStore,
|
||||||
|
userStore user.UserStore,
|
||||||
|
referralStore referralservice.ReferralStore,
|
||||||
|
branchStore branch.BranchStore,
|
||||||
|
chapaClient ChapaClient,
|
||||||
|
store *repository.Store,
|
||||||
|
) *Service {
|
||||||
|
return &Service{
|
||||||
|
transactionStore: txStore,
|
||||||
|
walletStore: walletStore,
|
||||||
|
userStore: userStore,
|
||||||
|
referralStore: referralStore,
|
||||||
|
branchStore: branchStore,
|
||||||
|
chapaClient: chapaClient,
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
// Use your services normally (they don’t use the transaction, unless you wire `q`)
|
||||||
|
referenceID, err := strconv.ParseInt(req.Reference, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid reference ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return fmt.Errorf("transaction with ID %d not found", referenceID)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if txn.Verified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookAmount, _ := decimal.NewFromString(req.Amount)
|
||||||
|
storedAmount, _ := decimal.NewFromString(txn.Amount.String())
|
||||||
|
if !webhookAmount.Equal(storedAmount) {
|
||||||
|
return fmt.Errorf("amount mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.Verified = true
|
||||||
|
if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, txn.Verified, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
if req.Status != "success" {
|
||||||
|
return fmt.Errorf("payment status not successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Parse reference ID
|
||||||
|
referenceID, err := strconv.ParseInt(req.TxRef, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid tx_ref: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fetch transaction
|
||||||
|
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return fmt.Errorf("transaction with ID %d not found", referenceID)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if txn.Verified {
|
||||||
|
return nil // already processed
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookAmount, _ := strconv.ParseFloat(req.Amount, 32)
|
||||||
|
if webhookAmount < float64(txn.Amount) {
|
||||||
|
return fmt.Errorf("webhook amount is less than expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fetch wallet
|
||||||
|
wallet, err := s.walletStore.GetWalletByID(ctx, txn.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Update wallet balance
|
||||||
|
newBalance := wallet.Balance + txn.Amount
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.ID, newBalance); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Mark transaction as verified
|
||||||
|
if err := s.transactionStore.UpdateTransactionVerified(ctx, txn.ID, true, txn.ApprovedBy.Value, txn.ApproverName.Value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Check & Create Referral
|
||||||
|
stats, err := s.referralStore.GetReferralStats(ctx, strconv.FormatInt(wallet.UserID, 10))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if stats == nil {
|
||||||
|
if err := s.referralStore.CreateReferral(ctx, wallet.UserID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) WithdrawUsingChapa(ctx context.Context, userID int64, req domain.ChapaWithdrawRequest) error {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
// Get the requesting user
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("user not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
branch, err := s.branchStore.GetBranchByID(ctx, req.BranchID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetWallet *domain.Wallet
|
||||||
|
for _, w := range wallets {
|
||||||
|
if w.ID == req.WalletID {
|
||||||
|
targetWallet = &w
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetWallet == nil {
|
||||||
|
return fmt.Errorf("no wallet found with the specified ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !targetWallet.IsWithdraw || !targetWallet.IsActive {
|
||||||
|
return fmt.Errorf("wallet not eligible for withdrawal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetWallet.Balance < domain.Currency(req.Amount) {
|
||||||
|
return fmt.Errorf("insufficient balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
txID := uuid.New().String()
|
||||||
|
|
||||||
|
payload := domain.ChapaTransferPayload{
|
||||||
|
AccountName: req.AccountName,
|
||||||
|
AccountNumber: req.AccountNumber,
|
||||||
|
Amount: strconv.FormatInt(req.Amount, 10),
|
||||||
|
Currency: req.Currency,
|
||||||
|
BeneficiaryName: req.BeneficiaryName,
|
||||||
|
TxRef: txID,
|
||||||
|
Reference: txID,
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := s.chapaClient.IssuePayment(ctx, payload)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return fmt.Errorf("chapa transfer failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create transaction using user and wallet info
|
||||||
|
_, err = s.transactionStore.CreateTransaction(ctx, domain.CreateTransaction{
|
||||||
|
Amount: domain.Currency(req.Amount),
|
||||||
|
Type: domain.TransactionType(domain.TRANSACTION_CASHOUT),
|
||||||
|
ReferenceNumber: txID,
|
||||||
|
AccountName: req.AccountName,
|
||||||
|
AccountNumber: req.AccountNumber,
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
BeneficiaryName: req.BeneficiaryName,
|
||||||
|
PaymentOption: domain.PaymentOption(domain.BANK),
|
||||||
|
BranchID: req.BranchID,
|
||||||
|
BranchName: branch.Name,
|
||||||
|
BranchLocation: branch.Location,
|
||||||
|
// CashierID: user.ID,
|
||||||
|
// CashierName: user.FullName,
|
||||||
|
FullName: user.FirstName + " " + user.LastName,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
CompanyID: branch.CompanyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBalance := domain.Currency(req.Amount)
|
||||||
|
err = s.walletStore.UpdateBalance(ctx, targetWallet.ID, newBalance)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error) {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
branch, err := s.branchStore.GetBranchByID(ctx, req.BranchID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
txID := uuid.New().String()
|
||||||
|
|
||||||
|
_, err = s.transactionStore.CreateTransaction(ctx, domain.CreateTransaction{
|
||||||
|
Amount: req.Amount,
|
||||||
|
Type: domain.TransactionType(domain.TRANSACTION_DEPOSIT),
|
||||||
|
ReferenceNumber: txID,
|
||||||
|
BranchID: req.BranchID,
|
||||||
|
BranchName: branch.Name,
|
||||||
|
BranchLocation: branch.Location,
|
||||||
|
FullName: user.FirstName + " " + user.LastName,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
CompanyID: branch.CompanyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user details for Chapa payment
|
||||||
|
userInfo, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Chapa InitPaymentRequest (matches Chapa API)
|
||||||
|
paymentReq := domain.InitPaymentRequest{
|
||||||
|
Amount: req.Amount,
|
||||||
|
Currency: req.Currency,
|
||||||
|
Email: userInfo.Email,
|
||||||
|
FirstName: userInfo.FirstName,
|
||||||
|
LastName: userInfo.LastName,
|
||||||
|
TxRef: txID,
|
||||||
|
CallbackURL: s.config.CHAPA_CALLBACK_URL,
|
||||||
|
ReturnURL: s.config.CHAPA_RETURN_URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Chapa to initialize payment
|
||||||
|
paymentURL, err := s.chapaClient.InitPayment(ctx, paymentReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit DB transaction
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSupportedBanks() ([]domain.ChapaSupportedBank, error) {
|
||||||
|
banks, err := s.chapaClient.FetchBanks()
|
||||||
|
fmt.Printf("\n\nfetched banks: %+v\n\n", banks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add formatting logic (same as in original controller)
|
||||||
|
for i := range banks {
|
||||||
|
if banks[i].IsMobilemoney != nil && *(banks[i].IsMobilemoney) == 1 {
|
||||||
|
banks[i].AcctNumberRegex = "/^09[0-9]{8}$/"
|
||||||
|
banks[i].ExampleValue = "0952097177"
|
||||||
|
} else {
|
||||||
|
switch banks[i].AcctLength {
|
||||||
|
case 8:
|
||||||
|
banks[i].ExampleValue = "16967608"
|
||||||
|
case 13:
|
||||||
|
banks[i].ExampleValue = "1000222215735"
|
||||||
|
case 14:
|
||||||
|
banks[i].ExampleValue = "01320089280800"
|
||||||
|
case 16:
|
||||||
|
banks[i].ExampleValue = "1000222215735123"
|
||||||
|
}
|
||||||
|
banks[i].AcctNumberRegex = formatRegex(banks[i].AcctLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return banks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatRegex(length int) string {
|
||||||
|
return fmt.Sprintf("/^[0-9]{%d}$/", length)
|
||||||
|
}
|
||||||
|
|
@ -47,7 +47,6 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
|
||||||
s.fetchLiveEvents(ctx, url.name, url.source)
|
s.fetchLiveEvents(ctx, url.name, url.source)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +74,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
|
||||||
events := []domain.Event{}
|
events := []domain.Event{}
|
||||||
switch source {
|
switch source {
|
||||||
case "bet365":
|
case "bet365":
|
||||||
events = handleBet365prematch(body, sportID)
|
events = handleBet365prematch(body, sportID, source)
|
||||||
case "betfair":
|
case "betfair":
|
||||||
events = handleBetfairprematch(body, sportID, source)
|
events = handleBetfairprematch(body, sportID, source)
|
||||||
case "1xbet":
|
case "1xbet":
|
||||||
|
|
@ -97,7 +96,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleBet365prematch(body []byte, sportID int) []domain.Event {
|
func handleBet365prematch(body []byte, sportID int, source string) []domain.Event {
|
||||||
var data struct {
|
var data struct {
|
||||||
Success int `json:"success"`
|
Success int `json:"success"`
|
||||||
Results [][]map[string]interface{} `json:"results"`
|
Results [][]map[string]interface{} `json:"results"`
|
||||||
|
|
@ -105,7 +104,7 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
|
||||||
|
|
||||||
events := []domain.Event{}
|
events := []domain.Event{}
|
||||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
|
fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,24 +116,24 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
|
||||||
|
|
||||||
event := domain.Event{
|
event := domain.Event{
|
||||||
ID: getString(ev["ID"]),
|
ID: getString(ev["ID"]),
|
||||||
SportID: fmt.Sprintf("%d", sportID),
|
SportID: int32(sportID),
|
||||||
MatchName: getString(ev["NA"]),
|
MatchName: getString(ev["NA"]),
|
||||||
Score: getString(ev["SS"]),
|
Score: getString(ev["SS"]),
|
||||||
MatchMinute: getInt(ev["TM"]),
|
MatchMinute: getInt(ev["TM"]),
|
||||||
TimerStatus: getString(ev["TT"]),
|
TimerStatus: getString(ev["TT"]),
|
||||||
HomeTeamID: getString(ev["HT"]),
|
HomeTeamID: getInt32(ev["HT"]),
|
||||||
AwayTeamID: getString(ev["AT"]),
|
AwayTeamID: getInt32(ev["AT"]),
|
||||||
HomeKitImage: getString(ev["K1"]),
|
HomeKitImage: getString(ev["K1"]),
|
||||||
AwayKitImage: getString(ev["K2"]),
|
AwayKitImage: getString(ev["K2"]),
|
||||||
LeagueName: getString(ev["CT"]),
|
LeagueName: getString(ev["CT"]),
|
||||||
LeagueID: getString(ev["C2"]),
|
LeagueID: getInt32(ev["C2"]),
|
||||||
LeagueCC: getString(ev["CB"]),
|
LeagueCC: getString(ev["CB"]),
|
||||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||||
IsLive: true,
|
IsLive: true,
|
||||||
Status: "live",
|
Status: "live",
|
||||||
MatchPeriod: getInt(ev["MD"]),
|
MatchPeriod: getInt(ev["MD"]),
|
||||||
AddedTime: getInt(ev["TA"]),
|
AddedTime: getInt(ev["TA"]),
|
||||||
Source: "bet365",
|
Source: source,
|
||||||
}
|
}
|
||||||
|
|
||||||
events = append(events, event)
|
events = append(events, event)
|
||||||
|
|
@ -152,23 +151,20 @@ func handleBetfairprematch(body []byte, sportID int, source string) []domain.Eve
|
||||||
|
|
||||||
events := []domain.Event{}
|
events := []domain.Event{}
|
||||||
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
|
fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ev := range data.Results {
|
for _, ev := range data.Results {
|
||||||
homeRaw, _ := ev["home"].(map[string]interface{})
|
homeRaw, _ := ev["home"].(map[string]interface{})
|
||||||
homeId, _ := homeRaw["id"].(string)
|
|
||||||
|
|
||||||
awayRaw, _ := ev["home"].(map[string]interface{})
|
awayRaw, _ := ev["home"].(map[string]interface{})
|
||||||
awayId, _ := awayRaw["id"].(string)
|
|
||||||
|
|
||||||
event := domain.Event{
|
event := domain.Event{
|
||||||
ID: getString(ev["id"]),
|
ID: getString(ev["id"]),
|
||||||
SportID: fmt.Sprintf("%d", sportID),
|
SportID: int32(sportID),
|
||||||
TimerStatus: getString(ev["time_status"]),
|
TimerStatus: getString(ev["time_status"]),
|
||||||
HomeTeamID: homeId,
|
HomeTeamID: getInt32(homeRaw["id"]),
|
||||||
AwayTeamID: awayId,
|
AwayTeamID: getInt32(awayRaw["id"]),
|
||||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||||
IsLive: true,
|
IsLive: true,
|
||||||
Status: "live",
|
Status: "live",
|
||||||
|
|
@ -221,8 +217,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
||||||
log.Printf("Sport ID %d", sportID)
|
log.Printf("Sport ID %d", sportID)
|
||||||
for page <= totalPages {
|
for page <= totalPages {
|
||||||
page = page + 1
|
page = page + 1
|
||||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
url := fmt.Sprintf(url, sportID, s.token, page)
|
||||||
log.Printf("📡 Fetching data for sport %d at page %d", sportID, page)
|
log.Printf("📡 Fetching data from %s - sport %d, for event data page %d", source, sportID, page)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
|
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
|
||||||
|
|
@ -246,33 +242,35 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
||||||
// continue
|
// continue
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
// doesn't make sense to save and check back to back, but for now it can be here
|
||||||
// // fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
|
s.store.SaveLeague(ctx, domain.League{
|
||||||
// _, err = fmt.Fprintf(b, "Skipped league %s (%d) in sport %d\n", ev.League.Name, leagueID, sportID)
|
ID: leagueID,
|
||||||
// if err != nil {
|
Name: ev.League.Name,
|
||||||
// fmt.Printf(" Error while logging skipped league")
|
IsActive: true,
|
||||||
// }
|
})
|
||||||
// skippedLeague = append(skippedLeague, ev.League.Name)
|
|
||||||
// continue
|
if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
|
||||||
// }
|
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
event := domain.UpcomingEvent{
|
event := domain.UpcomingEvent{
|
||||||
ID: ev.ID,
|
ID: ev.ID,
|
||||||
SportID: ev.SportID,
|
SportID: convertInt32(ev.SportID),
|
||||||
MatchName: "",
|
MatchName: "",
|
||||||
HomeTeam: ev.Home.Name,
|
HomeTeam: ev.Home.Name,
|
||||||
AwayTeam: "", // handle nil safely
|
AwayTeam: "", // handle nil safely
|
||||||
HomeTeamID: ev.Home.ID,
|
HomeTeamID: convertInt32(ev.Home.ID),
|
||||||
AwayTeamID: "",
|
AwayTeamID: 0,
|
||||||
HomeKitImage: "",
|
HomeKitImage: "",
|
||||||
AwayKitImage: "",
|
AwayKitImage: "",
|
||||||
LeagueID: ev.League.ID,
|
LeagueID: convertInt32(ev.League.ID),
|
||||||
LeagueName: ev.League.Name,
|
LeagueName: ev.League.Name,
|
||||||
LeagueCC: "",
|
LeagueCC: "",
|
||||||
StartTime: time.Unix(startUnix, 0).UTC(),
|
StartTime: time.Unix(startUnix, 0).UTC(),
|
||||||
|
|
@ -281,7 +279,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
||||||
|
|
||||||
if ev.Away != nil {
|
if ev.Away != nil {
|
||||||
event.AwayTeam = ev.Away.Name
|
event.AwayTeam = ev.Away.Name
|
||||||
event.AwayTeamID = ev.Away.ID
|
event.AwayTeamID = convertInt32(ev.Away.ID)
|
||||||
event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
|
event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,6 +317,20 @@ func getInt(v interface{}) int {
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getInt32(v interface{}) int32 {
|
||||||
|
if n, err := strconv.Atoi(getString(v)); err == nil {
|
||||||
|
return int32(n)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertInt32(num string) int32 {
|
||||||
|
if n, err := strconv.Atoi(num); err == nil {
|
||||||
|
return int32(n)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
|
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
|
||||||
return s.store.GetAllUpcomingEvents(ctx)
|
return s.store.GetAllUpcomingEvents(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
internal/services/league/port.go
Normal file
12
internal/services/league/port.go
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package league
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
GetAllLeagues(ctx context.Context) ([]domain.League, error)
|
||||||
|
SetLeagueActive(ctx context.Context, leagueId int64) error
|
||||||
|
}
|
||||||
26
internal/services/league/service.go
Normal file
26
internal/services/league/service.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package league
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
store *repository.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(store *repository.Store) Service {
|
||||||
|
return &service{
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) GetAllLeagues(ctx context.Context) ([]domain.League, error) {
|
||||||
|
return s.store.GetAllLeagues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) SetLeagueActive(ctx context.Context, leagueId int64) error {
|
||||||
|
return s.store.SetLeagueActive(ctx, leagueId)
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
|
@ -35,6 +36,38 @@ func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *Serv
|
||||||
|
|
||||||
// TODO Add the optimization to get 10 events at the same time
|
// TODO Add the optimization to get 10 events at the same time
|
||||||
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errChan := make(chan error, 2)
|
||||||
|
// wg.Add(2)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
// go func() {
|
||||||
|
// defer wg.Done()
|
||||||
|
// if err := s.fetchBet365Odds(ctx); err != nil {
|
||||||
|
// errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := s.fetchBwinOdds(ctx); err != nil {
|
||||||
|
errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for err := range errChan {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
|
||||||
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
|
log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
|
||||||
|
|
@ -95,6 +128,180 @@ func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
|
||||||
|
// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
|
||||||
|
// so instead of having event and odds fetched separetly event will also be fetched along with the odds
|
||||||
|
sportIds := []int{4, 12, 7}
|
||||||
|
for _, sportId := range sportIds {
|
||||||
|
url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Success int `json:"success"`
|
||||||
|
Results []map[string]interface{} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
|
fmt.Printf("Decode failed for sport_id=%d\nRaw: %s\n", sportId, string(body))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, res := range data.Results {
|
||||||
|
if getInt(res["Id"]) == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event := domain.Event{
|
||||||
|
ID: strconv.Itoa(getInt(res["Id"])),
|
||||||
|
SportID: int32(getInt(res["SportId"])),
|
||||||
|
LeagueID: int32(getInt(res["LeagueId"])),
|
||||||
|
LeagueName: getString(res["Leaguename"]),
|
||||||
|
HomeTeam: getString(res["HomeTeam"]),
|
||||||
|
HomeTeamID: int32(getInt(res["HomeTeamId"])),
|
||||||
|
AwayTeam: getString(res["AwayTeam"]),
|
||||||
|
AwayTeamID: int32(getInt(res["AwayTeamId"])),
|
||||||
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||||
|
TimerStatus: "1",
|
||||||
|
IsLive: true,
|
||||||
|
Status: "live",
|
||||||
|
Source: "bwin",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.SaveEvent(ctx, event); err != nil {
|
||||||
|
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, market := range []string{"Markets, optionMarkets"} {
|
||||||
|
for _, m := range getMapArray(res[market]) {
|
||||||
|
name := getMap(m["name"])
|
||||||
|
marketName := getString(name["value"])
|
||||||
|
|
||||||
|
market := domain.Market{
|
||||||
|
EventID: event.ID,
|
||||||
|
MarketID: getString(m["id"]),
|
||||||
|
MarketCategory: getString(m["category"]),
|
||||||
|
MarketName: marketName,
|
||||||
|
Source: "bwin",
|
||||||
|
}
|
||||||
|
|
||||||
|
results := getMapArray(m["results"])
|
||||||
|
market.Odds = results
|
||||||
|
|
||||||
|
s.store.SaveNonLiveMarket(ctx, market)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
|
||||||
|
// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
|
||||||
|
// so instead of having event and odds fetched separetly event will also be fetched along with the odds
|
||||||
|
sportIds := []int{4, 12, 7}
|
||||||
|
for _, sportId := range sportIds {
|
||||||
|
url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := s.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Success int `json:"success"`
|
||||||
|
Results []map[string]interface{} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
|
||||||
|
fmt.Printf("Decode failed for sport_id=%d\nRaw: %s\n", sportId, string(body))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, res := range data.Results {
|
||||||
|
if getInt(res["Id"]) == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event := domain.Event{
|
||||||
|
ID: strconv.Itoa(getInt(res["Id"])),
|
||||||
|
SportID: int32(getInt(res["SportId"])),
|
||||||
|
LeagueID: int32(getInt(res["LeagueId"])),
|
||||||
|
LeagueName: getString(res["Leaguename"]),
|
||||||
|
HomeTeam: getString(res["HomeTeam"]),
|
||||||
|
HomeTeamID: int32(getInt(res["HomeTeamId"])),
|
||||||
|
AwayTeam: getString(res["AwayTeam"]),
|
||||||
|
AwayTeamID: int32(getInt(res["AwayTeamId"])),
|
||||||
|
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||||
|
TimerStatus: "1",
|
||||||
|
IsLive: true,
|
||||||
|
Status: "live",
|
||||||
|
Source: "bwin",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.store.SaveEvent(ctx, event); err != nil {
|
||||||
|
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, market := range []string{"Markets, optionMarkets"} {
|
||||||
|
for _, m := range getMapArray(res[market]) {
|
||||||
|
name := getMap(m["name"])
|
||||||
|
marketName := getString(name["value"])
|
||||||
|
|
||||||
|
market := domain.Market{
|
||||||
|
EventID: event.ID,
|
||||||
|
MarketID: getString(m["id"]),
|
||||||
|
MarketCategory: getString(m["category"]),
|
||||||
|
MarketName: marketName,
|
||||||
|
Source: "bwin",
|
||||||
|
}
|
||||||
|
|
||||||
|
results := getMapArray(m["results"])
|
||||||
|
market.Odds = results
|
||||||
|
|
||||||
|
s.store.SaveNonLiveMarket(ctx, market)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServiceImpl) FetchNonLiveOddsByEventID(ctx context.Context, eventIDStr string) (domain.BaseNonLiveOddResponse, error) {
|
func (s *ServiceImpl) FetchNonLiveOddsByEventID(ctx context.Context, eventIDStr string) (domain.BaseNonLiveOddResponse, error) {
|
||||||
|
|
||||||
eventID, err := strconv.ParseInt(eventIDStr, 10, 64)
|
eventID, err := strconv.ParseInt(eventIDStr, 10, 64)
|
||||||
|
|
@ -336,6 +543,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
marketOdds, err := convertRawMessage(market.Odds)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to conver json.RawMessage to []map[string]interface{} for market_id: ", market.ID)
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
marketRecord := domain.Market{
|
marketRecord := domain.Market{
|
||||||
EventID: eventID,
|
EventID: eventID,
|
||||||
FI: fi,
|
FI: fi,
|
||||||
|
|
@ -344,7 +558,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
||||||
MarketName: market.Name,
|
MarketName: market.Name,
|
||||||
MarketID: marketIDstr,
|
MarketID: marketIDstr,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
Odds: market.Odds,
|
Odds: marketOdds,
|
||||||
|
// bwin won't reach this code so bet365 is hardcoded for now
|
||||||
|
Source: "bet365",
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
err = s.store.SaveNonLiveMarket(ctx, marketRecord)
|
||||||
|
|
@ -385,3 +601,49 @@ func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingI
|
||||||
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
|
||||||
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getString(v interface{}) string {
|
||||||
|
if str, ok := v.(string); ok {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInt(v interface{}) int {
|
||||||
|
if n, ok := v.(float64); ok {
|
||||||
|
return int(n)
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMap(v interface{}) map[string]interface{} {
|
||||||
|
if m, ok := v.(map[string]interface{}); ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMapArray(v interface{}) []map[string]interface{} {
|
||||||
|
result := []map[string]interface{}{}
|
||||||
|
if arr, ok := v.([]interface{}); ok {
|
||||||
|
for _, item := range arr {
|
||||||
|
if m, ok := item.(map[string]interface{}); ok {
|
||||||
|
result = append(result, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRawMessage(rawMessages []json.RawMessage) ([]map[string]interface{}, error) {
|
||||||
|
var result []map[string]interface{}
|
||||||
|
for _, raw := range rawMessages {
|
||||||
|
var m map[string]interface{}
|
||||||
|
if err := json.Unmarshal(raw, &m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NFL evaluations
|
// NFL evaluations
|
||||||
func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
if score.Home > score.Away {
|
if score.Home > score.Away {
|
||||||
|
|
@ -25,7 +25,7 @@ func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||||
|
|
@ -56,7 +56,7 @@ func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalPoints := float64(score.Home + score.Away)
|
totalPoints := float64(score.Home + score.Away)
|
||||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -81,8 +81,8 @@ func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateRugbyMoneyLine evaluates Rugby money line bets
|
// EvaluateRugbyMoneyLine Evaluates Rugby money line bets
|
||||||
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
if score.Home > score.Away {
|
if score.Home > score.Away {
|
||||||
|
|
@ -99,8 +99,8 @@ func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateRugbySpread evaluates Rugby spread bets
|
// EvaluateRugbySpread Evaluates Rugby spread bets
|
||||||
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||||
|
|
@ -131,8 +131,8 @@ func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateRugbyTotalPoints evaluates Rugby total points bets
|
// EvaluateRugbyTotalPoints Evaluates Rugby total points bets
|
||||||
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalPoints := float64(score.Home + score.Away)
|
totalPoints := float64(score.Home + score.Away)
|
||||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -157,8 +157,8 @@ func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Awa
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateBaseballMoneyLine evaluates Baseball money line bets
|
// EvaluateBaseballMoneyLine Evaluates Baseball money line bets
|
||||||
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
if score.Home > score.Away {
|
if score.Home > score.Away {
|
||||||
|
|
@ -175,8 +175,8 @@ func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Aw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateBaseballSpread evaluates Baseball spread bets
|
// EvaluateBaseballSpread Evaluates Baseball spread bets
|
||||||
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||||
|
|
@ -207,8 +207,8 @@ func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away
|
||||||
return domain.OUTCOME_STATUS_VOID, nil
|
return domain.OUTCOME_STATUS_VOID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateBaseballTotalRuns evaluates Baseball total runs bets
|
// EvaluateBaseballTotalRuns Evaluates Baseball total runs bets
|
||||||
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
totalRuns := float64(score.Home + score.Away)
|
totalRuns := float64(score.Home + score.Away)
|
||||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -233,8 +233,8 @@ func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Aw
|
||||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateBaseballFirstInning evaluates Baseball first inning bets
|
// EvaluateBaseballFirstInning Evaluates Baseball first inning bets
|
||||||
func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
if score.Home > score.Away {
|
if score.Home > score.Away {
|
||||||
|
|
@ -256,8 +256,8 @@ func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateBaseballFirst5Innings evaluates Baseball first 5 innings bets
|
// EvaluateBaseballFirst5Innings Evaluates Baseball first 5 innings bets
|
||||||
func evaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
func EvaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||||
switch outcome.OddHeader {
|
switch outcome.OddHeader {
|
||||||
case "1":
|
case "1":
|
||||||
if score.Home > score.Away {
|
if score.Home > score.Away {
|
||||||
|
|
|
||||||
|
|
@ -29,75 +29,75 @@ func TestNFLMarkets(t *testing.T) {
|
||||||
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||||
t.Run("Home Win", func(t *testing.T) {
|
t.Run("Home Win", func(t *testing.T) {
|
||||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
status, err := EvaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win", func(t *testing.T) {
|
t.Run("Away Win", func(t *testing.T) {
|
||||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 14, Away: 21})
|
status, err := EvaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 14, Away: 21})
|
||||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Draw", func(t *testing.T) {
|
t.Run("Draw", func(t *testing.T) {
|
||||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 17, Away: 17})
|
status, err := EvaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 17, Away: 17})
|
||||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||||
})
|
})
|
||||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||||
status, err := evaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
status, err := EvaluateNFLMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-3.5"}, struct{ Home, Away int }{Home: 24, Away: 20})
|
status, err := EvaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-3.5"}, struct{ Home, Away int }{Home: 24, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+3.5"}, struct{ Home, Away int }{Home: 20, Away: 24})
|
status, err := EvaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+3.5"}, struct{ Home, Away int }{Home: 20, Away: 24})
|
||||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
status, err := EvaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
status, err := EvaluateNFLSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||||
t.Run("Over Win", func(t *testing.T) {
|
t.Run("Over Win", func(t *testing.T) {
|
||||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "44.5"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
status, err := EvaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "44.5"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Under Win", func(t *testing.T) {
|
t.Run("Under Win", func(t *testing.T) {
|
||||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "44.5"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
status, err := EvaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "44.5"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "37"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
status, err := EvaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "37"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||||
status, err := evaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
status, err := EvaluateNFLTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 17})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
|
|
@ -128,75 +128,75 @@ func TestRugbyMarkets(t *testing.T) {
|
||||||
case int64(domain.RUGBY_MONEY_LINE):
|
case int64(domain.RUGBY_MONEY_LINE):
|
||||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||||
t.Run("Home Win", func(t *testing.T) {
|
t.Run("Home Win", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
status, err := EvaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 30, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win", func(t *testing.T) {
|
t.Run("Away Win", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 20, Away: 30})
|
status, err := EvaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 20, Away: 30})
|
||||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Draw", func(t *testing.T) {
|
t.Run("Draw", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 25, Away: 25})
|
status, err := EvaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 25, Away: 25})
|
||||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||||
})
|
})
|
||||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
status, err := EvaluateRugbyMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.RUGBY_SPREAD), int64(domain.RUGBY_HANDICAP):
|
case int64(domain.RUGBY_SPREAD), int64(domain.RUGBY_HANDICAP):
|
||||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-6.5"}, struct{ Home, Away int }{Home: 28, Away: 20})
|
status, err := EvaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-6.5"}, struct{ Home, Away int }{Home: 28, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+6.5"}, struct{ Home, Away int }{Home: 20, Away: 28})
|
status, err := EvaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+6.5"}, struct{ Home, Away int }{Home: 20, Away: 28})
|
||||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
status, err := EvaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 21, Away: 21})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
status, err := EvaluateRugbySpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 21, Away: 14})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.RUGBY_TOTAL_POINTS):
|
case int64(domain.RUGBY_TOTAL_POINTS):
|
||||||
t.Run("Over Win", func(t *testing.T) {
|
t.Run("Over Win", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "40.5"}, struct{ Home, Away int }{Home: 25, Away: 20})
|
status, err := EvaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "40.5"}, struct{ Home, Away int }{Home: 25, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Under Win", func(t *testing.T) {
|
t.Run("Under Win", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "40.5"}, struct{ Home, Away int }{Home: 15, Away: 20})
|
status, err := EvaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "40.5"}, struct{ Home, Away int }{Home: 15, Away: 20})
|
||||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "35"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
status, err := EvaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "35"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||||
status, err := evaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
status, err := EvaluateRugbyTotalPoints(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 20, Away: 15})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
|
|
@ -226,75 +226,75 @@ func TestBaseballMarkets(t *testing.T) {
|
||||||
case int64(domain.BASEBALL_MONEY_LINE):
|
case int64(domain.BASEBALL_MONEY_LINE):
|
||||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||||
t.Run("Home Win", func(t *testing.T) {
|
t.Run("Home Win", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 6, Away: 3})
|
status, err := EvaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 6, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
t.Logf("Market: %s, Scenario: Home Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win", func(t *testing.T) {
|
t.Run("Away Win", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 2, Away: 5})
|
status, err := EvaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2"}, struct{ Home, Away int }{Home: 2, Away: 5})
|
||||||
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
t.Logf("Market: %s, Scenario: Away Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Draw", func(t *testing.T) {
|
t.Run("Draw", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
status, err := EvaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||||
t.Logf("Market: %s, Scenario: Draw", m.name)
|
t.Logf("Market: %s, Scenario: Draw", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||||
})
|
})
|
||||||
t.Run("Invalid OddHeader", func(t *testing.T) {
|
t.Run("Invalid OddHeader", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
status, err := EvaluateBaseballMoneyLine(domain.BetOutcome{MarketID: m.marketID, OddHeader: "X"}, struct{ Home, Away int }{Home: 10, Away: 7})
|
||||||
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
t.Logf("Market: %s, Scenario: Invalid OddHeader", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.BASEBALL_SPREAD):
|
case int64(domain.BASEBALL_SPREAD):
|
||||||
t.Run("Home Win with Handicap", func(t *testing.T) {
|
t.Run("Home Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-1.5"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
status, err := EvaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "-1.5"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Home Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Away Win with Handicap", func(t *testing.T) {
|
t.Run("Away Win with Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+1.5"}, struct{ Home, Away int }{Home: 3, Away: 5})
|
status, err := EvaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "2", OddHandicap: "+1.5"}, struct{ Home, Away int }{Home: 3, Away: 5})
|
||||||
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Away Win with Handicap", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
status, err := EvaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "0"}, struct{ Home, Away int }{Home: 4, Away: 4})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
t.Run("Non-numeric Handicap", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
status, err := EvaluateBaseballSpread(domain.BetOutcome{MarketID: m.marketID, OddHeader: "1", OddHandicap: "notanumber"}, struct{ Home, Away int }{Home: 5, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric Handicap", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
})
|
})
|
||||||
case int64(domain.BASEBALL_TOTAL_RUNS):
|
case int64(domain.BASEBALL_TOTAL_RUNS):
|
||||||
t.Run("Over Win", func(t *testing.T) {
|
t.Run("Over Win", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7.5"}, struct{ Home, Away int }{Home: 5, Away: 4})
|
status, err := EvaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7.5"}, struct{ Home, Away int }{Home: 5, Away: 4})
|
||||||
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
t.Logf("Market: %s, Scenario: Over Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Under Win", func(t *testing.T) {
|
t.Run("Under Win", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "7.5"}, struct{ Home, Away int }{Home: 2, Away: 3})
|
status, err := EvaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Under", OddName: "7.5"}, struct{ Home, Away int }{Home: 2, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
t.Logf("Market: %s, Scenario: Under Win", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||||
})
|
})
|
||||||
t.Run("Push (Void)", func(t *testing.T) {
|
t.Run("Push (Void)", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
status, err := EvaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "7"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
t.Logf("Market: %s, Scenario: Push (Void)", m.name)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||||
})
|
})
|
||||||
t.Run("Non-numeric OddName", func(t *testing.T) {
|
t.Run("Non-numeric OddName", func(t *testing.T) {
|
||||||
status, err := evaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
status, err := EvaluateBaseballTotalRuns(domain.BetOutcome{MarketID: m.marketID, OddHeader: "Over", OddName: "notanumber"}, struct{ Home, Away int }{Home: 4, Away: 3})
|
||||||
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
t.Logf("Market: %s, Scenario: Non-numeric OddName", m.name)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||||
|
|
@ -338,7 +338,7 @@ func TestEvaluateFootballOutcome(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
status, _ := service.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, events)
|
status, _ := service.EvaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, events)
|
||||||
|
|
||||||
fmt.Printf("\n\nBet Outcome: %v\n\n", &status)
|
fmt.Printf("\n\nBet Outcome: %v\n\n", &status)
|
||||||
|
|
||||||
|
|
@ -357,7 +357,7 @@ func TestEvaluateTotalLegs(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateTotalLegs(tt.outcome, tt.score)
|
status, _ := EvaluateTotalLegs(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +380,7 @@ func TestEvaluateGameLines(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateGameLines(tt.outcome, tt.score)
|
status, _ := EvaluateGameLines(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +408,7 @@ func TestEvaluateFirstTeamToScore(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateFirstTeamToScore(tt.outcome, tt.events)
|
status, _ := EvaluateFirstTeamToScore(tt.outcome, tt.events)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +430,7 @@ func TestEvaluateGoalsOverUnder(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateGoalsOverUnder(tt.outcome, tt.score)
|
status, _ := EvaluateGoalsOverUnder(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -452,7 +452,7 @@ func TestEvaluateGoalsOddEven(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateGoalsOddEven(tt.outcome, tt.score)
|
status, _ := EvaluateGoalsOddEven(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +474,7 @@ func TestEvaluateCorrectScore(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateCorrectScore(tt.outcome, tt.score)
|
status, _ := EvaluateCorrectScore(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +498,7 @@ func TestEvaluateHighestScoringHalf(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateHighestScoringHalf(tt.outcome, tt.firstScore, tt.secondScore)
|
status, _ := EvaluateHighestScoringHalf(tt.outcome, tt.firstScore, tt.secondScore)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -551,7 +551,7 @@ func TestEvaluateHighestScoringQuarter(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateHighestScoringQuarter(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore, tt.fourthScore)
|
status, _ := EvaluateHighestScoringQuarter(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore, tt.fourthScore)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -577,7 +577,7 @@ func TestEvaluateWinningMargin(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateWinningMargin(tt.outcome, tt.score)
|
status, _ := EvaluateWinningMargin(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -605,7 +605,7 @@ func TestEvaluateDoubleResult(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateDoubleResult(tt.outcome, tt.firstHalfScore, tt.fullTimeScore)
|
status, _ := EvaluateDoubleResult(tt.outcome, tt.firstHalfScore, tt.fullTimeScore)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -632,7 +632,7 @@ func TestEvaluateHighestScoringPeriod(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateHighestScoringPeriod(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore)
|
status, _ := EvaluateHighestScoringPeriod(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -656,7 +656,7 @@ func TestEvalauteTiedAfterRegulation(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateTiedAfterRegulation(tt.outcome, tt.score)
|
status, _ := EvaluateTiedAfterRegulation(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -680,7 +680,7 @@ func TestEvaluateTeamTotal(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateTeamTotal(tt.outcome, tt.score)
|
status, _ := EvaluateTeamTotal(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -703,7 +703,7 @@ func TestDrawNoBet(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateDrawNoBet(tt.outcome, tt.score)
|
status, _ := EvaluateDrawNoBet(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -727,7 +727,7 @@ func TestEvaluateMoneyLine(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateMoneyLine(tt.outcome, tt.score)
|
status, _ := EvaluateMoneyLine(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -751,7 +751,7 @@ func TestEvaluateDoubleChance(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateDoubleChance(tt.outcome, tt.score)
|
status, _ := EvaluateDoubleChance(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -775,7 +775,7 @@ func TestEvaluateResultAndTotal(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateResultAndTotal(tt.outcome, tt.score)
|
status, _ := EvaluateResultAndTotal(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -826,7 +826,7 @@ func TestEvaluateBTTSX(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateBTTSX(tt.outcome, tt.score)
|
status, _ := EvaluateBTTSX(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -852,7 +852,7 @@ func TestEvaluateResultAndBTTSX(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateResultAndBTTSX(tt.outcome, tt.score)
|
status, _ := EvaluateResultAndBTTSX(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -876,7 +876,7 @@ func TestEvaluateMoneyLine3Way(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateMoneyLine3Way(tt.outcome, tt.score)
|
status, _ := EvaluateMoneyLine3Way(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -937,7 +937,7 @@ func TestEvaluateAsianHandicap(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateAsianHandicap(tt.outcome, tt.score)
|
status, _ := EvaluateAsianHandicap(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
@ -971,7 +971,7 @@ func TestEvaluateHandicapAndTotal(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
status, _ := evaluateHandicapAndTotal(tt.outcome, tt.score)
|
status, _ := EvaluateHandicapAndTotal(tt.outcome, tt.score)
|
||||||
if status != tt.expected {
|
if status != tt.expected {
|
||||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
|
|
@ -44,6 +46,7 @@ type App struct {
|
||||||
userSvc *user.Service
|
userSvc *user.Service
|
||||||
betSvc *bet.Service
|
betSvc *bet.Service
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
|
chapaSvc *chapa.Service
|
||||||
walletSvc *wallet.Service
|
walletSvc *wallet.Service
|
||||||
transactionSvc *transaction.Service
|
transactionSvc *transaction.Service
|
||||||
ticketSvc *ticket.Service
|
ticketSvc *ticket.Service
|
||||||
|
|
@ -54,6 +57,7 @@ type App struct {
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
prematchSvc *odds.ServiceImpl
|
prematchSvc *odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc event.Service
|
||||||
|
leagueSvc league.Service
|
||||||
resultSvc *result.Service
|
resultSvc *result.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,6 +69,7 @@ func NewApp(
|
||||||
userSvc *user.Service,
|
userSvc *user.Service,
|
||||||
ticketSvc *ticket.Service,
|
ticketSvc *ticket.Service,
|
||||||
betSvc *bet.Service,
|
betSvc *bet.Service,
|
||||||
|
chapaSvc *chapa.Service,
|
||||||
walletSvc *wallet.Service,
|
walletSvc *wallet.Service,
|
||||||
transactionSvc *transaction.Service,
|
transactionSvc *transaction.Service,
|
||||||
branchSvc *branch.Service,
|
branchSvc *branch.Service,
|
||||||
|
|
@ -72,6 +77,7 @@ func NewApp(
|
||||||
notidicationStore *notificationservice.Service,
|
notidicationStore *notificationservice.Service,
|
||||||
prematchSvc *odds.ServiceImpl,
|
prematchSvc *odds.ServiceImpl,
|
||||||
eventSvc event.Service,
|
eventSvc event.Service,
|
||||||
|
leagueSvc league.Service,
|
||||||
referralSvc referralservice.ReferralStore,
|
referralSvc referralservice.ReferralStore,
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||||
aleaVirtualGameService alea.AleaVirtualGameService,
|
aleaVirtualGameService alea.AleaVirtualGameService,
|
||||||
|
|
@ -104,6 +110,7 @@ func NewApp(
|
||||||
userSvc: userSvc,
|
userSvc: userSvc,
|
||||||
ticketSvc: ticketSvc,
|
ticketSvc: ticketSvc,
|
||||||
betSvc: betSvc,
|
betSvc: betSvc,
|
||||||
|
chapaSvc: chapaSvc,
|
||||||
walletSvc: walletSvc,
|
walletSvc: walletSvc,
|
||||||
transactionSvc: transactionSvc,
|
transactionSvc: transactionSvc,
|
||||||
branchSvc: branchSvc,
|
branchSvc: branchSvc,
|
||||||
|
|
@ -113,6 +120,7 @@ func NewApp(
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
prematchSvc: prematchSvc,
|
prematchSvc: prematchSvc,
|
||||||
eventSvc: eventSvc,
|
eventSvc: eventSvc,
|
||||||
|
leagueSvc: leagueSvc,
|
||||||
virtualGameSvc: virtualGameSvc,
|
virtualGameSvc: virtualGameSvc,
|
||||||
aleaVirtualGameService: aleaVirtualGameService,
|
aleaVirtualGameService: aleaVirtualGameService,
|
||||||
veliVirtualGameService: veliVirtualGameService,
|
veliVirtualGameService: veliVirtualGameService,
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,28 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||||
userID := c.Locals("user_id").(int64)
|
userID := c.Locals("user_id").(int64)
|
||||||
// role := c.Locals("role").(domain.Role)
|
// role := c.Locals("role").(domain.Role)
|
||||||
|
|
||||||
leagueIDQuery := c.Query("league_id")
|
leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
|
||||||
sportIDQuery := c.Query("sport_id")
|
if err != nil {
|
||||||
|
h.logger.Error("invalid league id", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
sportIDQuery, err := strconv.Atoi(c.Query("sport_id"))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid sport id", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
firstStartTimeQuery := c.Query("first_start_time")
|
firstStartTimeQuery := c.Query("first_start_time")
|
||||||
lastStartTimeQuery := c.Query("last_start_time")
|
lastStartTimeQuery := c.Query("last_start_time")
|
||||||
|
|
||||||
leagueID := domain.ValidString{
|
leagueID := domain.ValidInt32{
|
||||||
Value: leagueIDQuery,
|
Value: int32(leagueIDQuery),
|
||||||
Valid: leagueIDQuery != "",
|
Valid: leagueIDQuery != 0,
|
||||||
}
|
}
|
||||||
sportID := domain.ValidString{
|
sportID := domain.ValidInt32{
|
||||||
Value: sportIDQuery,
|
Value: int32(sportIDQuery),
|
||||||
Valid: sportIDQuery != "",
|
Valid: sportIDQuery != 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstStartTime domain.ValidTime
|
var firstStartTime domain.ValidTime
|
||||||
|
|
@ -123,7 +133,6 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var res domain.CreateBetRes
|
var res domain.CreateBetRes
|
||||||
var err error
|
|
||||||
for i := 0; i < int(req.NumberOfBets); i++ {
|
for i := 0; i < int(req.NumberOfBets); i++ {
|
||||||
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
|
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,283 +1,464 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"encoding/json"
|
// "encoding/json"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"io"
|
// "io"
|
||||||
"net/http"
|
// "net/http"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetBanks godoc
|
// // GetBanks godoc
|
||||||
// @Summary Get list of banks
|
// // @Summary Get list of banks
|
||||||
// @Description Fetch all supported banks from Chapa
|
// // @Description Fetch all supported banks from Chapa
|
||||||
// @Tags Chapa
|
// // @Tags Chapa
|
||||||
// @Accept json
|
// // @Accept json
|
||||||
// @Produce json
|
// // @Produce json
|
||||||
// @Success 200 {object} domain.ChapaSupportedBanksResponse
|
// // @Success 200 {object} domain.ChapaSupportedBanksResponse
|
||||||
// @Router /api/v1/chapa/banks [get]
|
// // @Router /api/v1/chapa/banks [get]
|
||||||
func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
// func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
||||||
httpReq, err := http.NewRequest("GET", h.Cfg.CHAPA_BASE_URL+"/banks", nil)
|
// httpReq, err := http.NewRequest("GET", h.Cfg.CHAPA_BASE_URL+"/banks", nil)
|
||||||
// log.Printf("\n\nbase url is: %v\n\n", h.Cfg.CHAPA_BASE_URL)
|
// // log.Printf("\n\nbase url is: %v\n\n", h.Cfg.CHAPA_BASE_URL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to create request", "details": err.Error()})
|
// return c.Status(500).JSON(fiber.Map{"error": "Failed to create request", "details": err.Error()})
|
||||||
}
|
// }
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(httpReq)
|
// resp, err := http.DefaultClient.Do(httpReq)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
// return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
||||||
}
|
// }
|
||||||
defer resp.Body.Close()
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
// body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()})
|
// return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()})
|
||||||
}
|
// }
|
||||||
|
|
||||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// InitializePayment godoc
|
// // InitializePayment godoc
|
||||||
// @Summary Initialize a payment transaction
|
// // @Summary Initialize a payment transaction
|
||||||
// @Description Initiate a payment through Chapa
|
// // @Description Initiate a payment through Chapa
|
||||||
// @Tags Chapa
|
// // @Tags Chapa
|
||||||
// @Accept json
|
// // @Accept json
|
||||||
// @Produce json
|
// // @Produce json
|
||||||
// @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
// // @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
||||||
// @Success 200 {object} domain.InitPaymentResponse
|
// // @Success 200 {object} domain.InitPaymentResponse
|
||||||
// @Router /api/v1/chapa/payments/initialize [post]
|
// // @Router /api/v1/chapa/payments/initialize [post]
|
||||||
func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
// func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
||||||
var req InitPaymentRequest
|
// var req InitPaymentRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
// if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": "Invalid request body",
|
// "error": "Invalid request body",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Generate and assign a unique transaction reference
|
// // Generate and assign a unique transaction reference
|
||||||
req.TxRef = uuid.New().String()
|
// req.TxRef = uuid.New().String()
|
||||||
|
|
||||||
payload, err := json.Marshal(req)
|
// payload, err := json.Marshal(req)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to serialize request",
|
// "error": "Failed to serialize request",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
// httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to create request",
|
// "error": "Failed to create request",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
// httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(httpReq)
|
// resp, err := http.DefaultClient.Do(httpReq)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to initialize payment",
|
// "error": "Failed to initialize payment",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
defer resp.Body.Close()
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
// body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to read response",
|
// "error": "Failed to read response",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// VerifyTransaction godoc
|
// // VerifyTransaction godoc
|
||||||
// @Summary Verify a payment transaction
|
// // @Summary Verify a payment transaction
|
||||||
// @Description Verify the transaction status from Chapa using tx_ref
|
// // @Description Verify the transaction status from Chapa using tx_ref
|
||||||
// @Tags Chapa
|
// // @Tags Chapa
|
||||||
// @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.VerifyTransactionResponse
|
// // @Success 200 {object} domain.VerifyTransactionResponse
|
||||||
// @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
// // @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
||||||
func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
// func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
||||||
txRef := c.Params("tx_ref")
|
// txRef := c.Params("tx_ref")
|
||||||
if txRef == "" {
|
// if txRef == "" {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
"error": "Missing transaction reference",
|
// "error": "Missing transaction reference",
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/transaction/verify/%s", h.Cfg.CHAPA_BASE_URL, txRef)
|
// url := fmt.Sprintf("%s/transaction/verify/%s", h.Cfg.CHAPA_BASE_URL, txRef)
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("GET", url, nil)
|
// httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to create request",
|
// "error": "Failed to create request",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(httpReq)
|
// resp, err := http.DefaultClient.Do(httpReq)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to verify transaction",
|
// "error": "Failed to verify transaction",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
defer resp.Body.Close()
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
// body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return c.Status(500).JSON(fiber.Map{
|
// return c.Status(500).JSON(fiber.Map{
|
||||||
"error": "Failed to read response",
|
// "error": "Failed to read response",
|
||||||
"details": err.Error(),
|
// "details": err.Error(),
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ReceiveWebhook godoc
|
// // ReceiveWebhook godoc
|
||||||
// @Summary Receive Chapa webhook
|
// // @Summary Receive Chapa webhook
|
||||||
// @Description Endpoint to receive webhook payloads from Chapa
|
// // @Description Endpoint to receive webhook payloads from Chapa
|
||||||
|
// // @Tags Chapa
|
||||||
|
// // @Accept json
|
||||||
|
// // @Produce json
|
||||||
|
// // @Param payload body object true "Webhook Payload (dynamic)"
|
||||||
|
// // @Success 200 {string} string "ok"
|
||||||
|
// // @Router /api/v1/chapa/payments/callback [post]
|
||||||
|
// func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error {
|
||||||
|
// var payload map[string]interface{}
|
||||||
|
// if err := c.BodyParser(&payload); err != nil {
|
||||||
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
// "error": "Invalid webhook data",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// h.logger.Info("Chapa webhook received", "payload", payload)
|
||||||
|
|
||||||
|
// // Optional: you can verify tx_ref here again if needed
|
||||||
|
|
||||||
|
// return c.SendStatus(fiber.StatusOK)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // CreateTransfer godoc
|
||||||
|
// // @Summary Create a money transfer
|
||||||
|
// // @Description Initiate a transfer request via Chapa
|
||||||
|
// // @Tags Chapa
|
||||||
|
// // @Accept json
|
||||||
|
// // @Produce json
|
||||||
|
// // @Param payload body domain.TransferRequest true "Transfer request body"
|
||||||
|
// // @Success 200 {object} domain.CreateTransferResponse
|
||||||
|
// // @Router /api/v1/chapa/transfers [post]
|
||||||
|
// func (h *Handler) CreateTransfer(c *fiber.Ctx) error {
|
||||||
|
// var req TransferRequest
|
||||||
|
// if err := c.BodyParser(&req); err != nil {
|
||||||
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
// "error": "Invalid request",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Inject unique transaction reference
|
||||||
|
// req.Reference = uuid.New().String()
|
||||||
|
|
||||||
|
// payload, err := json.Marshal(req)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Failed to serialize request",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload))
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Failed to create HTTP request",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||||
|
// httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// resp, err := http.DefaultClient.Do(httpReq)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Transfer request failed",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
|
// body, err := io.ReadAll(resp.Body)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Failed to read response",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // VerifyTransfer godoc
|
||||||
|
// // @Summary Verify a transfer
|
||||||
|
// // @Description Check the status of a money transfer via reference
|
||||||
|
// // @Tags Chapa
|
||||||
|
// // @Accept json
|
||||||
|
// // @Produce json
|
||||||
|
// // @Param transfer_ref path string true "Transfer Reference"
|
||||||
|
// // @Success 200 {object} domain.VerifyTransferResponse
|
||||||
|
// // @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get]
|
||||||
|
// func (h *Handler) VerifyTransfer(c *fiber.Ctx) error {
|
||||||
|
// transferRef := c.Params("transfer_ref")
|
||||||
|
// if transferRef == "" {
|
||||||
|
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
// "error": "Missing transfer reference in URL",
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// url := fmt.Sprintf("%s/transfers/verify/%s", h.Cfg.CHAPA_BASE_URL, transferRef)
|
||||||
|
|
||||||
|
// httpReq, err := http.NewRequest("GET", url, nil)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Failed to create HTTP request",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
|
// resp, err := http.DefaultClient.Do(httpReq)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Verification request failed",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
|
// body, err := io.ReadAll(resp.Body)
|
||||||
|
// if err != nil {
|
||||||
|
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
// "error": "Failed to read response body",
|
||||||
|
// "details": err.Error(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// VerifyChapaPayment godoc
|
||||||
|
// @Summary Verifies Chapa webhook transaction
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body object true "Webhook Payload (dynamic)"
|
// @Param payload body domain.ChapaTransactionType true "Webhook Payload"
|
||||||
// @Success 200 {string} string "ok"
|
// @Success 200 {object} domain.Response
|
||||||
// @Router /api/v1/chapa/payments/callback [post]
|
// @Router /api/v1/chapa/payments/verify [post]
|
||||||
func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error {
|
func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
|
||||||
var payload map[string]interface{}
|
var txType domain.ChapaTransactionType
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
if err := c.BodyParser(&txType); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return domain.UnProcessableEntityResponse(c)
|
||||||
"error": "Invalid webhook data",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h.logger.Info("Chapa webhook received", "payload", payload)
|
switch txType.Type {
|
||||||
|
case "Payout":
|
||||||
|
var payload domain.ChapaWebHookTransfer
|
||||||
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
// Optional: you can verify tx_ref here again if needed
|
if err := h.chapaSvc.HandleChapaTransferWebhook(c.Context(), payload); err != nil {
|
||||||
|
return domain.FiberErrorResponse(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusOK)
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transfer verified successfully",
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
|
||||||
|
case "API":
|
||||||
|
var payload domain.ChapaWebHookPayment
|
||||||
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.chapaSvc.HandleChapaPaymentWebhook(c.Context(), payload); err != nil {
|
||||||
|
return domain.FiberErrorResponse(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa payment verified successfully",
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.Response{
|
||||||
|
Message: "Invalid Chapa transaction type",
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusBadRequest,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTransfer godoc
|
// WithdrawUsingChapa godoc
|
||||||
// @Summary Create a money transfer
|
// @Summary Withdraw using Chapa
|
||||||
// @Description Initiate a transfer request via Chapa
|
// @Description Initiates a withdrawal transaction using Chapa for the authenticated user.
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body domain.TransferRequest true "Transfer request body"
|
// @Param request body domain.ChapaWithdrawRequest true "Chapa Withdraw Request"
|
||||||
// @Success 200 {object} domain.CreateTransferResponse
|
// @Success 200 {object} domain.Response{data=string} "Withdrawal requested successfully"
|
||||||
// @Router /api/v1/chapa/transfers [post]
|
// @Failure 400 {object} domain.Response "Invalid request"
|
||||||
func (h *Handler) CreateTransfer(c *fiber.Ctx) error {
|
// @Failure 401 {object} domain.Response "Unauthorized"
|
||||||
var req TransferRequest
|
// @Failure 422 {object} domain.Response "Unprocessable Entity"
|
||||||
|
// @Failure 500 {object} domain.Response "Internal Server Error"
|
||||||
|
// @Router /api/v1/chapa/payments/withdraw [post]
|
||||||
|
func (h *Handler) WithdrawUsingChapa(c *fiber.Ctx) error {
|
||||||
|
var req domain.ChapaWithdrawRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return domain.UnProcessableEntityResponse(c)
|
||||||
"error": "Invalid request",
|
}
|
||||||
"details": err.Error(),
|
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok || userID == 0 {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.Response{
|
||||||
|
Message: "Unauthorized",
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusUnauthorized,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject unique transaction reference
|
if err := h.chapaSvc.WithdrawUsingChapa(c.Context(), userID, req); err != nil {
|
||||||
req.Reference = uuid.New().String()
|
return domain.FiberErrorResponse(c, err)
|
||||||
|
|
||||||
payload, err := json.Marshal(req)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Failed to serialize request",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload))
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
if err != nil {
|
Message: "Withdrawal requested successfully",
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
Success: true,
|
||||||
"error": "Failed to create HTTP request",
|
StatusCode: fiber.StatusOK,
|
||||||
"details": err.Error(),
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(httpReq)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Transfer request failed",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Failed to read response",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyTransfer godoc
|
// DepositUsingChapa godoc
|
||||||
// @Summary Verify a transfer
|
// @Summary Deposit money into user wallet using Chapa
|
||||||
// @Description Check the status of a money transfer via reference
|
// @Description Deposits money into user wallet from user account using Chapa
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param payload body domain.ChapaDepositRequest true "Deposit request payload"
|
||||||
|
// @Success 200 {object} domain.ChapaPaymentUrlResponseWrapper
|
||||||
|
// @Failure 400 {object} domain.Response "Invalid request"
|
||||||
|
// @Failure 422 {object} domain.Response "Validation error"
|
||||||
|
// @Failure 500 {object} domain.Response "Internal server error"
|
||||||
|
// @Router /api/v1/chapa/payments/deposit [post]
|
||||||
|
func (h *Handler) DepositUsingChapa(c *fiber.Ctx) error {
|
||||||
|
// Extract user info from token (adjust as per your auth middleware)
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok || userID == 0 {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.Response{
|
||||||
|
Message: "Unauthorized",
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusUnauthorized,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req domain.ChapaDepositRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input in domain/domain (you may have a Validate method)
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.Response{
|
||||||
|
Message: err.Error(),
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusBadRequest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call service to handle the deposit logic and get payment URL
|
||||||
|
paymentUrl, svcErr := h.chapaSvc.DepositUsingChapa(c.Context(), userID, req)
|
||||||
|
if svcErr != nil {
|
||||||
|
return domain.FiberErrorResponse(c, svcErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.ResponseWDataFactory[domain.ChapaPaymentUrlResponse]{
|
||||||
|
Data: domain.ChapaPaymentUrlResponse{
|
||||||
|
PaymentURL: paymentUrl,
|
||||||
|
},
|
||||||
|
Response: domain.Response{
|
||||||
|
Message: "Deposit process started on wallet, fulfill payment using the URL provided",
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadChapaBanks godoc
|
||||||
|
// @Summary fetches chapa supported banks
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param transfer_ref path string true "Transfer Reference"
|
// @Success 200 {object} domain.ChapaSupportedBanksResponseWrapper
|
||||||
// @Success 200 {object} domain.VerifyTransferResponse
|
// @Failure 400,401,404,422,500 {object} domain.Response
|
||||||
// @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get]
|
// @Router /api/v1/chapa/banks [get]
|
||||||
func (h *Handler) VerifyTransfer(c *fiber.Ctx) error {
|
func (h *Handler) ReadChapaBanks(c *fiber.Ctx) error {
|
||||||
transferRef := c.Params("transfer_ref")
|
banks, err := h.chapaSvc.GetSupportedBanks()
|
||||||
if transferRef == "" {
|
fmt.Printf("\n\nhandler fetched banks: %+v\n\n", banks)
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Missing transfer reference in URL",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/transfers/verify/%s", h.Cfg.CHAPA_BASE_URL, transferRef)
|
|
||||||
|
|
||||||
httpReq, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.Response{
|
||||||
"error": "Failed to create HTTP request",
|
Message: "Internal server error",
|
||||||
"details": err.Error(),
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusInternalServerError,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
return c.Status(fiber.StatusOK).JSON(domain.ResponseWDataFactory[[]domain.ChapaSupportedBank]{
|
||||||
|
Data: banks,
|
||||||
resp, err := http.DefaultClient.Do(httpReq)
|
Response: domain.Response{
|
||||||
if err != nil {
|
Message: "read successful on chapa supported banks",
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
Success: true,
|
||||||
"error": "Verification request failed",
|
StatusCode: fiber.StatusOK,
|
||||||
"details": err.Error(),
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Failed to read response body",
|
|
||||||
"details": err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||||
|
|
@ -30,6 +32,7 @@ type Handler struct {
|
||||||
notificationSvc *notificationservice.Service
|
notificationSvc *notificationservice.Service
|
||||||
userSvc *user.Service
|
userSvc *user.Service
|
||||||
referralSvc referralservice.ReferralStore
|
referralSvc referralservice.ReferralStore
|
||||||
|
chapaSvc chapa.ChapaPort
|
||||||
walletSvc *wallet.Service
|
walletSvc *wallet.Service
|
||||||
transactionSvc *transaction.Service
|
transactionSvc *transaction.Service
|
||||||
ticketSvc *ticket.Service
|
ticketSvc *ticket.Service
|
||||||
|
|
@ -38,6 +41,7 @@ type Handler struct {
|
||||||
companySvc *company.Service
|
companySvc *company.Service
|
||||||
prematchSvc *odds.ServiceImpl
|
prematchSvc *odds.ServiceImpl
|
||||||
eventSvc event.Service
|
eventSvc event.Service
|
||||||
|
leagueSvc league.Service
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService
|
virtualGameSvc virtualgameservice.VirtualGameService
|
||||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||||
|
|
@ -53,6 +57,7 @@ func New(
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
validator *customvalidator.CustomValidator,
|
validator *customvalidator.CustomValidator,
|
||||||
|
chapaSvc chapa.ChapaPort,
|
||||||
walletSvc *wallet.Service,
|
walletSvc *wallet.Service,
|
||||||
referralSvc referralservice.ReferralStore,
|
referralSvc referralservice.ReferralStore,
|
||||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||||
|
|
@ -69,12 +74,14 @@ func New(
|
||||||
companySvc *company.Service,
|
companySvc *company.Service,
|
||||||
prematchSvc *odds.ServiceImpl,
|
prematchSvc *odds.ServiceImpl,
|
||||||
eventSvc event.Service,
|
eventSvc event.Service,
|
||||||
|
leagueSvc league.Service,
|
||||||
resultSvc result.Service,
|
resultSvc result.Service,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
) *Handler {
|
) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
notificationSvc: notificationSvc,
|
notificationSvc: notificationSvc,
|
||||||
|
chapaSvc: chapaSvc,
|
||||||
walletSvc: walletSvc,
|
walletSvc: walletSvc,
|
||||||
referralSvc: referralSvc,
|
referralSvc: referralSvc,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
|
|
@ -86,6 +93,7 @@ func New(
|
||||||
companySvc: companySvc,
|
companySvc: companySvc,
|
||||||
prematchSvc: prematchSvc,
|
prematchSvc: prematchSvc,
|
||||||
eventSvc: eventSvc,
|
eventSvc: eventSvc,
|
||||||
|
leagueSvc: leagueSvc,
|
||||||
virtualGameSvc: virtualGameSvc,
|
virtualGameSvc: virtualGameSvc,
|
||||||
aleaVirtualGameSvc: aleaVirtualGameSvc,
|
aleaVirtualGameSvc: aleaVirtualGameSvc,
|
||||||
veliVirtualGameSvc: veliVirtualGameSvc,
|
veliVirtualGameSvc: veliVirtualGameSvc,
|
||||||
|
|
|
||||||
34
internal/web_server/handlers/leagues.go
Normal file
34
internal/web_server/handlers/leagues.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *Handler) GetAllLeagues(c *fiber.Ctx) error {
|
||||||
|
leagues, err := h.leagueSvc.GetAllLeagues(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to get leagues", err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "All leagues retrived", leagues, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) SetLeagueActive(c *fiber.Ctx) error {
|
||||||
|
leagueIdStr := c.Params("id")
|
||||||
|
if leagueIdStr == "" {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Missing league id", nil, nil)
|
||||||
|
}
|
||||||
|
leagueId, err := strconv.Atoi(leagueIdStr)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.leagueSvc.SetLeagueActive(c.Context(), int64(leagueId)); err != nil {
|
||||||
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to update league", err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "League updated successfully", nil, nil)
|
||||||
|
}
|
||||||
|
|
@ -107,18 +107,27 @@ func (h *Handler) GetRawOddsByMarketID(c *fiber.Ctx) error {
|
||||||
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
func (h *Handler) GetAllUpcomingEvents(c *fiber.Ctx) error {
|
||||||
page := c.QueryInt("page", 1)
|
page := c.QueryInt("page", 1)
|
||||||
pageSize := c.QueryInt("page_size", 10)
|
pageSize := c.QueryInt("page_size", 10)
|
||||||
leagueIDQuery := c.Query("league_id")
|
leagueIDQuery, err := strconv.Atoi(c.Query("league_id"))
|
||||||
sportIDQuery := c.Query("sport_id")
|
if err != nil {
|
||||||
|
h.logger.Error("invalid league id", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid league id", nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
sportIDQuery, err := strconv.Atoi(c.Query("sport_id"))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("invalid sport id", "error", err)
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "invalid sport id", nil, nil)
|
||||||
|
}
|
||||||
firstStartTimeQuery := c.Query("first_start_time")
|
firstStartTimeQuery := c.Query("first_start_time")
|
||||||
lastStartTimeQuery := c.Query("last_start_time")
|
lastStartTimeQuery := c.Query("last_start_time")
|
||||||
|
|
||||||
leagueID := domain.ValidString{
|
leagueID := domain.ValidInt32{
|
||||||
Value: leagueIDQuery,
|
Value: int32(leagueIDQuery),
|
||||||
Valid: leagueIDQuery != "",
|
Valid: leagueIDQuery != 0,
|
||||||
}
|
}
|
||||||
sportID := domain.ValidString{
|
sportID := domain.ValidInt32{
|
||||||
Value: sportIDQuery,
|
Value: int32(sportIDQuery),
|
||||||
Valid: sportIDQuery != "",
|
Valid: sportIDQuery != 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstStartTime domain.ValidTime
|
var firstStartTime domain.ValidTime
|
||||||
|
|
|
||||||
131
internal/web_server/handlers/read_chapa_banks_handler_test.go
Normal file
131
internal/web_server/handlers/read_chapa_banks_handler_test.go
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Mock service ---
|
||||||
|
|
||||||
|
type MockChapaService struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChapaService) GetSupportedBanks() ([]domain.ChapaSupportedBank, error) {
|
||||||
|
args := m.Called()
|
||||||
|
return args.Get(0).([]domain.ChapaSupportedBank), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Tests ---
|
||||||
|
|
||||||
|
func (h *Handler) TestReadChapaBanks_Success(t *testing.T) {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
mockService := new(MockChapaService)
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
isMobile := 1
|
||||||
|
isRtgs := 1
|
||||||
|
is24hrs := 1
|
||||||
|
|
||||||
|
mockBanks := []domain.ChapaSupportedBank{
|
||||||
|
{
|
||||||
|
Id: 101,
|
||||||
|
Slug: "bank-a",
|
||||||
|
Swift: "BKAETHAA",
|
||||||
|
Name: "Bank A",
|
||||||
|
AcctLength: 13,
|
||||||
|
AcctNumberRegex: "^[0-9]{13}$",
|
||||||
|
ExampleValue: "1000222215735",
|
||||||
|
CountryId: 1,
|
||||||
|
IsMobilemoney: &isMobile,
|
||||||
|
IsActive: 1,
|
||||||
|
IsRtgs: &isRtgs,
|
||||||
|
Active: 1,
|
||||||
|
Is24Hrs: &is24hrs,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
Currency: "ETB",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mockService.On("GetSupportedBanks").Return(mockBanks, nil)
|
||||||
|
|
||||||
|
// handler := handlers.NewChapaHandler(mockService)
|
||||||
|
app.Post("/chapa/banks", h.ReadChapaBanks)
|
||||||
|
|
||||||
|
req := createTestRequest(t, "POST", "/chapa/banks", nil)
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var body domain.ResponseWDataFactory[[]domain.ChapaSupportedBank]
|
||||||
|
err = parseJSONBody(resp, &body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.True(t, body.Success)
|
||||||
|
assert.Equal(t, "read successful on chapa supported banks", body.Message)
|
||||||
|
require.Len(t, body.Data, 1)
|
||||||
|
assert.Equal(t, mockBanks[0].Name, body.Data[0].Name)
|
||||||
|
assert.Equal(t, mockBanks[0].AcctNumberRegex, body.Data[0].AcctNumberRegex)
|
||||||
|
|
||||||
|
mockService.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) TestReadChapaBanks_Failure(t *testing.T) {
|
||||||
|
app := fiber.New()
|
||||||
|
|
||||||
|
mockService := new(MockChapaService)
|
||||||
|
mockService.On("GetSupportedBanks").Return(nil, errors.New("chapa service unavailable"))
|
||||||
|
|
||||||
|
// handler := handlers.NewChapaHandler(mockService)
|
||||||
|
app.Post("/chapa/banks", h.ReadChapaBanks)
|
||||||
|
|
||||||
|
req := createTestRequest(t, "POST", "/chapa/banks", nil)
|
||||||
|
resp, err := app.Test(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
|
||||||
|
|
||||||
|
var body domain.Response
|
||||||
|
err = parseJSONBody(resp, &body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.False(t, body.Success)
|
||||||
|
assert.Equal(t, "Internal server error", body.Message)
|
||||||
|
mockService.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestRequest(t *testing.T, method, url string, body interface{}) *http.Request {
|
||||||
|
var buf io.Reader
|
||||||
|
if body != nil {
|
||||||
|
b, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf = bytes.NewBuffer(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJSONBody(resp *http.Response, target interface{}) error {
|
||||||
|
return json.NewDecoder(resp.Body).Decode(target)
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ func (a *App) initAppRoutes() {
|
||||||
a.logger,
|
a.logger,
|
||||||
a.NotidicationStore,
|
a.NotidicationStore,
|
||||||
a.validator,
|
a.validator,
|
||||||
|
a.chapaSvc,
|
||||||
a.walletSvc,
|
a.walletSvc,
|
||||||
a.referralSvc,
|
a.referralSvc,
|
||||||
a.virtualGameSvc,
|
a.virtualGameSvc,
|
||||||
|
|
@ -35,6 +36,7 @@ func (a *App) initAppRoutes() {
|
||||||
a.prematchSvc,
|
a.prematchSvc,
|
||||||
a.eventSvc,
|
a.eventSvc,
|
||||||
*a.resultSvc,
|
*a.resultSvc,
|
||||||
|
a.leagueSvc,
|
||||||
a.cfg,
|
a.cfg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -114,13 +116,17 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
|
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
|
||||||
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
|
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
|
||||||
|
|
||||||
a.fiber.Get("/prematch/odds/:event_id", h.GetPrematchOdds)
|
a.fiber.Get("/events/odds/:event_id", h.GetPrematchOdds)
|
||||||
a.fiber.Get("/prematch/odds", h.GetALLPrematchOdds)
|
a.fiber.Get("/events/odds", h.GetALLPrematchOdds)
|
||||||
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
|
a.fiber.Get("/events/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
|
||||||
|
|
||||||
a.fiber.Get("/prematch/events/:id", h.GetUpcomingEventByID)
|
a.fiber.Get("/events/:id", h.GetUpcomingEventByID)
|
||||||
a.fiber.Get("/prematch/events", h.GetAllUpcomingEvents)
|
a.fiber.Get("/events", h.GetAllUpcomingEvents)
|
||||||
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID)
|
a.fiber.Get("/events/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID)
|
||||||
|
|
||||||
|
// Leagues
|
||||||
|
a.fiber.Get("/leagues", h.GetAllLeagues)
|
||||||
|
a.fiber.Get("/leagues/:id/set-active", h.SetLeagueActive)
|
||||||
|
|
||||||
a.fiber.Get("/result/:id", h.GetResultsByEventID)
|
a.fiber.Get("/result/:id", h.GetResultsByEventID)
|
||||||
|
|
||||||
|
|
@ -184,13 +190,17 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
|
group.Post("/chapa/payments/verify", a.authMiddleware, h.VerifyChapaPayment)
|
||||||
|
group.Post("/chapa/payments/withdraw", a.authMiddleware, h.WithdrawUsingChapa)
|
||||||
|
group.Post("/chapa/payments/deposit", a.authMiddleware, h.DepositUsingChapa)
|
||||||
|
group.Get("/chapa/banks", a.authMiddleware, h.ReadChapaBanks)
|
||||||
|
|
||||||
group.Post("/chapa/payments/initialize", h.InitializePayment)
|
// group.Post("/chapa/payments/initialize", h.InitializePayment)
|
||||||
group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||||
group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
// group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
||||||
group.Get("/chapa/banks", h.GetBanks)
|
// group.Get("/chapa/banks", h.GetBanks)
|
||||||
group.Post("/chapa/transfers", h.CreateTransfer)
|
// group.Post("/chapa/transfers", h.CreateTransfer)
|
||||||
group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
// group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
||||||
|
|
||||||
//Alea Play Virtual Game Routes
|
//Alea Play Virtual Game Routes
|
||||||
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
||||||
|
|
|
||||||
4
makefile
4
makefile
|
|
@ -19,7 +19,7 @@ build:
|
||||||
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run:
|
||||||
@docker compose up -d
|
@docker compose up
|
||||||
|
|
||||||
.PHONY: stop
|
.PHONY: stop
|
||||||
stop:
|
stop:
|
||||||
|
|
@ -56,8 +56,6 @@ db-up:
|
||||||
db-down:
|
db-down:
|
||||||
@docker compose down
|
@docker compose down
|
||||||
@docker volume rm fortunebet-backend_postgres_data
|
@docker volume rm fortunebet-backend_postgres_data
|
||||||
postgres:
|
|
||||||
@docker exec -it fortunebet-backend-postgres-1 psql -U root -d gh
|
|
||||||
.PHONY: sqlc-gen
|
.PHONY: sqlc-gen
|
||||||
sqlc-gen:
|
sqlc-gen:
|
||||||
@sqlc generate
|
@sqlc generate
|
||||||
Loading…
Reference in New Issue
Block a user