diff --git a/cmd/main.go b/cmd/main.go index a9b4970..5f26003 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -153,7 +153,7 @@ func main() { virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger) veliCLient := veli.NewClient(cfg, walletSvc) - veliVirtualGameService := veli.New(veliCLient, walletSvc) + veliVirtualGameService := veli.New(veliCLient, walletSvc,cfg) recommendationSvc := recommendation.NewService(recommendationRepo) chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY) diff --git a/internal/domain/veli_games.go b/internal/domain/veli_games.go index 6768c75..6f40841 100644 --- a/internal/domain/veli_games.go +++ b/internal/domain/veli_games.go @@ -38,7 +38,7 @@ type GameStartRequest struct { ProviderID string `json:"providerId"` GameID string `json:"gameId"` Language string `json:"language"` - PlayerID string `json:"playerId"` + PlayerID string `json:"playerId,omitempty"` Currency string `json:"currency"` DeviceType string `json:"deviceType"` Country string `json:"country"` @@ -69,9 +69,9 @@ type BalanceRequest struct { SessionID string `json:"sessionId"` ProviderID string `json:"providerId"` PlayerID string `json:"playerId"` - Currency string `json:"currency"` BrandID string `json:"brandId"` GameID string `json:"gameId,omitempty"` + Currency string `json:"currency"` } type BalanceResponse struct { @@ -170,6 +170,7 @@ type BalanceDetail struct { Currency string `json:"currency"` Amount float64 `json:"amount"` } + // Request type GamingActivityRequest struct { FromDate string `json:"fromDate"` // YYYY-MM-DD @@ -181,7 +182,6 @@ type GamingActivityRequest struct { Page int `json:"page,omitempty"` // Optional, default 1 Size int `json:"size,omitempty"` // Optional, default 100 PlayerIDs []string `json:"playerIds,omitempty"` // Optional - BrandID string `json:"brandId"` // Required } // Response @@ -212,9 +212,44 @@ type GamingActivityItem struct { } type PaginationMeta struct { - TotalItems int `json:"totalItems"` - ItemCount int `json:"itemCount"` - ItemsPerPage int `json:"itemsPerPage"` - TotalPages int `json:"totalPages"` - CurrentPage int `json:"currentPage"` + TotalItems int `json:"totalItems"` + ItemCount int `json:"itemCount"` + ItemsPerPage int `json:"itemsPerPage"` + TotalPages int `json:"totalPages"` + CurrentPage int `json:"currentPage"` +} + +type HugeWinsRequest struct { + FromDate string `json:"fromDate"` + ToDate string `json:"toDate"` + ProviderID string `json:"providerId,omitempty"` + Currencies []string `json:"currencies,omitempty"` + BrandID string `json:"brandId"` + GameIDs []string `json:"gameIds,omitempty"` + Page int `json:"page,omitempty"` + Size int `json:"size,omitempty"` +} + +type HugeWinsResponse struct { + Items []HugeWinItem `json:"items"` + Meta PaginationMeta `json:"meta"` +} + +type HugeWinItem struct { + BetTransactionID string `json:"betTransactionId"` + WinTransactionID string `json:"winTransactionId"` + Currency string `json:"currency"` + GameID string `json:"gameId"` + BetAmount float64 `json:"betAmount"` + WinAmount float64 `json:"winAmount"` + BetAmountUsd float64 `json:"betAmountUsd"` + WinAmountUsd float64 `json:"winAmountUsd"` + ProviderID string `json:"providerId"` + PlayerID string `json:"playerId"` + RoundID string `json:"roundId"` + CorrelationID string `json:"correlationId"` + BrandID string `json:"brandId"` + OperatorID string `json:"operatorId"` + CreatedAt string `json:"createdAt"` + Reason string `json:"reason"` } diff --git a/internal/services/virtualGame/veli/client.go b/internal/services/virtualGame/veli/client.go index 13b0773..cf899e0 100644 --- a/internal/services/virtualGame/veli/client.go +++ b/internal/services/virtualGame/veli/client.go @@ -39,7 +39,7 @@ func NewClient(cfg *config.Config, walletSvc *wallet.Service) *Client { } // Signature generator -func (c *Client) generateSignature(params map[string]string) (string, error) { +func (c *Client) generateSignature(params map[string]any) (string, error) { keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) @@ -47,11 +47,31 @@ func (c *Client) generateSignature(params map[string]string) (string, error) { sort.Strings(keys) var b strings.Builder - for i, k := range keys { - if i > 0 { + first := true + + appendKV := func(_ string, value string) { + if !first { b.WriteString(";") } - b.WriteString(fmt.Sprintf("%s:%s", k, params[k])) + b.WriteString(value) + first = false + } + + for _, k := range keys { + v := params[k] + + switch val := v.(type) { + case []string: + for i, item := range val { + appendKV(k, fmt.Sprintf("%s:%d:%s", k, i, item)) + } + case []any: + for i, item := range val { + appendKV(k, fmt.Sprintf("%s:%d:%v", k, i, item)) + } + default: + appendKV(k, fmt.Sprintf("%s:%v", k, val)) + } } fmt.Println("String being signed:", b.String()) @@ -63,12 +83,12 @@ func (c *Client) generateSignature(params map[string]string) (string, error) { signature := base64.StdEncoding.EncodeToString(hash) fmt.Println("Generated signature:", signature) return fmt.Sprintf("%s:%s", c.OperatorID, signature), nil - - // return fmt.Sprintf("%s:%s", c.OperatorID, base64.StdEncoding.EncodeToString(h.Sum(nil))), nil } + + // POST helper -func (c *Client) post(ctx context.Context, path string, body any, sigParams map[string]string, result any) error { +func (c *Client) post(ctx context.Context, path string, body any, sigParams map[string]any, result any) error { data, _ := json.Marshal(body) sig, err := c.generateSignature(sigParams) if err != nil { diff --git a/internal/services/virtualGame/veli/port.go b/internal/services/virtualGame/veli/port.go index 67e6e38..22c78ba 100644 --- a/internal/services/virtualGame/veli/port.go +++ b/internal/services/virtualGame/veli/port.go @@ -17,4 +17,5 @@ type VeliVirtualGameService interface { ProcessWin(ctx context.Context, req domain.WinRequest) (*domain.WinResponse, error) ProcessCancel(ctx context.Context, req domain.CancelRequest) (*domain.CancelResponse, error) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) + GetHugeWins(ctx context.Context, req domain.HugeWinsRequest) (*domain.HugeWinsResponse, error) } diff --git a/internal/services/virtualGame/veli/service.go b/internal/services/virtualGame/veli/service.go index 1a07114..4c675cc 100644 --- a/internal/services/virtualGame/veli/service.go +++ b/internal/services/virtualGame/veli/service.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" "strconv" - "strings" + "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" ) @@ -19,20 +19,22 @@ var ( ) type service struct { - client *Client + client *Client walletSvc *wallet.Service + cfg *config.Config } -func New(client *Client, walletSvc *wallet.Service) VeliVirtualGameService { +func New(client *Client, walletSvc *wallet.Service, cfg *config.Config) VeliVirtualGameService { return &service{ - client: client, + client: client, walletSvc: walletSvc, + cfg: cfg, } } func (s *service) GetProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) { // Always mirror request body fields into sigParams - sigParams := map[string]string{ + sigParams := map[string]any{ "brandId": req.BrandID, } @@ -54,9 +56,8 @@ func (s *service) GetProviders(ctx context.Context, req domain.ProviderRequest) return &res, err } - func (s *service) GetGames(ctx context.Context, req domain.GameListRequest) ([]domain.GameEntity, error) { - sigParams := map[string]string{ + sigParams := map[string]any{ "brandId": req.BrandID, "providerId": req.ProviderID, } var res struct { @@ -67,10 +68,10 @@ func (s *service) GetGames(ctx context.Context, req domain.GameListRequest) ([]d } func (s *service) StartGame(ctx context.Context, req domain.GameStartRequest) (*domain.GameStartResponse, error) { - sigParams := map[string]string{ + sigParams := map[string]any{ "sessionId": req.SessionID, "providerId": req.ProviderID, "gameId": req.GameID, "language": req.Language, "playerId": req.PlayerID, - "currency": req.Currency, "deviceType": req.DeviceType, "country": req.Country, + "currency": req.Currency, "deviceType": req.DeviceType, "country": "US", "ip": req.IP, "brandId": req.BrandID, } var res domain.GameStartResponse @@ -79,7 +80,7 @@ func (s *service) StartGame(ctx context.Context, req domain.GameStartRequest) (* } func (s *service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest) (*domain.GameStartResponse, error) { - sigParams := map[string]string{ + sigParams := map[string]any{ "providerId": req.ProviderID, "gameId": req.GameID, "language": req.Language, "deviceType": req.DeviceType, "ip": req.IP, "brandId": req.BrandID, @@ -99,6 +100,9 @@ func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d if err != nil { return nil, fmt.Errorf("failed to get real balance: %w", err) } + if len(playerWallets) == 0 { + return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallet found for player %s", req.PlayerID) + } realBalance := playerWallets[0].Balance @@ -116,7 +120,7 @@ func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d Currency string `json:"currency"` Amount float64 `json:"amount"` }{ - Currency: string(playerWallets[0].Currency), + Currency: req.Currency, Amount: float64(realBalance), }, } @@ -135,139 +139,299 @@ func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d } func (s *service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain.BetResponse, error) { - sigParams := map[string]string{ - "sessionId": req.SessionID, - "providerId": req.ProviderID, - "playerId": req.PlayerID, - "currency": req.Amount.Currency, - "brandId": req.BrandID, - "gameId": req.GameID, - "roundId": req.RoundID, - "transactionId": req.TransactionID, - "correlationId": req.CorrelationID, - } - if req.GameType != "" { - sigParams["gameType"] = req.GameType - } - if req.IsAdjustment { - sigParams["isAdjustment"] = "true" - } - if req.JackpotID != "" { - sigParams["jackpotId"] = req.JackpotID - sigParams["jackpotContribution"] = fmt.Sprintf("%.2f", req.JackpotContribution) - } - - var res domain.BetResponse - err := s.client.post(ctx, "/bet", req, sigParams, &res) + // --- 1. Validate PlayerID --- playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) if err != nil { - return &domain.BetResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) } - wallets, err := s.client.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + + // // --- 2. Validate session (optional, if you have sessionSvc) --- + // sessionValid, expired, err := s.sessionSvc.ValidateSession(ctx, req.SessionID, req.PlayerID) + // if err != nil { + // return nil, fmt.Errorf("session validation failed") + // } + // if !sessionValid { + // if expired { + // return nil, fmt.Errorf("SESSION_EXPIRED: session %s expired", req.SessionID) + // } + // return nil, fmt.Errorf("SESSION_NOT_FOUND: session %s not found", req.SessionID) + // } + + // --- 3. Get player wallets --- + playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) if err != nil { - return &domain.BetResponse{}, err + return nil, fmt.Errorf("failed to get real balance: %w", err) + } + if len(playerWallets) == 0 { + return nil, fmt.Errorf("no wallets found for player %s", req.PlayerID) } - s.client.walletSvc.DeductFromWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, - fmt.Sprintf("Deducting %v from wallet for creating Veli Game Bet", req.Amount.Amount), - ) + realWallet := playerWallets[0] + realBalance := float64(realWallet.Balance) - return &res, err + var bonusBalance float64 + if len(playerWallets) > 1 { + bonusBalance = float64(playerWallets[1].Balance) + } + + // --- 4. Check sufficient balance --- + totalBalance := realBalance + bonusBalance + if totalBalance < req.Amount.Amount { + return nil, fmt.Errorf("INSUFFICIENT_BALANCE") + } + + // --- 5. Deduct funds (bonus first, then real) --- + remaining := req.Amount.Amount + var usedBonus, usedReal float64 + + if bonusBalance > 0 { + if bonusBalance >= remaining { + // fully cover from bonus + usedBonus = remaining + bonusBalance -= remaining + remaining = 0 + } else { + // partially cover from bonus + usedBonus = bonusBalance + remaining -= bonusBalance + bonusBalance = 0 + } + } + + if remaining > 0 { + if realBalance >= remaining { + usedReal = remaining + realBalance -= remaining + remaining = 0 + } else { + // should never happen because of totalBalance check + return nil, fmt.Errorf("INSUFFICIENT_BALANCE") + } + } + + // --- 6. Persist wallet deductions --- + if usedBonus > 0 && len(playerWallets) > 1 { + _, err = s.walletSvc.DeductFromWallet(ctx, playerWallets[1].ID, + domain.Currency(usedBonus), + domain.ValidInt64{}, + domain.TRANSFER_DIRECT, + fmt.Sprintf("Deduct bonus %.2f for bet %s", usedBonus, req.TransactionID), + ) + if err != nil { + return nil, fmt.Errorf("bonus deduction failed: %w", err) + } + } + + if usedReal > 0 { + _, err = s.walletSvc.DeductFromWallet(ctx, realWallet.ID, + domain.Currency(usedReal), + domain.ValidInt64{}, + domain.TRANSFER_DIRECT, + fmt.Sprintf("Deduct real %.2f for bet %s", usedReal, req.TransactionID), + ) + if err != nil { + return nil, fmt.Errorf("real deduction failed: %w", err) + } + } + + // --- 7. Build response --- + res := &domain.BetResponse{ + Real: domain.BalanceDetail{ + Currency: "ETB", + Amount: realBalance, + }, + WalletTransactionID: req.TransactionID, + UsedRealAmount: usedReal, + UsedBonusAmount: usedBonus, + } + + if bonusBalance > 0 { + res.Bonus = &domain.BalanceDetail{ + Currency: "ETB", + Amount: bonusBalance, + } + } + + return res, nil } func (s *service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domain.WinResponse, error) { - sigParams := map[string]string{ - "sessionId": req.SessionID, - "providerId": req.ProviderID, - "playerId": req.PlayerID, - "currency": req.Amount.Currency, - "brandId": req.BrandID, - "gameId": req.GameID, - "roundId": req.RoundID, - "transactionId": req.TransactionID, - "correlationId": req.CorrelationID, - "winType": req.WinType, - } - if req.GameType != "" { - sigParams["gameType"] = req.GameType - } - if req.RewardID != "" { - sigParams["rewardId"] = req.RewardID - } - if req.IsCashOut { - sigParams["isCashOut"] = "true" - } - - var res domain.WinResponse - err := s.client.post(ctx, "/win", req, sigParams, &res) - + // --- 1. Validate PlayerID --- playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) if err != nil { - return &domain.WinResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) } - wallets, err := s.client.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + // --- 2. Get player wallets --- + playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) if err != nil { - return &domain.WinResponse{}, err + return nil, fmt.Errorf("failed to get wallets: %w", err) + } + if len(playerWallets) == 0 { + return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallets for player %s", req.PlayerID) } - s.client.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.Amount.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Adding %v to wallet due to winning Veli Games bet", req.Amount), - ) + realWallet := playerWallets[0] + realBalance := float64(realWallet.Balance) - return &res, err + var bonusBalance float64 + if len(playerWallets) > 1 { + bonusBalance = float64(playerWallets[1].Balance) + } + + // --- 3. Apply winnings (for now, everything goes to real wallet) --- + winAmount := req.Amount.Amount + usedReal := winAmount + usedBonus := 0.0 + + // TODO: If you want to split between bonus/real (e.g. free spins), + // you can extend logic here based on req.WinType / req.RewardID. + + _, err = s.walletSvc.AddToWallet( + ctx, + realWallet.ID, + domain.Currency(winAmount), + domain.ValidInt64{}, + domain.TRANSFER_DIRECT, + domain.PaymentDetails{}, + fmt.Sprintf("Win %.2f for transaction %s", winAmount, req.TransactionID), + ) + if err != nil { + return nil, fmt.Errorf("failed to credit real wallet: %w", err) + } + + // --- 4. Reload balances after credit --- + updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + if err != nil { + return nil, fmt.Errorf("failed to reload balances: %w", err) + } + + updatedReal := updatedWallets[0] + realBalance = float64(updatedReal.Balance) + + if len(updatedWallets) > 1 { + bonusBalance = float64(updatedWallets[1].Balance) + } + + // --- 5. Build response --- + res := &domain.WinResponse{ + Real: domain.BalanceDetail{ + Currency: req.Amount.Currency, + Amount: realBalance, + }, + WalletTransactionID: req.TransactionID, + UsedRealAmount: usedReal, + UsedBonusAmount: usedBonus, + } + + if bonusBalance > 0 { + res.Bonus = &domain.BalanceDetail{ + Currency: req.Amount.Currency, + Amount: bonusBalance, + } + } + + return res, nil } func (s *service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (*domain.CancelResponse, error) { - sigParams := map[string]string{ - "sessionId": req.SessionID, - "providerId": req.ProviderID, - "playerId": req.PlayerID, - "brandId": req.BrandID, - "gameId": req.GameID, - "roundId": req.RoundID, - "transactionId": req.TransactionID, - "cancelType": req.CancelType, - } - if req.GameType != "" { - sigParams["gameType"] = req.GameType - } - if req.CorrelationID != "" { - sigParams["correlationId"] = req.CorrelationID - } - if req.RefTransactionID != "" { - sigParams["refTransactionId"] = req.RefTransactionID - } - if req.AdjustmentRefund.Amount > 0 { - sigParams["adjustmentRefundAmount"] = fmt.Sprintf("%.2f", req.AdjustmentRefund.Amount) - sigParams["adjustmentRefundCurrency"] = req.AdjustmentRefund.Currency - } - - var res domain.CancelResponse - err := s.client.post(ctx, "/cancel", req, sigParams, &res) - + // --- 1. Validate PlayerID --- playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) if err != nil { - return &domain.CancelResponse{}, fmt.Errorf("invalid PlayerID: %w", err) + return nil, fmt.Errorf("invalid PlayerID %s", req.PlayerID) } - wallets, err := s.client.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + // --- 2. Get player wallets --- + playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) if err != nil { - return &domain.CancelResponse{}, err + return nil, fmt.Errorf("failed to get wallets: %w", err) + } + if len(playerWallets) == 0 { + return nil, fmt.Errorf("no wallets for player %s", req.PlayerID) } - s.client.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.AdjustmentRefund.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, - fmt.Sprintf("Adding %v to wallet due to cancelling virtual game bet", req.AdjustmentRefund.Amount), + realWallet := playerWallets[0] + realBalance := float64(realWallet.Balance) + + var bonusBalance float64 + if len(playerWallets) > 1 { + bonusBalance = float64(playerWallets[1].Balance) + } + + // --- 3. Refund handling --- + var refundAmount float64 + if req.AdjustmentRefund.Amount > 0 { + refundAmount = req.AdjustmentRefund.Amount + } else { + // If cancelType = CANCEL_BET and no explicit adjustmentRefund, + // we may need to look up the original bet transaction and refund that. + // TODO: implement transaction lookup if required by your domain. + return nil, fmt.Errorf("missing adjustmentRefund for CANCEL_BET") + } + + // For now, we assume refund goes back fully to real wallet + usedReal := refundAmount + usedBonus := 0.0 + + _, err = s.walletSvc.AddToWallet( + ctx, + realWallet.ID, + domain.Currency(refundAmount), + domain.ValidInt64{}, + domain.TRANSFER_DIRECT, + domain.PaymentDetails{ + ReferenceNumber: domain.ValidString{ + Value: req.TransactionID, + Valid: true, + }, + BankNumber: domain.ValidString{}, + }, + fmt.Sprintf("Cancel %s refunded %.2f for transaction %s", req.CancelType, refundAmount, req.RefTransactionID), ) - return &res, err + if err != nil { + return nil, fmt.Errorf("failed to refund wallet: %w", err) + } + + // --- 4. Reload balances after refund --- + updatedWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64) + if err != nil { + return nil, fmt.Errorf("failed to reload balances: %w", err) + } + + updatedReal := updatedWallets[0] + realBalance = float64(updatedReal.Balance) + + if len(updatedWallets) > 1 { + bonusBalance = float64(updatedWallets[1].Balance) + } + + // --- 5. Build response --- + res := &domain.CancelResponse{ + WalletTransactionID: req.TransactionID, + Real: domain.BalanceDetail{ + Currency: req.AdjustmentRefund.Currency, + Amount: realBalance, + }, + UsedRealAmount: usedReal, + UsedBonusAmount: usedBonus, + } + + if bonusBalance > 0 { + res.Bonus = &domain.BalanceDetail{ + Currency: req.AdjustmentRefund.Currency, + Amount: bonusBalance, + } + } + + return res, nil } func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivityRequest) (*domain.GamingActivityResponse, error) { - // Prepare signature parameters (sorted string map of non-nested fields) - sigParams := map[string]string{ + // --- Signature Params (flattened strings for signing) --- + sigParams := map[string]any{ "fromDate": req.FromDate, "toDate": req.ToDate, - "brandId": req.BrandID, + "brandId": s.cfg.VeliGames.BrandID, } // Optional filters @@ -275,28 +439,77 @@ func (s *service) GetGamingActivity(ctx context.Context, req domain.GamingActivi sigParams["providerId"] = req.ProviderID } if len(req.PlayerIDs) > 0 { - sigParams["playerIds"] = strings.Join(req.PlayerIDs, ",") + sigParams["playerIds"] = req.PlayerIDs // pass as []string, not joined } if len(req.GameIDs) > 0 { - sigParams["gameIds"] = strings.Join(req.GameIDs, ",") + sigParams["gameIds"] = req.GameIDs // pass as []string } if len(req.Currencies) > 0 { - sigParams["currencies"] = strings.Join(req.Currencies, ",") + sigParams["currencies"] = req.Currencies // pass as []string } if req.Page > 0 { - sigParams["page"] = fmt.Sprintf("%d", req.Page) + sigParams["page"] = req.Page + } else { + sigParams["page"] = 1 + req.Page = 1 } if req.Size > 0 { - sigParams["size"] = fmt.Sprintf("%d", req.Size) + sigParams["size"] = req.Size + } else { + sigParams["size"] = 100 + req.Size = 100 } if req.ExcludeFreeWin != nil { - sigParams["excludeFreeWin"] = fmt.Sprintf("%t", *req.ExcludeFreeWin) + sigParams["excludeFreeWin"] = *req.ExcludeFreeWin } + // --- Actual API Call --- var res domain.GamingActivityResponse err := s.client.post(ctx, "/report-api/public/gaming-activity", req, sigParams, &res) if err != nil { return nil, err } + + // --- Return parsed response --- + return &res, nil +} + +func (s *service) GetHugeWins(ctx context.Context, req domain.HugeWinsRequest) (*domain.HugeWinsResponse, error) { + // --- Signature Params (flattened strings for signing) --- + sigParams := map[string]any{ + "fromDate": req.FromDate, + "toDate": req.ToDate, + "brandId": req.BrandID, + } + + if req.ProviderID != "" { + sigParams["providerId"] = req.ProviderID + } + if len(req.GameIDs) > 0 { + sigParams["gameIds"] = req.GameIDs // pass slice directly + } + if len(req.Currencies) > 0 { + sigParams["currencies"] = req.Currencies // pass slice directly + } + if req.Page > 0 { + sigParams["page"] = req.Page + } else { + sigParams["page"] = 1 + req.Page = 1 + } + if req.Size > 0 { + sigParams["size"] = req.Size + } else { + sigParams["size"] = 100 + req.Size = 100 + } + + // --- Actual API Call --- + var res domain.HugeWinsResponse + err := s.client.post(ctx, "/report-api/public/gaming-activity/huge-wins", req, sigParams, &res) + if err != nil { + return nil, err + } + return &res, nil } diff --git a/internal/web_server/handlers/veli_games.go b/internal/web_server/handlers/veli_games.go index 4378f9b..8a005e6 100644 --- a/internal/web_server/handlers/veli_games.go +++ b/internal/web_server/handlers/veli_games.go @@ -3,6 +3,7 @@ package handlers import ( "context" "errors" + "fmt" "log" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" @@ -102,6 +103,14 @@ func (h *Handler) GetGamesByProvider(c *fiber.Ctx) error { // @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/veli/start-game [post] func (h *Handler) StartGame(c *fiber.Ctx) error { + userId, ok := c.Locals("user_id").(int64) + if !ok { + return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{ + Error: "missing user id", + Message: "Unauthorized", + }) + } + var req domain.GameStartRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ @@ -110,6 +119,7 @@ func (h *Handler) StartGame(c *fiber.Ctx) error { }) } + req.PlayerID = fmt.Sprintf("%d", userId) if req.BrandID == "" { req.BrandID = h.Cfg.VeliGames.BrandID } @@ -293,11 +303,6 @@ func (h *Handler) GetGamingActivity(c *fiber.Ctx) error { }) } - // Inject BrandID if not provided - if req.BrandID == "" { - req.BrandID = h.Cfg.VeliGames.BrandID - } - resp, err := h.veliVirtualGameSvc.GetGamingActivity(c.Context(), req) if err != nil { log.Println("GetGamingActivity error:", err) @@ -314,3 +319,40 @@ func (h *Handler) GetGamingActivity(c *fiber.Ctx) error { Success: true, }) } + +// GetHugeWins godoc +// @Summary Get Veli Huge Wins +// @Description Retrieves huge win transactions based on brand configuration (e.g. win > 10000 USD or 100x bet) +// @Tags Virtual Games - VeliGames +// @Accept json +// @Produce json +// @Param request body domain.HugeWinsRequest true "Huge Wins Request" +// @Success 200 {object} domain.Response{data=domain.HugeWinsResponse} +// @Failure 400 {object} domain.ErrorResponse +// @Failure 500 {object} domain.ErrorResponse +// @Router /api/v1/veli/huge-wins [post] +func (h *Handler) GetHugeWins(c *fiber.Ctx) error { + var req domain.HugeWinsRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Message: "Invalid request payload", + Error: err.Error(), + }) + } + + resp, err := h.veliVirtualGameSvc.GetHugeWins(c.Context(), req) + if err != nil { + log.Println("GetHugeWins error:", err) + return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ + Message: "Failed to retrieve huge wins", + Error: err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(domain.Response{ + Message: "Huge wins retrieved successfully", + Data: resp, + StatusCode: fiber.StatusOK, + Success: true, + }) +} diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index d27718f..2d40bfa 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -283,12 +283,13 @@ func (a *App) initAppRoutes() { groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback) //Veli Virtual Game Routes - groupV1.Post("/veli/providers", h.GetProviders) - groupV1.Post("/veli/games-list", h.GetGamesByProvider) - groupV1.Post("/veli/start-game", h.StartGame) + groupV1.Post("/veli/providers", a.authMiddleware, h.GetProviders) + groupV1.Post("/veli/games-list", a.authMiddleware, h.GetGamesByProvider) + groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame) groupV1.Post("/veli/start-demo-game", h.StartDemoGame) a.fiber.Post("/balance", h.GetBalance) - groupV1.Post("/veli/gaming-activity", h.GetGamingActivity) + groupV1.Post("/veli/gaming-activity", a.authMiddleware, h.GetGamingActivity) + groupV1.Post("/veli/huge-wins", a.authMiddleware, h.GetHugeWins) //mongoDB logs groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background()))