veli games error types and bonus balance fixes

This commit is contained in:
Yared Yemane 2025-11-14 16:27:55 +03:00
parent 2a41a2426c
commit e8c2213d30
4 changed files with 113 additions and 42 deletions

View File

@ -149,12 +149,12 @@ func main() {
cfg, cfg,
) )
marketSettingRepo := repository.NewMarketSettingStore(store) marketSettingRepo := repository.NewMarketSettingStore(store)
if err := marketSettingRepo.EnsureAllMarketSettingsExist(context.Background()); err != nil { if err := marketSettingRepo.EnsureAllMarketSettingsExist(context.Background()); err != nil {
log.Fatalf("failed to ensure market settings: %v", err) log.Fatalf("failed to ensure market settings: %v", err)
} }
oddsSvc := odds.New( oddsSvc := odds.New(
repository.NewOddStore(store), repository.NewOddStore(store),
marketSettingRepo, marketSettingRepo,
@ -251,7 +251,7 @@ func main() {
virtualGameSvc := virtualgameservice.New(virtualGamesRepo, *walletSvc, store, cfg, logger) virtualGameSvc := virtualgameservice.New(virtualGamesRepo, *walletSvc, store, cfg, logger)
aleaService := alea.NewAleaPlayService(virtualGamesRepo, *walletSvc, cfg, logger) aleaService := alea.NewAleaPlayService(virtualGamesRepo, *walletSvc, cfg, logger)
veliCLient := veli.NewClient(cfg, walletSvc) veliCLient := veli.NewClient(cfg, walletSvc)
veliVirtualGameService := veli.New(virtualGameSvc, virtualGamesRepo, veliCLient, walletSvc, repository.NewTransferStore(store), domain.MongoDBLogger, cfg) veliVirtualGameService := veli.New(virtualGameSvc, virtualGamesRepo, *store, veliCLient, walletSvc, repository.NewTransferStore(store), domain.MongoDBLogger, cfg)
orchestrationSvc := orchestration.New( orchestrationSvc := orchestration.New(
virtualGameSvc, virtualGameSvc,
virtualGamesRepo, virtualGamesRepo,

View File

@ -166,6 +166,7 @@ RETURNING id,
status, status,
created_at, created_at,
updated_at; updated_at;
-- name: GetVirtualGameTransactionByExternalID :one -- name: GetVirtualGameTransactionByExternalID :one
SELECT id, SELECT id,
session_id, session_id,
@ -180,6 +181,7 @@ SELECT id,
updated_at updated_at
FROM virtual_game_transactions FROM virtual_game_transactions
WHERE external_transaction_id = $1; WHERE external_transaction_id = $1;
-- name: UpdateVirtualGameTransactionStatus :exec -- name: UpdateVirtualGameTransactionStatus :exec
UPDATE virtual_game_transactions UPDATE virtual_game_transactions
SET status = $2, SET status = $2,

View File

@ -30,6 +30,7 @@ var (
type Service struct { type Service struct {
virtualGameSvc virtualgameservice.VirtualGameService virtualGameSvc virtualgameservice.VirtualGameService
repo repository.VirtualGameRepository repo repository.VirtualGameRepository
genRepo repository.Store
client *Client client *Client
walletSvc *wallet.Service walletSvc *wallet.Service
transfetStore ports.TransferStore transfetStore ports.TransferStore
@ -40,6 +41,7 @@ type Service struct {
func New( func New(
virtualGameSvc virtualgameservice.VirtualGameService, virtualGameSvc virtualgameservice.VirtualGameService,
repo repository.VirtualGameRepository, repo repository.VirtualGameRepository,
genRepo repository.Store,
client *Client, client *Client,
walletSvc *wallet.Service, walletSvc *wallet.Service,
transferStore ports.TransferStore, transferStore ports.TransferStore,
@ -49,6 +51,7 @@ func New(
return &Service{ return &Service{
virtualGameSvc: virtualGameSvc, virtualGameSvc: virtualGameSvc,
repo: repo, repo: repo,
genRepo: genRepo,
client: client, client: client,
walletSvc: walletSvc, walletSvc: walletSvc,
transfetStore: transferStore, transfetStore: transferStore,
@ -160,6 +163,15 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
// return nil, fmt.Errorf("provider %s is disabled", req.ProviderID) // return nil, fmt.Errorf("provider %s is disabled", req.ProviderID)
// } // }
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid PlayerID: %w", err)
}
if _, err := s.genRepo.GetUserByID(ctx, playerIDInt64); err != nil {
return nil, ErrPlayerNotFound
}
// 2. Prepare signature params // 2. Prepare signature params
sigParams := map[string]any{ sigParams := map[string]any{
"sessionId": req.SessionID, "sessionId": req.SessionID,
@ -180,10 +192,10 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err) return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err)
} }
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64) // playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil { // if err != nil {
return nil, fmt.Errorf("invalid PlayerID: %w", err) // return nil, fmt.Errorf("invalid PlayerID: %w", err)
} // }
session := &domain.VirtualGameSession{ session := &domain.VirtualGameSession{
UserID: playerIDInt64, UserID: playerIDInt64,
@ -243,6 +255,15 @@ func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
// return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallet found for player %s", req.PlayerID) // return nil, fmt.Errorf("PLAYER_NOT_FOUND: no wallet found for player %s", req.PlayerID)
// } // }
// playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
// if err != nil {
// return nil, fmt.Errorf("invalid PlayerID: %w", err)
// }
if _, err := s.genRepo.GetUserByID(ctx, playerIDInt64); err != nil {
return nil, ErrPlayerNotFound
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read user wallets") return nil, fmt.Errorf("failed to read user wallets")
@ -285,13 +306,26 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID)
} }
if _, err := s.genRepo.GetUserByID(ctx, playerIDInt64); err != nil {
return nil, ErrPlayerNotFound
}
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil {
return nil, fmt.Errorf("failed checking idempotency: %w", err)
}
if existingTx != nil {
// Idempotent return — already processed
return nil, fmt.Errorf("DUPLICATE_TRANSACTION")
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read user wallets") return nil, fmt.Errorf("failed to read user wallets")
} }
// bonusBalance := float64(wallet.StaticBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32())
// --- 4. Check sufficient balance --- // --- 4. Check sufficient balance ---
if float64(wallet.RegularBalance.Float32()) < req.Amount.Amount { if float64(wallet.RegularBalance.Float32()) < req.Amount.Amount {
@ -321,11 +355,26 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
UsedBonusAmount: 0, UsedBonusAmount: 0,
} }
if bonusBalance > 0 { // if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{ // res.Bonus = &domain.BalanceDetail{
Currency: req.Amount.Currency, // Currency: req.Amount.Currency,
Amount: bonusBalance, // Amount: bonusBalance,
} // }
// }
if err = s.repo.CreateVirtualGameTransaction(ctx, &domain.VirtualGameTransaction{
UserID: playerIDInt64,
Provider: req.ProviderID,
GameID: req.GameID,
WalletID: wallet.RegularID,
TransactionType: "BET",
Amount: int64(req.Amount.Amount),
Currency: req.Amount.Currency,
ExternalTransactionID: req.TransactionID,
Status: "pending",
GameRoundID: req.RoundID,
}); err != nil {
return nil, fmt.Errorf("failed to log virtual game transaction: %w", err)
} }
return res, nil return res, nil
@ -338,6 +387,10 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID) return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %s", req.PlayerID)
} }
if _, err := s.genRepo.GetUserByID(ctx, playerIDInt64); err != nil {
return nil, ErrPlayerNotFound
}
// --- 2. Get player wallets --- // --- 2. Get player wallets ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
if err != nil { if err != nil {
@ -346,7 +399,7 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
// --- 3. Convert balances safely using Float32() --- // --- 3. Convert balances safely using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32()) realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32()) // bonusBalance := float64(wallet.StaticBalance.Float32())
// --- 4. Apply winnings --- // --- 4. Apply winnings ---
winAmount := req.Amount.Amount winAmount := req.Amount.Amount
@ -378,12 +431,12 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
UsedBonusAmount: usedBonus, UsedBonusAmount: usedBonus,
} }
if bonusBalance > 0 { // if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{ // res.Bonus = &domain.BalanceDetail{
Currency: req.Amount.Currency, // Currency: req.Amount.Currency,
Amount: bonusBalance, // Amount: bonusBalance,
} // }
} // }
return res, nil return res, nil
} }
@ -395,6 +448,10 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %q", req.PlayerID) return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %q", req.PlayerID)
} }
if _, err := s.genRepo.GetUserByID(ctx, playerIDInt64); err != nil {
return nil, ErrPlayerNotFound
}
// --- 2. Get player wallets --- // --- 2. Get player wallets ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt64)
if err != nil { if err != nil {
@ -403,7 +460,7 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
// --- 3. Convert balances using Float32() --- // --- 3. Convert balances using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32()) realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32()) // bonusBalance := float64(wallet.StaticBalance.Float32())
// --- 4. Determine refund amount --- // --- 4. Determine refund amount ---
var refundAmount float64 var refundAmount float64
@ -454,12 +511,12 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
UsedBonusAmount: usedBonus, UsedBonusAmount: usedBonus,
} }
if bonusBalance > 0 { // if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{ // res.Bonus = &domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency, // Currency: req.AdjustmentRefund.Currency,
Amount: bonusBalance, // Amount: bonusBalance,
} // }
} // }
return res, nil return res, nil
} }

View File

@ -312,14 +312,6 @@ func (h *Handler) HandlePlayerInfo(c *fiber.Ctx) error {
} }
func (h *Handler) HandleBet(c *fiber.Ctx) error { func (h *Handler) HandleBet(c *fiber.Ctx) error {
// userID := c.Locals("user_id")
// fmt.Printf("\n\nBet User ID is%v\n\n",userID)
// if userID == "" {
// return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
// Message: "Failed to process Bet request",
// Error: "Invalid user identification",
// })
// }
// Read the raw body // Read the raw body
body := c.Body() body := c.Body()
if len(body) == 0 { if len(body) == 0 {
@ -354,8 +346,18 @@ func (h *Handler) HandleBet(c *fiber.Ctx) error {
if err != nil { if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
Message: "Duplicate transaction", // Message: "Duplicate transaction",
Error: "DUPLICATE_TRANSACTION", Error: veli.ErrDuplicateTransaction.Error(),
})
} else if errors.Is(err, veli.ErrInsufficientBalance) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
// Message: "Wallet balance is insufficient",
Error: veli.ErrInsufficientBalance.Error(),
})
} else if errors.Is(err, veli.ErrPlayerNotFound) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
// Message: "User not found",
Error: veli.ErrPlayerNotFound.Error(),
}) })
} }
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -471,8 +473,13 @@ func (h *Handler) HandleWin(c *fiber.Ctx) error {
if err != nil { if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
Message: "Duplicate transaction", // Message: "Duplicate transaction",
Error: "DUPLICATE_TRANSACTION", Error: veli.ErrDuplicateTransaction.Error(),
})
} else if errors.Is(err, veli.ErrPlayerNotFound) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
// Message: "Duplicate transaction",
Error: veli.ErrPlayerNotFound.Error(),
}) })
} }
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
@ -543,8 +550,13 @@ func (h *Handler) HandleCancel(c *fiber.Ctx) error {
if err != nil { if err != nil {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
Message: "Duplicate transaction", // Message: "Duplicate transaction",
Error: "DUPLICATE_TRANSACTION", Error: veli.ErrDuplicateTransaction.Error(),
})
} else if errors.Is(err, veli.ErrPlayerNotFound) {
return c.Status(fiber.StatusConflict).JSON(domain.ErrorResponse{
// Message: "User not found",
Error: veli.ErrPlayerNotFound.Error(),
}) })
} }
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{