Yimaru-BackEnd/internal/services/virtualGame/Alea/service.go

160 lines
4.6 KiB
Go

package alea
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"log/slog"
"net/url"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)
type AleaPlayService struct {
repo repository.VirtualGameRepository
walletSvc wallet.Service
config *config.AleaPlayConfig
logger *slog.Logger
}
func NewAleaPlayService(
repo repository.VirtualGameRepository,
walletSvc wallet.Service,
cfg *config.Config,
logger *slog.Logger,
) *AleaPlayService {
return &AleaPlayService{
repo: repo,
walletSvc: walletSvc,
config: &cfg.AleaPlay,
logger: logger,
}
}
func (s *AleaPlayService) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
session := &domain.VirtualGameSession{
UserID: userID,
GameID: gameID,
SessionToken: generateSessionToken(userID),
Currency: currency,
Status: "ACTIVE",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
ExpiresAt: time.Now().Add(24 * time.Hour),
}
if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
return "", fmt.Errorf("failed to create game session: %w", err)
}
params := url.Values{
"operator_id": []string{s.config.OperatorID},
"user_id": []string{fmt.Sprintf("%d", userID)},
"game_id": []string{gameID},
"currency": []string{currency},
"session_token": []string{session.SessionToken},
"mode": []string{mode},
"timestamp": []string{fmt.Sprintf("%d", time.Now().Unix())},
}
signature := s.generateSignature(params.Encode())
params.Add("signature", signature)
return fmt.Sprintf("%s/launch?%s", s.config.BaseURL, params.Encode()), nil
}
func (s *AleaPlayService) HandleCallback(ctx context.Context, callback *domain.AleaPlayCallback) error {
if !s.verifyCallbackSignature(callback) {
return errors.New("invalid callback signature")
}
if existing, _ := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.TransactionID); existing != nil {
s.logger.Warn("duplicate transaction detected", "tx_id", callback.TransactionID)
return nil
}
session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
if err != nil {
return fmt.Errorf("failed to get game session: %w", err)
}
tx := &domain.VirtualGameTransaction{
SessionID: session.ID,
UserID: session.UserID,
TransactionType: callback.Type,
Amount: convertAmount(callback.Amount, callback.Type),
Currency: callback.Currency,
ExternalTransactionID: callback.TransactionID,
Status: "COMPLETED",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.processTransaction(ctx, tx, session.UserID); err != nil {
return fmt.Errorf("failed to process transaction: %w", err)
}
// Update session status using the proper repository method
if callback.Type == "SESSION_END" {
if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
s.logger.Error("failed to update session status",
"sessionID", session.ID,
"error", err)
}
}
return nil
}
func convertAmount(amount float64, txType string) int64 {
cents := int64(amount * 100)
if txType == "BET" {
return -cents
}
return cents
}
func (s *AleaPlayService) processTransaction(ctx context.Context, tx *domain.VirtualGameTransaction, userID int64) error {
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
if err != nil || len(wallets) == 0 {
return errors.New("no wallet available for user")
}
tx.WalletID = wallets[0].ID
if _, err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}); err != nil {
return fmt.Errorf("wallet update failed: %w", err)
}
return s.repo.CreateVirtualGameTransaction(ctx, tx)
}
func (s *AleaPlayService) generateSignature(data string) string {
h := hmac.New(sha256.New, []byte(s.config.SecretKey))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func (s *AleaPlayService) verifyCallbackSignature(cb *domain.AleaPlayCallback) bool {
signData := fmt.Sprintf("%s%s%s%.2f%s%d",
cb.TransactionID,
cb.SessionID,
cb.Type,
cb.Amount,
cb.Currency,
cb.Timestamp,
)
expectedSig := s.generateSignature(signData)
return expectedSig == cb.Signature
}
func generateSessionToken(userID int64) string {
return fmt.Sprintf("alea-%d-%d", userID, time.Now().UnixNano())
}