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
restart: always
ports:
- "27017:27017"
- "27020:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: secret

View File

@ -72,168 +72,176 @@ func (s *Service) GetUserData(ctx context.Context, req domain.AtlasGetUserDataRe
// 4. Build response
res := &domain.AtlasGetUserDataResponse{
PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}
return res, nil
}
func (s *Service) ProcessBet(ctx context.Context, req domain.AtlasBetRequest) (*domain.AtlasBetResponse, error) {
// --- 1. Validate 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)
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)
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
// exists, err := s.repo.TransactionExists(ctx, req.TransactionID)
// if err != nil {
// return nil, fmt.Errorf("failed to check transaction: %w", err)
// }
// if exists {
// return nil, ErrDuplicateTransaction
// }
// --- 4. Convert balance using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32())
// // 4. Get current balance
// balance, err := s.walletSvc.GetBalance(ctx, req.PlayerID)
// if err != nil {
// return nil, fmt.Errorf("failed to fetch wallet balance: %w", err)
// }
// 5. Ensure sufficient balance
if float64(wallet.RegularBalance) < req.Amount {
// --- 5. Ensure sufficient balance ---
if realBalance < req.Amount {
return nil, domain.ErrInsufficientBalance
}
// 6. Deduct amount from wallet (record transaction)
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.Amount))
// --- 6. Deduct amount from wallet ---
newBalance := realBalance - req.Amount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(newBalance)))
if err != nil {
return nil, fmt.Errorf("failed to debit wallet: %w", err)
}
// 7. Save transaction record to DB (optional but recommended)
// 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
// --- 7. Build response ---
res := &domain.AtlasBetResponse{
PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance) - req.Amount,
Balance: newBalance,
}
return res, nil
}
func (s *Service) ProcessBetWin(ctx context.Context, req domain.AtlasBetWinRequest) (*domain.AtlasBetWinResponse, error) {
// --- 1. Validate 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)
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)
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
if float64(wallet.RegularBalance) < req.BetAmount {
// --- 4. Convert balance using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32())
// --- 5. Ensure sufficient balance for bet ---
if realBalance < req.BetAmount {
return nil, domain.ErrInsufficientBalance
}
// 6. Deduct amount from wallet (record transaction)
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.Currency(float64(wallet.RegularBalance)-req.BetAmount))
// --- 6. Deduct bet amount ---
debitedBalance := realBalance - req.BetAmount
err = s.walletSvc.UpdateBalance(ctx, wallet.RegularID, domain.ToCurrency(float32(debitedBalance)))
if err != nil {
return nil, fmt.Errorf("failed to debit wallet: %w", err)
}
// --- 7. Apply win amount (if any) ---
finalBalance := debitedBalance
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 {
return nil, fmt.Errorf("failed to credit wallet: %w", err)
}
}
// 8. Build response
// --- 8. Build response ---
res := &domain.AtlasBetWinResponse{
PlayerID: req.PlayerID,
Balance: float64(wallet.RegularBalance),
Balance: finalBalance,
}
return res, nil
}
func (s *Service) ProcessRoundResult(ctx context.Context, req domain.RoundResultRequest) (*domain.RoundResultResponse, error) {
// --- 1. Validate required fields ---
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 {
// This will credit player's balance
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
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)
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 {
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) {
// --- 1. Validate required fields ---
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
// This will credit player's balance
// --- 2. Parse PlayerID ---
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
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)
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)
if err != nil {
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 {
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))
if err != nil {
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 &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) {
@ -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) {
if req.PlayerID == "" || req.TransactionID == "" {
return nil, errors.New("missing player_id or transaction_id")
}
// Credit player with win amount if > 0
if req.Amount > 0 {
// This will credit player's balance
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)
}
if req.Amount <= 0 {
// No winnings to process
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 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
}
func (s *Service) ProcessJackPot(ctx context.Context, req domain.JackpotRequest) (*domain.JackpotResponse, error) {
if req.PlayerID == "" || req.TransactionID == "" {
return nil, errors.New("missing player_id or transaction_id")
}
// Credit player with win amount if > 0
if req.Amount > 0 {
// This will credit player's balance
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid playerID: %w", err)
}
if req.Amount <= 0 {
// No jackpot winnings
return &domain.JackpotResponse{Success: true}, nil
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, playerIDInt)
if err != nil {
return nil, fmt.Errorf("failed to fetch walllets for player %d: %w", playerIDInt, err)
}
playerIDInt, err := strconv.ParseInt(req.PlayerID, 10, 64)
if err != nil {
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{}, "")
if err != nil {
return nil, fmt.Errorf("failed to credit wallet: %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.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

View File

@ -233,42 +233,53 @@ func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfo
return &domain.PopOKPlayerInfoResponse{
Country: "ET",
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),
}, nil
}
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)
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)
// amount := int64(req.Amount)
// Deduct from wallet
// Validate required fields
if req.Amount <= 0 {
return nil, errors.New("invalid bet amount")
}
if req.TransactionID == "" {
return nil, errors.New("missing transaction_id")
}
// Retrieve user's wallet
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
}
_, 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")
return nil, fmt.Errorf("failed to fetch wallet for user %d: %w", claims.UserID, err)
}
// 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{
UserID: claims.UserID,
CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID,
TransactionType: "BET",
Amount: int64(req.Amount), // Negative for bets
Amount: -int64(req.Amount), // Represent bet as negative in accounting
Currency: req.Currency,
ExternalTransactionID: req.TransactionID,
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 {
s.logger.Error("Failed to create bet transaction", "error", err)
return nil, fmt.Errorf("transaction failed")
// Optionally rollback wallet deduction if transaction record fails
_, 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{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID
Balance: float64(wallet.RegularBalance),
ExternalTrxID: fmt.Sprintf("%v", tx.ID), // internal reference
Balance: float64(updatedWallet.RegularBalance.Float32()),
}, nil
}
@ -306,15 +337,15 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
if existingTx != nil && existingTx.TransactionType == "WIN" {
s.logger.Warn("Duplicate win transaction", "transactionID", req.TransactionID)
wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
balance := 0.0
if len(wallets) > 0 {
balance = float64(wallets[0].Balance)
}
wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
return &domain.PopOKWinResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%d", existingTx.ID),
Balance: balance,
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
@ -323,7 +354,7 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
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
@ -359,29 +390,24 @@ func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (
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{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
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)
if err != nil {
s.logger.Error("Invalid token in tournament win request", "error", err)
return nil, fmt.Errorf("invalid token")
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return nil, fmt.Errorf("Failed to read user wallets")
}
// 2. Check for duplicate tournament win transaction
// --- 2. Check for duplicate tournament win transaction (idempotency) ---
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil {
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" {
s.logger.Warn("Duplicate tournament win", "transactionID", req.TransactionID)
// wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
return &domain.PopOKWinResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
// 3. Convert amount to cents
amountCents := int64(req.Amount)
// --- 3. 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)
}
// 4. Credit user wallet
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
fmt.Sprintf("Added %v to wallet for winning Popok Tournament", req.Amount))
// --- 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 Tournament", req.Amount),
)
if err != nil {
s.logger.Error("Failed to credit wallet for tournament", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("wallet credit failed")
}
// 5. Log tournament win transaction
// --- 5. Record tournament win transaction ---
tx := &domain.VirtualGameTransaction{
UserID: claims.UserID,
CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID,
TransactionType: "TOURNAMENT_WIN",
Amount: amountCents,
Amount: int64(req.Amount),
Currency: req.Currency,
ExternalTransactionID: req.TransactionID,
Status: "COMPLETED",
@ -428,31 +468,23 @@ func (s *service) ProcessTournamentWin(ctx context.Context, req *domain.PopOKWin
return nil, fmt.Errorf("transaction recording failed")
}
// 6. Fetch updated balance
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// if err != nil {
// return nil, fmt.Errorf("Failed to get wallet balance")
// }
// --- 6. Return response with updated balance ---
return &domain.PopOKWinResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
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)
if err != nil {
s.logger.Error("Invalid token in promo win request", "error", err)
return nil, fmt.Errorf("invalid token")
}
wallet, err := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
if err != nil {
return nil, fmt.Errorf("Failed to read user wallets")
}
// --- 2. Check for duplicate promo win transaction (idempotency) ---
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
if err != nil {
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" {
s.logger.Warn("Duplicate promo win", "transactionID", req.TransactionID)
// wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// balance := 0.0
// if len(wallets) > 0 {
// balance = float64(wallets[0].Balance)
// }
wallet, _ := s.walletSvc.GetCustomerWallet(ctx, claims.UserID)
return &domain.PopOKWinResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", existingTx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
// amountCents := int64(req.Amount * 100)
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{},
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet for winning PopOk Promo Win", req.Amount))
// --- 3. 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)
}
// --- 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 {
s.logger.Error("Failed to credit wallet for promo", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("wallet credit failed")
}
// --- 5. Record promo win transaction ---
tx := &domain.VirtualGameTransaction{
UserID: claims.UserID,
CompanyID: claims.CompanyID.Value,
Provider: string(domain.PROVIDER_POPOK),
GameID: req.GameID,
TransactionType: "PROMO_WIN",
Amount: int64(wallet.RegularBalance),
Amount: int64(req.Amount),
Currency: req.Currency,
ExternalTransactionID: req.TransactionID,
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 {
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")
}
// wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// if err != nil {
// return nil, fmt.Errorf("failed to read wallets")
// }
// --- 6. Return response with updated balance ---
return &domain.PopOKWinResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", tx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, 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) {
// 1. Validate token and get user ID
// --- 1. Validate token and get user ID ---
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 {
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)
if err != nil {
s.logger.Error("Failed to find original bet", "transactionID", req.TransactionID, "error", err)
return nil, fmt.Errorf("original bet not found")
}
// 3. Validate the original transaction
// --- 4. Validate original transaction ---
if originalBet == nil || originalBet.TransactionType != "BET" {
s.logger.Error("Invalid original transaction for cancel", "transactionID", req.TransactionID)
return nil, fmt.Errorf("invalid original transaction")
}
// 4. Check if already cancelled
// --- 5. Check for duplicate cancellation ---
if originalBet.Status == "CANCELLED" {
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{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", originalBet.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}
// 5. Refund the bet amount (absolute value since bet amount is negative)
refundAmount := -originalBet.Amount
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(refundAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
fmt.Sprintf("Added %v to wallet as refund for cancelling PopOk bet", refundAmount),
// --- 6. Refund the bet 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{
ReferenceNumber: domain.ValidString{
Value: req.TransactionID,
Valid: true,
},
},
fmt.Sprintf("Refunded %v to wallet for cancelling PopOK bet", refundAmount),
)
if err != nil {
s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err)
return nil, fmt.Errorf("refund failed")
}
// userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
// if err != nil {
// return &domain.PopOKCancelResponse{}, fmt.Errorf("Failed to read user wallets")
// }
// 6. Mark original bet as cancelled and create cancel record
// --- 7. Mark original bet as cancelled and create cancel record ---
cancelTx := &domain.VirtualGameTransaction{
UserID: claims.UserID,
TransactionType: "CANCEL",
@ -610,6 +623,7 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
CreatedAt: time.Now(),
}
// Update original transaction status
if err := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, "CANCELLED"); err != nil {
s.logger.Error("Failed to update transaction status", "error", err)
return nil, fmt.Errorf("update failed")
@ -618,24 +632,18 @@ func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequ
// Create cancel transaction
if err := s.repo.CreateVirtualGameTransaction(ctx, cancelTx); err != nil {
s.logger.Error("Failed to create cancel transaction", "error", err)
// Attempt to revert the status update
// Attempt to revert original transaction status
if revertErr := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, originalBet.Status); revertErr != nil {
s.logger.Error("Failed to revert transaction status", "error", revertErr)
}
return nil, fmt.Errorf("create failed")
return nil, fmt.Errorf("create cancel transaction failed")
}
// if err != nil {
// s.logger.Error("Failed to process cancel transaction", "error", err)
// return nil, fmt.Errorf("transaction processing failed")
// }
// --- 8. Return response with updated balance ---
return &domain.PopOKCancelResponse{
TransactionID: req.TransactionID,
ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID),
Balance: float64(wallet.RegularBalance),
Balance: float64(wallet.RegularBalance.Float32()),
}, nil
}

View File

@ -261,7 +261,7 @@ func (s *Service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*d
Amount float64 `json:"amount"`
}{
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)
}
// // --- 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)
if err != nil {
return nil, fmt.Errorf("failed to read user wallets")
}
// realWallet := playerWallets[0]
// realBalance := float64(realWallet.Balance)
// var bonusBalance float64
// if len(playerWallets) > 1 {
// bonusBalance = float64(playerWallets[1].Balance)
// }
bonusBalance := float64(wallet.StaticBalance)
bonusBalance := float64(wallet.StaticBalance.Float32())
// --- 4. Check sufficient balance ---
// totalBalance := float64(wallet.RegularBalance) + bonusBalance
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) {
if float64(wallet.RegularBalance.Float32()) < req.Amount.Amount {
return nil, fmt.Errorf("INSUFFICIENT_BALANCE")
}
// --- 6. Persist wallet deductions ---
_, err = s.walletSvc.DeductFromWallet(ctx, wallet.RegularID,
domain.Currency(req.Amount.Amount),
domain.ToCurrency(float32(req.Amount.Amount)),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
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 ---
res := &domain.BetResponse{
Real: domain.BalanceDetail{
Currency: "ETB",
Amount: float64(wallet.RegularBalance),
Currency: req.Amount.Currency,
Amount: float64(wallet.RegularBalance.Float32()) - req.Amount.Amount,
},
WalletTransactionID: req.TransactionID,
UsedRealAmount: req.Amount.Amount,
@ -360,7 +323,7 @@ func (s *Service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domai
if bonusBalance > 0 {
res.Bonus = &domain.BalanceDetail{
Currency: "ETB",
Currency: req.Amount.Currency,
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")
}
// realWallet := playerWallets[0]
realBalance := float64(wallet.RegularBalance)
// --- 3. Convert balances safely using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32())
// var bonusBalance float64
// if len(playerWallets) > 1 {
// bonusBalance = float64(playerWallets[1].Balance)
// }
bonusBalance := float64(wallet.StaticBalance)
// --- 3. Apply winnings (for now, everything goes to real wallet) ---
// --- 4. Apply winnings ---
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.
// Future extension: split winnings between bonus and real wallets if needed
_, err = s.walletSvc.AddToWallet(
ctx,
wallet.RegularID,
domain.Currency(winAmount),
domain.ToCurrency(float32(winAmount)),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
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)
}
// // --- 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 ---
res := &domain.WinResponse{
Real: domain.BalanceDetail{
Currency: req.Amount.Currency,
Amount: realBalance,
Amount: realBalance + winAmount, // reflect the credited win amount
},
WalletTransactionID: req.TransactionID,
UsedRealAmount: usedReal,
@ -449,7 +392,7 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
// --- 1. Validate PlayerID ---
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
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 ---
@ -458,15 +401,11 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
return nil, fmt.Errorf("failed to read user wallets")
}
// realWallet := playerWallets[0]
realBalance := float64(wallet.RegularBalance)
// --- 3. Convert balances using Float32() ---
realBalance := float64(wallet.RegularBalance.Float32())
bonusBalance := float64(wallet.StaticBalance.Float32())
// var bonusBalance float64
// if len(playerWallets) > 1 {
bonusBalance := float64(wallet.StaticBalance)
// }
// --- 3. Determine refund amount based on IsAdjustment ---
// --- 4. Determine refund amount ---
var refundAmount float64
if req.IsAdjustment {
if req.AdjustmentRefund.Amount <= 0 {
@ -474,7 +413,6 @@ func (s *Service) ProcessCancel(ctx context.Context, req domain.CancelRequest) (
}
refundAmount = req.AdjustmentRefund.Amount
} else {
// Regular cancel: fetch original bet amount if needed
originalTransfer, err := s.transfetStore.GetTransferByReference(ctx, req.RefTransactionID)
if err != nil {
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)
}
// --- 4. Refund to wallet ---
// --- 5. Refund to wallet ---
usedReal := refundAmount
usedBonus := 0.0
_, err = s.walletSvc.AddToWallet(
ctx,
wallet.RegularID,
domain.Currency(refundAmount),
domain.ToCurrency(float32(refundAmount)),
domain.ValidInt64{},
domain.TRANSFER_DIRECT,
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)
}
// --- 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 ---
res := &domain.CancelResponse{
WalletTransactionID: req.TransactionID,
Real: domain.BalanceDetail{
Currency: req.AdjustmentRefund.Currency,
Amount: realBalance,
Amount: realBalance + refundAmount, // reflect refunded balance
},
UsedRealAmount: usedReal,
UsedBonusAmount: usedBonus,