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/bet"
|
||||
"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/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
|
|
@ -85,6 +87,7 @@ func main() {
|
|||
transactionSvc := transaction.NewService(store)
|
||||
branchSvc := branch.NewService(store)
|
||||
companySvc := company.NewService(store)
|
||||
leagueSvc := league.New(store)
|
||||
betSvc := bet.NewService(store, eventSvc, oddsSvc, *walletSvc, *branchSvc, logger)
|
||||
resultSvc := result.NewService(store, cfg, logger, *betSvc, oddsSvc, eventSvc)
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
|
|
@ -108,6 +111,17 @@ func main() {
|
|||
logger,
|
||||
)
|
||||
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.StartTicketCrons(*ticketSvc)
|
||||
|
|
@ -116,7 +130,7 @@ func main() {
|
|||
JwtAccessKey: cfg.JwtKey,
|
||||
JwtAccessExpiry: cfg.AccessExpiry,
|
||||
}, 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)
|
||||
|
||||
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 otps;
|
||||
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 (
|
||||
id TEXT PRIMARY KEY,
|
||||
sport_id TEXT,
|
||||
sport_id INT,
|
||||
match_name TEXT,
|
||||
home_team TEXT,
|
||||
away_team TEXT,
|
||||
home_team_id TEXT,
|
||||
away_team_id TEXT,
|
||||
home_team_id INT,
|
||||
away_team_id INT,
|
||||
home_kit_image TEXT,
|
||||
away_kit_image TEXT,
|
||||
league_id TEXT,
|
||||
league_id INT,
|
||||
league_name TEXT,
|
||||
league_cc TEXT,
|
||||
start_time TIMESTAMP,
|
||||
|
|
@ -233,6 +233,20 @@ CREATE TABLE companies (
|
|||
admin_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
|
||||
CREATE VIEW companies_details AS
|
||||
SELECT companies.*,
|
||||
|
|
@ -297,6 +311,7 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
|
|||
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_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE companies
|
||||
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;
|
||||
|
|
|
|||
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": {
|
||||
"get": {
|
||||
"description": "Fetch all supported banks from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -316,20 +315,50 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Get list of banks",
|
||||
"summary": "fetches chapa supported banks",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
||||
"description": "Deposits money into user wallet from user account using Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -339,31 +368,48 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Receive Chapa webhook",
|
||||
"summary": "Deposit money into user wallet using Chapa",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Webhook Payload (dynamic)",
|
||||
"description": "Deposit request payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "Initiate a payment through Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -373,15 +419,15 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initialize a payment transaction",
|
||||
"summary": "Verifies Chapa webhook transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Payment initialization request",
|
||||
"description": "Webhook Payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
||||
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -389,47 +435,15 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||
"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": {
|
||||
"/api/v1/chapa/payments/withdraw": {
|
||||
"post": {
|
||||
"description": "Initiate a transfer request via Chapa",
|
||||
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -439,55 +453,59 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Create a money transfer",
|
||||
"summary": "Withdraw using Chapa",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Transfer request body",
|
||||
"name": "payload",
|
||||
"description": "Chapa Withdraw Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.TransferRequest"
|
||||
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"description": "Withdrawal requested successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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",
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4513,17 +4571,56 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBanksResponse": {
|
||||
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
||||
}
|
||||
},
|
||||
"data": {},
|
||||
"message": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -298,7 +298,6 @@
|
|||
},
|
||||
"/api/v1/chapa/banks": {
|
||||
"get": {
|
||||
"description": "Fetch all supported banks from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -308,20 +307,50 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Get list of banks",
|
||||
"summary": "fetches chapa supported banks",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
||||
"description": "Deposits money into user wallet from user account using Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -331,31 +360,48 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Receive Chapa webhook",
|
||||
"summary": "Deposit money into user wallet using Chapa",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Webhook Payload (dynamic)",
|
||||
"description": "Deposit request payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "Initiate a payment through Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -365,15 +411,15 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initialize a payment transaction",
|
||||
"summary": "Verifies Chapa webhook transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Payment initialization request",
|
||||
"description": "Webhook Payload",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
||||
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -381,47 +427,15 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||
"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": {
|
||||
"/api/v1/chapa/payments/withdraw": {
|
||||
"post": {
|
||||
"description": "Initiate a transfer request via Chapa",
|
||||
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -431,55 +445,59 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Create a money transfer",
|
||||
"summary": "Withdraw using Chapa",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Transfer request body",
|
||||
"name": "payload",
|
||||
"description": "Chapa Withdraw Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.TransferRequest"
|
||||
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"description": "Withdrawal requested successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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",
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4505,17 +4563,56 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBanksResponse": {
|
||||
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
||||
}
|
||||
},
|
||||
"data": {},
|
||||
"message": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -4871,6 +4898,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Role": {
|
||||
"type": "string",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,32 @@ definitions:
|
|||
example: 2
|
||||
type: integer
|
||||
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:
|
||||
properties:
|
||||
acct_length:
|
||||
|
|
@ -159,14 +185,40 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.ChapaSupportedBanksResponse:
|
||||
domain.ChapaSupportedBanksResponseWrapper:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ChapaSupportedBank'
|
||||
type: array
|
||||
data: {}
|
||||
message:
|
||||
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
|
||||
domain.CreateBetOutcomeReq:
|
||||
properties:
|
||||
|
|
@ -203,52 +255,6 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
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:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -408,6 +414,16 @@ definitions:
|
|||
totalRewardEarned:
|
||||
type: number
|
||||
type: object
|
||||
domain.Response:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
status_code:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.Role:
|
||||
enum:
|
||||
- super_admin
|
||||
|
|
@ -468,58 +484,6 @@ definitions:
|
|||
example: 1
|
||||
type: integer
|
||||
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:
|
||||
properties:
|
||||
awayKitImage:
|
||||
|
|
@ -598,24 +562,6 @@ definitions:
|
|||
description: Veli's user identifier
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -1691,123 +1637,133 @@ paths:
|
|||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Fetch all supported banks from Chapa
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChapaSupportedBanksResponse'
|
||||
summary: Get list of banks
|
||||
$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'
|
||||
summary: fetches chapa supported banks
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/callback:
|
||||
/api/v1/chapa/payments/deposit:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Endpoint to receive webhook payloads from Chapa
|
||||
description: Deposits money into user wallet from user account using Chapa
|
||||
parameters:
|
||||
- description: Webhook Payload (dynamic)
|
||||
- description: Deposit request payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
$ref: '#/definitions/domain.ChapaDepositRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: Receive Chapa webhook
|
||||
$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'
|
||||
summary: Deposit money into user wallet using Chapa
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/initialize:
|
||||
/api/v1/chapa/payments/verify:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Initiate a payment through Chapa
|
||||
parameters:
|
||||
- description: Payment initialization request
|
||||
- description: Webhook Payload
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.InitPaymentRequest'
|
||||
$ref: '#/definitions/domain.ChapaTransactionType'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.InitPaymentResponse'
|
||||
summary: Initialize a payment transaction
|
||||
$ref: '#/definitions/domain.Response'
|
||||
summary: Verifies Chapa webhook transaction
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/verify/{tx_ref}:
|
||||
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:
|
||||
/api/v1/chapa/payments/withdraw:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Initiate a transfer request via Chapa
|
||||
description: Initiates a withdrawal transaction using Chapa for the authenticated
|
||||
user.
|
||||
parameters:
|
||||
- description: Transfer request body
|
||||
- description: Chapa Withdraw Request
|
||||
in: body
|
||||
name: payload
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.TransferRequest'
|
||||
$ref: '#/definitions/domain.ChapaWithdrawRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
description: Withdrawal requested successfully
|
||||
schema:
|
||||
$ref: '#/definitions/domain.CreateTransferResponse'
|
||||
summary: Create a money transfer
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/transfers/verify/{transfer_ref}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
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
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Invalid request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VerifyTransferResponse'
|
||||
summary: Verify a transfer
|
||||
$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'
|
||||
summary: Withdraw using Chapa
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/virtual-games/recommendations/{userID}:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: cashier.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -47,15 +47,15 @@ ORDER BY start_time ASC
|
|||
|
||||
type GetAllUpcomingEventsRow struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -132,15 +132,15 @@ ORDER BY start_time ASC
|
|||
|
||||
type GetExpiredUpcomingEventsRow struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -230,8 +230,8 @@ LIMIT $6 OFFSET $5
|
|||
`
|
||||
|
||||
type GetPaginatedUpcomingEventsParams struct {
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LeagueID pgtype.Int4 `json:"league_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
Offset pgtype.Int4 `json:"offset"`
|
||||
|
|
@ -240,15 +240,15 @@ type GetPaginatedUpcomingEventsParams struct {
|
|||
|
||||
type GetPaginatedUpcomingEventsRow struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -327,8 +327,8 @@ WHERE is_live = false
|
|||
`
|
||||
|
||||
type GetTotalEventsParams struct {
|
||||
LeagueID pgtype.Text `json:"league_id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
LeagueID pgtype.Int4 `json:"league_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
LastStartTime pgtype.Timestamp `json:"last_start_time"`
|
||||
FirstStartTime pgtype.Timestamp `json:"first_start_time"`
|
||||
}
|
||||
|
|
@ -372,15 +372,15 @@ LIMIT 1
|
|||
|
||||
type GetUpcomingByIDRow struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -488,15 +488,15 @@ SET sport_id = EXCLUDED.sport_id,
|
|||
|
||||
type InsertEventParams struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -595,15 +595,15 @@ SET sport_id = EXCLUDED.sport_id,
|
|||
|
||||
type InsertUpcomingEventParams struct {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
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 {
|
||||
ID string `json:"id"`
|
||||
SportID pgtype.Text `json:"sport_id"`
|
||||
SportID pgtype.Int4 `json:"sport_id"`
|
||||
MatchName pgtype.Text `json:"match_name"`
|
||||
HomeTeam pgtype.Text `json:"home_team"`
|
||||
AwayTeam pgtype.Text `json:"away_team"`
|
||||
HomeTeamID pgtype.Text `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Text `json:"away_team_id"`
|
||||
HomeTeamID pgtype.Int4 `json:"home_team_id"`
|
||||
AwayTeamID pgtype.Int4 `json:"away_team_id"`
|
||||
HomeKitImage pgtype.Text `json:"home_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"`
|
||||
LeagueCc pgtype.Text `json:"league_cc"`
|
||||
StartTime pgtype.Timestamp `json:"start_time"`
|
||||
|
|
@ -201,6 +201,14 @@ type Event struct {
|
|||
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 {
|
||||
ID string `json:"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/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/joho/godotenv v1.5.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/swaggo/fiber-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/valyala/fasthttp v1.59.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 (
|
||||
// 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/andybalholm/brotli v1.1.1 // indirect
|
||||
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/bytedance/sonic/loader v0.2.4 // 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/go-openapi/jsonpointer v0.21.1 // 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-playground/locales v0.14.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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // 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-isatty v0.0.20 // 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/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/twitchyliquid64/golang-asm v0.15.1 // 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/net v0.38.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/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
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/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/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.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.6.1/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
|
||||
return nil
|
||||
}
|
||||
|
||||
type ChapaConfig struct {
|
||||
ChapaPaymentType string `mapstructure:"chapa_payment_type"`
|
||||
ChapaTransferType string `mapstructure:"chapa_transfer_type"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package domain
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ChapaSecret string
|
||||
|
|
@ -8,14 +11,14 @@ var (
|
|||
)
|
||||
|
||||
type InitPaymentRequest struct {
|
||||
Amount string `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
ReturnURL string `json:"return_url"`
|
||||
Amount Currency `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Email string `json:"email"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
ReturnURL string `json:"return_url"`
|
||||
}
|
||||
|
||||
type TransferRequest struct {
|
||||
|
|
@ -105,3 +108,121 @@ type VerifyTransferResponse struct {
|
|||
Message string `json:"message"`
|
||||
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
|
||||
Valid bool
|
||||
}
|
||||
type ValidInt32 struct {
|
||||
Value int32
|
||||
Valid bool
|
||||
}
|
||||
|
||||
type ValidString struct {
|
||||
Value string
|
||||
|
|
@ -48,6 +52,18 @@ func (m Currency) String() string {
|
|||
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 {
|
||||
|
||||
vat := amount.Float32() * 0.15
|
||||
|
|
|
|||
|
|
@ -37,15 +37,15 @@ const (
|
|||
|
||||
type Event struct {
|
||||
ID string
|
||||
SportID string
|
||||
SportID int32
|
||||
MatchName string
|
||||
HomeTeam string
|
||||
AwayTeam string
|
||||
HomeTeamID string
|
||||
AwayTeamID string
|
||||
HomeTeamID int32
|
||||
AwayTeamID int32
|
||||
HomeKitImage string
|
||||
AwayKitImage string
|
||||
LeagueID string
|
||||
LeagueID int32
|
||||
LeagueName string
|
||||
LeagueCC string
|
||||
StartTime string
|
||||
|
|
@ -87,15 +87,15 @@ type BetResult struct {
|
|||
|
||||
type UpcomingEvent struct {
|
||||
ID string // Event ID
|
||||
SportID string // Sport ID
|
||||
SportID int32 // Sport ID
|
||||
MatchName string // Match or event name
|
||||
HomeTeam string // Home team name (if available)
|
||||
AwayTeam string // Away team name (can be empty/null)
|
||||
HomeTeamID string // Home team ID
|
||||
AwayTeamID string // Away team ID (can be empty/null)
|
||||
HomeTeamID int32 // Home team ID
|
||||
AwayTeamID int32 // Away team ID (can be empty/null)
|
||||
HomeKitImage string // Kit or image for home team (optional)
|
||||
AwayKitImage string // Kit or image for away team (optional)
|
||||
LeagueID string // League ID
|
||||
LeagueID int32 // League ID
|
||||
LeagueName string // League name
|
||||
LeagueCC string // League country code
|
||||
StartTime time.Time // Converted from "time" field in UNIX format
|
||||
|
|
|
|||
|
|
@ -1,66 +1,9 @@
|
|||
package domain
|
||||
|
||||
// TODO Will make this dynamic by moving into the database
|
||||
|
||||
var SupportedLeagues = []int64{
|
||||
// Football
|
||||
10041282, //Premier League
|
||||
10083364, //La Liga
|
||||
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
|
||||
|
||||
type League struct {
|
||||
ID int64
|
||||
Name string
|
||||
CountryCode string
|
||||
Bet365ID int32
|
||||
IsActive bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -15,10 +14,11 @@ type Market struct {
|
|||
MarketName string
|
||||
MarketID string
|
||||
UpdatedAt time.Time
|
||||
Odds []json.RawMessage
|
||||
Odds []map[string]interface{}
|
||||
Name string
|
||||
Handicap string
|
||||
OddsVal float64
|
||||
Source string
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type League struct {
|
||||
type LeagueRes struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CC string `json:"cc"`
|
||||
|
|
@ -39,14 +39,14 @@ type CommonResultResponse struct {
|
|||
}
|
||||
|
||||
type FootballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League LeagueRes `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstHalf Score `json:"1"`
|
||||
SecondHalf Score `json:"2"`
|
||||
|
|
@ -78,14 +78,14 @@ type FootballResultResponse struct {
|
|||
}
|
||||
|
||||
type BasketballResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League LeagueRes `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstQuarter Score `json:"1"`
|
||||
SecondQuarter Score `json:"2"`
|
||||
|
|
@ -125,14 +125,14 @@ type BasketballResultResponse struct {
|
|||
Bet365ID string `json:"bet365_id"`
|
||||
}
|
||||
type IceHockeyResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League League `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
ID string `json:"id"`
|
||||
SportID string `json:"sport_id"`
|
||||
Time string `json:"time"`
|
||||
TimeStatus string `json:"time_status"`
|
||||
League LeagueRes `json:"league"`
|
||||
Home Team `json:"home"`
|
||||
Away Team `json:"away"`
|
||||
SS string `json:"ss"`
|
||||
Scores struct {
|
||||
FirstPeriod Score `json:"1"`
|
||||
SecondPeriod Score `json:"2"`
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ type CreateTransaction struct {
|
|||
PaymentOption PaymentOption
|
||||
FullName string
|
||||
PhoneNumber string
|
||||
// Payment Details for bank
|
||||
BankCode string
|
||||
BeneficiaryName string
|
||||
AccountName string
|
||||
|
|
|
|||
|
|
@ -18,13 +18,24 @@ var Environment = map[string]string{
|
|||
|
||||
func NewLogger(env string, lvl slog.Level) *slog.Logger {
|
||||
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 {
|
||||
case "development":
|
||||
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
logHandler = slog.NewTextHandler(file, &slog.HandlerOptions{
|
||||
Level: lvl,
|
||||
})
|
||||
default:
|
||||
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
logHandler = slog.NewJSONHandler(file, &slog.HandlerOptions{
|
||||
Level: lvl,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
|
|||
|
||||
return s.queries.InsertEvent(ctx, dbgen.InsertEventParams{
|
||||
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},
|
||||
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
||||
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
||||
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
|
||||
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
|
||||
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
|
||||
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
|
||||
HomeKitImage: pgtype.Text{String: e.HomeKitImage, 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},
|
||||
LeagueCc: pgtype.Text{String: e.LeagueCC, 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 {
|
||||
return s.queries.InsertUpcomingEvent(ctx, dbgen.InsertUpcomingEventParams{
|
||||
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},
|
||||
HomeTeam: pgtype.Text{String: e.HomeTeam, Valid: true},
|
||||
AwayTeam: pgtype.Text{String: e.AwayTeam, Valid: true},
|
||||
HomeTeamID: pgtype.Text{String: e.HomeTeamID, Valid: true},
|
||||
AwayTeamID: pgtype.Text{String: e.AwayTeamID, Valid: true},
|
||||
HomeTeamID: pgtype.Int4{Int32: e.HomeTeamID, Valid: true},
|
||||
AwayTeamID: pgtype.Int4{Int32: e.AwayTeamID, Valid: true},
|
||||
HomeKitImage: pgtype.Text{String: e.HomeKitImage, 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},
|
||||
LeagueCc: pgtype.Text{String: e.LeagueCC, 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 {
|
||||
upcomingEvents[i] = domain.UpcomingEvent{
|
||||
ID: e.ID,
|
||||
SportID: e.SportID.String,
|
||||
SportID: e.SportID.Int32,
|
||||
MatchName: e.MatchName.String,
|
||||
HomeTeam: e.HomeTeam.String,
|
||||
AwayTeam: e.AwayTeam.String,
|
||||
HomeTeamID: e.HomeTeamID.String,
|
||||
AwayTeamID: e.AwayTeamID.String,
|
||||
HomeTeamID: e.HomeTeamID.Int32,
|
||||
AwayTeamID: e.AwayTeamID.Int32,
|
||||
HomeKitImage: e.HomeKitImage.String,
|
||||
AwayKitImage: e.AwayKitImage.String,
|
||||
LeagueID: e.LeagueID.String,
|
||||
LeagueID: e.LeagueID.Int32,
|
||||
LeagueName: e.LeagueName.String,
|
||||
LeagueCC: e.LeagueCc.String,
|
||||
StartTime: e.StartTime.Time.UTC(),
|
||||
|
|
@ -106,15 +106,15 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context, filter domain.Even
|
|||
for i, e := range events {
|
||||
upcomingEvents[i] = domain.UpcomingEvent{
|
||||
ID: e.ID,
|
||||
SportID: e.SportID.String,
|
||||
SportID: e.SportID.Int32,
|
||||
MatchName: e.MatchName.String,
|
||||
HomeTeam: e.HomeTeam.String,
|
||||
AwayTeam: e.AwayTeam.String,
|
||||
HomeTeamID: e.HomeTeamID.String,
|
||||
AwayTeamID: e.AwayTeamID.String,
|
||||
HomeTeamID: e.HomeTeamID.Int32,
|
||||
AwayTeamID: e.AwayTeamID.Int32,
|
||||
HomeKitImage: e.HomeKitImage.String,
|
||||
AwayKitImage: e.AwayKitImage.String,
|
||||
LeagueID: e.LeagueID.String,
|
||||
LeagueID: e.LeagueID.Int32,
|
||||
LeagueName: e.LeagueName.String,
|
||||
LeagueCC: e.LeagueCc.String,
|
||||
StartTime: e.StartTime.Time.UTC(),
|
||||
|
|
@ -160,15 +160,15 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.Ev
|
|||
for i, e := range events {
|
||||
upcomingEvents[i] = domain.UpcomingEvent{
|
||||
ID: e.ID,
|
||||
SportID: e.SportID.String,
|
||||
SportID: e.SportID.Int32,
|
||||
MatchName: e.MatchName.String,
|
||||
HomeTeam: e.HomeTeam.String,
|
||||
AwayTeam: e.AwayTeam.String,
|
||||
HomeTeamID: e.HomeTeamID.String,
|
||||
AwayTeamID: e.AwayTeamID.String,
|
||||
HomeTeamID: e.HomeTeamID.Int32,
|
||||
AwayTeamID: e.AwayTeamID.Int32,
|
||||
HomeKitImage: e.HomeKitImage.String,
|
||||
AwayKitImage: e.AwayKitImage.String,
|
||||
LeagueID: e.LeagueID.String,
|
||||
LeagueID: e.LeagueID.Int32,
|
||||
LeagueName: e.LeagueName.String,
|
||||
LeagueCC: e.LeagueCc.String,
|
||||
StartTime: e.StartTime.Time.UTC(),
|
||||
|
|
@ -208,15 +208,15 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
|
|||
|
||||
return domain.UpcomingEvent{
|
||||
ID: event.ID,
|
||||
SportID: event.SportID.String,
|
||||
SportID: event.SportID.Int32,
|
||||
MatchName: event.MatchName.String,
|
||||
HomeTeam: event.HomeTeam.String,
|
||||
AwayTeam: event.AwayTeam.String,
|
||||
HomeTeamID: event.HomeTeamID.String,
|
||||
AwayTeamID: event.AwayTeamID.String,
|
||||
HomeTeamID: event.HomeTeamID.Int32,
|
||||
AwayTeamID: event.AwayTeamID.Int32,
|
||||
HomeKitImage: event.HomeKitImage.String,
|
||||
AwayKitImage: event.AwayKitImage.String,
|
||||
LeagueID: event.LeagueID.String,
|
||||
LeagueID: event.LeagueID.Int32,
|
||||
LeagueName: event.LeagueName.String,
|
||||
LeagueCC: event.LeagueCc.String,
|
||||
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
|
||||
}
|
||||
|
||||
for _, raw := range m.Odds {
|
||||
var item map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &item); err != nil {
|
||||
continue
|
||||
}
|
||||
for _, item := range m.Odds {
|
||||
var name string
|
||||
var oddsVal float64
|
||||
|
||||
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"])
|
||||
oddsVal := getFloat(item["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},
|
||||
RawOdds: rawOddsBytes,
|
||||
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},
|
||||
}
|
||||
|
||||
|
|
@ -85,23 +89,6 @@ func writeFailedMarketLog(m domain.Market, err error) error {
|
|||
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) {
|
||||
odds, err := s.queries.GetPrematchOdds(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -286,3 +273,34 @@ func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID stri
|
|||
|
||||
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"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
|
|
@ -39,3 +40,12 @@ func OpenDB(url string) (*pgxpool.Pool, func(), error) {
|
|||
conn.Close()
|
||||
}, 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 {
|
||||
return domain.CreateBetOutcome{}, err
|
||||
}
|
||||
sportID, err := strconv.ParseInt(event.SportID, 10, 64)
|
||||
if err != nil {
|
||||
return domain.CreateBetOutcome{}, err
|
||||
}
|
||||
newOutcome := domain.CreateBetOutcome{
|
||||
EventID: eventID,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
SportID: sportID,
|
||||
SportID: int64(event.SportID),
|
||||
HomeTeamName: event.HomeTeam,
|
||||
AwayTeamName: event.AwayTeam,
|
||||
MarketName: odds.MarketName,
|
||||
|
|
@ -287,7 +283,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
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 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)
|
||||
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)
|
||||
if err != nil {
|
||||
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,
|
||||
OddID: oddID,
|
||||
MarketID: marketID,
|
||||
SportID: sportID,
|
||||
SportID: int64(sportID),
|
||||
HomeTeamName: HomeTeam,
|
||||
AwayTeamName: AwayTeam,
|
||||
MarketName: marketName,
|
||||
|
|
@ -388,7 +379,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID, sportI
|
|||
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
|
||||
|
||||
|
|
|
|||
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)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -75,7 +74,7 @@ func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error
|
|||
events := []domain.Event{}
|
||||
switch source {
|
||||
case "bet365":
|
||||
events = handleBet365prematch(body, sportID)
|
||||
events = handleBet365prematch(body, sportID, source)
|
||||
case "betfair":
|
||||
events = handleBetfairprematch(body, sportID, source)
|
||||
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 {
|
||||
Success int `json:"success"`
|
||||
Results [][]map[string]interface{} `json:"results"`
|
||||
|
|
@ -105,7 +104,7 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
|
|||
|
||||
events := []domain.Event{}
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -117,24 +116,24 @@ func handleBet365prematch(body []byte, sportID int) []domain.Event {
|
|||
|
||||
event := domain.Event{
|
||||
ID: getString(ev["ID"]),
|
||||
SportID: fmt.Sprintf("%d", sportID),
|
||||
SportID: int32(sportID),
|
||||
MatchName: getString(ev["NA"]),
|
||||
Score: getString(ev["SS"]),
|
||||
MatchMinute: getInt(ev["TM"]),
|
||||
TimerStatus: getString(ev["TT"]),
|
||||
HomeTeamID: getString(ev["HT"]),
|
||||
AwayTeamID: getString(ev["AT"]),
|
||||
HomeTeamID: getInt32(ev["HT"]),
|
||||
AwayTeamID: getInt32(ev["AT"]),
|
||||
HomeKitImage: getString(ev["K1"]),
|
||||
AwayKitImage: getString(ev["K2"]),
|
||||
LeagueName: getString(ev["CT"]),
|
||||
LeagueID: getString(ev["C2"]),
|
||||
LeagueID: getInt32(ev["C2"]),
|
||||
LeagueCC: getString(ev["CB"]),
|
||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||
IsLive: true,
|
||||
Status: "live",
|
||||
MatchPeriod: getInt(ev["MD"]),
|
||||
AddedTime: getInt(ev["TA"]),
|
||||
Source: "bet365",
|
||||
Source: source,
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
|
|
@ -152,23 +151,20 @@ func handleBetfairprematch(body []byte, sportID int, source string) []domain.Eve
|
|||
|
||||
events := []domain.Event{}
|
||||
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
|
||||
}
|
||||
|
||||
for _, ev := range data.Results {
|
||||
homeRaw, _ := ev["home"].(map[string]interface{})
|
||||
homeId, _ := homeRaw["id"].(string)
|
||||
|
||||
awayRaw, _ := ev["home"].(map[string]interface{})
|
||||
awayId, _ := awayRaw["id"].(string)
|
||||
|
||||
event := domain.Event{
|
||||
ID: getString(ev["id"]),
|
||||
SportID: fmt.Sprintf("%d", sportID),
|
||||
SportID: int32(sportID),
|
||||
TimerStatus: getString(ev["time_status"]),
|
||||
HomeTeamID: homeId,
|
||||
AwayTeamID: awayId,
|
||||
HomeTeamID: getInt32(homeRaw["id"]),
|
||||
AwayTeamID: getInt32(awayRaw["id"]),
|
||||
StartTime: time.Now().UTC().Format(time.RFC3339),
|
||||
IsLive: true,
|
||||
Status: "live",
|
||||
|
|
@ -221,8 +217,8 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
|||
log.Printf("Sport ID %d", sportID)
|
||||
for page <= totalPages {
|
||||
page = page + 1
|
||||
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
|
||||
log.Printf("📡 Fetching data for sport %d at page %d", sportID, page)
|
||||
url := fmt.Sprintf(url, sportID, s.token, page)
|
||||
log.Printf("📡 Fetching data from %s - sport %d, for event data page %d", source, sportID, page)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
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
|
||||
// }
|
||||
|
||||
// leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
||||
// if err != nil {
|
||||
// log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
||||
// continue
|
||||
// }
|
||||
leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
// if !slices.Contains(domain.SupportedLeagues, leagueID) {
|
||||
// // fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID)
|
||||
// _, err = fmt.Fprintf(b, "Skipped league %s (%d) in sport %d\n", ev.League.Name, leagueID, sportID)
|
||||
// if err != nil {
|
||||
// fmt.Printf(" Error while logging skipped league")
|
||||
// }
|
||||
// skippedLeague = append(skippedLeague, ev.League.Name)
|
||||
// continue
|
||||
// }
|
||||
// doesn't make sense to save and check back to back, but for now it can be here
|
||||
s.store.SaveLeague(ctx, domain.League{
|
||||
ID: leagueID,
|
||||
Name: ev.League.Name,
|
||||
IsActive: true,
|
||||
})
|
||||
|
||||
if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
|
||||
skippedLeague = append(skippedLeague, ev.League.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
event := domain.UpcomingEvent{
|
||||
ID: ev.ID,
|
||||
SportID: ev.SportID,
|
||||
SportID: convertInt32(ev.SportID),
|
||||
MatchName: "",
|
||||
HomeTeam: ev.Home.Name,
|
||||
AwayTeam: "", // handle nil safely
|
||||
HomeTeamID: ev.Home.ID,
|
||||
AwayTeamID: "",
|
||||
HomeTeamID: convertInt32(ev.Home.ID),
|
||||
AwayTeamID: 0,
|
||||
HomeKitImage: "",
|
||||
AwayKitImage: "",
|
||||
LeagueID: ev.League.ID,
|
||||
LeagueID: convertInt32(ev.League.ID),
|
||||
LeagueName: ev.League.Name,
|
||||
LeagueCC: "",
|
||||
StartTime: time.Unix(startUnix, 0).UTC(),
|
||||
|
|
@ -281,7 +279,7 @@ func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, sour
|
|||
|
||||
if ev.Away != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +317,20 @@ func getInt(v interface{}) int {
|
|||
}
|
||||
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) {
|
||||
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"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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
|
||||
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)
|
||||
if err != nil {
|
||||
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...)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
eventID, err := strconv.ParseInt(eventIDStr, 10, 64)
|
||||
|
|
@ -336,6 +543,13 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
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{
|
||||
EventID: eventID,
|
||||
FI: fi,
|
||||
|
|
@ -344,7 +558,9 @@ func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName
|
|||
MarketName: market.Name,
|
||||
MarketID: marketIDstr,
|
||||
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)
|
||||
|
|
@ -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) {
|
||||
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
|
||||
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 {
|
||||
case "1":
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
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)
|
||||
}
|
||||
|
||||
// evaluateRugbyMoneyLine evaluates Rugby money line bets
|
||||
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbyMoneyLine Evaluates Rugby money line bets
|
||||
func EvaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -99,8 +99,8 @@ func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateRugbySpread evaluates Rugby spread bets
|
||||
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbySpread Evaluates Rugby spread bets
|
||||
func EvaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// evaluateRugbyTotalPoints evaluates Rugby total points bets
|
||||
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbyTotalPoints Evaluates Rugby total points bets
|
||||
func EvaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
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)
|
||||
}
|
||||
|
||||
// evaluateBaseballMoneyLine evaluates Baseball money line bets
|
||||
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballMoneyLine Evaluates Baseball money line bets
|
||||
func EvaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -175,8 +175,8 @@ func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Aw
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballSpread evaluates Baseball spread bets
|
||||
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballSpread Evaluates Baseball spread bets
|
||||
func EvaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// evaluateBaseballTotalRuns evaluates Baseball total runs bets
|
||||
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballTotalRuns Evaluates Baseball total runs bets
|
||||
func EvaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalRuns := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
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)
|
||||
}
|
||||
|
||||
// evaluateBaseballFirstInning evaluates Baseball first inning bets
|
||||
func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballFirstInning Evaluates Baseball first inning bets
|
||||
func EvaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -256,8 +256,8 @@ func evaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home,
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballFirst5Innings evaluates Baseball first 5 innings bets
|
||||
func evaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballFirst5Innings Evaluates Baseball first 5 innings bets
|
||||
func EvaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
|
|||
|
|
@ -29,75 +29,75 @@ func TestNFLMarkets(t *testing.T) {
|
|||
case int64(domain.AMERICAN_FOOTBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_SPREAD):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.AMERICAN_FOOTBALL_TOTAL_POINTS):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
|
|
@ -128,75 +128,75 @@ func TestRugbyMarkets(t *testing.T) {
|
|||
case int64(domain.RUGBY_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_SPREAD), int64(domain.RUGBY_HANDICAP):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.RUGBY_TOTAL_POINTS):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
|
|
@ -226,75 +226,75 @@ func TestBaseballMarkets(t *testing.T) {
|
|||
case int64(domain.BASEBALL_MONEY_LINE):
|
||||
// Home win, away win, draw, and invalid OddHeader for Money Line
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_LOSS, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_SPREAD):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
})
|
||||
case int64(domain.BASEBALL_TOTAL_RUNS):
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_WIN, status)
|
||||
})
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_VOID, status)
|
||||
})
|
||||
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)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, domain.OUTCOME_STATUS_PENDING, status)
|
||||
|
|
@ -338,7 +338,7 @@ func TestEvaluateFootballOutcome(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
|
|
@ -357,7 +357,7 @@ func TestEvaluateTotalLegs(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateTotalLegs(tt.outcome, tt.score)
|
||||
status, _ := EvaluateTotalLegs(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -380,7 +380,7 @@ func TestEvaluateGameLines(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateGameLines(tt.outcome, tt.score)
|
||||
status, _ := EvaluateGameLines(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -408,7 +408,7 @@ func TestEvaluateFirstTeamToScore(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateFirstTeamToScore(tt.outcome, tt.events)
|
||||
status, _ := EvaluateFirstTeamToScore(tt.outcome, tt.events)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -430,7 +430,7 @@ func TestEvaluateGoalsOverUnder(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateGoalsOverUnder(tt.outcome, tt.score)
|
||||
status, _ := EvaluateGoalsOverUnder(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -452,7 +452,7 @@ func TestEvaluateGoalsOddEven(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateGoalsOddEven(tt.outcome, tt.score)
|
||||
status, _ := EvaluateGoalsOddEven(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -474,7 +474,7 @@ func TestEvaluateCorrectScore(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateCorrectScore(tt.outcome, tt.score)
|
||||
status, _ := EvaluateCorrectScore(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -498,7 +498,7 @@ func TestEvaluateHighestScoringHalf(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -551,7 +551,7 @@ func TestEvaluateHighestScoringQuarter(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -577,7 +577,7 @@ func TestEvaluateWinningMargin(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateWinningMargin(tt.outcome, tt.score)
|
||||
status, _ := EvaluateWinningMargin(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -605,7 +605,7 @@ func TestEvaluateDoubleResult(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -632,7 +632,7 @@ func TestEvaluateHighestScoringPeriod(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -656,7 +656,7 @@ func TestEvalauteTiedAfterRegulation(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateTiedAfterRegulation(tt.outcome, tt.score)
|
||||
status, _ := EvaluateTiedAfterRegulation(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -680,7 +680,7 @@ func TestEvaluateTeamTotal(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateTeamTotal(tt.outcome, tt.score)
|
||||
status, _ := EvaluateTeamTotal(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -703,7 +703,7 @@ func TestDrawNoBet(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateDrawNoBet(tt.outcome, tt.score)
|
||||
status, _ := EvaluateDrawNoBet(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -727,7 +727,7 @@ func TestEvaluateMoneyLine(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateMoneyLine(tt.outcome, tt.score)
|
||||
status, _ := EvaluateMoneyLine(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -751,7 +751,7 @@ func TestEvaluateDoubleChance(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateDoubleChance(tt.outcome, tt.score)
|
||||
status, _ := EvaluateDoubleChance(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -775,7 +775,7 @@ func TestEvaluateResultAndTotal(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateResultAndTotal(tt.outcome, tt.score)
|
||||
status, _ := EvaluateResultAndTotal(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -826,7 +826,7 @@ func TestEvaluateBTTSX(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateBTTSX(tt.outcome, tt.score)
|
||||
status, _ := EvaluateBTTSX(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -852,7 +852,7 @@ func TestEvaluateResultAndBTTSX(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateResultAndBTTSX(tt.outcome, tt.score)
|
||||
status, _ := EvaluateResultAndBTTSX(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -876,7 +876,7 @@ func TestEvaluateMoneyLine3Way(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateMoneyLine3Way(tt.outcome, tt.score)
|
||||
status, _ := EvaluateMoneyLine3Way(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -937,7 +937,7 @@ func TestEvaluateAsianHandicap(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateAsianHandicap(tt.outcome, tt.score)
|
||||
status, _ := EvaluateAsianHandicap(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
t.Errorf("expected %d, got %d", tt.expected, status)
|
||||
}
|
||||
|
|
@ -971,7 +971,7 @@ func TestEvaluateHandicapAndTotal(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status, _ := evaluateHandicapAndTotal(tt.outcome, tt.score)
|
||||
status, _ := EvaluateHandicapAndTotal(tt.outcome, tt.score)
|
||||
if status != tt.expected {
|
||||
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/bet"
|
||||
"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/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
|
|
@ -44,6 +46,7 @@ type App struct {
|
|||
userSvc *user.Service
|
||||
betSvc *bet.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
chapaSvc *chapa.Service
|
||||
walletSvc *wallet.Service
|
||||
transactionSvc *transaction.Service
|
||||
ticketSvc *ticket.Service
|
||||
|
|
@ -54,6 +57,7 @@ type App struct {
|
|||
Logger *slog.Logger
|
||||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
resultSvc *result.Service
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +69,7 @@ func NewApp(
|
|||
userSvc *user.Service,
|
||||
ticketSvc *ticket.Service,
|
||||
betSvc *bet.Service,
|
||||
chapaSvc *chapa.Service,
|
||||
walletSvc *wallet.Service,
|
||||
transactionSvc *transaction.Service,
|
||||
branchSvc *branch.Service,
|
||||
|
|
@ -72,6 +77,7 @@ func NewApp(
|
|||
notidicationStore *notificationservice.Service,
|
||||
prematchSvc *odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
leagueSvc league.Service,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||
aleaVirtualGameService alea.AleaVirtualGameService,
|
||||
|
|
@ -104,6 +110,7 @@ func NewApp(
|
|||
userSvc: userSvc,
|
||||
ticketSvc: ticketSvc,
|
||||
betSvc: betSvc,
|
||||
chapaSvc: chapaSvc,
|
||||
walletSvc: walletSvc,
|
||||
transactionSvc: transactionSvc,
|
||||
branchSvc: branchSvc,
|
||||
|
|
@ -113,6 +120,7 @@ func NewApp(
|
|||
Logger: logger,
|
||||
prematchSvc: prematchSvc,
|
||||
eventSvc: eventSvc,
|
||||
leagueSvc: leagueSvc,
|
||||
virtualGameSvc: virtualGameSvc,
|
||||
aleaVirtualGameService: aleaVirtualGameService,
|
||||
veliVirtualGameService: veliVirtualGameService,
|
||||
|
|
|
|||
|
|
@ -72,18 +72,28 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
|||
userID := c.Locals("user_id").(int64)
|
||||
// role := c.Locals("role").(domain.Role)
|
||||
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
sportIDQuery := c.Query("sport_id")
|
||||
leagueIDQuery, err := strconv.Atoi(c.Query("league_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")
|
||||
lastStartTimeQuery := c.Query("last_start_time")
|
||||
|
||||
leagueID := domain.ValidString{
|
||||
Value: leagueIDQuery,
|
||||
Valid: leagueIDQuery != "",
|
||||
leagueID := domain.ValidInt32{
|
||||
Value: int32(leagueIDQuery),
|
||||
Valid: leagueIDQuery != 0,
|
||||
}
|
||||
sportID := domain.ValidString{
|
||||
Value: sportIDQuery,
|
||||
Valid: sportIDQuery != "",
|
||||
sportID := domain.ValidInt32{
|
||||
Value: int32(sportIDQuery),
|
||||
Valid: sportIDQuery != 0,
|
||||
}
|
||||
|
||||
var firstStartTime domain.ValidTime
|
||||
|
|
@ -123,7 +133,6 @@ func (h *Handler) RandomBet(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
var res domain.CreateBetRes
|
||||
var err error
|
||||
for i := 0; i < int(req.NumberOfBets); i++ {
|
||||
res, err = h.betSvc.PlaceRandomBet(c.Context(), userID, req.BranchID, leagueID, sportID, firstStartTime, lastStartTime)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,283 +1,464 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
// "bytes"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "net/http"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// GetBanks godoc
|
||||
// @Summary Get list of banks
|
||||
// @Description Fetch all supported banks from Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.ChapaSupportedBanksResponse
|
||||
// @Router /api/v1/chapa/banks [get]
|
||||
func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
// // GetBanks godoc
|
||||
// // @Summary Get list of banks
|
||||
// // @Description Fetch all supported banks from Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Success 200 {object} domain.ChapaSupportedBanksResponse
|
||||
// // @Router /api/v1/chapa/banks [get]
|
||||
// func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
||||
// 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)
|
||||
// if err != nil {
|
||||
// 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)
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// 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
|
||||
// @Summary Initialize a payment transaction
|
||||
// @Description Initiate a payment through Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
||||
// @Success 200 {object} domain.InitPaymentResponse
|
||||
// @Router /api/v1/chapa/payments/initialize [post]
|
||||
func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
||||
var req InitPaymentRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request body",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// // InitializePayment godoc
|
||||
// // @Summary Initialize a payment transaction
|
||||
// // @Description Initiate a payment through Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
||||
// // @Success 200 {object} domain.InitPaymentResponse
|
||||
// // @Router /api/v1/chapa/payments/initialize [post]
|
||||
// func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
||||
// var req InitPaymentRequest
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Invalid request body",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
// Generate and assign a unique transaction reference
|
||||
req.TxRef = uuid.New().String()
|
||||
// // Generate and assign a unique transaction reference
|
||||
// req.TxRef = uuid.New().String()
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to serialize request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// payload, err := json.Marshal(req)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to serialize request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
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("Content-Type", "application/json")
|
||||
// httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
||||
// if err != nil {
|
||||
// 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("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to initialize payment",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to initialize payment",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to read response",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// 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)
|
||||
// }
|
||||
|
||||
// VerifyTransaction godoc
|
||||
// @Summary Verify a payment transaction
|
||||
// @Description Verify the transaction status from Chapa using tx_ref
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param tx_ref path string true "Transaction Reference"
|
||||
// @Success 200 {object} domain.VerifyTransactionResponse
|
||||
// @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
||||
func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
||||
txRef := c.Params("tx_ref")
|
||||
if txRef == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Missing transaction reference",
|
||||
})
|
||||
}
|
||||
// // VerifyTransaction godoc
|
||||
// // @Summary Verify a payment transaction
|
||||
// // @Description Verify the transaction status from Chapa using tx_ref
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param tx_ref path string true "Transaction Reference"
|
||||
// // @Success 200 {object} domain.VerifyTransactionResponse
|
||||
// // @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
||||
// func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
||||
// txRef := c.Params("tx_ref")
|
||||
// if txRef == "" {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "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)
|
||||
if err != nil {
|
||||
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, err := http.NewRequest("GET", url, nil)
|
||||
// if err != nil {
|
||||
// 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)
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to verify transaction",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to verify transaction",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to read response",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// 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)
|
||||
// }
|
||||
|
||||
// ReceiveWebhook godoc
|
||||
// @Summary Receive Chapa webhook
|
||||
// @Description Endpoint to receive webhook payloads from Chapa
|
||||
// // ReceiveWebhook godoc
|
||||
// // @Summary Receive Chapa webhook
|
||||
// // @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
|
||||
// @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(),
|
||||
})
|
||||
// @Param payload body domain.ChapaTransactionType true "Webhook Payload"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Router /api/v1/chapa/payments/verify [post]
|
||||
func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
|
||||
var txType domain.ChapaTransactionType
|
||||
if err := c.BodyParser(&txType); err != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
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
|
||||
// @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
|
||||
// WithdrawUsingChapa godoc
|
||||
// @Summary Withdraw using Chapa
|
||||
// @Description Initiates a withdrawal transaction using Chapa for the authenticated user.
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.ChapaWithdrawRequest true "Chapa Withdraw Request"
|
||||
// @Success 200 {object} domain.Response{data=string} "Withdrawal requested successfully"
|
||||
// @Failure 400 {object} domain.Response "Invalid request"
|
||||
// @Failure 401 {object} domain.Response "Unauthorized"
|
||||
// @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 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request",
|
||||
"details": err.Error(),
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
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
|
||||
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(),
|
||||
})
|
||||
if err := h.chapaSvc.WithdrawUsingChapa(c.Context(), userID, req); err != nil {
|
||||
return domain.FiberErrorResponse(c, err)
|
||||
}
|
||||
|
||||
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)
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Withdrawal requested successfully",
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyTransfer godoc
|
||||
// @Summary Verify a transfer
|
||||
// @Description Check the status of a money transfer via reference
|
||||
// DepositUsingChapa godoc
|
||||
// @Summary Deposit money into user wallet using Chapa
|
||||
// @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
|
||||
// @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)
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.ChapaSupportedBanksResponseWrapper
|
||||
// @Failure 400,401,404,422,500 {object} domain.Response
|
||||
// @Router /api/v1/chapa/banks [get]
|
||||
func (h *Handler) ReadChapaBanks(c *fiber.Ctx) error {
|
||||
banks, err := h.chapaSvc.GetSupportedBanks()
|
||||
fmt.Printf("\n\nhandler fetched banks: %+v\n\n", banks)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to create HTTP request",
|
||||
"details": err.Error(),
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.Response{
|
||||
Message: "Internal server error",
|
||||
Success: false,
|
||||
StatusCode: fiber.StatusInternalServerError,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
return c.Status(fiber.StatusOK).JSON(domain.ResponseWDataFactory[[]domain.ChapaSupportedBank]{
|
||||
Data: banks,
|
||||
Response: domain.Response{
|
||||
Message: "read successful on chapa supported banks",
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
"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/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
|
|
@ -30,6 +32,7 @@ type Handler struct {
|
|||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
referralSvc referralservice.ReferralStore
|
||||
chapaSvc chapa.ChapaPort
|
||||
walletSvc *wallet.Service
|
||||
transactionSvc *transaction.Service
|
||||
ticketSvc *ticket.Service
|
||||
|
|
@ -38,6 +41,7 @@ type Handler struct {
|
|||
companySvc *company.Service
|
||||
prematchSvc *odds.ServiceImpl
|
||||
eventSvc event.Service
|
||||
leagueSvc league.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
aleaVirtualGameSvc alea.AleaVirtualGameService
|
||||
veliVirtualGameSvc veli.VeliVirtualGameService
|
||||
|
|
@ -53,6 +57,7 @@ func New(
|
|||
logger *slog.Logger,
|
||||
notificationSvc *notificationservice.Service,
|
||||
validator *customvalidator.CustomValidator,
|
||||
chapaSvc chapa.ChapaPort,
|
||||
walletSvc *wallet.Service,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||
|
|
@ -69,12 +74,14 @@ func New(
|
|||
companySvc *company.Service,
|
||||
prematchSvc *odds.ServiceImpl,
|
||||
eventSvc event.Service,
|
||||
leagueSvc league.Service,
|
||||
resultSvc result.Service,
|
||||
cfg *config.Config,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
logger: logger,
|
||||
notificationSvc: notificationSvc,
|
||||
chapaSvc: chapaSvc,
|
||||
walletSvc: walletSvc,
|
||||
referralSvc: referralSvc,
|
||||
validator: validator,
|
||||
|
|
@ -86,6 +93,7 @@ func New(
|
|||
companySvc: companySvc,
|
||||
prematchSvc: prematchSvc,
|
||||
eventSvc: eventSvc,
|
||||
leagueSvc: leagueSvc,
|
||||
virtualGameSvc: virtualGameSvc,
|
||||
aleaVirtualGameSvc: aleaVirtualGameSvc,
|
||||
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 {
|
||||
page := c.QueryInt("page", 1)
|
||||
pageSize := c.QueryInt("page_size", 10)
|
||||
leagueIDQuery := c.Query("league_id")
|
||||
sportIDQuery := c.Query("sport_id")
|
||||
leagueIDQuery, err := strconv.Atoi(c.Query("league_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")
|
||||
lastStartTimeQuery := c.Query("last_start_time")
|
||||
|
||||
leagueID := domain.ValidString{
|
||||
Value: leagueIDQuery,
|
||||
Valid: leagueIDQuery != "",
|
||||
leagueID := domain.ValidInt32{
|
||||
Value: int32(leagueIDQuery),
|
||||
Valid: leagueIDQuery != 0,
|
||||
}
|
||||
sportID := domain.ValidString{
|
||||
Value: sportIDQuery,
|
||||
Valid: sportIDQuery != "",
|
||||
sportID := domain.ValidInt32{
|
||||
Value: int32(sportIDQuery),
|
||||
Valid: sportIDQuery != 0,
|
||||
}
|
||||
|
||||
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.NotidicationStore,
|
||||
a.validator,
|
||||
a.chapaSvc,
|
||||
a.walletSvc,
|
||||
a.referralSvc,
|
||||
a.virtualGameSvc,
|
||||
|
|
@ -35,6 +36,7 @@ func (a *App) initAppRoutes() {
|
|||
a.prematchSvc,
|
||||
a.eventSvc,
|
||||
*a.resultSvc,
|
||||
a.leagueSvc,
|
||||
a.cfg,
|
||||
)
|
||||
|
||||
|
|
@ -114,13 +116,17 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Put("/managers/:id", a.authMiddleware, h.UpdateManagers)
|
||||
a.fiber.Get("/manager/:id/branch", a.authMiddleware, h.GetBranchByManagerID)
|
||||
|
||||
a.fiber.Get("/prematch/odds/:event_id", h.GetPrematchOdds)
|
||||
a.fiber.Get("/prematch/odds", h.GetALLPrematchOdds)
|
||||
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
|
||||
a.fiber.Get("/events/odds/:event_id", h.GetPrematchOdds)
|
||||
a.fiber.Get("/events/odds", h.GetALLPrematchOdds)
|
||||
a.fiber.Get("/events/odds/upcoming/:upcoming_id/market/:market_id", h.GetRawOddsByMarketID)
|
||||
|
||||
a.fiber.Get("/prematch/events/:id", h.GetUpcomingEventByID)
|
||||
a.fiber.Get("/prematch/events", h.GetAllUpcomingEvents)
|
||||
a.fiber.Get("/prematch/odds/upcoming/:upcoming_id", h.GetPrematchOddsByUpcomingID)
|
||||
a.fiber.Get("/events/:id", h.GetUpcomingEventByID)
|
||||
a.fiber.Get("/events", h.GetAllUpcomingEvents)
|
||||
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)
|
||||
|
||||
|
|
@ -184,13 +190,17 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
||||
|
||||
//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.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||
group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
||||
group.Get("/chapa/banks", h.GetBanks)
|
||||
group.Post("/chapa/transfers", h.CreateTransfer)
|
||||
group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
||||
// group.Post("/chapa/payments/initialize", h.InitializePayment)
|
||||
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||
// group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
||||
// group.Get("/chapa/banks", h.GetBanks)
|
||||
// group.Post("/chapa/transfers", h.CreateTransfer)
|
||||
// group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
||||
|
||||
//Alea Play Virtual Game Routes
|
||||
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
||||
|
|
|
|||
4
makefile
4
makefile
|
|
@ -19,7 +19,7 @@ build:
|
|||
|
||||
.PHONY: run
|
||||
run:
|
||||
@docker compose up -d
|
||||
@docker compose up
|
||||
|
||||
.PHONY: stop
|
||||
stop:
|
||||
|
|
@ -56,8 +56,6 @@ db-up:
|
|||
db-down:
|
||||
@docker compose down
|
||||
@docker volume rm fortunebet-backend_postgres_data
|
||||
postgres:
|
||||
@docker exec -it fortunebet-backend-postgres-1 psql -U root -d gh
|
||||
.PHONY: sqlc-gen
|
||||
sqlc-gen:
|
||||
@sqlc generate
|
||||
Loading…
Reference in New Issue
Block a user