game list
This commit is contained in:
parent
22ec5d3ff8
commit
7c70b23a3d
|
|
@ -262,6 +262,7 @@ CREATE TABLE teams (
|
||||||
bet365_id INT,
|
bet365_id INT,
|
||||||
logo_url TEXT
|
logo_url TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Views
|
-- Views
|
||||||
CREATE VIEW companies_details AS
|
CREATE VIEW companies_details AS
|
||||||
SELECT companies.*,
|
SELECT companies.*,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,28 @@ CREATE TABLE virtual_game_transactions (
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE virtual_game_histories (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
session_id VARCHAR(100), -- nullable
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
wallet_id BIGINT, -- nullable
|
||||||
|
game_id BIGINT, -- nullable
|
||||||
|
transaction_type VARCHAR(20) NOT NULL, -- e.g., BET, WIN, CANCEL
|
||||||
|
amount BIGINT NOT NULL, -- in cents or smallest currency unit
|
||||||
|
currency VARCHAR(10) NOT NULL,
|
||||||
|
external_transaction_id VARCHAR(100) NOT NULL,
|
||||||
|
reference_transaction_id VARCHAR(100), -- nullable, for cancel/refund
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'COMPLETED', -- transaction status
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Optional: Indexes for performance
|
||||||
|
CREATE INDEX idx_virtual_game_user_id ON virtual_game_histories(user_id);
|
||||||
|
CREATE INDEX idx_virtual_game_transaction_type ON virtual_game_histories(transaction_type);
|
||||||
|
CREATE INDEX idx_virtual_game_game_id ON virtual_game_histories(game_id);
|
||||||
|
CREATE INDEX idx_virtual_game_external_transaction_id ON virtual_game_histories(external_transaction_id);
|
||||||
|
|
||||||
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
||||||
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||||
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,36 @@ INSERT INTO virtual_game_transactions (
|
||||||
$1, $2, $3, $4, $5, $6, $7, $8
|
$1, $2, $3, $4, $5, $6, $7, $8
|
||||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||||
|
|
||||||
|
-- name: CreateVirtualGameHistory :one
|
||||||
|
INSERT INTO virtual_game_histories (
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
|
) RETURNING
|
||||||
|
id,
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status,
|
||||||
|
created_at,
|
||||||
|
updated_at;
|
||||||
|
|
||||||
|
|
||||||
-- name: GetVirtualGameTransactionByExternalID :one
|
-- name: GetVirtualGameTransactionByExternalID :one
|
||||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||||
FROM virtual_game_transactions
|
FROM virtual_game_transactions
|
||||||
|
|
|
||||||
353
docs/docs.go
353
docs/docs.go
|
|
@ -811,76 +811,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/veli/launch/{game_id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Generates authenticated launch URL for Veli games",
|
|
||||||
"tags": [
|
|
||||||
"Veli Games"
|
|
||||||
],
|
|
||||||
"summary": "Launch a Veli game",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Game ID (e.g., veli_aviator_v1)",
|
|
||||||
"name": "game_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "USD",
|
|
||||||
"description": "Currency code",
|
|
||||||
"name": "currency",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"enum": [
|
|
||||||
"real",
|
|
||||||
"demo"
|
|
||||||
],
|
|
||||||
"type": "string",
|
|
||||||
"default": "real",
|
|
||||||
"description": "Game mode",
|
|
||||||
"name": "mode",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Returns launch URL",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/auth/login": {
|
"/auth/login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Login customer",
|
"description": "Login customer",
|
||||||
|
|
@ -2957,6 +2887,85 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/popok/games": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieves the list of available PopOK slot games",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Virtual Games - PopOK"
|
||||||
|
],
|
||||||
|
"summary": "Get PopOK Games List",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "USD",
|
||||||
|
"description": "Currency (e.g. USD, ETB)",
|
||||||
|
"name": "currency",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.PopOKGame"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"502": {
|
||||||
|
"description": "Bad Gateway",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/popok/games/recommend": {
|
||||||
|
"get": {
|
||||||
|
"description": "Recommends games based on user history or randomly",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Virtual Games - PopOK"
|
||||||
|
],
|
||||||
|
"summary": "Recommend virtual games",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.GameRecommendation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/random/bet": {
|
"/random/bet": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Generate a random bet",
|
"description": "Generate a random bet",
|
||||||
|
|
@ -4352,7 +4361,7 @@ const docTemplate = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"virtual-game"
|
"Virtual Games - PopOK"
|
||||||
],
|
],
|
||||||
"summary": "Handle PopOK game callback",
|
"summary": "Handle PopOK game callback",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -4403,7 +4412,7 @@ const docTemplate = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"virtual-game"
|
"Virtual Games - PopOK"
|
||||||
],
|
],
|
||||||
"summary": "Launch a PopOK virtual game",
|
"summary": "Launch a PopOK virtual game",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -4577,70 +4586,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/webhooks/veli": {
|
|
||||||
"post": {
|
|
||||||
"description": "Processes game round settlements from Veli",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Veli Games"
|
|
||||||
],
|
|
||||||
"summary": "Veli Games webhook handler",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Callback payload",
|
|
||||||
"name": "payload",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/domain.VeliCallback"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Callback processed",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid payload",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "Invalid signature",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Processing error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -5113,6 +5058,30 @@ const docTemplate = `{
|
||||||
"STATUS_REMOVED"
|
"STATUS_REMOVED"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"domain.GameRecommendation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"game_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"game_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"description": "e.g., \"Based on your activity\", \"Popular\", \"Random pick\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.League": {
|
"domain.League": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -5193,6 +5162,17 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.OtpProvider": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"twilio",
|
||||||
|
"aformessage"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"TwilioSms",
|
||||||
|
"AfroMessage"
|
||||||
|
]
|
||||||
|
},
|
||||||
"domain.OutcomeStatus": {
|
"domain.OutcomeStatus": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -5273,6 +5253,29 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.PopOKGame": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gameName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.RandomBetReq": {
|
"domain.RandomBetReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -5551,51 +5554,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.VeliCallback": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"description": "Transaction amount",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"description": "e.g., \"USD\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"event_type": {
|
|
||||||
"description": "\"bet_placed\", \"game_result\", etc.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_id": {
|
|
||||||
"description": "e.g., \"veli_aviator_v1\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"multiplier": {
|
|
||||||
"description": "For games with multipliers (Aviator/Plinko)",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"round_id": {
|
|
||||||
"description": "Unique round identifier (replaces transaction_id)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"session_id": {
|
|
||||||
"description": "Matches VirtualGameSession.SessionToken",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"signature": {
|
|
||||||
"description": "HMAC-SHA256",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"timestamp": {
|
|
||||||
"description": "Unix timestamp",
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"description": "Veli's user identifier",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VirtualGame": {
|
"domain.VirtualGame": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -6249,6 +6207,9 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"handlers.RegisterCodeReq": {
|
"handlers.RegisterCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6257,11 +6218,22 @@ const docTemplate = `{
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.RegisterUserReq": {
|
"handlers.RegisterUserReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6287,6 +6259,14 @@ const docTemplate = `{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
},
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
|
},
|
||||||
"referal_code": {
|
"referal_code": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "ABC123"
|
"example": "ABC123"
|
||||||
|
|
@ -6295,6 +6275,9 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"handlers.ResetCodeReq": {
|
"handlers.ResetCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6303,6 +6286,14 @@ const docTemplate = `{
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -803,76 +803,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/veli/launch/{game_id}": {
|
|
||||||
"get": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Generates authenticated launch URL for Veli games",
|
|
||||||
"tags": [
|
|
||||||
"Veli Games"
|
|
||||||
],
|
|
||||||
"summary": "Launch a Veli game",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Game ID (e.g., veli_aviator_v1)",
|
|
||||||
"name": "game_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"default": "USD",
|
|
||||||
"description": "Currency code",
|
|
||||||
"name": "currency",
|
|
||||||
"in": "query"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"enum": [
|
|
||||||
"real",
|
|
||||||
"demo"
|
|
||||||
],
|
|
||||||
"type": "string",
|
|
||||||
"default": "real",
|
|
||||||
"description": "Game mode",
|
|
||||||
"name": "mode",
|
|
||||||
"in": "query"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Returns launch URL",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Internal server error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/auth/login": {
|
"/auth/login": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Login customer",
|
"description": "Login customer",
|
||||||
|
|
@ -2949,6 +2879,85 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/popok/games": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieves the list of available PopOK slot games",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Virtual Games - PopOK"
|
||||||
|
],
|
||||||
|
"summary": "Get PopOK Games List",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "USD",
|
||||||
|
"description": "Currency (e.g. USD, ETB)",
|
||||||
|
"name": "currency",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.PopOKGame"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"502": {
|
||||||
|
"description": "Bad Gateway",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/popok/games/recommend": {
|
||||||
|
"get": {
|
||||||
|
"description": "Recommends games based on user history or randomly",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Virtual Games - PopOK"
|
||||||
|
],
|
||||||
|
"summary": "Recommend virtual games",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/domain.GameRecommendation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/random/bet": {
|
"/random/bet": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Generate a random bet",
|
"description": "Generate a random bet",
|
||||||
|
|
@ -4344,7 +4353,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"virtual-game"
|
"Virtual Games - PopOK"
|
||||||
],
|
],
|
||||||
"summary": "Handle PopOK game callback",
|
"summary": "Handle PopOK game callback",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -4395,7 +4404,7 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"virtual-game"
|
"Virtual Games - PopOK"
|
||||||
],
|
],
|
||||||
"summary": "Launch a PopOK virtual game",
|
"summary": "Launch a PopOK virtual game",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
|
@ -4569,70 +4578,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/webhooks/veli": {
|
|
||||||
"post": {
|
|
||||||
"description": "Processes game round settlements from Veli",
|
|
||||||
"consumes": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Veli Games"
|
|
||||||
],
|
|
||||||
"summary": "Veli Games webhook handler",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Callback payload",
|
|
||||||
"name": "payload",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/domain.VeliCallback"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Callback processed",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid payload",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "Invalid signature",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"500": {
|
|
||||||
"description": "Processing error",
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -5105,6 +5050,30 @@
|
||||||
"STATUS_REMOVED"
|
"STATUS_REMOVED"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"domain.GameRecommendation": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"game_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"game_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"description": "e.g., \"Based on your activity\", \"Popular\", \"Random pick\"",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.League": {
|
"domain.League": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -5185,6 +5154,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.OtpProvider": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"twilio",
|
||||||
|
"aformessage"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"TwilioSms",
|
||||||
|
"AfroMessage"
|
||||||
|
]
|
||||||
|
},
|
||||||
"domain.OutcomeStatus": {
|
"domain.OutcomeStatus": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -5265,6 +5245,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.PopOKGame": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bets": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gameName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.RandomBetReq": {
|
"domain.RandomBetReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|
@ -5543,51 +5546,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.VeliCallback": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"description": "Transaction amount",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"currency": {
|
|
||||||
"description": "e.g., \"USD\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"event_type": {
|
|
||||||
"description": "\"bet_placed\", \"game_result\", etc.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"game_id": {
|
|
||||||
"description": "e.g., \"veli_aviator_v1\"",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"multiplier": {
|
|
||||||
"description": "For games with multipliers (Aviator/Plinko)",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"round_id": {
|
|
||||||
"description": "Unique round identifier (replaces transaction_id)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"session_id": {
|
|
||||||
"description": "Matches VirtualGameSession.SessionToken",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"signature": {
|
|
||||||
"description": "HMAC-SHA256",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"timestamp": {
|
|
||||||
"description": "Unix timestamp",
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"description": "Veli's user identifier",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.VirtualGame": {
|
"domain.VirtualGame": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -6241,6 +6199,9 @@
|
||||||
},
|
},
|
||||||
"handlers.RegisterCodeReq": {
|
"handlers.RegisterCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6249,11 +6210,22 @@
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"handlers.RegisterUserReq": {
|
"handlers.RegisterUserReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6279,6 +6251,14 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
},
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
|
},
|
||||||
"referal_code": {
|
"referal_code": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "ABC123"
|
"example": "ABC123"
|
||||||
|
|
@ -6287,6 +6267,9 @@
|
||||||
},
|
},
|
||||||
"handlers.ResetCodeReq": {
|
"handlers.ResetCodeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"provider"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -6295,6 +6278,14 @@
|
||||||
"phone_number": {
|
"phone_number": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "1234567890"
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.OtpProvider"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "twilio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,22 @@ definitions:
|
||||||
- STATUS_SUSPENDED
|
- STATUS_SUSPENDED
|
||||||
- STATUS_DECIDED_BY_FA
|
- STATUS_DECIDED_BY_FA
|
||||||
- STATUS_REMOVED
|
- STATUS_REMOVED
|
||||||
|
domain.GameRecommendation:
|
||||||
|
properties:
|
||||||
|
bets:
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
type: array
|
||||||
|
game_id:
|
||||||
|
type: integer
|
||||||
|
game_name:
|
||||||
|
type: string
|
||||||
|
reason:
|
||||||
|
description: e.g., "Based on your activity", "Popular", "Random pick"
|
||||||
|
type: string
|
||||||
|
thumbnail:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.League:
|
domain.League:
|
||||||
properties:
|
properties:
|
||||||
bet365_id:
|
bet365_id:
|
||||||
|
|
@ -378,6 +394,14 @@ definitions:
|
||||||
source:
|
source:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.OtpProvider:
|
||||||
|
enum:
|
||||||
|
- twilio
|
||||||
|
- aformessage
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- TwilioSms
|
||||||
|
- AfroMessage
|
||||||
domain.OutcomeStatus:
|
domain.OutcomeStatus:
|
||||||
enum:
|
enum:
|
||||||
- 0
|
- 0
|
||||||
|
|
@ -439,6 +463,21 @@ definitions:
|
||||||
description: BET, WIN, REFUND, JACKPOT_WIN
|
description: BET, WIN, REFUND, JACKPOT_WIN
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.PopOKGame:
|
||||||
|
properties:
|
||||||
|
bets:
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
type: array
|
||||||
|
gameName:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
status:
|
||||||
|
type: integer
|
||||||
|
thumbnail:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
domain.RandomBetReq:
|
domain.RandomBetReq:
|
||||||
properties:
|
properties:
|
||||||
branch_id:
|
branch_id:
|
||||||
|
|
@ -632,39 +671,6 @@ definitions:
|
||||||
- $ref: '#/definitions/domain.EventStatus'
|
- $ref: '#/definitions/domain.EventStatus'
|
||||||
description: Match Status for event
|
description: Match Status for event
|
||||||
type: object
|
type: object
|
||||||
domain.VeliCallback:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
description: Transaction amount
|
|
||||||
type: number
|
|
||||||
currency:
|
|
||||||
description: e.g., "USD"
|
|
||||||
type: string
|
|
||||||
event_type:
|
|
||||||
description: '"bet_placed", "game_result", etc.'
|
|
||||||
type: string
|
|
||||||
game_id:
|
|
||||||
description: e.g., "veli_aviator_v1"
|
|
||||||
type: string
|
|
||||||
multiplier:
|
|
||||||
description: For games with multipliers (Aviator/Plinko)
|
|
||||||
type: number
|
|
||||||
round_id:
|
|
||||||
description: Unique round identifier (replaces transaction_id)
|
|
||||||
type: string
|
|
||||||
session_id:
|
|
||||||
description: Matches VirtualGameSession.SessionToken
|
|
||||||
type: string
|
|
||||||
signature:
|
|
||||||
description: HMAC-SHA256
|
|
||||||
type: string
|
|
||||||
timestamp:
|
|
||||||
description: Unix timestamp
|
|
||||||
type: integer
|
|
||||||
user_id:
|
|
||||||
description: Veli's user identifier
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.VirtualGame:
|
domain.VirtualGame:
|
||||||
properties:
|
properties:
|
||||||
category:
|
category:
|
||||||
|
|
@ -1126,6 +1132,12 @@ definitions:
|
||||||
phone_number:
|
phone_number:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
|
provider:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.OtpProvider'
|
||||||
|
example: twilio
|
||||||
|
required:
|
||||||
|
- provider
|
||||||
type: object
|
type: object
|
||||||
handlers.RegisterUserReq:
|
handlers.RegisterUserReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1147,9 +1159,15 @@ definitions:
|
||||||
phone_number:
|
phone_number:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
|
provider:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.OtpProvider'
|
||||||
|
example: twilio
|
||||||
referal_code:
|
referal_code:
|
||||||
example: ABC123
|
example: ABC123
|
||||||
type: string
|
type: string
|
||||||
|
required:
|
||||||
|
- provider
|
||||||
type: object
|
type: object
|
||||||
handlers.ResetCodeReq:
|
handlers.ResetCodeReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1159,6 +1177,12 @@ definitions:
|
||||||
phone_number:
|
phone_number:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
|
provider:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.OtpProvider'
|
||||||
|
example: twilio
|
||||||
|
required:
|
||||||
|
- provider
|
||||||
type: object
|
type: object
|
||||||
handlers.ResetPasswordReq:
|
handlers.ResetPasswordReq:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -2077,52 +2101,6 @@ paths:
|
||||||
summary: Process Alea Play game callback
|
summary: Process Alea Play game callback
|
||||||
tags:
|
tags:
|
||||||
- Alea Virtual Games
|
- Alea Virtual Games
|
||||||
/api/veli/launch/{game_id}:
|
|
||||||
get:
|
|
||||||
description: Generates authenticated launch URL for Veli games
|
|
||||||
parameters:
|
|
||||||
- description: Game ID (e.g., veli_aviator_v1)
|
|
||||||
in: path
|
|
||||||
name: game_id
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- default: USD
|
|
||||||
description: Currency code
|
|
||||||
in: query
|
|
||||||
name: currency
|
|
||||||
type: string
|
|
||||||
- default: real
|
|
||||||
description: Game mode
|
|
||||||
enum:
|
|
||||||
- real
|
|
||||||
- demo
|
|
||||||
in: query
|
|
||||||
name: mode
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Returns launch URL
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Invalid request
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Internal server error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
security:
|
|
||||||
- BearerAuth: []
|
|
||||||
summary: Launch a Veli game
|
|
||||||
tags:
|
|
||||||
- Veli Games
|
|
||||||
/auth/login:
|
/auth/login:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -3494,6 +3472,58 @@ paths:
|
||||||
summary: Create a operation
|
summary: Create a operation
|
||||||
tags:
|
tags:
|
||||||
- branch
|
- branch
|
||||||
|
/popok/games:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Retrieves the list of available PopOK slot games
|
||||||
|
parameters:
|
||||||
|
- default: USD
|
||||||
|
description: Currency (e.g. USD, ETB)
|
||||||
|
in: query
|
||||||
|
name: currency
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.PopOKGame'
|
||||||
|
type: array
|
||||||
|
"502":
|
||||||
|
description: Bad Gateway
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Get PopOK Games List
|
||||||
|
tags:
|
||||||
|
- Virtual Games - PopOK
|
||||||
|
/popok/games/recommend:
|
||||||
|
get:
|
||||||
|
description: Recommends games based on user history or randomly
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: query
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/domain.GameRecommendation'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
|
summary: Recommend virtual games
|
||||||
|
tags:
|
||||||
|
- Virtual Games - PopOK
|
||||||
/random/bet:
|
/random/bet:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -4426,7 +4456,7 @@ paths:
|
||||||
$ref: '#/definitions/response.APIResponse'
|
$ref: '#/definitions/response.APIResponse'
|
||||||
summary: Handle PopOK game callback
|
summary: Handle PopOK game callback
|
||||||
tags:
|
tags:
|
||||||
- virtual-game
|
- Virtual Games - PopOK
|
||||||
/virtual-game/launch:
|
/virtual-game/launch:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -4462,7 +4492,7 @@ paths:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Launch a PopOK virtual game
|
summary: Launch a PopOK virtual game
|
||||||
tags:
|
tags:
|
||||||
- virtual-game
|
- Virtual Games - PopOK
|
||||||
/wallet:
|
/wallet:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -4551,48 +4581,6 @@ paths:
|
||||||
summary: Activate and Deactivate Wallet
|
summary: Activate and Deactivate Wallet
|
||||||
tags:
|
tags:
|
||||||
- wallet
|
- wallet
|
||||||
/webhooks/veli:
|
|
||||||
post:
|
|
||||||
consumes:
|
|
||||||
- application/json
|
|
||||||
description: Processes game round settlements from Veli
|
|
||||||
parameters:
|
|
||||||
- description: Callback payload
|
|
||||||
in: body
|
|
||||||
name: payload
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/domain.VeliCallback'
|
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: Callback processed
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"400":
|
|
||||||
description: Invalid payload
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"403":
|
|
||||||
description: Invalid signature
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
"500":
|
|
||||||
description: Processing error
|
|
||||||
schema:
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
summary: Veli Games webhook handler
|
|
||||||
tags:
|
|
||||||
- Veli Games
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
Bearer:
|
Bearer:
|
||||||
in: header
|
in: header
|
||||||
|
|
|
||||||
|
|
@ -445,6 +445,22 @@ type VirtualGame struct {
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualGameHistory struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||||
|
GameID pgtype.Int8 `json:"game_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
ReferenceTransactionID pgtype.Text `json:"reference_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGameSession struct {
|
type VirtualGameSession struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,81 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CreateVirtualGameHistory = `-- name: CreateVirtualGameHistory :one
|
||||||
|
INSERT INTO virtual_game_histories (
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10
|
||||||
|
) RETURNING
|
||||||
|
id,
|
||||||
|
session_id,
|
||||||
|
user_id,
|
||||||
|
wallet_id,
|
||||||
|
game_id,
|
||||||
|
transaction_type,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
external_transaction_id,
|
||||||
|
reference_transaction_id,
|
||||||
|
status,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateVirtualGameHistoryParams struct {
|
||||||
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
WalletID pgtype.Int8 `json:"wallet_id"`
|
||||||
|
GameID pgtype.Int8 `json:"game_id"`
|
||||||
|
TransactionType string `json:"transaction_type"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"`
|
||||||
|
ReferenceTransactionID pgtype.Text `json:"reference_transaction_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateVirtualGameHistory(ctx context.Context, arg CreateVirtualGameHistoryParams) (VirtualGameHistory, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateVirtualGameHistory,
|
||||||
|
arg.SessionID,
|
||||||
|
arg.UserID,
|
||||||
|
arg.WalletID,
|
||||||
|
arg.GameID,
|
||||||
|
arg.TransactionType,
|
||||||
|
arg.Amount,
|
||||||
|
arg.Currency,
|
||||||
|
arg.ExternalTransactionID,
|
||||||
|
arg.ReferenceTransactionID,
|
||||||
|
arg.Status,
|
||||||
|
)
|
||||||
|
var i VirtualGameHistory
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SessionID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.GameID,
|
||||||
|
&i.TransactionType,
|
||||||
|
&i.Amount,
|
||||||
|
&i.Currency,
|
||||||
|
&i.ExternalTransactionID,
|
||||||
|
&i.ReferenceTransactionID,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id, game_id, session_token, currency, status, expires_at
|
user_id, game_id, session_token, currency, status, expires_at
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,22 @@ type VirtualGameSession struct {
|
||||||
GameMode string `json:"game_mode"` // real, demo, tournament
|
GameMode string `json:"game_mode"` // real, demo, tournament
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualGameHistory struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
SessionID string `json:"session_id,omitempty"` // Optional, if session tracking is used
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
WalletID *int64 `json:"wallet_id,omitempty"` // Optional if wallet detail is needed
|
||||||
|
GameID *int64 `json:"game_id,omitempty"` // Optional for game-level analysis
|
||||||
|
TransactionType string `json:"transaction_type"` // BET, WIN, CANCEL, etc.
|
||||||
|
Amount int64 `json:"amount"` // Stored in minor units (e.g. cents)
|
||||||
|
Currency string `json:"currency"` // e.g., ETB, USD
|
||||||
|
ExternalTransactionID string `json:"external_transaction_id"` // Provider transaction ID
|
||||||
|
ReferenceTransactionID string `json:"reference_transaction_id,omitempty"` // For CANCELs pointing to BETs
|
||||||
|
Status string `json:"status"` // COMPLETED, CANCELLED, FAILED, etc.
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type VirtualGameTransaction struct {
|
type VirtualGameTransaction struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SessionID int64 `json:"session_id"`
|
SessionID int64 `json:"session_id"`
|
||||||
|
|
@ -191,3 +207,27 @@ type GameSpecificData struct {
|
||||||
RiskLevel string `json:"risk_level,omitempty"` // For Mines
|
RiskLevel string `json:"risk_level,omitempty"` // For Mines
|
||||||
BucketIndex int `json:"bucket_index,omitempty"` // For Plinko
|
BucketIndex int `json:"bucket_index,omitempty"` // For Plinko
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PopOKGame struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
GameName string `json:"gameName"`
|
||||||
|
Bets []float64 `json:"bets"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PopOKGameListResponse struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data struct {
|
||||||
|
Slots []PopOKGame `json:"slots"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameRecommendation struct {
|
||||||
|
GameID int `json:"game_id"`
|
||||||
|
GameName string `json:"game_name"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
Bets []float64 `json:"bets"`
|
||||||
|
Reason string `json:"reason"` // e.g., "Based on your activity", "Popular", "Random pick"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ type VirtualGameRepository interface {
|
||||||
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
// WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
|
||||||
|
|
||||||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
|
GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error)
|
||||||
|
CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualGameRepo struct {
|
type VirtualGameRepo struct {
|
||||||
|
|
@ -92,6 +94,21 @@ func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) CreateVirtualGameHistory(ctx context.Context, his *domain.VirtualGameHistory) error {
|
||||||
|
params := dbgen.CreateVirtualGameHistoryParams{
|
||||||
|
SessionID: pgtype.Text{String: his.SessionID, Valid: true},
|
||||||
|
UserID: his.UserID,
|
||||||
|
WalletID: pgtype.Int8{Int64: *his.WalletID, Valid: true},
|
||||||
|
TransactionType: his.TransactionType,
|
||||||
|
Amount: his.Amount,
|
||||||
|
Currency: his.Currency,
|
||||||
|
ExternalTransactionID: his.ExternalTransactionID,
|
||||||
|
Status: his.Status,
|
||||||
|
}
|
||||||
|
_, err := r.store.queries.CreateVirtualGameHistory(ctx, params)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) {
|
func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) {
|
||||||
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -153,6 +170,24 @@ func (r *VirtualGameRepo) GetGameCounts(ctx context.Context, filter domain.Repor
|
||||||
return total, active, inactive, nil
|
return total, active, inactive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) GetUserGameHistory(ctx context.Context, userID int64) ([]domain.VirtualGameHistory, error) {
|
||||||
|
query := `SELECT game_id FROM virtual_game_histories WHERE user_id = $1 AND transaction_type = 'BET' ORDER BY created_at DESC LIMIT 100`
|
||||||
|
rows, err := r.store.conn.Query(ctx, query, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var history []domain.VirtualGameHistory
|
||||||
|
for rows.Next() {
|
||||||
|
var tx domain.VirtualGameHistory
|
||||||
|
if err := rows.Scan(&tx.GameID); err == nil {
|
||||||
|
history = append(history, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return history, nil
|
||||||
|
}
|
||||||
|
|
||||||
// func (r *VirtualGameRepo) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
// func (r *VirtualGameRepo) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error {
|
||||||
// _, tx, err := r.store.BeginTx(ctx)
|
// _, tx, err := r.store.BeginTx(ctx)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,6 @@ type VirtualGameService interface {
|
||||||
ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error)
|
ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error)
|
||||||
|
|
||||||
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error)
|
||||||
|
ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error)
|
||||||
|
RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package virtualgameservice
|
package virtualgameservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
|
@ -8,7 +9,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math/rand/v2"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
|
@ -43,14 +49,14 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionToken := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
sessionId := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
||||||
token, err := jwtutil.CreatePopOKJwt(
|
token, err := jwtutil.CreatePopOKJwt(
|
||||||
userID,
|
userID,
|
||||||
user.PhoneNumber,
|
user.PhoneNumber,
|
||||||
currency,
|
currency,
|
||||||
"en",
|
"en",
|
||||||
mode,
|
mode,
|
||||||
sessionToken,
|
sessionId,
|
||||||
s.config.PopOK.SecretKey,
|
s.config.PopOK.SecretKey,
|
||||||
24*time.Hour,
|
24*time.Hour,
|
||||||
)
|
)
|
||||||
|
|
@ -59,19 +65,31 @@ func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameI
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record game launch as a transaction (for history and recommendation purposes)
|
||||||
|
tx := &domain.VirtualGameHistory{
|
||||||
|
SessionID: sessionId, // Optional: populate if session tracking is implemented
|
||||||
|
UserID: userID,
|
||||||
|
GameID: toInt64Ptr(gameID),
|
||||||
|
TransactionType: "LAUNCH",
|
||||||
|
Amount: 0,
|
||||||
|
Currency: currency,
|
||||||
|
ExternalTransactionID: sessionId,
|
||||||
|
Status: "COMPLETED",
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameHistory(ctx, tx); err != nil {
|
||||||
|
s.logger.Error("Failed to record game launch transaction", "error", err)
|
||||||
|
// Do not fail game launch on logging error — just log and continue
|
||||||
|
}
|
||||||
|
|
||||||
params := fmt.Sprintf(
|
params := fmt.Sprintf(
|
||||||
"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
|
"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
|
||||||
s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token,
|
s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token,
|
||||||
)
|
)
|
||||||
|
|
||||||
// params = fmt.Sprintf(
|
|
||||||
// "partnerId=%s&gameId=%sgameMode=%s&lang=en&platform=%s",
|
|
||||||
// "1", "1", "fun", "111",
|
|
||||||
// )
|
|
||||||
|
|
||||||
// signature := s.generateSignature(params)
|
|
||||||
return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
||||||
// return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
|
func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
|
||||||
|
|
@ -148,10 +166,10 @@ func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCall
|
||||||
|
|
||||||
func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error) {
|
func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error) {
|
||||||
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// s.logger.Error("Failed to parse JWT", "error", err)
|
s.logger.Error("Failed to parse JWT", "error", err)
|
||||||
// return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
// }
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
|
||||||
if err != nil || len(wallets) == 0 {
|
if err != nil || len(wallets) == 0 {
|
||||||
|
|
@ -170,9 +188,9 @@ func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfo
|
||||||
func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error) {
|
func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error) {
|
||||||
// Validate token and get user ID
|
// Validate token and get user ID
|
||||||
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return nil, fmt.Errorf("invalid token")
|
return nil, fmt.Errorf("invalid token")
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Convert amount to cents (assuming wallet uses cents)
|
// Convert amount to cents (assuming wallet uses cents)
|
||||||
amountCents := int64(req.Amount * 100)
|
amountCents := int64(req.Amount * 100)
|
||||||
|
|
@ -399,3 +417,126 @@ func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
|
||||||
func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
|
||||||
return s.repo.GetGameCounts(ctx, filter)
|
return s.repo.GetGameCounts(ctx, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) ListGames(ctx context.Context, currency string) ([]domain.PopOKGame, error) {
|
||||||
|
now := time.Now().Format("02-01-2006 15:04:05") // dd-mm-yyyy hh:mm:ss
|
||||||
|
|
||||||
|
// Calculate hash: sha256(privateKey + time)
|
||||||
|
rawHash := s.config.PopOK.SecretKey + now
|
||||||
|
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(rawHash)))
|
||||||
|
|
||||||
|
// Construct request payload
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"action": "gameList",
|
||||||
|
"platform": s.config.PopOK.Platform,
|
||||||
|
"partnerId": s.config.PopOK.ClientID,
|
||||||
|
"currency": currency,
|
||||||
|
"time": now,
|
||||||
|
"hash": hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to marshal game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", s.config.PopOK.BaseURL+"/serviceApi.php", bytes.NewReader(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to create game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to send game list request", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
b, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("PopOK game list failed with status %d: %s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameList domain.PopOKGameListResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&gameList); err != nil {
|
||||||
|
s.logger.Error("Failed to decode game list response", "error", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if gameList.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("PopOK error: %s", gameList.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameList.Data.Slots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) RecommendGames(ctx context.Context, userID int64) ([]domain.GameRecommendation, error) {
|
||||||
|
// Fetch all available games
|
||||||
|
games, err := s.ListGames(ctx, "ETB") // currency can be dynamic
|
||||||
|
if err != nil || len(games) == 0 {
|
||||||
|
return nil, fmt.Errorf("could not fetch games")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has existing interaction
|
||||||
|
history, err := s.repo.GetUserGameHistory(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Warn("No previous game history", "userID", userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendations := []domain.GameRecommendation{}
|
||||||
|
|
||||||
|
if len(history) > 0 {
|
||||||
|
// Score games based on interaction frequency
|
||||||
|
gameScores := map[int64]int{}
|
||||||
|
for _, h := range history {
|
||||||
|
if h.GameID != nil {
|
||||||
|
gameScores[*h.GameID]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by score descending
|
||||||
|
sort.SliceStable(games, func(i, j int) bool {
|
||||||
|
return gameScores[int64(games[i].ID)] > gameScores[int64(games[j].ID)]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pick top 3
|
||||||
|
for _, g := range games[:min(3, len(games))] {
|
||||||
|
recommendations = append(recommendations, domain.GameRecommendation{
|
||||||
|
GameID: g.ID,
|
||||||
|
GameName: g.GameName,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Bets: g.Bets,
|
||||||
|
Reason: "Based on your activity",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pick 3 random games for new users
|
||||||
|
rand.Shuffle(len(games), func(i, j int) {
|
||||||
|
games[i], games[j] = games[j], games[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, g := range games[:min(3, len(games))] {
|
||||||
|
recommendations = append(recommendations, domain.GameRecommendation{
|
||||||
|
GameID: g.ID,
|
||||||
|
GameName: g.GameName,
|
||||||
|
Thumbnail: g.Thumbnail,
|
||||||
|
Bets: g.Bets,
|
||||||
|
Reason: "Random pick",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt64Ptr(s string) *int64 {
|
||||||
|
id, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &id
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type launchVirtualGameRes struct {
|
||||||
// LaunchVirtualGame godoc
|
// LaunchVirtualGame godoc
|
||||||
// @Summary Launch a PopOK virtual game
|
// @Summary Launch a PopOK virtual game
|
||||||
// @Description Generates a URL to launch a PopOK game
|
// @Description Generates a URL to launch a PopOK game
|
||||||
// @Tags virtual-game
|
// @Tags Virtual Games - PopOK
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
|
|
@ -60,7 +60,7 @@ func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
||||||
// HandleVirtualGameCallback godoc
|
// HandleVirtualGameCallback godoc
|
||||||
// @Summary Handle PopOK game callback
|
// @Summary Handle PopOK game callback
|
||||||
// @Description Processes callbacks from PopOK for game events
|
// @Description Processes callbacks from PopOK for game events
|
||||||
// @Tags virtual-game
|
// @Tags Virtual Games - PopOK
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param callback body domain.PopOKCallback true "Callback data"
|
// @Param callback body domain.PopOKCallback true "Callback data"
|
||||||
|
|
@ -155,3 +155,47 @@ func (h *Handler) HandleCancel(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return response.WriteJSON(c, fiber.StatusOK, "Cancel processed", resp, nil)
|
return response.WriteJSON(c, fiber.StatusOK, "Cancel processed", resp, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGameList godoc
|
||||||
|
// @Summary Get PopOK Games List
|
||||||
|
// @Description Retrieves the list of available PopOK slot games
|
||||||
|
// @Tags Virtual Games - PopOK
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param currency query string false "Currency (e.g. USD, ETB)" default(USD)
|
||||||
|
// @Success 200 {array} domain.PopOKGame
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /popok/games [get]
|
||||||
|
func (h *Handler) GetGameList(c *fiber.Ctx) error {
|
||||||
|
currency := c.Query("currency", "ETB") // fallback default
|
||||||
|
|
||||||
|
games, err := h.virtualGameSvc.ListGames(c.Context(), currency)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadGateway, "failed to fetch games")
|
||||||
|
}
|
||||||
|
return c.JSON(games)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecommendGames godoc
|
||||||
|
// @Summary Recommend virtual games
|
||||||
|
// @Description Recommends games based on user history or randomly
|
||||||
|
// @Tags Virtual Games - PopOK
|
||||||
|
// @Produce json
|
||||||
|
// @Param user_id query int true "User ID"
|
||||||
|
// @Success 200 {array} domain.GameRecommendation
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /popok/games/recommend [get]
|
||||||
|
func (h *Handler) RecommendGames(c *fiber.Ctx) error {
|
||||||
|
userIDVal := c.Locals("user_id")
|
||||||
|
userID, ok := userIDVal.(int64)
|
||||||
|
if !ok || userID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "invalid user ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendations, err := h.virtualGameSvc.RecommendGames(c.Context(), userID)
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to recommend games")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(recommendations)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func CreateJwt(userId int64, Role domain.Role, CompanyID domain.ValidInt64, key
|
||||||
func CreatePopOKJwt(userID int64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
|
func CreatePopOKJwt(userID int64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PopOKClaim{
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PopOKClaim{
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: "fortune-bet",
|
Issuer: "github.com/lafetz/snippitstash",
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
Audience: jwt.ClaimStrings{"popokgaming.com"},
|
Audience: jwt.ClaimStrings{"popokgaming.com"},
|
||||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,8 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/bet", h.HandleBet)
|
a.fiber.Post("/bet", h.HandleBet)
|
||||||
a.fiber.Post("/win", h.HandleWin)
|
a.fiber.Post("/win", h.HandleWin)
|
||||||
a.fiber.Post("/cancel", h.HandleCancel)
|
a.fiber.Post("/cancel", h.HandleCancel)
|
||||||
|
a.fiber.Get("/popok/games", h.GetGameList)
|
||||||
|
a.fiber.Get("/popok/games/recommend", a.authMiddleware, h.RecommendGames)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user