virtual games transaction amount mapping to domain.Currency fix

This commit is contained in:
Yared Yemane 2025-11-13 16:00:52 +03:00
parent 5693526bb5
commit 2a41a2426c
4 changed files with 300 additions and 341 deletions

View File

@ -26,7 +26,7 @@ services:
image: mongo:7.0.11 image: mongo:7.0.11
restart: always restart: always
ports: ports:
- "27017:27017" - "27020:27017"
environment: environment:
MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: secret MONGO_INITDB_ROOT_PASSWORD: secret

View File

@ -72,168 +72,176 @@ func (s *Service) GetUserData(ctx context.Context, req domain.AtlasGetUserDataRe
// 4. Build response // 4. Build response
res := &domain.AtlasGetUserDataResponse{ res := &domain.AtlasGetUserDataResponse{
PlayerID: req.PlayerID, PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
} }
return res, nil return res, nil
} }
func (s *Service) ProcessBet(ctx context.Context, req domain.AtlasBetRequest) (*domain.AtlasBetResponse, error) { func (s *Service) ProcessBet(ctx context.Context, req domain.AtlasBetRequest) (*domain.AtlasBetResponse, error) {
// --- 1. Validate CasinoID ---
if req.CasinoID != s.client.CasinoID { if req.CasinoID != s.client.CasinoID {
return nil, fmt.Errorf("invalid casino_id") return nil, fmt.Errorf("BAD_REQUEST: invalid casino_id")
} }
// --- 2. Validate PlayerID ---
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64) playerIDInt, 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("BAD_REQUEST: invalid playerID %q", req.PlayerID)
} }
// --- 3. Fetch player wallet ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err) return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
} }
// if player == nil {
// return nil, ErrPlayerNotFound
// }
// 3. Check for duplicate transaction // --- 4. Convert balance using Float32() ---
// exists, err := s.repo.TransactionExists(ctx, req.TransactionID) realBalance := float64(wallet.RegularBalance.Float32())
// if err != nil {
// return nil, fmt.Errorf("failed to check transaction: %w", err)
// }
// if exists {
// return nil, ErrDuplicateTransaction
// }
// // 4. Get current balance // --- 5. Ensure sufficient balance ---
// balance, err := s.walletSvc.GetBalance(ctx, req.PlayerID) if realBalance < req.Amount {
// if err != nil {
// return nil, fmt.Errorf("failed to fetch wallet balance: %w", err)
// }
// 5. Ensure sufficient balance
if float64(wallet.RegularBalance) < req.Amount {
return nil, domain.ErrInsufficientBalance return nil, domain.ErrInsufficientBalance
} }
// 6. Deduct amount from wallet (record transaction) // --- 6. Deduct amount from wallet ---
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.Amount)) newBalance := realBalance - req.Amount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(newBalance)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to debit wallet: %w", err) return nil, fmt.Errorf("failed to debit wallet: %w", err)
} }
// 7. Save transaction record to DB (optional but recommended) // --- 7. Build response ---
// if err := s.repo.SaveBetTransaction(ctx, req); err != nil {
// // log warning but dont fail response to Atlas
// fmt.Printf("warning: failed to save bet transaction: %v\n", err)
// }
// 8. Build response
res := &domain.AtlasBetResponse{ res := &domain.AtlasBetResponse{
PlayerID: req.PlayerID, PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance) - req.Amount, Balance: newBalance,
} }
return res, nil return res, nil
} }
func (s *Service) ProcessBetWin(ctx context.Context, req domain.AtlasBetWinRequest) (*domain.AtlasBetWinResponse, error) { func (s *Service) ProcessBetWin(ctx context.Context, req domain.AtlasBetWinRequest) (*domain.AtlasBetWinResponse, error) {
// --- 1. Validate CasinoID ---
if req.CasinoID != s.client.CasinoID { if req.CasinoID != s.client.CasinoID {
return nil, fmt.Errorf("invalid casino_id") return nil, fmt.Errorf("BAD_REQUEST: invalid casino_id")
} }
// --- 2. Validate PlayerID ---
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64) playerIDInt, 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("BAD_REQUEST: invalid playerID %q", req.PlayerID)
} }
// --- 3. Fetch player wallet ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err) return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
} }
// 5. Ensure sufficient balance // --- 4. Convert balance using Float32() ---
if float64(wallet.RegularBalance) < req.BetAmount { realBalance := float64(wallet.RegularBalance.Float32())
// --- 5. Ensure sufficient balance for bet ---
if realBalance < req.BetAmount {
return nil, domain.ErrInsufficientBalance return nil, domain.ErrInsufficientBalance
} }
// 6. Deduct amount from wallet (record transaction) // --- 6. Deduct bet amount ---
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.BetAmount)) debitedBalance := realBalance - req.BetAmount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(debitedBalance)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to debit wallet: %w", err) return nil, fmt.Errorf("failed to debit wallet: %w", err)
} }
// --- 7. Apply win amount (if any) ---
finalBalance := debitedBalance
if req.WinAmount > 0 { if req.WinAmount > 0 {
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.WinAmount)) finalBalance = debitedBalance + req.WinAmount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(finalBalance)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err) return nil, fmt.Errorf("failed to credit wallet: %w", err)
} }
} }
// 8. Build response // --- 8. Build response ---
res := &domain.AtlasBetWinResponse{ res := &domain.AtlasBetWinResponse{
PlayerID: req.PlayerID, PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance), Balance: finalBalance,
} }
return res, nil return res, nil
} }
func (s *Service) ProcessRoundResult(ctx context.Context, req domain.RoundResultRequest) (*domain.RoundResultResponse, error) { func (s *Service) ProcessRoundResult(ctx context.Context, req domain.RoundResultRequest) (*domain.RoundResultResponse, error) {
// --- 1. Validate required fields ---
if req.PlayerID == "" || req.TransactionID == "" { if req.PlayerID == "" || req.TransactionID == "" {
return nil, errors.New("missing player_id or transaction_id") return nil, fmt.Errorf("BAD_REQUEST: missing player_id or transaction_id")
} }
// Credit player with win amount if > 0 // --- 2. Credit player if win amount > 0 ---
if req.Amount > 0 { if req.Amount > 0 {
// This will credit player's balance
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64) playerIDInt, 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("BAD_REQUEST: invalid playerID %q", req.PlayerID)
} }
// --- 3. Fetch player wallet ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err) return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
} }
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.Amount)) // --- 4. Convert balance and apply win amount ---
currentBalance := float64(wallet.RegularBalance.Float32())
newBalance := currentBalance + req.Amount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(newBalance)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err) return nil, fmt.Errorf("failed to credit wallet: %w", err)
} }
} }
return &domain.RoundResultResponse{Success: true}, nil // --- 5. Build response ---
return &domain.RoundResultResponse{
Success: true,
}, nil
} }
func (s *Service) ProcessRollBack(ctx context.Context, req domain.RollbackRequest) (*domain.RollbackResponse, error) { func (s *Service) ProcessRollBack(ctx context.Context, req domain.RollbackRequest) (*domain.RollbackResponse, error) {
// --- 1. Validate required fields ---
if req.PlayerID == "" || req.BetTransactionID == "" { if req.PlayerID == "" || req.BetTransactionID == "" {
return nil, errors.New("missing player_id or transaction_id") return nil, fmt.Errorf("BAD_REQUEST: missing player_id or bet_transaction_id")
} }
// Credit player with win amount if > 0 // --- 2. Parse PlayerID ---
// This will credit player's balance
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64) playerIDInt, 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("BAD_REQUEST: invalid playerID %q", req.PlayerID)
} }
// --- 3. Fetch player wallet ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt) wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err) return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
} }
// --- 4. Fetch original transfer ---
transfer, err := s.transfetStore.GetTransferByReference(ctx, req.BetTransactionID) transfer, err := s.transfetStore.GetTransferByReference(ctx, req.BetTransactionID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch transfer for reference %s: %w", req.BetTransactionID, err) return nil, fmt.Errorf("failed to fetch transfer for reference %s: %w", req.BetTransactionID, err)
} }
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+float64(transfer.Amount))) // --- 5. Compute new balance using Float32() conversion ---
currentBalance := float64(wallet.RegularBalance.Float32())
newBalance := currentBalance + float64(transfer.Amount)
// --- 6. Credit player wallet ---
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(newBalance)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err) return nil, fmt.Errorf("failed to credit wallet: %w", err)
} }
// --- 7. Update transfer status and verification ---
err = s.transfetStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.STATUS_CANCELLED)) err = s.transfetStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.STATUS_CANCELLED))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to update transfer status: %w", err) return nil, fmt.Errorf("failed to update transfer status: %w", err)
@ -244,7 +252,10 @@ func (s *Service) ProcessRollBack(ctx context.Context, req domain.RollbackReques
return nil, fmt.Errorf("failed to update transfer verification: %w", err) return nil, fmt.Errorf("failed to update transfer verification: %w", err)
} }
return &domain.RollbackResponse{Success: true}, nil // --- 8. Build response ---
return &domain.RollbackResponse{
Success: true,
}, nil
} }
func (s *Service) CreateFreeSpin(ctx context.Context, req domain.FreeSpinRequest) (*domain.FreeSpinResponse, error) { func (s *Service) CreateFreeSpin(ctx context.Context, req domain.FreeSpinRequest) (*domain.FreeSpinResponse, error) {
@ -266,58 +277,73 @@ func (s *Service) CreateFreeSpin(ctx context.Context, req domain.FreeSpinRequest
} }
func (s *Service) ProcessFreeSpinResult(ctx context.Context, req domain.FreeSpinResultRequest) (*domain.FreeSpinResultResponse, error) { func (s *Service) ProcessFreeSpinResult(ctx context.Context, req domain.FreeSpinResultRequest) (*domain.FreeSpinResultResponse, error) {
if req.PlayerID == "" || req.TransactionID == "" { if req.PlayerID == "" || req.TransactionID == "" {
return nil, errors.New("missing player_id or transaction_id") return nil, errors.New("missing player_id or transaction_id")
} }
// Credit player with win amount if > 0 if req.Amount <= 0 {
if req.Amount > 0 { // No winnings to process
// This will credit player's balance return &domain.FreeSpinResultResponse{Success: true}, nil
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid playerID: %w", err)
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
}
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.Amount))
if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err)
}
} }
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid playerID: %w", err)
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil {
return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
}
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)+req.Amount))
if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err)
}
// Optionally record the transaction in your transfer history for auditing
// _ = s.transfetStore.CreateGamePayoutTransfer(...)
return &domain.FreeSpinResultResponse{Success: true}, nil return &domain.FreeSpinResultResponse{Success: true}, nil
} }
func (s *Service) ProcessJackPot(ctx context.Context, req domain.JackpotRequest) (*domain.JackpotResponse, error) { func (s *Service) ProcessJackPot(ctx context.Context, req domain.JackpotRequest) (*domain.JackpotResponse, error) {
if req.PlayerID == "" || req.TransactionID == "" { if req.PlayerID == "" || req.TransactionID == "" {
return nil, errors.New("missing player_id or transaction_id") return nil, errors.New("missing player_id or transaction_id")
} }
// Credit player with win amount if > 0 if req.Amount <= 0 {
if req.Amount > 0 { // No jackpot winnings
// This will credit player's balance return &domain.JackpotResponse{Success: true}, nil
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64) }
if err != nil {
return nil, fmt.Errorf("invalid playerID: %w", err)
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt) playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err) return nil, fmt.Errorf("invalid playerID: %w", err)
} }
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.PaymentMethod(domain.DEPOSIT), domain.PaymentDetails{}, "") wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err) return nil, fmt.Errorf("failed to fetch wallet for player %d: %w", playerIDInt, err)
} }
_, err = s.walletSvc.AddToWallet(
ctx,
wallet.RegularID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.PaymentMethod(domain.DEPOSIT),
domain.PaymentDetails{
ReferenceNumber: domain.ValidString{
Value: req.TransactionID,
Valid: true,
},
// Description: fmt.Sprintf("Jackpot win - Txn: %s", req.TransactionID),
},
"",
)
if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", err)
} }
return &domain.JackpotResponse{Success: true}, nil return &domain.JackpotResponse{Success: true}, nil

View File

@ -233,42 +233,53 @@ func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfo
return &domain.PopOKPlayerInfoResponse{ return &domain.PopOKPlayerInfoResponse{
Country: "ET", Country: "ET",
Currency: claims.Currency, Currency: claims.Currency,
Balance: float64(wallet.RegularBalance), // Convert cents to currency Balance: float64(wallet.RegularBalance.Float32()), // Convert cents to currency
PlayerID: fmt.Sprintf("%d", claims.UserID), PlayerID: fmt.Sprintf("%d", claims.UserID),
}, nil }, nil
} }
func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error) { func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error) {
// Validate token and get user ID // Validate external token and extract user context
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey) claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid token") return nil, fmt.Errorf("invalid or expired token: %w", err)
} }
// Convert amount to cents (assuming wallet uses cents) // Validate required fields
// amount := int64(req.Amount) if req.Amount <= 0 {
return nil, errors.New("invalid bet amount")
// Deduct from wallet }
if req.TransactionID == "" {
return nil, errors.New("missing transaction_id")
}
// Retrieve user's wallet
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID) wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil { if err != nil {
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets") return nil, fmt.Errorf("failed to fetch wallet for user %d: %w", claims.UserID, err)
}
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID, domain.Currency(req.Amount),
domain.ValidInt64{}, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deducted %v amount from wallet by system while placing virtual game bet", req.Amount))
if err != nil {
return nil, fmt.Errorf("insufficient balance")
} }
// Create transaction record // Deduct amount from wallet
_, err = s.walletSvc.DeductFromWallet(
ctx,
wallet.RegularID,
domain.Currency(req.Amount),
domain.ValidInt64{}, // optional linked transfer ID
domain.TRANSFER_DIRECT,
fmt.Sprintf("Virtual game bet placed (PopOK) - Txn: %s", req.TransactionID),
)
if err != nil {
return nil, fmt.Errorf("failed to deduct from wallet (insufficient balance or wallet error): %w", err)
}
// Record the transaction
tx := &domain.VirtualGameTransaction{ tx := &domain.VirtualGameTransaction{
UserID: claims.UserID, UserID: claims.UserID,
CompanyID: claims.CompanyID.Value, CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK), Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID, GameID: req.GameID,
TransactionType: "BET", TransactionType: "BET",
Amount: int64(req.Amount), // Negative for bets Amount: -int64(req.Amount), // Represent bet as negative in accounting
Currency: req.Currency, Currency: req.Currency,
ExternalTransactionID: req.TransactionID, ExternalTransactionID: req.TransactionID,
Status: "COMPLETED", Status: "COMPLETED",
@ -276,14 +287,34 @@ func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (
} }
if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil { if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
s.logger.Error("Failed to create bet transaction", "error", err) // Optionally rollback wallet deduction if transaction record fails
return nil, fmt.Errorf("transaction failed") _, addErr := s.walletSvc.AddToWallet(
ctx,
wallet.RegularID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
domain.PaymentDetails{},
fmt.Sprintf("Rollback refund for failed bet transaction (PopOK) - Txn: %s", req.TransactionID),
)
if addErr != nil {
return nil, fmt.Errorf("failed to credit wallet: %w", addErr)
}
s.logger.Error("Failed to create bet transaction", "error", err, "txn_id", req.TransactionID, "user_id", claims.UserID)
return nil, fmt.Errorf("failed to record bet transaction: %w", err)
}
// Return updated wallet balance
updatedWallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return nil, fmt.Errorf("failed to refresh wallet balance: %w", err)
} }
return &domain.PopOKBetResponse{ return &domain.PopOKBetResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID ExternalTrxID: fmt.Sprintf("%v", tx.ID), // internal reference
Balance: float64(wallet.RegularBalance), Balance: float64(updatedWallet.RegularBalance.Float32()),
}, nil }, nil
} }
@ -306,15 +337,15 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
if existingTx != nil && existingTx.TransactionType == "WIN" { if existingTx != nil && existingTx.TransactionType == "WIN" {
s.logger.Warn("Duplicate win transaction", "transactionID", req.TransactionID) s.logger.Warn("Duplicate win transaction", "transactionID", req.TransactionID)
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
balance := 0.0 // balance := 0.0
if len(wallets) > 0 { // if len(wallets) > 0 {
balance = float64(wallets[0].Balance) // balance = float64(wallets[0].Balance)
} // }
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%d", existingTx.ID), ExternalTrxID: fmt.Sprintf("%d", existingTx.ID),
Balance: balance, Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
@ -323,7 +354,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID) wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read user wallets") return nil, fmt.Errorf("failed to read user wallets")
} }
// 4. Credit to wallet // 4. Credit to wallet
@ -359,29 +390,24 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
return nil, fmt.Errorf("transaction recording failed") return nil, fmt.Errorf("transaction recording failed")
} }
fmt.Printf("\n\n Win balance is:%v\n\n", float64(wallet.RegularBalance)) fmt.Printf("\n\n Win balance is:%v\n\n", float64(wallet.RegularBalance.Float32()))
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID), ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) { func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) {
// 1. Validate token and get user ID // --- 1. Validate token and get user ID ---
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey) claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
if err != nil { if err != nil {
s.logger.Error("Invalid token in tournament win request", "error", err) s.logger.Error("Invalid token in tournament win request", "error", err)
return nil, fmt.Errorf("invalid token") return nil, fmt.Errorf("invalid token")
} }
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID) // --- 2. Check for duplicate tournament win transaction (idempotency) ---
if err != nil {
return nil, fmt.Errorf("Failed to read user wallets")
}
// 2. Check for duplicate tournament win transaction
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID) existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil { if err != nil {
s.logger.Error("Failed to check existing tournament transaction", "error", err) s.logger.Error("Failed to check existing tournament transaction", "error", err)
@ -389,34 +415,48 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
} }
if existingTx != nil && existingTx.TransactionType == "TOURNAMENT_WIN" { if existingTx != nil && existingTx.TransactionType == "TOURNAMENT_WIN" {
s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID) s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID)
// wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID) wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID), ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
// 3. Convert amount to cents // --- 3. Fetch wallet ---
amountCents := int64(req.Amount) wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return nil, fmt.Errorf("failed to fetch wallet for user %d: %w", claims.UserID, err)
}
// 4. Credit user wallet // --- 4. Credit user wallet ---
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, _, err = s.walletSvc.AddToWallet(
fmt.Sprintf("Added %v to wallet for winning Popok Tournament", req.Amount)) ctx,
wallet.RegularID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
domain.PaymentDetails{
ReferenceNumber: domain.ValidString{
Value: req.TransactionID,
Valid: true,
},
},
fmt.Sprintf("Added %v to wallet for winning PopOK Tournament", req.Amount),
)
if err != nil { if err != nil {
s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err) s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("wallet credit failed") return nil, fmt.Errorf("wallet credit failed")
} }
// 5. Log tournament win transaction // --- 5. Record tournament win transaction ---
tx := &domain.VirtualGameTransaction{ tx := &domain.VirtualGameTransaction{
UserID: claims.UserID, UserID: claims.UserID,
CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID,
TransactionType: "TOURNAMENT_WIN", TransactionType: "TOURNAMENT_WIN",
Amount: amountCents, Amount: int64(req.Amount),
Currency: req.Currency, Currency: req.Currency,
ExternalTransactionID: req.TransactionID, ExternalTransactionID: req.TransactionID,
Status: "COMPLETED", Status: "COMPLETED",
@ -428,31 +468,23 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
return nil, fmt.Errorf("transaction recording failed") return nil, fmt.Errorf("transaction recording failed")
} }
// 6. Fetch updated balance // --- 6. Return response with updated balance ---
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// if err != nil {
// return nil, fmt.Errorf("Failed to get wallet balance")
// }
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID), ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) { func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) {
// --- 1. Validate token and get user ID ---
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey) claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
if err != nil { if err != nil {
s.logger.Error("Invalid token in promo win request", "error", err) s.logger.Error("Invalid token in promo win request", "error", err)
return nil, fmt.Errorf("invalid token") return nil, fmt.Errorf("invalid token")
} }
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID) // --- 2. Check for duplicate promo win transaction (idempotency) ---
if err != nil {
return nil, fmt.Errorf("Failed to read user wallets")
}
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID) existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil { if err != nil {
s.logger.Error("Failed to check existing promo transaction", "error", err) s.logger.Error("Failed to check existing promo transaction", "error", err)
@ -460,30 +492,48 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
} }
if existingTx != nil && existingTx.TransactionType == "PROMO_WIN" { if existingTx != nil && existingTx.TransactionType == "PROMO_WIN" {
s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID) s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID)
// wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID), ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
// amountCents := int64(req.Amount * 100) // --- 3. Fetch wallet ---
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet for winning PopOk Promo Win", req.Amount)) if err != nil {
return nil, fmt.Errorf("failed to fetch wallet for user %d: %w", claims.UserID, err)
}
// --- 4. Credit user wallet ---
_, err = s.walletSvc.AddToWallet(
ctx,
wallet.RegularID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
domain.PaymentDetails{
ReferenceNumber: domain.ValidString{
Value: req.TransactionID,
Valid: true,
},
},
fmt.Sprintf("Added %v to wallet for winning PopOK Promo Win", req.Amount),
)
if err != nil { if err != nil {
s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err) s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("wallet credit failed") return nil, fmt.Errorf("wallet credit failed")
} }
// --- 5. Record promo win transaction ---
tx := &domain.VirtualGameTransaction{ tx := &domain.VirtualGameTransaction{
UserID: claims.UserID, UserID: claims.UserID,
CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID,
TransactionType: "PROMO_WIN", TransactionType: "PROMO_WIN",
Amount: int64(wallet.RegularBalance), Amount: int64(req.Amount),
Currency: req.Currency, Currency: req.Currency,
ExternalTransactionID: req.TransactionID, ExternalTransactionID: req.TransactionID,
Status: "COMPLETED", Status: "COMPLETED",
@ -491,114 +541,77 @@ func (s *service) ProcessPromoWin(ctx context.Context, req *domain.PopOKWinReque
} }
if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil { if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
s.logger.Error("Failed to create promo win transaction", "error", err) s.logger.Error("Failed to record promo win transaction", "error", err)
return nil, fmt.Errorf("transaction recording failed") return nil, fmt.Errorf("transaction recording failed")
} }
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) // --- 6. Return response with updated balance ---
// if err != nil {
// return nil, fmt.Errorf("failed to read wallets")
// }
return &domain.PopOKWinResponse{ return &domain.PopOKWinResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID), ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
// func (s *service) GenerateNewToken(ctx context.Context, req *domain.PopOKGenerateTokenRequest) (*domain.PopOKGenerateTokenResponse, error) {
// userID, err := strconv.ParseInt(req.PlayerID, 10, 64)
// if err != nil {
// s.logger.Error("Invalid player ID", "playerID", req.PlayerID, "error", err)
// return nil, fmt.Errorf("invalid player ID")
// }
// user, err := s.store.GetUserByID(ctx, userID)
// if err != nil {
// s.logger.Error("Failed to find user for token refresh", "userID", userID, "error", err)
// return nil, fmt.Errorf("user not found")
// }
// newSessionID := fmt.Sprintf("%d-%s-%d", userID, req.GameID, time.Now().UnixNano())
// token, err := jwtutil.CreatePopOKJwt(
// userID,
// user.FirstName,
// req.Currency,
// "en",
// req.Mode,
// newSessionID,
// s.config.PopOK.SecretKey,
// 24*time.Hour,
// )
// if err != nil {
// s.logger.Error("Failed to generate new token", "userID", userID, "error", err)
// return nil, fmt.Errorf("token generation failed")
// }
// return &domain.PopOKGenerateTokenResponse{
// NewToken: token,
// }, nil
// }
func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error) { func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error) {
// 1. Validate token and get user ID // --- 1. Validate token and get user ID ---
claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey) claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
// if err != nil {
// s.logger.Error("Invalid token in cancel request", "error", err)
// return nil, fmt.Errorf("invalid token")
// }
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read user wallets") s.logger.Error("Invalid token in cancel request", "error", err)
return nil, fmt.Errorf("invalid token")
} }
// 2. Find the original bet transaction // --- 2. Fetch wallet ---
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return nil, fmt.Errorf("failed to fetch wallet for user %d: %w", claims.UserID, err)
}
// --- 3. Find the original bet transaction ---
originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID) originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil { if err != nil {
s.logger.Error("Failed to find original bet", "transactionID", req.TransactionID, "error", err) s.logger.Error("Failed to find original bet", "transactionID", req.TransactionID, "error", err)
return nil, fmt.Errorf("original bet not found") return nil, fmt.Errorf("original bet not found")
} }
// 3. Validate the original transaction // --- 4. Validate original transaction ---
if originalBet == nil || originalBet.TransactionType != "BET" { if originalBet == nil || originalBet.TransactionType != "BET" {
s.logger.Error("Invalid original transaction for cancel", "transactionID", req.TransactionID) s.logger.Error("Invalid original transaction for cancel", "transactionID", req.TransactionID)
return nil, fmt.Errorf("invalid original transaction") return nil, fmt.Errorf("invalid original transaction")
} }
// 4. Check if already cancelled // --- 5. Check for duplicate cancellation ---
if originalBet.Status == "CANCELLED" { if originalBet.Status == "CANCELLED" {
s.logger.Warn("Transaction already cancelled", "transactionID", req.TransactionID) s.logger.Warn("Transaction already cancelled", "transactionID", req.TransactionID)
// wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
return &domain.PopOKCancelResponse{ return &domain.PopOKCancelResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", originalBet.ID), ExternalTrxID: fmt.Sprintf("%v", originalBet.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }
// 5. Refund the bet amount (absolute value since bet amount is negative) // --- 6. Refund the bet amount ---
refundAmount := -originalBet.Amount refundAmount := -originalBet.Amount // Bet amounts are negative
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, _, err = s.walletSvc.AddToWallet(
fmt.Sprintf("Added %v to wallet as refund for cancelling PopOk bet", refundAmount), ctx,
wallet.RegularID,
domain.Currency(refundAmount),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
domain.PaymentDetails{
ReferenceNumber: domain.ValidString{
Value: req.TransactionID,
Valid: true,
},
},
fmt.Sprintf("Refunded %v to wallet for cancelling PopOK bet", refundAmount),
) )
if err != nil { if err != nil {
s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err) s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("refund failed") return nil, fmt.Errorf("refund failed")
} }
// userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID) // --- 7. Mark original bet as cancelled and create cancel record ---
// if err != nil {
// return &domain.PopOKCancelResponse{}, fmt.Errorf("Failed to read user wallets")
// }
// 6. Mark original bet as cancelled and create cancel record
cancelTx := &domain.VirtualGameTransaction{ cancelTx := &domain.VirtualGameTransaction{
UserID: claims.UserID, UserID: claims.UserID,
TransactionType: "CANCEL", TransactionType: "CANCEL",
@ -610,6 +623,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
// Update original transaction status
if err := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, "CANCELLED"); err != nil { if err := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, "CANCELLED"); err != nil {
s.logger.Error("Failed to update transaction status", "error", err) s.logger.Error("Failed to update transaction status", "error", err)
return nil, fmt.Errorf("update failed") return nil, fmt.Errorf("update failed")
@ -618,24 +632,18 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
// Create cancel transaction // Create cancel transaction
if err := s.repo.CreateVirtualGameTransaction(ctx, cancelTx); err != nil { if err := s.repo.CreateVirtualGameTransaction(ctx, cancelTx); err != nil {
s.logger.Error("Failed to create cancel transaction", "error", err) s.logger.Error("Failed to create cancel transaction", "error", err)
// Attempt to revert original transaction status
// Attempt to revert the status update
if revertErr := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, originalBet.Status); revertErr != nil { if revertErr := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, originalBet.Status); revertErr != nil {
s.logger.Error("Failed to revert transaction status", "error", revertErr) s.logger.Error("Failed to revert transaction status", "error", revertErr)
} }
return nil, fmt.Errorf("create cancel transaction failed")
return nil, fmt.Errorf("create failed")
} }
// if err != nil { // --- 8. Return response with updated balance ---
// s.logger.Error("Failed to process cancel transaction", "error", err)
// return nil, fmt.Errorf("transaction processing failed")
// }
return &domain.PopOKCancelResponse{ return &domain.PopOKCancelResponse{
TransactionID: req.TransactionID, TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID), ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID),
Balance: float64(wallet.RegularBalance), Balance: float64(wallet.RegularBalance.Float32()),
}, nil }, nil
} }

View File

@ -261,7 +261,7 @@ func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
Amount float64 `json:"amount"` Amount float64 `json:"amount"`
}{ }{
Currency: req.Currency, Currency: req.Currency,
Amount: float64(wallet.RegularBalance), Amount: (float64(wallet.RegularBalance.Float32())),
}, },
} }
@ -285,60 +285,23 @@ 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)
} }
// // --- 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 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)
// }
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")
} }
// realWallet := playerWallets[0]
// realBalance := float64(realWallet.Balance)
// var bonusBalance float64 bonusBalance := float64(wallet.StaticBalance.Float32())
// if len(playerWallets) > 1 {
// bonusBalance = float64(playerWallets[1].Balance)
// }
bonusBalance := float64(wallet.StaticBalance)
// --- 4. Check sufficient balance --- // --- 4. Check sufficient balance ---
// totalBalance := float64(wallet.RegularBalance) + bonusBalance if float64(wallet.RegularBalance.Float32()) < req.Amount.Amount {
if float64(wallet.RegularBalance) < 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 remaining > float64(wallet.RegularBalance) {
return nil, fmt.Errorf("INSUFFICIENT_BALANCE") return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
} }
// --- 6. Persist wallet deductions --- // --- 6. Persist wallet deductions ---
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID, _, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID,
domain.Currency(req.Amount.Amount), domain.ToCurrency(float32(req.Amount.Amount)),
domain.ValidInt64{}, domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.TRANSFER_DIRECT,
fmt.Sprintf("Deduct amount %.2f for bet %s", req.Amount.Amount, req.TransactionID), fmt.Sprintf("Deduct amount %.2f for bet %s", req.Amount.Amount, req.TransactionID),
@ -350,8 +313,8 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
// --- 7. Build response --- // --- 7. Build response ---
res := &domain.BetResponse{ res := &domain.BetResponse{
Real: domain.BalanceDetail{ Real: domain.BalanceDetail{
Currency: "ETB", Currency: req.Amount.Currency,
Amount: float64(wallet.RegularBalance), Amount: float64(wallet.RegularBalance.Float32()) - req.Amount.Amount,
}, },
WalletTransactionID: req.TransactionID, WalletTransactionID: req.TransactionID,
UsedRealAmount: req.Amount.Amount, UsedRealAmount: req.Amount.Amount,
@ -360,7 +323,7 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
if bonusBalance > 0 { if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{ res.Bonus = &domain.BalanceDetail{
Currency: "ETB", Currency: req.Amount.Currency,
Amount: bonusBalance, Amount: bonusBalance,
} }
} }
@ -381,27 +344,20 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
return nil, fmt.Errorf("failed to read user wallets") return nil, fmt.Errorf("failed to read user wallets")
} }
// realWallet := playerWallets[0] // --- 3. Convert balances safely using Float32() ---
realBalance := float64(wallet.RegularBalance) realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32())
// var bonusBalance float64 // --- 4. Apply winnings ---
// if len(playerWallets) > 1 {
// bonusBalance = float64(playerWallets[1].Balance)
// }
bonusBalance := float64(wallet.StaticBalance)
// --- 3. Apply winnings (for now, everything goes to real wallet) ---
winAmount := req.Amount.Amount winAmount := req.Amount.Amount
usedReal := winAmount usedReal := winAmount
usedBonus := 0.0 usedBonus := 0.0
// TODO: If you want to split between bonus/real (e.g. free spins), // Future extension: split winnings between bonus and real wallets if needed
// you can extend logic here based on req.WinType / req.RewardID.
_, err = s.walletSvc.AddToWallet( _, err = s.walletSvc.AddToWallet(
ctx, ctx,
wallet.RegularID, wallet.RegularID,
domain.Currency(winAmount), domain.ToCurrency(float32(winAmount)),
domain.ValidInt64{}, domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.TRANSFER_DIRECT,
domain.PaymentDetails{}, domain.PaymentDetails{},
@ -411,24 +367,11 @@ func (s *Service) ProcessWin(ctx context.Context, req domain.WinRequest) (*domai
return nil, fmt.Errorf("failed to credit real wallet: %w", err) 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(wallet.RegularBalance)
// if len(updatedWallets) > 1 {
// bonusBalance = float64(updatedWallets[1].Balance)
// }
// --- 5. Build response --- // --- 5. Build response ---
res := &domain.WinResponse{ res := &domain.WinResponse{
Real: domain.BalanceDetail{ Real: domain.BalanceDetail{
Currency: req.Amount.Currency, Currency: req.Amount.Currency,
Amount: realBalance, Amount: realBalance + winAmount, // reflect the credited win amount
}, },
WalletTransactionID: req.TransactionID, WalletTransactionID: req.TransactionID,
UsedRealAmount: usedReal, UsedRealAmount: usedReal,
@ -449,7 +392,7 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
// --- 1. Validate PlayerID --- // --- 1. Validate PlayerID ---
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 %q", req.PlayerID) return nil, fmt.Errorf("BAD_REQUEST: invalid PlayerID %q", req.PlayerID)
} }
// --- 2. Get player wallets --- // --- 2. Get player wallets ---
@ -458,15 +401,11 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return nil, fmt.Errorf("failed to read user wallets") return nil, fmt.Errorf("failed to read user wallets")
} }
// realWallet := playerWallets[0] // --- 3. Convert balances using Float32() ---
realBalance := float64(wallet.RegularBalance) realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32())
// var bonusBalance float64 // --- 4. Determine refund amount ---
// if len(playerWallets) > 1 {
bonusBalance := float64(wallet.StaticBalance)
// }
// --- 3. Determine refund amount based on IsAdjustment ---
var refundAmount float64 var refundAmount float64
if req.IsAdjustment { if req.IsAdjustment {
if req.AdjustmentRefund.Amount <= 0 { if req.AdjustmentRefund.Amount <= 0 {
@ -474,7 +413,6 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
} }
refundAmount = req.AdjustmentRefund.Amount refundAmount = req.AdjustmentRefund.Amount
} else { } else {
// Regular cancel: fetch original bet amount if needed
originalTransfer, err := s.transfetStore.GetTransferByReference(ctx, req.RefTransactionID) originalTransfer, err := s.transfetStore.GetTransferByReference(ctx, req.RefTransactionID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get original bet for cancellation: %w", err) return nil, fmt.Errorf("failed to get original bet for cancellation: %w", err)
@ -482,14 +420,14 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
refundAmount = float64(originalTransfer.Amount) refundAmount = float64(originalTransfer.Amount)
} }
// --- 4. Refund to wallet --- // --- 5. Refund to wallet ---
usedReal := refundAmount usedReal := refundAmount
usedBonus := 0.0 usedBonus := 0.0
_, err = s.walletSvc.AddToWallet( _, err = s.walletSvc.AddToWallet(
ctx, ctx,
wallet.RegularID, wallet.RegularID,
domain.Currency(refundAmount), domain.ToCurrency(float32(refundAmount)),
domain.ValidInt64{}, domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.TRANSFER_DIRECT,
domain.PaymentDetails{ domain.PaymentDetails{
@ -505,25 +443,12 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return nil, fmt.Errorf("failed to refund wallet: %w", err) return nil, fmt.Errorf("failed to refund wallet: %w", err)
} }
// --- 5. 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(wallet.RegularBalance)
// if len(updatedWallets) > 1 {
// bonusBalance = float64(updatedWallets[1].Balance)
// }
// --- 6. Build response --- // --- 6. Build response ---
res := &domain.CancelResponse{ res := &domain.CancelResponse{
WalletTransactionID: req.TransactionID, WalletTransactionID: req.TransactionID,
Real: domain.BalanceDetail{ Real: domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency, Currency: req.AdjustmentRefund.Currency,
Amount: realBalance, Amount: realBalance + refundAmount, // reflect refunded balance
}, },
UsedRealAmount: usedReal, UsedRealAmount: usedReal,
UsedBonusAmount: usedBonus, UsedBonusAmount: usedBonus,