From d8de92e7d61b34aa525a6e0d161d81594f0ee395 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Wed, 23 Jul 2025 14:02:52 +0300 Subject: [PATCH] 5 min report fix + arifpay integration --- cmd/main.go | 28 +- db/migrations/000007_setting_data.up.sql | 10 +- docker-compose.yml | 6 +- docs/docs.go | 1749 ++++++++--------- docs/swagger.json | 1749 ++++++++--------- docs/swagger.yaml | 1450 ++++++-------- go.mod | 29 +- go.sum | 67 +- internal/config/config.go | 15 + internal/domain/arifpay.go | 53 + internal/event/wallet_event.go | 18 + internal/services/arifpay/service.go | 192 ++ internal/services/kafka/consumer.go | 80 + internal/services/kafka/producer.go | 36 + internal/services/notfication/service.go | 2 +- internal/services/report/service.go | 158 -- internal/services/transaction/service.go | 7 +- internal/services/transaction/shop_deposit.go | 6 +- internal/services/wallet/service.go | 8 +- internal/services/wallet/wallet.go | 34 +- internal/web_server/app.go | 4 + internal/web_server/cron.go | 8 +- internal/web_server/handlers/arifpay.go | 179 ++ internal/web_server/handlers/handlers.go | 30 +- internal/web_server/routes.go | 47 +- internal/web_server/ws/ws.go | 106 +- 26 files changed, 3096 insertions(+), 2975 deletions(-) create mode 100644 internal/domain/arifpay.go create mode 100644 internal/event/wallet_event.go create mode 100644 internal/services/arifpay/service.go create mode 100644 internal/services/kafka/consumer.go create mode 100644 internal/services/kafka/producer.go create mode 100644 internal/web_server/handlers/arifpay.go diff --git a/cmd/main.go b/cmd/main.go index 9b8d3fc..830e0ac 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "log/slog" + "net/http" "os" "time" @@ -28,6 +29,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" // "github.com/SamuelTariku/FortuneBet-Backend/internal/router" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" @@ -38,6 +40,7 @@ import ( "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions" issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka" "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" @@ -119,6 +122,7 @@ func main() { // userStore, notificationSvc, logger, + kafka.NewProducer([]string{"localhost:9092"}, "wallet-events"), ) branchSvc := branch.NewService(store) @@ -209,32 +213,18 @@ func main() { httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc) httpserver.StartTicketCrons(*ticketSvc) - // Fetch companies and branches for live wallet metrics update - // ctx := context.Background() - - // companies := []domain.GetCompany{ - // {ID: 1, Name: "Company A", WalletBalance: 1000.0}, - // } - - // branches := []domain.BranchWallet{ - // {ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0}, - // } - - // notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches) - // if err != nil { - // log.Println("Failed to update live metrics:", err) - // } else { - // log.Println("Live metrics broadcasted successfully") - // } - issueReportingRepo := repository.NewReportedIssueRepository(store) issueReportingSvc := issuereporting.New(issueReportingRepo) - // go httpserver.SetupReportCronJob(reportWorker) + transferStore := wallet.TransferStore(store) + + arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, &http.Client{ + Timeout: 30 * time.Second}) // Initialize and start HTTP server app := httpserver.NewApp( + arifpaySvc, issueReportingSvc, instSvc, currSvc, diff --git a/db/migrations/000007_setting_data.up.sql b/db/migrations/000007_setting_data.up.sql index adbc6c2..0bee556 100644 --- a/db/migrations/000007_setting_data.up.sql +++ b/db/migrations/000007_setting_data.up.sql @@ -1,11 +1,11 @@ -- Settings Initial Data INSERT INTO settings (key, value) -VALUES ('max_number_of_outcomes', '30'), +VALUES + ('max_number_of_outcomes', '30'), ('bet_amount_limit', '100000'), ('daily_ticket_limit', '50'), ('total_winnings_limit', '1000000'), - ('amount_for_bet_referral', '1000000') + ('amount_for_bet_referral', '1000000'), ('cashback_amount_cap', '1000') - ON CONFLICT (key) DO -UPDATE -SET value = EXCLUDED.value; \ No newline at end of file +ON CONFLICT (key) +DO UPDATE SET value = EXCLUDED.value; diff --git a/docker-compose.yml b/docker-compose.yml index 39cb050..7df9ea8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: mongo: container_name: fortunebet-mongo - image: mongo:7.0 + image: mongo:7.0.11 restart: always ports: - "27017:27017" @@ -89,8 +89,8 @@ services: networks: - app command: ["/app/bin/web"] - volumes: - - "C:/Users/User/Desktop:/host-desktop" + # volumes: + # - "C:/Users/User/Desktop:/host-desktop" test: build: diff --git a/docs/docs.go b/docs/docs.go index 2807833..012d2f0 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -129,7 +129,7 @@ const docTemplate = `{ } } }, - "/admin-company": { + "/api/v1/admin-company": { "get": { "description": "Gets a single company by id", "consumes": [ @@ -146,7 +146,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.CompanyRes" + "$ref": "#/definitions/domain.GetCompanyRes" } }, "400": { @@ -164,7 +164,7 @@ const docTemplate = `{ } } }, - "/admin/{id}": { + "/api/v1/admin/{id}": { "get": { "description": "Get a single admin by id", "consumes": [ @@ -339,6 +339,195 @@ const docTemplate = `{ } } }, + "/api/v1/arifpay/b2c/transfer": { + "post": { + "description": "Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Initiate B2C Transfer", + "parameters": [ + { + "type": "string", + "description": "Transfer mode (Telebirr, CBE, MPESA)", + "name": "transfer_mode", + "in": "query", + "required": true + }, + { + "description": "Transfer request payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.ArifPayB2CRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/checkout": { + "post": { + "description": "Creates a payment session using Arifpay and returns a redirect URL.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Create Arifpay Checkout Session", + "parameters": [ + { + "description": "Checkout session request payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateCheckoutSessionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/session-id/verify-transaction/{session_id}": { + "get": { + "description": "Verifies an Arifpay transaction using a session ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Verify Arifpay Transaction by Session ID", + "parameters": [ + { + "type": "string", + "description": "Arifpay Session ID", + "name": "session_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/transaction-id/verify-transaction": { + "post": { + "description": "Verifies a transaction using transaction ID and payment type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Verify Arifpay Transaction", + "parameters": [ + { + "description": "Transaction verification payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.ArifpayVerifyByTransactionIDRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/auth/login": { "post": { "description": "Login customer", @@ -1134,61 +1323,7 @@ const docTemplate = `{ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - } - } - } - }, - "/api/v1/logs": { - "get": { - "description": "Fetches application logs from MongoDB with pagination, level filtering, and search", - "produces": [ - "application/json" - ], - "tags": [ - "Logs" - ], - "summary": "Retrieve application logs with filtering and pagination", - "parameters": [ - { - "type": "string", - "description": "Filter logs by level (debug, info, warn, error, dpanic, panic, fatal)", - "name": "level", - "in": "query" - }, - { - "type": "string", - "description": "Search term to match against message or fields", - "name": "search", - "in": "query" - }, - { - "type": "integer", - "default": 1, - "description": "Page number for pagination (default: 1)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 50, - "description": "Number of items per page (default: 50, max: 100)", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Paginated list of application logs", - "schema": { - "$ref": "#/definitions/domain.LogResponse" - } - }, - "400": { - "description": "Invalid request parameters", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" + "$ref": "#/definitions/response.APIResponse" } }, "500": { @@ -1246,47 +1381,31 @@ const docTemplate = `{ }, "/api/v1/branchWallet": { "get": { - "description": "Returns a paginated list of generated report CSV files with search capability", + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ "wallet" ], - "summary": "List available report CSV files", - "parameters": [ - { - "type": "string", - "description": "Search term to filter filenames", - "name": "search", - "in": "query" - }, - { - "type": "integer", - "default": 1, - "description": "Page number (default: 1)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 20, - "description": "Items per page (default: 20, max: 100)", - "name": "limit", - "in": "query" - } - ], + "summary": "Get all branch wallets", "responses": { "200": { - "description": "Paginated list of CSV report filenames", + "description": "OK", "schema": { - "$ref": "#/definitions/domain.PaginatedFileResponse" + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } } }, "400": { - "description": "Invalid pagination parameters", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/domain.ErrorResponse" + "$ref": "#/definitions/response.APIResponse" } }, "500": { @@ -1718,100 +1837,7 @@ const docTemplate = `{ "description": "OK", "schema": { "type": "object", - "additionalProperties": { - "allOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "status": { - "type": "string" - } - } - } - ] - } - } - } - } - } - }, - "/api/v1/win": { - "post": { - "description": "Processes win callbacks from either Veli or PopOK providers by auto-detecting the format", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Wins" - ], - "summary": "Handle win callback (Veli or PopOK)", - "responses": { - "200": { - "description": "Win processing result", - "schema": {} - }, - "400": { - "description": "Invalid request format", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "401": { - "description": "Authentication failed", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "409": { - "description": "Duplicate transaction", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - } - } - } - }, - "/auth/login": { - "post": { - "description": "Login customer", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Login customer", - "parameters": [ - { - "description": "Login customer", - "name": "login", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.loginCustomerReq" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.loginCustomerRes" + "additionalProperties": true } }, "400": { @@ -2831,202 +2857,9 @@ const docTemplate = `{ } } }, - "/customer": { - "get": { - "description": "Get all Customers", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Get all Customers", - "parameters": [ - { - "type": "integer", - "description": "Page number", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "description": "Page size", - "name": "page_size", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.CustomersRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customer/{id}": { - "get": { - "description": "Get a single customer by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Get customer by id", - "parameters": [ - { - "type": "integer", - "description": "User ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.CustomersRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "description": "Update Customers", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Update Customers", - "parameters": [ - { - "description": "Update Customers", - "name": "Customers", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.updateCustomerReq" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customerWallet": { - "get": { - "description": "Retrieve all customer wallets", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "wallet" - ], - "summary": "Get all customer wallets", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.CustomerWalletRes" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/events": { - "get": { - "description": "Retrieve all upcoming events from the database", + "/api/v1/issues/{issue_id}/status": { + "patch": { + "description": "Admin endpoint to update the status of a reported issue", "consumes": [ "application/json" ], @@ -3108,6 +2941,59 @@ const docTemplate = `{ } } }, + "/api/v1/leagues/{id}/featured": { + "put": { + "description": "Set the league to featured/un-featured", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "leagues" + ], + "summary": "Set the league to featured/un-featured", + "parameters": [ + { + "type": "integer", + "description": "League ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "League Featured Request", + "name": "active", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SetLeagueAsFeatured" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/leagues/{id}/set-active": { "put": { "description": "Set the league to active", @@ -3163,22 +3049,53 @@ const docTemplate = `{ }, "/api/v1/logs": { "get": { - "description": "Fetches the 100 most recent application logs from MongoDB", + "description": "Fetches application logs from MongoDB with pagination, level filtering, and search", "produces": [ "application/json" ], "tags": [ "Logs" ], - "summary": "Retrieve latest application logs", + "summary": "Retrieve application logs with filtering and pagination", + "parameters": [ + { + "type": "string", + "description": "Filter logs by level (debug, info, warn, error, dpanic, panic, fatal)", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "Search term to match against message or fields", + "name": "search", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page number for pagination (default: 1)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 50, + "description": "Number of items per page (default: 50, max: 100)", + "name": "limit", + "in": "query" + } + ], "responses": { "200": { - "description": "List of application logs", + "description": "Paginated list of application logs", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.LogEntry" - } + "$ref": "#/definitions/domain.LogResponse" + } + }, + "400": { + "description": "Invalid request parameters", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" } }, "500": { @@ -3633,86 +3550,7 @@ 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" - } - } - }, - "500": { - "description": "Internal Server Error", - "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" - } - } - } - } - }, - "/referral/settings": { + "/api/v1/referral/settings": { "get": { "security": [ { @@ -3902,7 +3740,7 @@ const docTemplate = `{ }, "/api/v1/report-files/list": { "get": { - "description": "Returns a list of all generated report CSV files available for download", + "description": "Returns a paginated list of generated report CSV files with search capability", "produces": [ "application/json" ], @@ -3910,26 +3748,39 @@ const docTemplate = `{ "Reports" ], "summary": "List available report CSV files", + "parameters": [ + { + "type": "string", + "description": "Search term to filter filenames", + "name": "search", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page number (default: 1)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page (default: 20, max: 100)", + "name": "limit", + "in": "query" + } + ], "responses": { "200": { - "description": "List of CSV report filenames", + "description": "Paginated list of CSV report filenames", "schema": { - "allOf": [ - { - "$ref": "#/definitions/domain.Response" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - ] + "$ref": "#/definitions/domain.PaginatedFileResponse" + } + }, + "400": { + "description": "Invalid pagination parameters", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" } }, "500": { @@ -4163,7 +4014,7 @@ const docTemplate = `{ } } }, - "/shop/bet": { + "/api/v1/shop/bet": { "post": { "description": "Create bet at branch", "consumes": [ @@ -4209,9 +4060,8 @@ const docTemplate = `{ } } }, - "/shop/bet/{id}": { + "/api/v1/shop/bet/{id}": { "get": { - "description": "Cashout bet at branch", "description": "Cashout bet at branch", "consumes": [ "application/json" @@ -4221,19 +4071,6 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" - ], - "summary": "Cashout bet at branch", - "parameters": [ - { - "description": "cashout bet", - "name": "createBet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CashoutReq" - } - } ], "summary": "Cashout bet at branch", "parameters": [ @@ -4269,7 +4106,7 @@ const docTemplate = `{ } } }, - "/shop/bet/{id}/cashout": { + "/api/v1/shop/bet/{id}/cashout": { "post": { "description": "Cashout bet at branch", "consumes": [ @@ -4315,9 +4152,8 @@ const docTemplate = `{ } } }, - "/shop/cashout": { + "/api/v1/shop/cashout": { "post": { - "description": "Cashout bet by cashoutID", "description": "Cashout bet by cashoutID", "consumes": [ "application/json" @@ -4327,21 +4163,16 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Cashout bet by cashoutID", - "summary": "Cashout bet by cashoutID", "parameters": [ { - "description": "cashout bet", - "name": "cashoutBet", "description": "cashout bet", "name": "cashoutBet", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.CashoutReq" - "$ref": "#/definitions/domain.CashoutReq" } } ], @@ -4350,7 +4181,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4368,9 +4198,8 @@ const docTemplate = `{ } } }, - "/shop/cashout/{id}": { + "/api/v1/shop/cashout/{id}": { "get": { - "description": "Cashout bet at branch", "description": "Cashout bet at branch", "consumes": [ "application/json" @@ -4380,10 +4209,8 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Cashout bet at branch", - "summary": "Cashout bet at branch", "parameters": [ { "description": "cashout bet", @@ -4393,13 +4220,6 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/domain.CashoutReq" } - "description": "cashout bet", - "name": "createBet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CashoutReq" - } } ], "responses": { @@ -4407,7 +4227,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4425,7 +4244,7 @@ const docTemplate = `{ } } }, - "/shop/deposit": { + "/api/v1/shop/deposit": { "post": { "description": "Transfers money from branch wallet to customer wallet", "consumes": [ @@ -4436,10 +4255,8 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Shop deposit into customer wallet", - "summary": "Shop deposit into customer wallet", "parameters": [ { "description": "ShopDepositReq", @@ -4449,20 +4266,13 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/domain.ShopDepositReq" } - "description": "ShopDepositReq", - "name": "transferToWallet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.ShopDepositReq" - } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.TransferWalletRes" + "$ref": "#/definitions/domain.ShopDepositRes" } }, "400": { @@ -4480,7 +4290,7 @@ const docTemplate = `{ } } }, - "/shop/transaction": { + "/api/v1/shop/transaction": { "get": { "description": "Gets all the transactions", "consumes": [ @@ -4491,7 +4301,6 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Gets all transactions", "responses": { @@ -4519,7 +4328,7 @@ const docTemplate = `{ } } }, - "/shop/transaction/{id}": { + "/api/v1/shop/transaction/{id}": { "get": { "description": "Gets a single transaction by id", "consumes": [ @@ -4536,7 +4345,6 @@ const docTemplate = `{ { "type": "integer", "description": "Transaction ID", - "description": "Transaction ID", "name": "id", "in": "path", "required": true @@ -4547,7 +4355,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4564,8 +4371,6 @@ const docTemplate = `{ } } }, - "put": { - "description": "Updates the verified status of a transaction", "put": { "description": "Updates the verified status of a transaction", "consumes": [ @@ -4576,28 +4381,23 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Updates the verified field of a transaction", - "summary": "Updates the verified field of a transaction", "parameters": [ { "type": "integer", "description": "Transaction ID", - "description": "Transaction ID", "name": "id", "in": "path", "required": true }, { - "description": "Updates Transaction Verification", - "name": "updateVerified", "description": "Updates Transaction Verification", "name": "updateVerified", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.UpdateTransactionVerifiedReq" + "$ref": "#/definitions/domain.UpdateTransactionVerifiedReq" } } ], @@ -4623,7 +4423,7 @@ const docTemplate = `{ } } }, - "/shop/transaction/{id}/bet": { + "/api/v1/shop/transaction/{id}/bet": { "get": { "description": "Gets a single shop bet by transaction id", "consumes": [ @@ -4634,10 +4434,8 @@ const docTemplate = `{ ], "tags": [ "transaction" - "transaction" ], "summary": "Gets shop bet by transaction id", - "summary": "Gets shop bet by transaction id", "parameters": [ { "type": "integer", @@ -4645,11 +4443,6 @@ const docTemplate = `{ "name": "id", "in": "path", "required": true - "type": "integer", - "description": "Transaction ID", - "name": "id", - "in": "path", - "required": true } ], "responses": { @@ -4657,7 +4450,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4675,9 +4467,8 @@ const docTemplate = `{ } } }, - "/sport/bet": { + "/api/v1/sport/bet": { "get": { - "description": "Gets all the bets", "description": "Gets all the bets", "consumes": [ "application/json" @@ -4687,10 +4478,8 @@ const docTemplate = `{ ], "tags": [ "bet" - "bet" ], "summary": "Gets all bets", - "summary": "Gets all bets", "responses": { "200": { "description": "OK", @@ -4698,7 +4487,6 @@ const docTemplate = `{ "type": "array", "items": { "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" } } }, @@ -4717,7 +4505,6 @@ const docTemplate = `{ } }, "post": { - "description": "Creates a bet", "description": "Creates a bet", "consumes": [ "application/json" @@ -4727,21 +4514,16 @@ const docTemplate = `{ ], "tags": [ "bet" - "bet" ], "summary": "Create a bet", - "summary": "Create a bet", "parameters": [ { - "description": "Creates bet", - "name": "createBet", "description": "Creates bet", "name": "createBet", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.CreateBetReq" - "$ref": "#/definitions/domain.CreateBetReq" } } ], @@ -4750,7 +4532,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" } }, "400": { @@ -4768,53 +4549,7 @@ const docTemplate = `{ } } }, - "/sport/bet/cashout/{id}": { - "get": { - "description": "Gets a single bet by cashout id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bet" - "bet" - ], - "summary": "Gets bet by cashout id", - "parameters": [ - { - "type": "string", - "description": "cashout ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/sport/bet/fastcode": { + "/api/v1/sport/bet/fastcode": { "post": { "description": "Creates a bet with fast code", "consumes": [ @@ -4825,7 +4560,6 @@ const docTemplate = `{ ], "tags": [ "bet" - "bet" ], "summary": "Create a bet with fast code", "parameters": [ @@ -4835,31 +4569,9 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/domain.CreateBetReq" + "$ref": "#/definitions/domain.CreateBetWithFastCodeReq" } } - } - }, - "delete": { - "description": "Deletes bet by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bet" - ], - "summary": "Deletes bet by id", - "parameters": [ - { - "type": "integer", - "description": "Bet ID", - "name": "id", - "in": "path", - "required": true - } ], "responses": { "200": { @@ -4883,7 +4595,7 @@ const docTemplate = `{ } } }, - "/sport/bet/{id}": { + "/api/v1/sport/bet/{id}": { "get": { "description": "Gets a single bet by id", "consumes": [ @@ -4894,26 +4606,15 @@ const docTemplate = `{ ], "tags": [ "bet" - "bet" ], "summary": "Gets bet by id", "parameters": [ { "type": "integer", "description": "Bet ID", - "description": "Bet ID", "name": "id", "in": "path", "required": true - }, - { - "description": "Updates Cashed Out", - "name": "updateCashOut", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.UpdateCashOutReq" - } } ], "responses": { @@ -4989,7 +4690,6 @@ const docTemplate = `{ ], "tags": [ "bet" - "bet" ], "summary": "Updates the cashed out field", "parameters": [ @@ -5032,7 +4732,7 @@ const docTemplate = `{ } } }, - "/sport/random/bet": { + "/api/v1/sport/random/bet": { "post": { "description": "Generate a random bet", "consumes": [ @@ -5078,9 +4778,8 @@ const docTemplate = `{ } } }, - "/supportedOperation": { + "/api/v1/supportedOperation": { "get": { - "description": "Gets all supported operations", "description": "Gets all supported operations", "consumes": [ "application/json" @@ -5090,17 +4789,15 @@ const docTemplate = `{ ], "tags": [ "branch" - "branch" ], "summary": "Gets all supported operations", - "summary": "Gets all supported operations", "responses": { "200": { "description": "OK", "schema": { "type": "array", "items": { - "$ref": "#/definitions/handlers.BranchDetailRes" + "$ref": "#/definitions/domain.BranchDetailRes" } } }, @@ -5119,7 +4816,6 @@ const docTemplate = `{ } }, "post": { - "description": "Creates a supported operation", "description": "Creates a supported operation", "consumes": [ "application/json" @@ -5129,20 +4825,16 @@ const docTemplate = `{ ], "tags": [ "branch" - "branch" ], "summary": "Create a supported operation", - "summary": "Create a supported operation", "parameters": [ { - "description": "Creates supported operation", - "name": "createSupportedOperation", "description": "Creates supported operation", "name": "createSupportedOperation", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + "$ref": "#/definitions/domain.CreateSupportedOperationReq" } } ], @@ -5150,7 +4842,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.SupportedOperationRes" + "$ref": "#/definitions/domain.SupportedOperationRes" } }, "400": { @@ -5168,9 +4860,8 @@ const docTemplate = `{ } } }, - "/ticket": { + "/api/v1/ticket": { "get": { - "description": "Retrieve all tickets", "description": "Retrieve all tickets", "consumes": [ "application/json" @@ -5206,45 +4897,6 @@ const docTemplate = `{ } } }, - "post": { - "description": "Creates a temporary ticket", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "ticket" - ], - "summary": "Create a temporary ticket", - "ticket" - ], - "summary": "Get all tickets", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.TicketRes" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, "post": { "description": "Creates a temporary ticket", "consumes": [ @@ -5266,13 +4918,6 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/domain.CreateTicketReq" } - "description": "Creates ticket", - "name": "createTicket", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CreateTicketReq" - } } ], "responses": { @@ -5280,7 +4925,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.CreateTicketRes" - "$ref": "#/definitions/domain.CreateTicketRes" } }, "400": { @@ -5298,7 +4942,7 @@ const docTemplate = `{ } } }, - "/ticket/{id}": { + "/api/v1/ticket/{id}": { "get": { "description": "Retrieve ticket details by ticket ID", "consumes": [ @@ -5309,15 +4953,12 @@ const docTemplate = `{ ], "tags": [ "ticket" - "ticket" ], "summary": "Get ticket by ID", - "summary": "Get ticket by ID", "parameters": [ { "type": "integer", "description": "Ticket ID", - "description": "Ticket ID", "name": "id", "in": "path", "required": true @@ -5328,7 +4969,6 @@ const docTemplate = `{ "description": "OK", "schema": { "$ref": "#/definitions/domain.TicketRes" - "$ref": "#/definitions/domain.TicketRes" } }, "400": { @@ -5346,7 +4986,7 @@ const docTemplate = `{ } } }, - "/top-leagues": { + "/api/v1/top-leagues": { "get": { "description": "Retrieve all top leagues", "consumes": [ @@ -5378,7 +5018,7 @@ const docTemplate = `{ } } }, - "/transfer/refill/:id": { + "/api/v1/transfer/refill/:id": { "post": { "description": "Super Admin route to refill a wallet", "consumes": [ @@ -6642,6 +6282,51 @@ const docTemplate = `{ } } }, + "/api/v1/win": { + "post": { + "description": "Processes win callbacks from either Veli or PopOK providers by auto-detecting the format", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wins" + ], + "summary": "Handle win callback (Veli or PopOK)", + "responses": { + "200": { + "description": "Win processing result", + "schema": {} + }, + "400": { + "description": "Invalid request format", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "401": { + "description": "Authentication failed", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "409": { + "description": "Duplicate transaction", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/popok/games": { "get": { "description": "Retrieves the list of available PopOK slot games", @@ -6674,8 +6359,8 @@ const docTemplate = `{ } } }, - "502": { - "description": "Bad Gateway", + "500": { + "description": "Internal Server Error", "schema": { "$ref": "#/definitions/domain.ErrorResponse" } @@ -6874,6 +6559,28 @@ const docTemplate = `{ } } }, + "domain.ArifPayB2CRequest": { + "type": "object", + "properties": { + "Phonenumber": { + "type": "string" + }, + "Sessionid": { + "type": "string" + } + } + }, + "domain.ArifpayVerifyByTransactionIDRequest": { + "type": "object", + "properties": { + "paymentType": { + "type": "integer" + }, + "transactionId": { + "type": "string" + } + } + }, "domain.Bank": { "type": "object", "properties": { @@ -6927,6 +6634,20 @@ const docTemplate = `{ } } }, + "domain.Beneficiary": { + "type": "object", + "properties": { + "accountNumber": { + "type": "string" + }, + "amount": { + "type": "number" + }, + "bank": { + "type": "string" + } + } + }, "domain.BetOutcome": { "type": "object", "properties": { @@ -7050,6 +6771,109 @@ const docTemplate = `{ } } }, + "domain.BranchDetailRes": { + "type": "object", + "properties": { + "balance": { + "type": "number", + "example": 100.5 + }, + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "is_wallet_active": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "domain.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "domain.CashoutReq": { "type": "object", "properties": { @@ -7214,6 +7038,11 @@ const docTemplate = `{ }, "domain.CreateBetReq": { "type": "object", + "required": [ + "amount", + "branch_id", + "outcomes" + ], "properties": { "amount": { "type": "number", @@ -7228,10 +7057,150 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/domain.CreateBetOutcomeReq" } + } + } + }, + "domain.CreateBetWithFastCodeReq": { + "type": "object", + "properties": { + "amount": { + "type": "number" }, - "phone_number": { + "branch_id": { + "type": "integer" + }, + "fast_code": { + "type": "string" + } + } + }, + "domain.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.CreateBranchReq": { + "type": "object", + "required": [ + "branch_manager_id", + "location", + "name", + "operations" + ], + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { "type": "string", - "example": "1234567890" + "maxLength": 100, + "minLength": 3, + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "domain.CreateCheckoutSessionRequest": { + "type": "object", + "properties": { + "beneficiaries": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Beneficiary" + } + }, + "cancelUrl": { + "type": "string" + }, + "email": { + "type": "string" + }, + "errorUrl": { + "type": "string" + }, + "expireDate": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Item" + } + }, + "lang": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "notifyUrl": { + "type": "string" + }, + "paymentMethods": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone": { + "type": "string" + }, + "successUrl": { + "type": "string" + } + } + }, + "domain.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, + "domain.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" } } }, @@ -7698,6 +7667,55 @@ const docTemplate = `{ } } }, + "domain.GetCompanyRes": { + "type": "object", + "properties": { + "admin_first_name": { + "type": "string", + "example": "John" + }, + "admin_id": { + "type": "integer", + "example": 1 + }, + "admin_last_name": { + "type": "string", + "example": "Doe" + }, + "admin_phone_number": { + "type": "string", + "example": "1234567890" + }, + "balance": { + "type": "number", + "example": 1 + }, + "deducted_percentage": { + "type": "number", + "example": 0.1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_wallet_active": { + "type": "boolean", + "example": false + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "domain.InstResponse": { "type": "object", "properties": { @@ -7720,6 +7738,26 @@ const docTemplate = `{ } } }, + "domain.Item": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "image": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + } + } + }, "domain.League": { "type": "object", "properties": { @@ -8311,6 +8349,23 @@ const docTemplate = `{ } } }, + "domain.ShopDepositRes": { + "type": "object", + "properties": { + "customer_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "shop_transaction_id": { + "type": "integer" + }, + "wallet_transfer_id": { + "type": "integer" + } + } + }, "domain.ShopTransactionRes": { "type": "object", "properties": { @@ -8421,6 +8476,23 @@ const docTemplate = `{ } } }, + "domain.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "domain.TicketOutcome": { "type": "object", "properties": { @@ -8656,109 +8728,6 @@ const docTemplate = `{ } } }, - "handlers.BranchDetailRes": { - "type": "object", - "properties": { - "balance": { - "type": "number", - "example": 100.5 - }, - "branch_manager_id": { - "type": "integer", - "example": 1 - }, - "company_id": { - "type": "integer", - "example": 1 - }, - "id": { - "type": "integer", - "example": 1 - }, - "is_active": { - "type": "boolean", - "example": false - }, - "is_self_owned": { - "type": "boolean", - "example": false - }, - "is_wallet_active": { - "type": "boolean", - "example": false - }, - "location": { - "type": "string", - "example": "Addis Ababa" - }, - "manager_name": { - "type": "string", - "example": "John Smith" - }, - "manager_phone_number": { - "type": "string", - "example": "0911111111" - }, - "name": { - "type": "string", - "example": "4-kilo Branch" - }, - "wallet_id": { - "type": "integer", - "example": 1 - } - } - }, - "handlers.BranchOperationRes": { - "type": "object", - "properties": { - "description": { - "type": "string", - "example": "Betting on sport events" - }, - "name": { - "type": "string", - "example": "SportsBook" - } - } - }, - "handlers.BranchRes": { - "type": "object", - "properties": { - "branch_manager_id": { - "type": "integer", - "example": 1 - }, - "company_id": { - "type": "integer", - "example": 1 - }, - "id": { - "type": "integer", - "example": 1 - }, - "is_active": { - "type": "boolean", - "example": false - }, - "is_self_owned": { - "type": "boolean", - "example": false - }, - "location": { - "type": "string", - "example": "Addis Ababa" - }, - "name": { - "type": "string", - "example": "4-kilo Branch" - }, - "wallet_id": { - "type": "integer", - "example": 1 - } - } - }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -8874,19 +8843,6 @@ const docTemplate = `{ } } }, - "handlers.CreateSupportedOperationReq": { - "type": "object", - "properties": { - "description": { - "type": "string", - "example": "Betting on sport events" - }, - "name": { - "type": "string", - "example": "SportsBook" - } - } - }, "handlers.CreateTransferReq": { "type": "object", "properties": { @@ -8914,10 +8870,6 @@ const docTemplate = `{ "type": "string", "example": "John" }, - "first_name": { - "type": "string", - "example": "John" - }, "id": { "type": "integer", "example": 1 @@ -8930,14 +8882,6 @@ const docTemplate = `{ "type": "string", "example": "0911111111" }, - "last_name": { - "type": "string", - "example": "Smith" - }, - "phone_number": { - "type": "string", - "example": "0911111111" - }, "regular_balance": { "type": "number", "example": 100 @@ -8950,10 +8894,6 @@ const docTemplate = `{ "type": "boolean", "example": true }, - "regular_is_active": { - "type": "boolean", - "example": true - }, "regular_updated_at": { "type": "string" }, @@ -8969,10 +8909,6 @@ const docTemplate = `{ "type": "boolean", "example": true }, - "static_is_active": { - "type": "boolean", - "example": true - }, "static_updated_at": { "type": "string" } @@ -9022,50 +8958,6 @@ const docTemplate = `{ } } }, - "handlers.CustomersRes": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "email": { - "type": "string" - }, - "email_verified": { - "type": "boolean" - }, - "first_name": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "last_login": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "phone_verified": { - "type": "boolean" - }, - "role": { - "$ref": "#/definitions/domain.Role" - }, - "suspended": { - "type": "boolean" - }, - "suspended_at": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "handlers.GetCashierRes": { "type": "object", "properties": { @@ -9281,36 +9173,33 @@ const docTemplate = `{ } } }, - "handlers.TopLeague": { + "handlers.SetLeagueAsFeatured": { + "type": "object", + "properties": { + "is_featured": { + "type": "boolean", + "example": true + } + } + }, "handlers.TopLeague": { "type": "object", "properties": { - "events": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.UpcomingEvent" - } "events": { "type": "array", "items": { "$ref": "#/definitions/domain.UpcomingEvent" } }, - "league_cc": { "league_cc": { "type": "string" }, - "league_id": { - "type": "integer" "league_id": { "type": "integer" }, - "league_name": { "league_name": { "type": "string" }, - "league_sport_id": { - "type": "integer" "league_sport_id": { "type": "integer" } @@ -9328,23 +9217,12 @@ const docTemplate = `{ "depositor_first_name": { "type": "string" }, - "depositor_id": { - "created_at": { - "type": "string" - }, - "depositor_first_name": { - "type": "string" - }, "depositor_id": { "type": "integer" }, "depositor_last_name": { "type": "string" }, - "depositor_phone_number": { - "depositor_last_name": { - "type": "string" - }, "depositor_phone_number": { "type": "string" }, @@ -9354,9 +9232,6 @@ const docTemplate = `{ "message": { "type": "string" }, - "message": { - "type": "string" - }, "payment_method": { "type": "string" }, @@ -9526,7 +9401,6 @@ const docTemplate = `{ "game_id": { "type": "string", "example": "1" - "example": "1" }, "mode": { "type": "string", @@ -9668,27 +9542,6 @@ const docTemplate = `{ } } }, - "handlers.updateCustomerReq": { - "type": "object", - "properties": { - "company_id": { - "type": "integer", - "example": 1 - }, - "first_name": { - "type": "string", - "example": "John" - }, - "last_name": { - "type": "string", - "example": "Doe" - }, - "suspended": { - "type": "boolean", - "example": false - } - } - }, "handlers.updateManagerReq": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 34d3519..f65862a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -121,7 +121,7 @@ } } }, - "/admin-company": { + "/api/v1/admin-company": { "get": { "description": "Gets a single company by id", "consumes": [ @@ -138,7 +138,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.CompanyRes" + "$ref": "#/definitions/domain.GetCompanyRes" } }, "400": { @@ -156,7 +156,7 @@ } } }, - "/admin/{id}": { + "/api/v1/admin/{id}": { "get": { "description": "Get a single admin by id", "consumes": [ @@ -331,6 +331,195 @@ } } }, + "/api/v1/arifpay/b2c/transfer": { + "post": { + "description": "Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Initiate B2C Transfer", + "parameters": [ + { + "type": "string", + "description": "Transfer mode (Telebirr, CBE, MPESA)", + "name": "transfer_mode", + "in": "query", + "required": true + }, + { + "description": "Transfer request payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.ArifPayB2CRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/checkout": { + "post": { + "description": "Creates a payment session using Arifpay and returns a redirect URL.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Create Arifpay Checkout Session", + "parameters": [ + { + "description": "Checkout session request payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateCheckoutSessionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/session-id/verify-transaction/{session_id}": { + "get": { + "description": "Verifies an Arifpay transaction using a session ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Verify Arifpay Transaction by Session ID", + "parameters": [ + { + "type": "string", + "description": "Arifpay Session ID", + "name": "session_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, + "/api/v1/arifpay/transaction-id/verify-transaction": { + "post": { + "description": "Verifies a transaction using transaction ID and payment type", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Arifpay" + ], + "summary": "Verify Arifpay Transaction", + "parameters": [ + { + "description": "Transaction verification payload", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.ArifpayVerifyByTransactionIDRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/api/v1/auth/login": { "post": { "description": "Login customer", @@ -1126,61 +1315,7 @@ "400": { "description": "Bad Request", "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - } - } - } - }, - "/api/v1/logs": { - "get": { - "description": "Fetches application logs from MongoDB with pagination, level filtering, and search", - "produces": [ - "application/json" - ], - "tags": [ - "Logs" - ], - "summary": "Retrieve application logs with filtering and pagination", - "parameters": [ - { - "type": "string", - "description": "Filter logs by level (debug, info, warn, error, dpanic, panic, fatal)", - "name": "level", - "in": "query" - }, - { - "type": "string", - "description": "Search term to match against message or fields", - "name": "search", - "in": "query" - }, - { - "type": "integer", - "default": 1, - "description": "Page number for pagination (default: 1)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 50, - "description": "Number of items per page (default: 50, max: 100)", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Paginated list of application logs", - "schema": { - "$ref": "#/definitions/domain.LogResponse" - } - }, - "400": { - "description": "Invalid request parameters", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" + "$ref": "#/definitions/response.APIResponse" } }, "500": { @@ -1238,47 +1373,31 @@ }, "/api/v1/branchWallet": { "get": { - "description": "Returns a paginated list of generated report CSV files with search capability", + "description": "Retrieve all branch wallets", + "consumes": [ + "application/json" + ], "produces": [ "application/json" ], "tags": [ "wallet" ], - "summary": "List available report CSV files", - "parameters": [ - { - "type": "string", - "description": "Search term to filter filenames", - "name": "search", - "in": "query" - }, - { - "type": "integer", - "default": 1, - "description": "Page number (default: 1)", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "default": 20, - "description": "Items per page (default: 20, max: 100)", - "name": "limit", - "in": "query" - } - ], + "summary": "Get all branch wallets", "responses": { "200": { - "description": "Paginated list of CSV report filenames", + "description": "OK", "schema": { - "$ref": "#/definitions/domain.PaginatedFileResponse" + "type": "array", + "items": { + "$ref": "#/definitions/handlers.WalletRes" + } } }, "400": { - "description": "Invalid pagination parameters", + "description": "Bad Request", "schema": { - "$ref": "#/definitions/domain.ErrorResponse" + "$ref": "#/definitions/response.APIResponse" } }, "500": { @@ -1710,100 +1829,7 @@ "description": "OK", "schema": { "type": "object", - "additionalProperties": { - "allOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "status": { - "type": "string" - } - } - } - ] - } - } - } - } - } - }, - "/api/v1/win": { - "post": { - "description": "Processes win callbacks from either Veli or PopOK providers by auto-detecting the format", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Wins" - ], - "summary": "Handle win callback (Veli or PopOK)", - "responses": { - "200": { - "description": "Win processing result", - "schema": {} - }, - "400": { - "description": "Invalid request format", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "401": { - "description": "Authentication failed", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "409": { - "description": "Duplicate transaction", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/domain.ErrorResponse" - } - } - } - } - }, - "/auth/login": { - "post": { - "description": "Login customer", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "auth" - ], - "summary": "Login customer", - "parameters": [ - { - "description": "Login customer", - "name": "login", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.loginCustomerReq" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.loginCustomerRes" + "additionalProperties": true } }, "400": { @@ -2823,202 +2849,9 @@ } } }, - "/customer": { - "get": { - "description": "Get all Customers", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Get all Customers", - "parameters": [ - { - "type": "integer", - "description": "Page number", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "description": "Page size", - "name": "page_size", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.CustomersRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customer/{id}": { - "get": { - "description": "Get a single customer by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Get customer by id", - "parameters": [ - { - "type": "integer", - "description": "User ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handlers.CustomersRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, - "put": { - "description": "Update Customers", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "customer" - ], - "summary": "Update Customers", - "parameters": [ - { - "description": "Update Customers", - "name": "Customers", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.updateCustomerReq" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/customerWallet": { - "get": { - "description": "Retrieve all customer wallets", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "wallet" - ], - "summary": "Get all customer wallets", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/handlers.CustomerWalletRes" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/events": { - "get": { - "description": "Retrieve all upcoming events from the database", + "/api/v1/issues/{issue_id}/status": { + "patch": { + "description": "Admin endpoint to update the status of a reported issue", "consumes": [ "application/json" ], @@ -3100,6 +2933,59 @@ } } }, + "/api/v1/leagues/{id}/featured": { + "put": { + "description": "Set the league to featured/un-featured", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "leagues" + ], + "summary": "Set the league to featured/un-featured", + "parameters": [ + { + "type": "integer", + "description": "League ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "League Featured Request", + "name": "active", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handlers.SetLeagueAsFeatured" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/leagues/{id}/set-active": { "put": { "description": "Set the league to active", @@ -3155,22 +3041,53 @@ }, "/api/v1/logs": { "get": { - "description": "Fetches the 100 most recent application logs from MongoDB", + "description": "Fetches application logs from MongoDB with pagination, level filtering, and search", "produces": [ "application/json" ], "tags": [ "Logs" ], - "summary": "Retrieve latest application logs", + "summary": "Retrieve application logs with filtering and pagination", + "parameters": [ + { + "type": "string", + "description": "Filter logs by level (debug, info, warn, error, dpanic, panic, fatal)", + "name": "level", + "in": "query" + }, + { + "type": "string", + "description": "Search term to match against message or fields", + "name": "search", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page number for pagination (default: 1)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 50, + "description": "Number of items per page (default: 50, max: 100)", + "name": "limit", + "in": "query" + } + ], "responses": { "200": { - "description": "List of application logs", + "description": "Paginated list of application logs", "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.LogEntry" - } + "$ref": "#/definitions/domain.LogResponse" + } + }, + "400": { + "description": "Invalid request parameters", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" } }, "500": { @@ -3625,86 +3542,7 @@ } } }, - "/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" - } - } - }, - "500": { - "description": "Internal Server Error", - "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" - } - } - } - } - }, - "/referral/settings": { + "/api/v1/referral/settings": { "get": { "security": [ { @@ -3894,7 +3732,7 @@ }, "/api/v1/report-files/list": { "get": { - "description": "Returns a list of all generated report CSV files available for download", + "description": "Returns a paginated list of generated report CSV files with search capability", "produces": [ "application/json" ], @@ -3902,26 +3740,39 @@ "Reports" ], "summary": "List available report CSV files", + "parameters": [ + { + "type": "string", + "description": "Search term to filter filenames", + "name": "search", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "Page number (default: 1)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "Items per page (default: 20, max: 100)", + "name": "limit", + "in": "query" + } + ], "responses": { "200": { - "description": "List of CSV report filenames", + "description": "Paginated list of CSV report filenames", "schema": { - "allOf": [ - { - "$ref": "#/definitions/domain.Response" - }, - { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - ] + "$ref": "#/definitions/domain.PaginatedFileResponse" + } + }, + "400": { + "description": "Invalid pagination parameters", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" } }, "500": { @@ -4155,7 +4006,7 @@ } } }, - "/shop/bet": { + "/api/v1/shop/bet": { "post": { "description": "Create bet at branch", "consumes": [ @@ -4201,9 +4052,8 @@ } } }, - "/shop/bet/{id}": { + "/api/v1/shop/bet/{id}": { "get": { - "description": "Cashout bet at branch", "description": "Cashout bet at branch", "consumes": [ "application/json" @@ -4213,19 +4063,6 @@ ], "tags": [ "transaction" - "transaction" - ], - "summary": "Cashout bet at branch", - "parameters": [ - { - "description": "cashout bet", - "name": "createBet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CashoutReq" - } - } ], "summary": "Cashout bet at branch", "parameters": [ @@ -4261,7 +4098,7 @@ } } }, - "/shop/bet/{id}/cashout": { + "/api/v1/shop/bet/{id}/cashout": { "post": { "description": "Cashout bet at branch", "consumes": [ @@ -4307,9 +4144,8 @@ } } }, - "/shop/cashout": { + "/api/v1/shop/cashout": { "post": { - "description": "Cashout bet by cashoutID", "description": "Cashout bet by cashoutID", "consumes": [ "application/json" @@ -4319,21 +4155,16 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Cashout bet by cashoutID", - "summary": "Cashout bet by cashoutID", "parameters": [ { - "description": "cashout bet", - "name": "cashoutBet", "description": "cashout bet", "name": "cashoutBet", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.CashoutReq" - "$ref": "#/definitions/domain.CashoutReq" } } ], @@ -4342,7 +4173,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4360,9 +4190,8 @@ } } }, - "/shop/cashout/{id}": { + "/api/v1/shop/cashout/{id}": { "get": { - "description": "Cashout bet at branch", "description": "Cashout bet at branch", "consumes": [ "application/json" @@ -4372,10 +4201,8 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Cashout bet at branch", - "summary": "Cashout bet at branch", "parameters": [ { "description": "cashout bet", @@ -4385,13 +4212,6 @@ "schema": { "$ref": "#/definitions/domain.CashoutReq" } - "description": "cashout bet", - "name": "createBet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CashoutReq" - } } ], "responses": { @@ -4399,7 +4219,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4417,7 +4236,7 @@ } } }, - "/shop/deposit": { + "/api/v1/shop/deposit": { "post": { "description": "Transfers money from branch wallet to customer wallet", "consumes": [ @@ -4428,10 +4247,8 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Shop deposit into customer wallet", - "summary": "Shop deposit into customer wallet", "parameters": [ { "description": "ShopDepositReq", @@ -4441,20 +4258,13 @@ "schema": { "$ref": "#/definitions/domain.ShopDepositReq" } - "description": "ShopDepositReq", - "name": "transferToWallet", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.ShopDepositReq" - } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.TransferWalletRes" + "$ref": "#/definitions/domain.ShopDepositRes" } }, "400": { @@ -4472,7 +4282,7 @@ } } }, - "/shop/transaction": { + "/api/v1/shop/transaction": { "get": { "description": "Gets all the transactions", "consumes": [ @@ -4483,7 +4293,6 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Gets all transactions", "responses": { @@ -4511,7 +4320,7 @@ } } }, - "/shop/transaction/{id}": { + "/api/v1/shop/transaction/{id}": { "get": { "description": "Gets a single transaction by id", "consumes": [ @@ -4528,7 +4337,6 @@ { "type": "integer", "description": "Transaction ID", - "description": "Transaction ID", "name": "id", "in": "path", "required": true @@ -4539,7 +4347,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4556,8 +4363,6 @@ } } }, - "put": { - "description": "Updates the verified status of a transaction", "put": { "description": "Updates the verified status of a transaction", "consumes": [ @@ -4568,28 +4373,23 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Updates the verified field of a transaction", - "summary": "Updates the verified field of a transaction", "parameters": [ { "type": "integer", "description": "Transaction ID", - "description": "Transaction ID", "name": "id", "in": "path", "required": true }, { - "description": "Updates Transaction Verification", - "name": "updateVerified", "description": "Updates Transaction Verification", "name": "updateVerified", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.UpdateTransactionVerifiedReq" + "$ref": "#/definitions/domain.UpdateTransactionVerifiedReq" } } ], @@ -4615,7 +4415,7 @@ } } }, - "/shop/transaction/{id}/bet": { + "/api/v1/shop/transaction/{id}/bet": { "get": { "description": "Gets a single shop bet by transaction id", "consumes": [ @@ -4626,10 +4426,8 @@ ], "tags": [ "transaction" - "transaction" ], "summary": "Gets shop bet by transaction id", - "summary": "Gets shop bet by transaction id", "parameters": [ { "type": "integer", @@ -4637,11 +4435,6 @@ "name": "id", "in": "path", "required": true - "type": "integer", - "description": "Transaction ID", - "name": "id", - "in": "path", - "required": true } ], "responses": { @@ -4649,7 +4442,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.ShopTransactionRes" - "$ref": "#/definitions/domain.ShopTransactionRes" } }, "400": { @@ -4667,9 +4459,8 @@ } } }, - "/sport/bet": { + "/api/v1/sport/bet": { "get": { - "description": "Gets all the bets", "description": "Gets all the bets", "consumes": [ "application/json" @@ -4679,10 +4470,8 @@ ], "tags": [ "bet" - "bet" ], "summary": "Gets all bets", - "summary": "Gets all bets", "responses": { "200": { "description": "OK", @@ -4690,7 +4479,6 @@ "type": "array", "items": { "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" } } }, @@ -4709,7 +4497,6 @@ } }, "post": { - "description": "Creates a bet", "description": "Creates a bet", "consumes": [ "application/json" @@ -4719,21 +4506,16 @@ ], "tags": [ "bet" - "bet" ], "summary": "Create a bet", - "summary": "Create a bet", "parameters": [ { - "description": "Creates bet", - "name": "createBet", "description": "Creates bet", "name": "createBet", "in": "body", "required": true, "schema": { "$ref": "#/definitions/domain.CreateBetReq" - "$ref": "#/definitions/domain.CreateBetReq" } } ], @@ -4742,7 +4524,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" } }, "400": { @@ -4760,53 +4541,7 @@ } } }, - "/sport/bet/cashout/{id}": { - "get": { - "description": "Gets a single bet by cashout id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bet" - "bet" - ], - "summary": "Gets bet by cashout id", - "parameters": [ - { - "type": "string", - "description": "cashout ID", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/domain.BetRes" - "$ref": "#/definitions/domain.BetRes" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - } - }, - "/sport/bet/fastcode": { + "/api/v1/sport/bet/fastcode": { "post": { "description": "Creates a bet with fast code", "consumes": [ @@ -4817,7 +4552,6 @@ ], "tags": [ "bet" - "bet" ], "summary": "Create a bet with fast code", "parameters": [ @@ -4827,31 +4561,9 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/domain.CreateBetReq" + "$ref": "#/definitions/domain.CreateBetWithFastCodeReq" } } - } - }, - "delete": { - "description": "Deletes bet by id", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "bet" - ], - "summary": "Deletes bet by id", - "parameters": [ - { - "type": "integer", - "description": "Bet ID", - "name": "id", - "in": "path", - "required": true - } ], "responses": { "200": { @@ -4875,7 +4587,7 @@ } } }, - "/sport/bet/{id}": { + "/api/v1/sport/bet/{id}": { "get": { "description": "Gets a single bet by id", "consumes": [ @@ -4886,26 +4598,15 @@ ], "tags": [ "bet" - "bet" ], "summary": "Gets bet by id", "parameters": [ { "type": "integer", "description": "Bet ID", - "description": "Bet ID", "name": "id", "in": "path", "required": true - }, - { - "description": "Updates Cashed Out", - "name": "updateCashOut", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handlers.UpdateCashOutReq" - } } ], "responses": { @@ -4981,7 +4682,6 @@ ], "tags": [ "bet" - "bet" ], "summary": "Updates the cashed out field", "parameters": [ @@ -5024,7 +4724,7 @@ } } }, - "/sport/random/bet": { + "/api/v1/sport/random/bet": { "post": { "description": "Generate a random bet", "consumes": [ @@ -5070,9 +4770,8 @@ } } }, - "/supportedOperation": { + "/api/v1/supportedOperation": { "get": { - "description": "Gets all supported operations", "description": "Gets all supported operations", "consumes": [ "application/json" @@ -5082,17 +4781,15 @@ ], "tags": [ "branch" - "branch" ], "summary": "Gets all supported operations", - "summary": "Gets all supported operations", "responses": { "200": { "description": "OK", "schema": { "type": "array", "items": { - "$ref": "#/definitions/handlers.BranchDetailRes" + "$ref": "#/definitions/domain.BranchDetailRes" } } }, @@ -5111,7 +4808,6 @@ } }, "post": { - "description": "Creates a supported operation", "description": "Creates a supported operation", "consumes": [ "application/json" @@ -5121,20 +4817,16 @@ ], "tags": [ "branch" - "branch" ], "summary": "Create a supported operation", - "summary": "Create a supported operation", "parameters": [ { - "description": "Creates supported operation", - "name": "createSupportedOperation", "description": "Creates supported operation", "name": "createSupportedOperation", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/handlers.CreateSupportedOperationReq" + "$ref": "#/definitions/domain.CreateSupportedOperationReq" } } ], @@ -5142,7 +4834,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/handlers.SupportedOperationRes" + "$ref": "#/definitions/domain.SupportedOperationRes" } }, "400": { @@ -5160,9 +4852,8 @@ } } }, - "/ticket": { + "/api/v1/ticket": { "get": { - "description": "Retrieve all tickets", "description": "Retrieve all tickets", "consumes": [ "application/json" @@ -5198,45 +4889,6 @@ } } }, - "post": { - "description": "Creates a temporary ticket", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "ticket" - ], - "summary": "Create a temporary ticket", - "ticket" - ], - "summary": "Get all tickets", - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.TicketRes" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/response.APIResponse" - } - } - } - }, "post": { "description": "Creates a temporary ticket", "consumes": [ @@ -5258,13 +4910,6 @@ "schema": { "$ref": "#/definitions/domain.CreateTicketReq" } - "description": "Creates ticket", - "name": "createTicket", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/domain.CreateTicketReq" - } } ], "responses": { @@ -5272,7 +4917,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.CreateTicketRes" - "$ref": "#/definitions/domain.CreateTicketRes" } }, "400": { @@ -5290,7 +4934,7 @@ } } }, - "/ticket/{id}": { + "/api/v1/ticket/{id}": { "get": { "description": "Retrieve ticket details by ticket ID", "consumes": [ @@ -5301,15 +4945,12 @@ ], "tags": [ "ticket" - "ticket" ], "summary": "Get ticket by ID", - "summary": "Get ticket by ID", "parameters": [ { "type": "integer", "description": "Ticket ID", - "description": "Ticket ID", "name": "id", "in": "path", "required": true @@ -5320,7 +4961,6 @@ "description": "OK", "schema": { "$ref": "#/definitions/domain.TicketRes" - "$ref": "#/definitions/domain.TicketRes" } }, "400": { @@ -5338,7 +4978,7 @@ } } }, - "/top-leagues": { + "/api/v1/top-leagues": { "get": { "description": "Retrieve all top leagues", "consumes": [ @@ -5370,7 +5010,7 @@ } } }, - "/transfer/refill/:id": { + "/api/v1/transfer/refill/:id": { "post": { "description": "Super Admin route to refill a wallet", "consumes": [ @@ -6634,6 +6274,51 @@ } } }, + "/api/v1/win": { + "post": { + "description": "Processes win callbacks from either Veli or PopOK providers by auto-detecting the format", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Wins" + ], + "summary": "Handle win callback (Veli or PopOK)", + "responses": { + "200": { + "description": "Win processing result", + "schema": {} + }, + "400": { + "description": "Invalid request format", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "401": { + "description": "Authentication failed", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "409": { + "description": "Duplicate transaction", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/domain.ErrorResponse" + } + } + } + } + }, "/popok/games": { "get": { "description": "Retrieves the list of available PopOK slot games", @@ -6666,8 +6351,8 @@ } } }, - "502": { - "description": "Bad Gateway", + "500": { + "description": "Internal Server Error", "schema": { "$ref": "#/definitions/domain.ErrorResponse" } @@ -6866,6 +6551,28 @@ } } }, + "domain.ArifPayB2CRequest": { + "type": "object", + "properties": { + "Phonenumber": { + "type": "string" + }, + "Sessionid": { + "type": "string" + } + } + }, + "domain.ArifpayVerifyByTransactionIDRequest": { + "type": "object", + "properties": { + "paymentType": { + "type": "integer" + }, + "transactionId": { + "type": "string" + } + } + }, "domain.Bank": { "type": "object", "properties": { @@ -6919,6 +6626,20 @@ } } }, + "domain.Beneficiary": { + "type": "object", + "properties": { + "accountNumber": { + "type": "string" + }, + "amount": { + "type": "number" + }, + "bank": { + "type": "string" + } + } + }, "domain.BetOutcome": { "type": "object", "properties": { @@ -7042,6 +6763,109 @@ } } }, + "domain.BranchDetailRes": { + "type": "object", + "properties": { + "balance": { + "type": "number", + "example": 100.5 + }, + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "is_wallet_active": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "manager_name": { + "type": "string", + "example": "John Smith" + }, + "manager_phone_number": { + "type": "string", + "example": "0911111111" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.BranchOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, + "domain.BranchRes": { + "type": "object", + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { + "type": "string", + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "example": "4-kilo Branch" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "domain.CashoutReq": { "type": "object", "properties": { @@ -7206,6 +7030,11 @@ }, "domain.CreateBetReq": { "type": "object", + "required": [ + "amount", + "branch_id", + "outcomes" + ], "properties": { "amount": { "type": "number", @@ -7220,10 +7049,150 @@ "items": { "$ref": "#/definitions/domain.CreateBetOutcomeReq" } + } + } + }, + "domain.CreateBetWithFastCodeReq": { + "type": "object", + "properties": { + "amount": { + "type": "number" }, - "phone_number": { + "branch_id": { + "type": "integer" + }, + "fast_code": { + "type": "string" + } + } + }, + "domain.CreateBranchOperationReq": { + "type": "object", + "properties": { + "branch_id": { + "type": "integer", + "example": 1 + }, + "operation_id": { + "type": "integer", + "example": 1 + } + } + }, + "domain.CreateBranchReq": { + "type": "object", + "required": [ + "branch_manager_id", + "location", + "name", + "operations" + ], + "properties": { + "branch_manager_id": { + "type": "integer", + "example": 1 + }, + "company_id": { + "type": "integer", + "example": 1 + }, + "is_self_owned": { + "type": "boolean", + "example": false + }, + "location": { "type": "string", - "example": "1234567890" + "maxLength": 100, + "minLength": 3, + "example": "Addis Ababa" + }, + "name": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "example": "4-kilo Branch" + }, + "operations": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "domain.CreateCheckoutSessionRequest": { + "type": "object", + "properties": { + "beneficiaries": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Beneficiary" + } + }, + "cancelUrl": { + "type": "string" + }, + "email": { + "type": "string" + }, + "errorUrl": { + "type": "string" + }, + "expireDate": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Item" + } + }, + "lang": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "notifyUrl": { + "type": "string" + }, + "paymentMethods": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone": { + "type": "string" + }, + "successUrl": { + "type": "string" + } + } + }, + "domain.CreateCompanyReq": { + "type": "object", + "properties": { + "admin_id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "CompanyName" + } + } + }, + "domain.CreateSupportedOperationReq": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "name": { + "type": "string", + "example": "SportsBook" } } }, @@ -7690,6 +7659,55 @@ } } }, + "domain.GetCompanyRes": { + "type": "object", + "properties": { + "admin_first_name": { + "type": "string", + "example": "John" + }, + "admin_id": { + "type": "integer", + "example": 1 + }, + "admin_last_name": { + "type": "string", + "example": "Doe" + }, + "admin_phone_number": { + "type": "string", + "example": "1234567890" + }, + "balance": { + "type": "number", + "example": 1 + }, + "deducted_percentage": { + "type": "number", + "example": 0.1 + }, + "id": { + "type": "integer", + "example": 1 + }, + "is_active": { + "type": "boolean", + "example": false + }, + "is_wallet_active": { + "type": "boolean", + "example": false + }, + "name": { + "type": "string", + "example": "CompanyName" + }, + "wallet_id": { + "type": "integer", + "example": 1 + } + } + }, "domain.InstResponse": { "type": "object", "properties": { @@ -7712,6 +7730,26 @@ } } }, + "domain.Item": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "image": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "quantity": { + "type": "integer" + } + } + }, "domain.League": { "type": "object", "properties": { @@ -8303,6 +8341,23 @@ } } }, + "domain.ShopDepositRes": { + "type": "object", + "properties": { + "customer_id": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "shop_transaction_id": { + "type": "integer" + }, + "wallet_transfer_id": { + "type": "integer" + } + } + }, "domain.ShopTransactionRes": { "type": "object", "properties": { @@ -8413,6 +8468,23 @@ } } }, + "domain.SupportedOperationRes": { + "type": "object", + "properties": { + "description": { + "type": "string", + "example": "Betting on sport events" + }, + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "SportsBook" + } + } + }, "domain.TicketOutcome": { "type": "object", "properties": { @@ -8648,109 +8720,6 @@ } } }, - "handlers.BranchDetailRes": { - "type": "object", - "properties": { - "balance": { - "type": "number", - "example": 100.5 - }, - "branch_manager_id": { - "type": "integer", - "example": 1 - }, - "company_id": { - "type": "integer", - "example": 1 - }, - "id": { - "type": "integer", - "example": 1 - }, - "is_active": { - "type": "boolean", - "example": false - }, - "is_self_owned": { - "type": "boolean", - "example": false - }, - "is_wallet_active": { - "type": "boolean", - "example": false - }, - "location": { - "type": "string", - "example": "Addis Ababa" - }, - "manager_name": { - "type": "string", - "example": "John Smith" - }, - "manager_phone_number": { - "type": "string", - "example": "0911111111" - }, - "name": { - "type": "string", - "example": "4-kilo Branch" - }, - "wallet_id": { - "type": "integer", - "example": 1 - } - } - }, - "handlers.BranchOperationRes": { - "type": "object", - "properties": { - "description": { - "type": "string", - "example": "Betting on sport events" - }, - "name": { - "type": "string", - "example": "SportsBook" - } - } - }, - "handlers.BranchRes": { - "type": "object", - "properties": { - "branch_manager_id": { - "type": "integer", - "example": 1 - }, - "company_id": { - "type": "integer", - "example": 1 - }, - "id": { - "type": "integer", - "example": 1 - }, - "is_active": { - "type": "boolean", - "example": false - }, - "is_self_owned": { - "type": "boolean", - "example": false - }, - "location": { - "type": "string", - "example": "Addis Ababa" - }, - "name": { - "type": "string", - "example": "4-kilo Branch" - }, - "wallet_id": { - "type": "integer", - "example": 1 - } - } - }, "handlers.CheckPhoneEmailExistReq": { "type": "object", "properties": { @@ -8866,19 +8835,6 @@ } } }, - "handlers.CreateSupportedOperationReq": { - "type": "object", - "properties": { - "description": { - "type": "string", - "example": "Betting on sport events" - }, - "name": { - "type": "string", - "example": "SportsBook" - } - } - }, "handlers.CreateTransferReq": { "type": "object", "properties": { @@ -8906,10 +8862,6 @@ "type": "string", "example": "John" }, - "first_name": { - "type": "string", - "example": "John" - }, "id": { "type": "integer", "example": 1 @@ -8922,14 +8874,6 @@ "type": "string", "example": "0911111111" }, - "last_name": { - "type": "string", - "example": "Smith" - }, - "phone_number": { - "type": "string", - "example": "0911111111" - }, "regular_balance": { "type": "number", "example": 100 @@ -8942,10 +8886,6 @@ "type": "boolean", "example": true }, - "regular_is_active": { - "type": "boolean", - "example": true - }, "regular_updated_at": { "type": "string" }, @@ -8961,10 +8901,6 @@ "type": "boolean", "example": true }, - "static_is_active": { - "type": "boolean", - "example": true - }, "static_updated_at": { "type": "string" } @@ -9014,50 +8950,6 @@ } } }, - "handlers.CustomersRes": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "email": { - "type": "string" - }, - "email_verified": { - "type": "boolean" - }, - "first_name": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "last_login": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "phone_verified": { - "type": "boolean" - }, - "role": { - "$ref": "#/definitions/domain.Role" - }, - "suspended": { - "type": "boolean" - }, - "suspended_at": { - "type": "string" - }, - "updated_at": { - "type": "string" - } - } - }, "handlers.GetCashierRes": { "type": "object", "properties": { @@ -9273,36 +9165,33 @@ } } }, - "handlers.TopLeague": { + "handlers.SetLeagueAsFeatured": { + "type": "object", + "properties": { + "is_featured": { + "type": "boolean", + "example": true + } + } + }, "handlers.TopLeague": { "type": "object", "properties": { - "events": { - "type": "array", - "items": { - "$ref": "#/definitions/domain.UpcomingEvent" - } "events": { "type": "array", "items": { "$ref": "#/definitions/domain.UpcomingEvent" } }, - "league_cc": { "league_cc": { "type": "string" }, - "league_id": { - "type": "integer" "league_id": { "type": "integer" }, - "league_name": { "league_name": { "type": "string" }, - "league_sport_id": { - "type": "integer" "league_sport_id": { "type": "integer" } @@ -9320,23 +9209,12 @@ "depositor_first_name": { "type": "string" }, - "depositor_id": { - "created_at": { - "type": "string" - }, - "depositor_first_name": { - "type": "string" - }, "depositor_id": { "type": "integer" }, "depositor_last_name": { "type": "string" }, - "depositor_phone_number": { - "depositor_last_name": { - "type": "string" - }, "depositor_phone_number": { "type": "string" }, @@ -9346,9 +9224,6 @@ "message": { "type": "string" }, - "message": { - "type": "string" - }, "payment_method": { "type": "string" }, @@ -9518,7 +9393,6 @@ "game_id": { "type": "string", "example": "1" - "example": "1" }, "mode": { "type": "string", @@ -9660,27 +9534,6 @@ } } }, - "handlers.updateCustomerReq": { - "type": "object", - "properties": { - "company_id": { - "type": "integer", - "example": 1 - }, - "first_name": { - "type": "string", - "example": "John" - }, - "last_name": { - "type": "string", - "example": "Doe" - }, - "suspended": { - "type": "boolean", - "example": false - } - } - }, "handlers.updateManagerReq": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 16431a3..2cd514d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -31,6 +31,20 @@ definitions: user_id: type: string type: object + domain.ArifPayB2CRequest: + properties: + Phonenumber: + type: string + Sessionid: + type: string + type: object + domain.ArifpayVerifyByTransactionIDRequest: + properties: + paymentType: + type: integer + transactionId: + type: string + type: object domain.Bank: properties: acct_length: @@ -67,6 +81,15 @@ definitions: updated_at: type: string type: object + domain.Beneficiary: + properties: + accountNumber: + type: string + amount: + type: number + bank: + type: string + type: object domain.BetOutcome: properties: away_team_name: @@ -153,6 +176,81 @@ definitions: example: 2 type: integer type: object + domain.BranchDetailRes: + properties: + balance: + example: 100.5 + type: number + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + id: + example: 1 + type: integer + is_active: + example: false + type: boolean + is_self_owned: + example: false + type: boolean + is_wallet_active: + example: false + type: boolean + location: + example: Addis Ababa + type: string + manager_name: + example: John Smith + type: string + manager_phone_number: + example: "0911111111" + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object + domain.BranchOperationRes: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string + type: object + domain.BranchRes: + properties: + branch_manager_id: + example: 1 + type: integer + company_id: + example: 1 + type: integer + id: + example: 1 + type: integer + is_active: + example: false + type: boolean + is_self_owned: + example: false + type: boolean + location: + example: Addis Ababa + type: string + name: + example: 4-kilo Branch + type: string + wallet_id: + example: 1 + type: integer + type: object domain.CashoutReq: properties: account_name: @@ -273,6 +371,19 @@ definitions: items: $ref: '#/definitions/domain.CreateBetOutcomeReq' type: array + required: + - amount + - branch_id + - outcomes + type: object + domain.CreateBetWithFastCodeReq: + properties: + amount: + type: number + branch_id: + type: integer + fast_code: + type: string type: object domain.CreateBranchOperationReq: properties: @@ -299,6 +410,71 @@ definitions: maxLength: 100 minLength: 3 type: string + name: + example: 4-kilo Branch + maxLength: 100 + minLength: 3 + type: string + operations: + items: + type: integer + type: array + required: + - branch_manager_id + - location + - name + - operations + type: object + domain.CreateCheckoutSessionRequest: + properties: + beneficiaries: + items: + $ref: '#/definitions/domain.Beneficiary' + type: array + cancelUrl: + type: string + email: + type: string + errorUrl: + type: string + expireDate: + type: string + items: + items: + $ref: '#/definitions/domain.Item' + type: array + lang: + type: string + nonce: + type: string + notifyUrl: + type: string + paymentMethods: + items: + type: string + type: array + phone: + type: string + successUrl: + type: string + type: object + domain.CreateCompanyReq: + properties: + admin_id: + example: 1 + type: integer + name: + example: CompanyName + type: string + type: object + domain.CreateSupportedOperationReq: + properties: + description: + example: Betting on sport events + type: string + name: + example: SportsBook + type: string type: object domain.CreateTicketOutcomeReq: properties: @@ -618,6 +794,42 @@ definitions: meta: $ref: '#/definitions/domain.PaginationMeta' type: object + domain.GetCompanyRes: + properties: + admin_first_name: + example: John + type: string + admin_id: + example: 1 + type: integer + admin_last_name: + example: Doe + type: string + admin_phone_number: + example: "1234567890" + type: string + balance: + example: 1 + type: number + deducted_percentage: + example: 0.1 + type: number + id: + example: 1 + type: integer + is_active: + example: false + type: boolean + is_wallet_active: + example: false + type: boolean + name: + example: CompanyName + type: string + wallet_id: + example: 1 + type: integer + type: object domain.InstResponse: properties: data: @@ -631,6 +843,19 @@ definitions: status: type: string type: object + domain.Item: + properties: + description: + type: string + image: + type: string + name: + type: string + price: + type: number + quantity: + type: integer + type: object domain.League: properties: bet365_id: @@ -1037,6 +1262,17 @@ definitions: reference_number: type: string type: object + domain.ShopDepositRes: + properties: + customer_id: + type: integer + id: + type: integer + shop_transaction_id: + type: integer + wallet_transfer_id: + type: integer + type: object domain.ShopTransactionRes: properties: account_name: @@ -1115,6 +1351,18 @@ definitions: example: true type: boolean type: object + domain.SupportedOperationRes: + properties: + description: + example: Betting on sport events + type: string + id: + example: 1 + type: integer + name: + example: SportsBook + type: string + type: object domain.TicketOutcome: properties: away_team_name: @@ -1279,81 +1527,6 @@ definitions: updated_at: type: string type: object - handlers.BranchDetailRes: - properties: - balance: - example: 100.5 - type: number - branch_manager_id: - example: 1 - type: integer - company_id: - example: 1 - type: integer - id: - example: 1 - type: integer - is_active: - example: false - type: boolean - is_self_owned: - example: false - type: boolean - is_wallet_active: - example: false - type: boolean - location: - example: Addis Ababa - type: string - manager_name: - example: John Smith - type: string - manager_phone_number: - example: "0911111111" - type: string - name: - example: 4-kilo Branch - type: string - wallet_id: - example: 1 - type: integer - type: object - handlers.BranchOperationRes: - properties: - description: - example: Betting on sport events - type: string - name: - example: SportsBook - type: string - type: object - handlers.BranchRes: - properties: - branch_manager_id: - example: 1 - type: integer - company_id: - example: 1 - type: integer - id: - example: 1 - type: integer - is_active: - example: false - type: boolean - is_self_owned: - example: false - type: boolean - location: - example: Addis Ababa - type: string - name: - example: 4-kilo Branch - type: string - wallet_id: - example: 1 - type: integer - type: object handlers.CheckPhoneEmailExistReq: properties: email: @@ -1436,15 +1609,6 @@ definitions: example: "1234567890" type: string type: object - handlers.CreateSupportedOperationReq: - properties: - description: - example: Betting on sport events - type: string - name: - example: SportsBook - type: string - type: object handlers.CreateTransferReq: properties: amount: @@ -1464,9 +1628,6 @@ definitions: first_name: example: John type: string - first_name: - example: John - type: string id: example: 1 type: integer @@ -1476,12 +1637,6 @@ definitions: phone_number: example: "0911111111" type: string - last_name: - example: Smith - type: string - phone_number: - example: "0911111111" - type: string regular_balance: example: 100 type: number @@ -1491,9 +1646,6 @@ definitions: regular_is_active: example: true type: boolean - regular_is_active: - example: true - type: boolean regular_updated_at: type: string static_balance: @@ -1505,9 +1657,6 @@ definitions: static_is_active: example: true type: boolean - static_is_active: - example: true - type: boolean static_updated_at: type: string type: object @@ -1540,35 +1689,6 @@ definitions: updated_at: type: string type: object - handlers.CustomersRes: - properties: - created_at: - type: string - email: - type: string - email_verified: - type: boolean - first_name: - type: string - id: - type: integer - last_login: - type: string - last_name: - type: string - phone_number: - type: string - phone_verified: - type: boolean - role: - $ref: '#/definitions/domain.Role' - suspended: - type: boolean - suspended_at: - type: string - updated_at: - type: string - type: object handlers.GetCashierRes: properties: branch_id: @@ -1715,27 +1835,24 @@ definitions: is_active: type: boolean type: object - handlers.TopLeague: + handlers.SetLeagueAsFeatured: + properties: + is_featured: + example: true + type: boolean + type: object handlers.TopLeague: properties: - events: - items: - $ref: '#/definitions/domain.UpcomingEvent' - type: array - league_cc: events: items: $ref: '#/definitions/domain.UpcomingEvent' type: array league_cc: type: string - league_id: league_id: type: integer - league_name: league_name: type: string - league_sport_id: league_sport_id: type: integer type: object @@ -1753,20 +1870,10 @@ definitions: type: string depositor_phone_number: type: string - depositor_first_name: - type: string - depositor_id: - type: integer - depositor_last_name: - type: string - depositor_phone_number: - type: string id: type: integer message: type: string - message: - type: string payment_method: type: string receiver_wallet_id: @@ -1877,7 +1984,6 @@ definitions: example: USD type: string game_id: - example: "1" example: "1" type: string mode: @@ -1981,21 +2087,6 @@ definitions: example: false type: boolean type: object - handlers.updateCustomerReq: - properties: - company_id: - example: 1 - type: integer - first_name: - example: John - type: string - last_name: - example: Doe - type: string - suspended: - example: false - type: boolean - type: object handlers.updateManagerReq: properties: company_id: @@ -2116,7 +2207,7 @@ paths: summary: Create Admin tags: - admin - /admin-company: + /api/v1/admin-company: get: consumes: - application/json @@ -2127,7 +2218,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/handlers.CompanyRes' + $ref: '#/definitions/domain.GetCompanyRes' "400": description: Bad Request schema: @@ -2139,7 +2230,7 @@ paths: summary: Gets company by id tags: - company - /admin/{id}: + /api/v1/admin/{id}: get: consumes: - application/json @@ -2252,6 +2343,131 @@ paths: summary: Launch an Alea Play virtual game tags: - Alea Virtual Games + /api/v1/arifpay/b2c/transfer: + post: + consumes: + - application/json + description: Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay + parameters: + - description: Transfer mode (Telebirr, CBE, MPESA) + in: query + name: transfer_mode + required: true + type: string + - description: Transfer request payload + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.ArifPayB2CRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.ErrorResponse' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Initiate B2C Transfer + tags: + - Arifpay + /api/v1/arifpay/checkout: + post: + consumes: + - application/json + description: Creates a payment session using Arifpay and returns a redirect + URL. + parameters: + - description: Checkout session request payload + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.CreateCheckoutSessionRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Create Arifpay Checkout Session + tags: + - Arifpay + /api/v1/arifpay/session-id/verify-transaction/{session_id}: + get: + consumes: + - application/json + description: Verifies an Arifpay transaction using a session ID + parameters: + - description: Arifpay Session ID + in: query + name: session_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.ErrorResponse' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Verify Arifpay Transaction by Session ID + tags: + - Arifpay + /api/v1/arifpay/transaction-id/verify-transaction: + post: + consumes: + - application/json + description: Verifies a transaction using transaction ID and payment type + parameters: + - description: Transaction verification payload + in: body + name: request + required: true + schema: + $ref: '#/definitions/domain.ArifpayVerifyByTransactionIDRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/domain.ErrorResponse' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Verify Arifpay Transaction + tags: + - Arifpay /api/v1/auth/login: post: consumes: @@ -2786,49 +3002,9 @@ paths: - branch /api/v1/branchCashier: get: - description: Fetches application logs from MongoDB with pagination, level filtering, - and search - parameters: - - description: Filter logs by level (debug, info, warn, error, dpanic, panic, - fatal) - in: query - name: level - type: string - - description: Search term to match against message or fields - in: query - name: search - type: string - - default: 1 - description: 'Page number for pagination (default: 1)' - in: query - name: page - type: integer - - default: 50 - description: 'Number of items per page (default: 50, max: 100)' - in: query - name: limit - type: integer - produces: + consumes: - application/json - responses: - "200": - description: Paginated list of application logs - schema: - $ref: '#/definitions/domain.LogResponse' - "400": - description: Invalid request parameters - schema: - $ref: '#/definitions/domain.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/domain.ErrorResponse' - summary: Retrieve application logs with filtering and pagination - tags: - - Logs - /api/v1/report-files/download/{filename}: - get: - description: Downloads a generated report CSV file from the server + description: Gets branch for cahier parameters: - description: Branch ID in: path @@ -2855,34 +3031,22 @@ paths: - branch /api/v1/branchWallet: get: - description: Returns a paginated list of generated report CSV files with search - capability - parameters: - - description: Search term to filter filenames - in: query - name: search - type: string - - default: 1 - description: 'Page number (default: 1)' - in: query - name: page - type: integer - - default: 20 - description: 'Items per page (default: 20, max: 100)' - in: query - name: limit - type: integer + consumes: + - application/json + description: Retrieve all branch wallets produces: - application/json responses: "200": - description: Paginated list of CSV report filenames + description: OK schema: - $ref: '#/definitions/domain.PaginatedFileResponse' + items: + $ref: '#/definitions/handlers.WalletRes' + type: array "400": - description: Invalid pagination parameters + description: Bad Request schema: - $ref: '#/definitions/domain.ErrorResponse' + $ref: '#/definitions/response.APIResponse' "500": description: Internal Server Error schema: @@ -3127,68 +3291,7 @@ paths: in: path name: tx_ref required: true - schema: - $ref: '#/definitions/domain.AleaPlayCallback' - produces: - - application/json - responses: - "200": - description: Callback processed successfully - schema: - additionalProperties: - allOf: - - type: string - - properties: - status: - type: string - type: object - type: object - summary: Process Alea Play game callback - tags: - - Alea Virtual Games - /api/v1/win: - post: - consumes: - - application/json - description: Processes win callbacks from either Veli or PopOK providers by - auto-detecting the format - produces: - - application/json - responses: - "200": - description: Win processing result - schema: {} - "400": - description: Invalid request format - schema: - $ref: '#/definitions/domain.ErrorResponse' - "401": - description: Authentication failed - schema: - $ref: '#/definitions/domain.ErrorResponse' - "409": - description: Duplicate transaction - schema: - $ref: '#/definitions/domain.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/domain.ErrorResponse' - summary: Handle win callback (Veli or PopOK) - tags: - - Wins - /auth/login: - post: - consumes: - - application/json - description: Login customer - parameters: - - description: Login customer - in: body - name: login - required: true - schema: - $ref: '#/definitions/handlers.loginCustomerReq' + type: string produces: - application/json responses: @@ -3923,135 +4026,8 @@ paths: $ref: '#/definitions/domain.ErrorResponse' summary: Get reported issues by a user tags: - - branch - /customer: - get: - consumes: - - application/json - description: Get all Customers - parameters: - - description: Page number - in: query - name: page - type: integer - - description: Page size - in: query - name: page_size - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handlers.CustomersRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Get all Customers - tags: - - customer - /customer/{id}: - get: - consumes: - - application/json - description: Get a single customer by id - parameters: - - description: User ID - in: path - name: id - required: true - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handlers.CustomersRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Get customer by id - tags: - - customer - put: - consumes: - - application/json - description: Update Customers - parameters: - - description: Update Customers - in: body - name: Customers - required: true - schema: - $ref: '#/definitions/handlers.updateCustomerReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Update Customers - tags: - - customer - /customerWallet: - get: - consumes: - - application/json - description: Retrieve all customer wallets - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/handlers.CustomerWalletRes' - type: array - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Get all customer wallets - tags: - - wallet - /events: + - Issues + /api/v1/leagues: get: consumes: - application/json @@ -4076,6 +4052,41 @@ paths: summary: Gets all leagues tags: - leagues + /api/v1/leagues/{id}/featured: + put: + consumes: + - application/json + description: Set the league to featured/un-featured + parameters: + - description: League ID + in: path + name: id + required: true + type: integer + - description: League Featured Request + in: body + name: active + required: true + schema: + $ref: '#/definitions/handlers.SetLeagueAsFeatured' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.APIResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Set the league to featured/un-featured + tags: + - leagues /api/v1/leagues/{id}/set-active: put: consumes: @@ -4113,21 +4124,44 @@ paths: - leagues /api/v1/logs: get: - description: Fetches the 100 most recent application logs from MongoDB + description: Fetches application logs from MongoDB with pagination, level filtering, + and search + parameters: + - description: Filter logs by level (debug, info, warn, error, dpanic, panic, + fatal) + in: query + name: level + type: string + - description: Search term to match against message or fields + in: query + name: search + type: string + - default: 1 + description: 'Page number for pagination (default: 1)' + in: query + name: page + type: integer + - default: 50 + description: 'Number of items per page (default: 50, max: 100)' + in: query + name: limit + type: integer produces: - application/json responses: "200": - description: List of application logs + description: Paginated list of application logs schema: - items: - $ref: '#/definitions/domain.LogEntry' - type: array + $ref: '#/definitions/domain.LogResponse' + "400": + description: Invalid request parameters + schema: + $ref: '#/definitions/domain.ErrorResponse' "500": description: Internal server error schema: $ref: '#/definitions/domain.ErrorResponse' - summary: Retrieve latest application logs + summary: Retrieve application logs with filtering and pagination tags: - Logs /api/v1/manager/{id}/branch: @@ -4423,59 +4457,7 @@ paths: summary: Create a operation tags: - 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 - "500": - description: Internal Server Error - 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 - /referral/settings: + /api/v1/referral/settings: get: consumes: - application/json @@ -4597,22 +4579,34 @@ paths: - Reports /api/v1/report-files/list: get: - description: Returns a list of all generated report CSV files available for - download + description: Returns a paginated list of generated report CSV files with search + capability + parameters: + - description: Search term to filter filenames + in: query + name: search + type: string + - default: 1 + description: 'Page number (default: 1)' + in: query + name: page + type: integer + - default: 20 + description: 'Items per page (default: 20, max: 100)' + in: query + name: limit + type: integer produces: - application/json responses: "200": - description: List of CSV report filenames + description: Paginated list of CSV report filenames schema: - allOf: - - $ref: '#/definitions/domain.Response' - - properties: - data: - items: - type: string - type: array - type: object + $ref: '#/definitions/domain.PaginatedFileResponse' + "400": + description: Invalid pagination parameters + schema: + $ref: '#/definitions/domain.ErrorResponse' "500": description: Failed to read report directory schema: @@ -4765,7 +4759,7 @@ paths: summary: Gets all companies tags: - company - /shop/bet: + /api/v1/shop/bet: post: consumes: - application/json @@ -4795,274 +4789,7 @@ paths: summary: Create bet at branch tags: - transaction - /shop/bet/{id}: - get: - consumes: - - application/json - description: Cashout bet at branch - parameters: - - description: cashout bet - in: body - name: createBet - required: true - schema: - $ref: '#/definitions/domain.CashoutReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Cashout bet at branch - tags: - - transaction - /shop/bet/{id}/cashout: - post: - consumes: - - application/json - description: Cashout bet at branch - parameters: - - description: cashout bet - in: body - name: cashoutBet - required: true - schema: - $ref: '#/definitions/domain.CashoutReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Cashout bet at branch - tags: - - transaction - /shop/cashout: - post: - consumes: - - application/json - description: Cashout bet by cashoutID - parameters: - - description: cashout bet - in: body - name: cashoutBet - required: true - schema: - $ref: '#/definitions/domain.CashoutReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Cashout bet by cashoutID - tags: - - transaction - /shop/cashout/{id}: - get: - consumes: - - application/json - description: Cashout bet at branch - parameters: - - description: cashout bet - in: body - name: createBet - required: true - schema: - $ref: '#/definitions/domain.CashoutReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Cashout bet at branch - tags: - - transaction - /shop/deposit: - post: - consumes: - - application/json - description: Transfers money from branch wallet to customer wallet - parameters: - - description: ShopDepositReq - in: body - name: transferToWallet - required: true - schema: - $ref: '#/definitions/domain.ShopDepositReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handlers.TransferWalletRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Shop deposit into customer wallet - tags: - - transaction - /shop/transaction: - get: - consumes: - - application/json - description: Gets all the transactions - produces: - - application/json - responses: - "200": - description: OK - schema: - items: - $ref: '#/definitions/domain.ShopTransactionRes' - type: array - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Gets all transactions - tags: - - transaction - /shop/transaction/{id}: - get: - consumes: - - application/json - description: Gets a single transaction by id - parameters: - - description: Transaction ID - in: path - name: id - required: true - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Gets transaction by id - tags: - - transaction - put: - consumes: - - application/json - description: Updates the verified status of a transaction - parameters: - - description: Transaction ID - in: path - name: id - required: true - type: integer - - description: Updates Transaction Verification - in: body - name: updateVerified - required: true - schema: - $ref: '#/definitions/handlers.UpdateTransactionVerifiedReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/response.APIResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Updates the verified field of a transaction - tags: - - transaction - /shop/transaction/{id}/bet: - get: - consumes: - - application/json - description: Gets a single shop bet by transaction id - parameters: - - description: Transaction ID - in: path - name: id - required: true - type: integer - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.ShopTransactionRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Gets shop bet by transaction id - tags: - - transaction - /sport/bet: + /api/v1/shop/bet/{id}: get: consumes: - application/json @@ -5354,37 +5081,6 @@ paths: summary: Gets all bets tags: - bet - /sport/bet/fastcode: - post: - consumes: - - application/json - description: Creates a bet with fast code - parameters: - - description: Creates bet - in: body - name: createBetWithFastCode - required: true - schema: - $ref: '#/definitions/domain.CreateBetReq' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/domain.BetRes' - "400": - description: Bad Request - schema: - $ref: '#/definitions/response.APIResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/response.APIResponse' - summary: Create a bet with fast code - tags: - - bet - /sport/random/bet: post: consumes: - application/json @@ -5516,7 +5212,7 @@ paths: name: createBetWithFastCode required: true schema: - $ref: '#/definitions/domain.CreateBetReq' + $ref: '#/definitions/domain.CreateBetWithFastCodeReq' produces: - application/json responses: @@ -5577,7 +5273,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/handlers.TopLeague' + $ref: '#/definitions/domain.BranchDetailRes' type: array "400": description: Bad Request @@ -5589,8 +5285,141 @@ paths: $ref: '#/definitions/response.APIResponse' summary: Gets all supported operations tags: + - branch + post: + consumes: + - application/json + description: Creates a supported operation + parameters: + - description: Creates supported operation + in: body + name: createSupportedOperation + required: true + schema: + $ref: '#/definitions/domain.CreateSupportedOperationReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.SupportedOperationRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a supported operation + tags: + - branch + /api/v1/ticket: + get: + consumes: + - application/json + description: Retrieve all tickets + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.TicketRes' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get all tickets + tags: + - ticket + post: + consumes: + - application/json + description: Creates a temporary ticket + parameters: + - description: Creates ticket + in: body + name: createTicket + required: true + schema: + $ref: '#/definitions/domain.CreateTicketReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.CreateTicketRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Create a temporary ticket + tags: + - ticket + /api/v1/ticket/{id}: + get: + consumes: + - application/json + description: Retrieve ticket details by ticket ID + parameters: + - description: Ticket ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.TicketRes' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Get ticket by ID + tags: + - ticket + /api/v1/top-leagues: + get: + consumes: + - application/json + description: Retrieve all top leagues + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/handlers.TopLeague' + type: array + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + summary: Retrieve all top leagues + tags: - prematch - /transfer/refill/:id: + /api/v1/transfer/refill/:id: post: consumes: - application/json @@ -6405,6 +6234,37 @@ paths: summary: Process Alea Play game callback tags: - Alea Virtual Games + /api/v1/win: + post: + consumes: + - application/json + description: Processes win callbacks from either Veli or PopOK providers by + auto-detecting the format + produces: + - application/json + responses: + "200": + description: Win processing result + schema: {} + "400": + description: Invalid request format + schema: + $ref: '#/definitions/domain.ErrorResponse' + "401": + description: Authentication failed + schema: + $ref: '#/definitions/domain.ErrorResponse' + "409": + description: Duplicate transaction + schema: + $ref: '#/definitions/domain.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/domain.ErrorResponse' + summary: Handle win callback (Veli or PopOK) + tags: + - Wins /popok/games: get: consumes: @@ -6425,8 +6285,8 @@ paths: items: $ref: '#/definitions/domain.PopOKGame' type: array - "502": - description: Bad Gateway + "500": + description: Internal Server Error schema: $ref: '#/definitions/domain.ErrorResponse' summary: Get PopOK Games List diff --git a/go.mod b/go.mod index 60ae591..f347417 100644 --- a/go.mod +++ b/go.mod @@ -16,15 +16,15 @@ require ( // 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 + github.com/valyala/fasthttp v1.63.0 + golang.org/x/crypto v0.39.0 ) 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/andybalholm/brotli v1.2.0 // 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 @@ -53,11 +53,11 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect go.mongodb.org/mongo-driver v1.17.3 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 - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/tools v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -77,10 +77,8 @@ require ( go.uber.org/multierr v1.10.0 // indirect ) -require ( - // github.com/go-resty/resty/v2 v2.16.5 - github.com/twilio/twilio-go v1.26.3 -) +// github.com/go-resty/resty/v2 v2.16.5 +require github.com/twilio/twilio-go v1.26.3 require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -90,3 +88,10 @@ require ( github.com/redis/go-redis/v9 v9.10.0 // direct go.uber.org/atomic v1.9.0 // indirect ) + +require ( + github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/segmentio/kafka-go v0.4.48 // direct +) + +require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct diff --git a/go.sum b/go.sum index 514814e..07c697f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f h1:UOp9At84RG8OT2Nw2TQidYWaoW1rAfTqChOJLdhYcm8= +github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f/go.mod h1:N2NQ6ad3i+oLQU+MlPci2f7mx6Mtg+wUcvzCv77KCl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= @@ -7,9 +9,13 @@ github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaW github.com/amanuelabay/afrosms-go v1.0.6 h1:/B9upckMqzr5/de7dbXPqIfmJm4utbQq0QUQePxmXtk= github.com/amanuelabay/afrosms-go v1.0.6/go.mod h1:5mzzZtWSCDdvQsA0OyYf5CtbdGpl9lXyrcpl8/DyBj0= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= @@ -54,8 +60,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= -github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= @@ -87,6 +91,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= @@ -124,6 +129,8 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -143,6 +150,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/kafka-go v0.4.48 h1:9jyu9CWK4W5W+SroCe8EffbrRZVqAOkuaLd/ApID4Vs= +github.com/segmentio/kafka-go v0.4.48/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/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= @@ -174,8 +183,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= -github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= +github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns= +github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -206,12 +215,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -222,13 +233,17 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -242,28 +257,36 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/config/config.go b/internal/config/config.go index 04f1fbe..e8ec70a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,6 +58,14 @@ type VeliConfig struct { Enabled bool `mapstructure:"Enabled"` } +type ARIFPAYConfig struct { + APIKey string `mapstructure:"VELI_API_KEY"` + CancelUrl string `mapstructure:"VELI_BASE_URL"` + ErrorUrl string `mapstructure:"VELI_SECRET_KEY"` + NotifyUrl string `mapstructure:"VELI_OPERATOR_ID"` + SuccessUrl string `mapstructure:"VELI_BRAND_ID"` +} + type Config struct { FIXER_API_KEY string FIXER_BASE_URL string @@ -87,6 +95,7 @@ type Config struct { PopOK domain.PopOKConfig AleaPlay AleaPlayConfig `mapstructure:"alea_play"` VeliGames VeliConfig `mapstructure:"veli_games"` + ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"` ResendApiKey string ResendSenderEmail string TwilioAccountSid string @@ -189,6 +198,12 @@ func (c *Config) loadEnv() error { c.CHAPA_CALLBACK_URL = os.Getenv("CHAPA_CALLBACK_URL") c.CHAPA_RETURN_URL = os.Getenv("CHAPA_RETURN_URL") + c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY") + c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL") + c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL") + c.ARIFPAY.NotifyUrl = os.Getenv("ARIFPAY_NOTIFY_URL") + c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL") + //Alea Play aleaEnabled := os.Getenv("ALEA_ENABLED") if aleaEnabled == "" { diff --git a/internal/domain/arifpay.go b/internal/domain/arifpay.go new file mode 100644 index 0000000..372a7b8 --- /dev/null +++ b/internal/domain/arifpay.go @@ -0,0 +1,53 @@ +package domain + +import "time" + +type Item struct { + Name string `json:"name"` + Quantity int `json:"quantity"` + Price float64 `json:"price"` + Description string `json:"description"` + Image string `json:"image"` +} + +type Beneficiary struct { + AccountNumber string `json:"accountNumber"` + Bank string `json:"bank"` + Amount float64 `json:"amount"` +} + +type CreateCheckoutSessionRequest struct { + CancelUrl string `json:"cancelUrl"` + Phone string `json:"phone"` + Email string `json:"email"` + Nonce string `json:"nonce"` + ErrorUrl string `json:"errorUrl"` + NotifyUrl string `json:"notifyUrl"` + SuccessUrl string `json:"successUrl"` + PaymentMethods []string `json:"paymentMethods"` + ExpireDate time.Time `json:"expireDate"` + Items []Item `json:"items"` + Beneficiaries []Beneficiary `json:"beneficiaries"` + Lang string `json:"lang"` +} + +type ArifPayCheckoutResponse struct { + Error bool `json:"error"` + Msg string `json:"msg"` + Data struct { + SessionID string `json:"sessionId"` + PaymentURL string `json:"paymentUrl"` + CancelURL string `json:"cancelUrl"` + TotalAmount float64 `json:"totalAmount"` + } `json:"data"` +} + +type ArifPayB2CRequest struct { + SessionID string `json:"Sessionid"` + PhoneNumber string `json:"Phonenumber"` +} + +type ArifpayVerifyByTransactionIDRequest struct { + TransactionID string `json:"transactionId"` + PaymentType int `json:"paymentType"` +} diff --git a/internal/event/wallet_event.go b/internal/event/wallet_event.go new file mode 100644 index 0000000..129104e --- /dev/null +++ b/internal/event/wallet_event.go @@ -0,0 +1,18 @@ +package event + +import "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + +type WalletEventType string + +const ( + WalletBalanceUpdated WalletEventType = "wallet.balance.updated" + WalletLowAlert WalletEventType = "wallet.alert.low_balance" +) + +type WalletEvent struct { + EventType WalletEventType `json:"event_type"` + WalletID int64 `json:"wallet_id"` + UserID int64 `json:"user_id"` + Balance domain.Currency `json:"balance"` + Trigger string `json:"trigger"` // e.g. "AddToWallet", "DeductFromWallet" +} diff --git a/internal/services/arifpay/service.go b/internal/services/arifpay/service.go new file mode 100644 index 0000000..8573bc5 --- /dev/null +++ b/internal/services/arifpay/service.go @@ -0,0 +1,192 @@ +package arifpay + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/AnaniyaBelew/ArifpayGoPlugin" + "github.com/SamuelTariku/FortuneBet-Backend/internal/config" + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" + "github.com/google/uuid" +) + +type ArifpayService struct { + cfg *config.Config + transferStore wallet.TransferStore + httpClient *http.Client +} + +func NewArifpayService(cfg *config.Config, transferStore wallet.TransferStore, httpClient *http.Client) *ArifpayService { + return &ArifpayService{ + cfg: cfg, + transferStore: transferStore, + httpClient: httpClient, + } +} + +func (s *ArifpayService) CreateCheckoutSession(req domain.CreateCheckoutSessionRequest) (string, error) { + // Create SDK-compatible payload + paymentPayload := ArifpayGoPlugin.PaymentRequest{ + CancelUrl: s.cfg.ARIFPAY.CancelUrl, + Phone: req.Phone, + Email: req.Email, + Nonce: req.Nonce, + ErrorUrl: s.cfg.ARIFPAY.ErrorUrl, + NotifyUrl: s.cfg.ARIFPAY.NotifyUrl, + SuccessUrl: s.cfg.ARIFPAY.SuccessUrl, + PaymentMethods: req.PaymentMethods, + Lang: req.Lang, + } + + // Convert items + for _, item := range req.Items { + paymentPayload.Items = append(paymentPayload.Items, domain.Item{ + Name: item.Name, + Quantity: item.Quantity, + Price: item.Price, + Description: item.Description, + Image: item.Image, + }) + } + + // Convert beneficiaries + for _, b := range req.Beneficiaries { + paymentPayload.Beneficiaries = append(paymentPayload.Beneficiaries, domain.Beneficiary{ + AccountNumber: b.AccountNumber, + Bank: b.Bank, + Amount: b.Amount, + }) + } + + // Instantiate payment client + expireDate := time.Now().AddDate(2, 0, 0) // 2 months from now + paymentClient := ArifpayGoPlugin.NewPayment(s.cfg.ARIFPAY.APIKey, expireDate) + + // Create checkout session + response, err := paymentClient.MakePayment(paymentPayload) + if err != nil { + return "", err + } + + transfer := domain.CreateTransfer{ + Amount: domain.Currency(req.Beneficiaries[0].Amount), + Verified: false, + Type: domain.DEPOSIT, + ReferenceNumber: uuid.NewString(), + Status: string(domain.PaymentStatusPending), + } + + if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil { + return "", err + } + + return response, nil +} + +func (s *ArifpayService) B2CTransfer(ctx context.Context, req domain.ArifPayB2CRequest, endpoint string) (*map[string]interface{}, error) { + // endpoint := c.baseURL + "/api/Telebirr/b2c/transfer" + + payloadBytes, err := json.Marshal(req) + if err != nil { + return nil, fmt.Errorf("failed to marshal request payload: %w", err) + } + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(payloadBytes)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + httpReq.Header.Set("Content-Type", "application/json") + httpReq.Header.Set("Accept", "application/json") + httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) + + resp, err := s.httpClient.Do(httpReq) + if err != nil { + return nil, fmt.Errorf("request to Telebirr B2C failed: %w", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Telebirr API returned status %d: %s", resp.StatusCode, string(body)) + } + + var response map[string]interface{} + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("failed to parse response body: %w", err) + } + + return &response, nil +} + +func (s *ArifpayService) VerifyByTransactionID(transactionID string, paymentType int) ([]byte, error) { + url := "https://gateway.arifpay.org/api/checkout/getSessionByTransactionId" + + var payload domain.ArifpayVerifyByTransactionIDRequest + + bodyBytes, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %w", err) + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) + + resp, err := s.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody)) + } + + return respBody, nil +} + +func (s *ArifpayService) VerifyBySessionID(sessionID string) ([]byte, error) { + url := "https://gateway.arifpay.org/api/ms/transaction/status/" + sessionID + + // Create GET request without body + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) + + resp, err := s.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody)) + } + + return respBody, nil +} diff --git a/internal/services/kafka/consumer.go b/internal/services/kafka/consumer.go new file mode 100644 index 0000000..c9775f8 --- /dev/null +++ b/internal/services/kafka/consumer.go @@ -0,0 +1,80 @@ +package kafka + +import ( + "context" + "encoding/json" + "log" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/event" + "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws" + "github.com/segmentio/kafka-go" +) + +type WalletConsumer struct { + reader *kafka.Reader + hub *ws.NotificationHub + topic string + groupID string + brokers []string +} + +func NewWalletConsumer(brokers []string, topic, groupID string, hub *ws.NotificationHub) *WalletConsumer { + return &WalletConsumer{ + brokers: brokers, + topic: topic, + groupID: groupID, + hub: hub, + reader: kafka.NewReader(kafka.ReaderConfig{ + Brokers: brokers, + GroupID: groupID, + Topic: topic, + }), + } +} + +func (c *WalletConsumer) Start(ctx context.Context) { + go func() { + for { + m, err := c.reader.ReadMessage(ctx) + if err != nil { + log.Printf("Error reading wallet Kafka message: %v", err) + continue + } + + var evt event.WalletEvent + if err := json.Unmarshal(m.Value, &evt); err != nil { + log.Printf("Failed to unmarshal wallet event: %v", err) + continue + } + + payload := map[string]interface{}{ + "type": evt.EventType, + "wallet_id": evt.WalletID, + "user_id": evt.UserID, + "balance": evt.Balance, + "trigger": evt.Trigger, + "recipient_id": evt.UserID, + } + + // Broadcast to appropriate WebSocket clients + c.hub.Broadcast <- payload + } + }() +} + +// func (c *WalletConsumer) Shutdown() error { +// return c.reader.Close() +// } + +// func (h *ws.NotificationHub) Shutdown() { +// close(h.Register) +// close(h.Unregister) +// close(h.Broadcast) + +// h.mu.Lock() +// defer h.mu.Unlock() +// for client := range h.Clients { +// client.Conn.Close() +// delete(h.Clients, client) +// } +// } diff --git a/internal/services/kafka/producer.go b/internal/services/kafka/producer.go new file mode 100644 index 0000000..1c3278d --- /dev/null +++ b/internal/services/kafka/producer.go @@ -0,0 +1,36 @@ +package kafka + +import ( + "context" + "encoding/json" + "time" + + "github.com/segmentio/kafka-go" +) + +type Producer struct { + writer *kafka.Writer +} + +func NewProducer(brokers []string, topic string) *Producer { + return &Producer{ + writer: &kafka.Writer{ + Addr: kafka.TCP(brokers...), + Topic: topic, + Balancer: &kafka.LeastBytes{}, + }, + } +} + +func (p *Producer) Publish(ctx context.Context, key string, event any) error { + msgBytes, err := json.Marshal(event) + if err != nil { + return err + } + + return p.writer.WriteMessages(ctx, kafka.Message{ + Key: []byte(key), + Value: msgBytes, + Time: time.Now(), + }) +} diff --git a/internal/services/notfication/service.go b/internal/services/notfication/service.go index f3ca2d6..fc44b71 100644 --- a/internal/services/notfication/service.go +++ b/internal/services/notfication/service.go @@ -394,7 +394,7 @@ func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error) return metric, nil } -func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) { +func (s *Service) UpdateLiveMetricForWallet(ctx context.Context, wallet domain.Wallet) { var ( payload domain.LiveWalletMetrics event map[string]interface{} diff --git a/internal/services/report/service.go b/internal/services/report/service.go index 6c530c1..efc5029 100644 --- a/internal/services/report/service.go +++ b/internal/services/report/service.go @@ -779,164 +779,6 @@ func getTimeRange(period string) (time.Time, time.Time) { } } -// func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) { -// // Get company bet activity -// companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get company bet activity", "error", err) -// return nil, err -// } - -// // Get company details -// companyDetails, err := s.branchStore.GetCompanyDetails(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get company details", "error", err) -// return nil, err -// } - -// // Get company branches -// companyBranches, err := s.branchStore.GetCompanyBranchCounts(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get company branch counts", "error", err) -// return nil, err -// } - -// // Combine data into performance report -// var performances []domain.CompanyPerformance -// for _, bet := range companyBets { -// performance := domain.CompanyPerformance{ -// CompanyID: bet.CompanyID, -// TotalBets: bet.TotalBets, -// TotalStakes: bet.TotalStakes, -// TotalWins: bet.TotalWins, -// TotalPayouts: bet.TotalPayouts, -// Profit: bet.TotalStakes - bet.TotalPayouts, -// } - -// // Add company details -// if details, ok := companyDetails[bet.CompanyID]; ok { -// performance.CompanyName = details.Name -// performance.ContactEmail = details.ContactEmail -// } - -// // Add branch counts -// if branches, ok := companyBranches[bet.CompanyID]; ok { -// performance.TotalBranches = branches.Total -// performance.ActiveBranches = branches.Active -// } - -// // Calculate metrics -// if bet.TotalBets > 0 { -// performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 -// performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) -// } - -// performances = append(performances, performance) -// } - -// // Sort by profit (descending) -// sort.Slice(performances, func(i, j int) bool { -// return performances[i].Profit > performances[j].Profit -// }) - -// return performances, nil -// } - -// GetCashierPerformance returns cashier performance report -// func (s *Service) GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error) { -// // Get cashier bet activity -// cashierBets, err := s.betStore.GetCashierBetActivity(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get cashier bet activity", "error", err) -// return nil, err -// } - -// // Get cashier details -// cashierDetails, err := s.userStore.GetCashierDetails(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get cashier details", "error", err) -// return nil, err -// } - -// // Get cashier transactions -// cashierTransactions, err := s.transactionStore.GetCashierTransactionTotals(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get cashier transactions", "error", err) -// return nil, err -// } - -// // Combine data into performance report -// var performances []domain.CashierPerformance -// for _, bet := range cashierBets { -// performance := domain.CashierPerformance{ -// CashierID: bet.CashierID, -// TotalBets: bet.TotalBets, -// TotalStakes: bet.TotalStakes, -// TotalWins: bet.TotalWins, -// TotalPayouts: bet.TotalPayouts, -// Profit: bet.TotalStakes - bet.TotalPayouts, -// } - -// // Add cashier details -// if details, ok := cashierDetails[bet.CashierID]; ok { -// performance.CashierName = details.Name -// performance.BranchID = details.BranchID -// performance.BranchName = details.BranchName -// } - -// // Add transactions -// if transactions, ok := cashierTransactions[bet.CashierID]; ok { -// performance.Deposits = transactions.Deposits -// performance.Withdrawals = transactions.Withdrawals -// } - -// // Calculate metrics -// if bet.TotalBets > 0 { -// performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100 -// performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets) -// } - -// performances = append(performances, performance) -// } - -// // Sort by total stakes (descending) -// sort.Slice(performances, func(i, j int) bool { -// return performances[i].TotalStakes > performances[j].TotalStakes -// }) - -// return performances, nil -// } - -// GetNotificationReport returns notification statistics report -// func (s *Service) GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error) { -// // Get notification counts by type -// countsByType, err := s.notificationStore.GetNotificationCountsByType(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get notification counts by type", "error", err) -// return domain.NotificationReport{}, err -// } - -// // Get notification delivery stats -// deliveryStats, err := s.notificationStore.GetNotificationDeliveryStats(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get notification delivery stats", "error", err) -// return domain.NotificationReport{}, err -// } - -// // Get most active notification recipients -// activeRecipients, err := s.notificationStore.GetMostActiveNotificationRecipients(ctx, filter) -// if err != nil { -// s.logger.Error("failed to get active notification recipients", "error", err) -// return domain.NotificationReport{}, err -// } - -// return domain.NotificationReport{ -// CountsByType: countsByType, -// DeliveryStats: deliveryStats, -// ActiveRecipients: activeRecipients, -// }, nil -// } - // Helper functions func validateTimeRange(filter domain.ReportFilter) error { if filter.StartTime.Valid && filter.EndTime.Valid { diff --git a/internal/services/transaction/service.go b/internal/services/transaction/service.go index 979553f..bca21f6 100644 --- a/internal/services/transaction/service.go +++ b/internal/services/transaction/service.go @@ -141,7 +141,8 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom // var branchID int64 // var companyID int64 - if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { + switch role { + case domain.RoleAdmin, domain.RoleBranchManager, domain.RoleSuperAdmin: if branchID == nil { // h.logger.Error("CashoutReq Branch ID is required for this user role") return nil, nil, ErrBranchRequiredForRole @@ -166,14 +167,14 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom } return &branch.ID, &branch.CompanyID, nil - } else if role == domain.RoleCashier { + case domain.RoleCashier: branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) if err != nil { // h.logger.Error("CashoutReq failed, branch id invalid") return nil, nil, ErrInvalidBranchID } return &branch.ID, &branch.CompanyID, nil - } else { + default: return nil, nil, ErrCustomerRoleNotAuthorized } } diff --git a/internal/services/transaction/shop_deposit.go b/internal/services/transaction/shop_deposit.go index bc155df..cc08874 100644 --- a/internal/services/transaction/shop_deposit.go +++ b/internal/services/transaction/shop_deposit.go @@ -49,9 +49,9 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma return domain.ShopDeposit{}, err } - if err != nil { - return domain.ShopDeposit{}, err - } + // if err != nil { + // return domain.ShopDeposit{}, err + // } newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{ Amount: domain.Currency(req.Amount), diff --git a/internal/services/wallet/service.go b/internal/services/wallet/service.go index 773918d..57b2cb9 100644 --- a/internal/services/wallet/service.go +++ b/internal/services/wallet/service.go @@ -3,6 +3,7 @@ package wallet import ( "log/slog" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" ) @@ -13,10 +14,10 @@ type Service struct { notificationStore notificationservice.NotificationStore notificationSvc *notificationservice.Service logger *slog.Logger - // userStore user.UserStore + kafkaProducer *kafka.Producer } -func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger) *Service { +func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger, kafkaProducer *kafka.Producer) *Service { return &Service{ walletStore: walletStore, transferStore: transferStore, @@ -24,7 +25,6 @@ func NewService(walletStore WalletStore, transferStore TransferStore, notificati notificationStore: notificationStore, notificationSvc: notificationSvc, logger: logger, - // userStore: userStore, - // userStore users + kafkaProducer: kafkaProducer, } } diff --git a/internal/services/wallet/wallet.go b/internal/services/wallet/wallet.go index 9973a69..b8d1b09 100644 --- a/internal/services/wallet/wallet.go +++ b/internal/services/wallet/wallet.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/SamuelTariku/FortuneBet-Backend/internal/event" ) var ( @@ -76,12 +77,21 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu return err } - wallet, err := s.GetWalletByID(ctx, id) + wallet, err := s.walletStore.GetWalletByID(ctx, id) if err != nil { return err } - go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) + go func() { + s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{ + EventType: event.WalletBalanceUpdated, + WalletID: wallet.ID, + UserID: wallet.UserID, + Balance: balance, + Trigger: "UpdateBalance", + }) + }() + return nil } @@ -97,7 +107,15 @@ func (s *Service) AddToWallet( return domain.Transfer{}, err } - // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) + go func() { + s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{ + EventType: event.WalletBalanceUpdated, + WalletID: wallet.ID, + UserID: wallet.UserID, + Balance: wallet.Balance + amount, + Trigger: "AddToWallet", + }) + }() // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ @@ -137,7 +155,15 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain. return domain.Transfer{}, nil } - // go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) + go func() { + s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{ + EventType: event.WalletBalanceUpdated, + WalletID: wallet.ID, + UserID: wallet.UserID, + Balance: wallet.Balance - amount, + Trigger: "DeductFromWallet", + }) + }() // Log the transfer here for reference newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ diff --git a/internal/web_server/app.go b/internal/web_server/app.go index a9dd1b2..201daeb 100644 --- a/internal/web_server/app.go +++ b/internal/web_server/app.go @@ -5,6 +5,7 @@ import ( "log/slog" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" @@ -40,6 +41,7 @@ import ( ) type App struct { + arifpaySvc *arifpay.ArifpayService issueReportingSvc *issuereporting.Service instSvc *institutions.Service currSvc *currency.Service @@ -76,6 +78,7 @@ type App struct { } func NewApp( + arifpaySvc *arifpay.ArifpayService, issueReportingSvc *issuereporting.Service, instSvc *institutions.Service, currSvc *currency.Service, @@ -122,6 +125,7 @@ func NewApp( })) s := &App{ + arifpaySvc: arifpaySvc, issueReportingSvc: issueReportingSvc, instSvc: instSvc, currSvc: currSvc, diff --git a/internal/web_server/cron.go b/internal/web_server/cron.go index 67df201..56565af 100644 --- a/internal/web_server/cron.go +++ b/internal/web_server/cron.go @@ -114,10 +114,10 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service) { spec string period string }{ - { - spec: "*/300 * * * * *", // Every 5 minutes (300 seconds) - period: "5min", - }, + // { + // spec: "*/300 * * * * *", // Every 5 minutes (300 seconds) + // period: "5min", + // }, { spec: "0 0 0 * * *", // Daily at midnight period: "daily", diff --git a/internal/web_server/handlers/arifpay.go b/internal/web_server/handlers/arifpay.go new file mode 100644 index 0000000..ad8b9ce --- /dev/null +++ b/internal/web_server/handlers/arifpay.go @@ -0,0 +1,179 @@ +package handlers + +import ( + "encoding/json" + + "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" + "github.com/gofiber/fiber/v2" +) + +// CreateCheckoutSessionHandler initializes a checkout session with Arifpay. +// +// @Summary Create Arifpay Checkout Session +// @Description Creates a payment session using Arifpay and returns a redirect URL. +// @Tags Arifpay +// @Accept json +// @Produce json +// @Param request body domain.CreateCheckoutSessionRequest true "Checkout session request payload" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 500 {object} domain.ErrorResponse +// @Router /api/v1/arifpay/checkout [post] +func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error { + var req domain.CreateCheckoutSessionRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to process your request", + }) + } + + paymentURL, err := h.arifpaySvc.CreateCheckoutSession(req) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to process your request", + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Checkout session created successfully", + Data: paymentURL, + Success: true, + StatusCode: fiber.StatusOK, + }) +} + +// B2CTransferHandler handles Arifpay B2C transfers based on the transfer_mode. +// +// @Summary Initiate B2C Transfer +// @Description Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay +// @Tags Arifpay +// @Accept json +// @Produce json +// @Param transfer_mode query string true "Transfer mode (Telebirr, CBE, MPESA)" +// @Param request body domain.ArifPayB2CRequest true "Transfer request payload" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 502 {object} domain.ErrorResponse +// @Router /api/v1/arifpay/b2c/transfer [post] +func (h *Handler) B2CTransferHandler(c *fiber.Ctx) error { + transferMode := c.Query("transfer_mode") + + var endpoint string + switch transferMode { + case "Telebirr": + endpoint = "https://telebirr-b2c.arifpay.net/api/Telebirr/b2c/transfer" + case "CBE": + endpoint = "https://cbe-b2c.arifpay.net/api/Cbebirr/b2c/transfer" + case "MPESA": + endpoint = "https://mpesa-b2c.arifpay.net/api/Mpesa/b2c/transfer" + default: + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: "invalid transfer_mode. Allowed values: Telebirr, CBE, MPESA", + Message: "Failed to process your request", + }) + } + + var req domain.ArifPayB2CRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to process your request", + }) + } + + resp, err := h.arifpaySvc.B2CTransfer(c.Context(), req, endpoint) + if err != nil { + return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to process your request", + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Transfer initiated successfully", + Data: resp, + Success: true, + StatusCode: fiber.StatusOK, + }) +} + +// ArifpayVerifyByTransactionIDHandler godoc +// @Summary Verify Arifpay Transaction +// @Description Verifies a transaction using transaction ID and payment type +// @Tags Arifpay +// @Accept json +// @Produce json +// @Param request body domain.ArifpayVerifyByTransactionIDRequest true "Transaction verification payload" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 502 {object} domain.ErrorResponse +// @Router /api/v1/arifpay/transaction-id/verify-transaction [post] +func (h *Handler) ArifpayVerifyByTransactionIDHandler(c *fiber.Ctx) error { + var req domain.ArifpayVerifyByTransactionIDRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to parse request body", + }) + } + + if req.TransactionID == "" || req.PaymentType == 0 { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: "missing transactionId or paymentType", + Message: "transactionId and paymentType are required fields", + }) + } + + resp, err := h.arifpaySvc.VerifyByTransactionID(req.TransactionID, req.PaymentType) + if err != nil { + return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to verify transaction", + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Transaction verified successfully", + Data: json.RawMessage(resp), + Success: true, + StatusCode: fiber.StatusOK, + }) +} + +// ArifpayVerifyBySessionIDHandler godoc +// @Summary Verify Arifpay Transaction by Session ID +// @Description Verifies an Arifpay transaction using a session ID +// @Tags Arifpay +// @Accept json +// @Produce json +// @Param session_id query string true "Arifpay Session ID" +// @Success 200 {object} domain.Response +// @Failure 400 {object} domain.ErrorResponse +// @Failure 502 {object} domain.ErrorResponse +// @Router /api/v1/arifpay/session-id/verify-transaction/{session_id} [get] +func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error { + sessionID := c.Query("session_id") + if sessionID == "" { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Error: "missing session_id", + Message: "session_id query parameter is required", + }) + } + + resp, err := h.arifpaySvc.VerifyBySessionID(sessionID) + if err != nil { + return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ + Error: err.Error(), + Message: "Failed to verify session", + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Session verified successfully", + Data: json.RawMessage(resp), + Success: true, + StatusCode: fiber.StatusOK, + }) +} diff --git a/internal/web_server/handlers/handlers.go b/internal/web_server/handlers/handlers.go index 6fc1e6b..b964817 100644 --- a/internal/web_server/handlers/handlers.go +++ b/internal/web_server/handlers/handlers.go @@ -4,6 +4,7 @@ import ( "log/slog" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" + "github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" @@ -35,6 +36,7 @@ import ( ) type Handler struct { + arifpaySvc *arifpay.ArifpayService issueReportingSvc *issuereporting.Service instSvc *institutions.Service currSvc *currency.Service @@ -58,16 +60,17 @@ type Handler struct { virtualGameSvc virtualgameservice.VirtualGameService aleaVirtualGameSvc alea.AleaVirtualGameService veliVirtualGameSvc veli.VeliVirtualGameService - recommendationSvc recommendation.RecommendationService - authSvc *authentication.Service - resultSvc result.Service - jwtConfig jwtutil.JwtConfig - validator *customvalidator.CustomValidator - Cfg *config.Config - mongoLoggerSvc *zap.Logger + recommendationSvc recommendation.RecommendationService + authSvc *authentication.Service + resultSvc result.Service + jwtConfig jwtutil.JwtConfig + validator *customvalidator.CustomValidator + Cfg *config.Config + mongoLoggerSvc *zap.Logger } func New( + arifpaySvc *arifpay.ArifpayService, issueReportingSvc *issuereporting.Service, instSvc *institutions.Service, currSvc *currency.Service, @@ -100,6 +103,7 @@ func New( mongoLoggerSvc *zap.Logger, ) *Handler { return &Handler{ + arifpaySvc: arifpaySvc, issueReportingSvc: issueReportingSvc, instSvc: instSvc, currSvc: currSvc, @@ -124,11 +128,11 @@ func New( virtualGameSvc: virtualGameSvc, aleaVirtualGameSvc: aleaVirtualGameSvc, veliVirtualGameSvc: veliVirtualGameSvc, - recommendationSvc: recommendationSvc, - authSvc: authSvc, - resultSvc: resultSvc, - jwtConfig: jwtConfig, - Cfg: cfg, - mongoLoggerSvc: mongoLoggerSvc, + recommendationSvc: recommendationSvc, + authSvc: authSvc, + resultSvc: resultSvc, + jwtConfig: jwtConfig, + Cfg: cfg, + mongoLoggerSvc: mongoLoggerSvc, } } diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index d5bf75a..d9df30d 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -20,6 +20,7 @@ import ( func (a *App) initAppRoutes() { h := handlers.New( + a.arifpaySvc, a.issueReportingSvc, a.instSvc, a.currSvc, @@ -58,6 +59,7 @@ func (a *App) initAppRoutes() { "version": "1.0dev10", }) }) + // Swagger a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) @@ -68,6 +70,7 @@ func (a *App) initAppRoutes() { "version": "1.0dev10", }) }) + // Auth Routes groupV1.Post("/auth/login", h.LoginCustomer) groupV1.Post("/auth/refresh", h.RefreshToken) @@ -98,6 +101,12 @@ func (a *App) initAppRoutes() { return c.SendString("Test endpoint") }) + //Arifpay + groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler) + groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.B2CTransferHandler) + groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler) + groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler) + // User Routes groupV1.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/sendResetCode", h.SendResetCode) @@ -161,7 +170,7 @@ func (a *App) initAppRoutes() { groupV1.Put("/leagues/:id/featured", h.SetLeagueFeatured) groupV1.Get("/result/:id", h.GetResultsByEventID) - + // Branch groupV1.Post("/branch", a.authMiddleware, h.CreateBranch) groupV1.Get("/branch", a.authMiddleware, h.GetAllBranches) @@ -225,11 +234,11 @@ func (a *App) initAppRoutes() { groupV1.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet) //Chapa Routes - a.fiber.Post("/chapa/payments/webhook/verify", h.WebhookCallback) - a.fiber.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction) - a.fiber.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit) - a.fiber.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal) - a.fiber.Get("/chapa/banks", h.GetSupportedBanks) + groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback) + groupV1.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction) + groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit) + groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal) + groupV1.Get("/chapa/banks", h.GetSupportedBanks) // Currencies groupV1.Get("/currencies", h.GetSupportedCurrencies) @@ -240,26 +249,6 @@ func (a *App) initAppRoutes() { groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile) groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) - //Wallet Monitor Service - // group.Get("/debug/wallet-monitor/status", func(c *fiber.Ctx) error { - // return c.JSON(fiber.Map{ - // "running": monitor.IsRunning(), - // "last_check": walletMonitorSvc.LastCheckTime(), - // }) - // }) - - // group.Post("/debug/wallet-monitor/trigger", func(c *fiber.Ctx) error { - // walletMonitorSvc.ForceCheck() - // return c.SendStatus(fiber.StatusOK) - // }) - - // 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 groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame) groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback) @@ -270,14 +259,10 @@ func (a *App) initAppRoutes() { groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame) groupV1.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame) a.fiber.Post("/balance", h.GetBalance) - // a.fiber.Post("/bet", h.PlaceBet) - // a.fiber.Post("/win", h.RegisterWin) - // a.fiber.Post("/cancel", h.CancelTransaction) groupV1.Post("/veli/gaming-activity", h.GetGamingActivity) //mongoDB logs - ctx := context.Background() - groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(ctx)) + groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background())) // Recommendation Routes // group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) diff --git a/internal/web_server/ws/ws.go b/internal/web_server/ws/ws.go index eb0a2ae..498a4b6 100644 --- a/internal/web_server/ws/ws.go +++ b/internal/web_server/ws/ws.go @@ -2,9 +2,14 @@ package ws import ( "net/http" + "strconv" "sync" + "github.com/SamuelTariku/FortuneBet-Backend/internal/event" + "github.com/gofiber/fiber/v2" "github.com/gorilla/websocket" + "github.com/valyala/fasthttp" + "github.com/valyala/fasthttp/fasthttpadaptor" ) type Client struct { @@ -36,7 +41,6 @@ func (h *NotificationHub) Run() { h.mu.Lock() h.Clients[client] = true h.mu.Unlock() - // log.Printf("Client registered: %d", client.RecipientID) case client := <-h.Unregister: h.mu.Lock() if _, ok := h.Clients[client]; ok { @@ -44,7 +48,6 @@ func (h *NotificationHub) Run() { client.Conn.Close() } h.mu.Unlock() - // log.Printf("Client unregistered: %d", client.RecipientID) case message := <-h.Broadcast: h.mu.Lock() for client := range h.Clients { @@ -70,3 +73,102 @@ var Upgrader = websocket.Upgrader{ return true }, } + +type responseWriterWrapper struct { + ctx *fasthttp.RequestCtx + header http.Header + wrote bool +} + +func newResponseWriterWrapper(ctx *fasthttp.RequestCtx) *responseWriterWrapper { + return &responseWriterWrapper{ + ctx: ctx, + header: make(http.Header), + } +} + +func (rw *responseWriterWrapper) Header() http.Header { + return rw.header +} + +func (rw *responseWriterWrapper) Write(b []byte) (int, error) { + rw.writeHeaderIfNeeded(http.StatusOK) + return rw.ctx.Write(b) +} + +func (rw *responseWriterWrapper) WriteHeader(statusCode int) { + if rw.wrote { + return + } + rw.writeHeaderIfNeeded(statusCode) +} + +func (rw *responseWriterWrapper) writeHeaderIfNeeded(statusCode int) { + if rw.wrote { + return + } + rw.wrote = true + + // Copy headers from rw.header to fasthttp.Response.Header + for k, vv := range rw.header { + for _, v := range vv { + rw.ctx.Response.Header.Add(k, v) + } + } + + rw.ctx.SetStatusCode(statusCode) +} + +// ✅ WebSocketHandler integrates Gorilla WebSocket with Fiber +func (h *NotificationHub) WebSocketHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + userIDStr := c.Params("user_id") + userID, err := strconv.ParseInt(userIDStr, 10, 64) + if err != nil { + return c.Status(fiber.StatusBadRequest).SendString("Invalid user ID") + } + + // Use your custom responseWriterWrapper here + rw := newResponseWriterWrapper(c.Context()) + + stdReq := new(http.Request) + if err := fasthttpadaptor.ConvertRequest(c.Context(), stdReq, true); err != nil { + return err + } + + conn, err := Upgrader.Upgrade(rw, stdReq, nil) + if err != nil { + return err + } + + client := &Client{ + Conn: conn, + RecipientID: userID, + } + + h.Register <- client + defer func() { + h.Unregister <- client + }() + + for { + if _, _, err := conn.ReadMessage(); err != nil { + break + } + } + + return nil + } +} + +func (h *NotificationHub) BroadcastWalletUpdate(userID int64, event event.WalletEvent) { + payload := map[string]interface{}{ + "type": event.EventType, + "wallet_id": event.WalletID, + "user_id": event.UserID, + "balance": event.Balance, + "trigger": event.Trigger, + "recipient_id": userID, + } + h.Broadcast <- payload +}