package referralservice import ( "context" "crypto/rand" "encoding/base32" "errors" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" ) type Service struct { repo repository.ReferralRepository walletSvc wallet.Service store *repository.Store } func NewService(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store) *Service { return &Service{ repo: repo, walletSvc: walletSvc, store: store, } } var ( ErrInvalidReferral = errors.New("invalid or expired referral") ErrInvalidReferralSignup = errors.New("referral requires phone signup") ErrUserNotFound = errors.New("user not found") ErrNoReferralFound = errors.New("no referral found for this user") ) func (s *Service) GenerateReferralCode() (string, error) { b := make([]byte, 8) if _, err := rand.Read(b); err != nil { return "", err } return base32.StdEncoding.EncodeToString(b)[:10], nil } func (s *Service) CreateReferral(ctx context.Context, userPhone string) (*domain.Referral, error) { settings, err := s.repo.GetSettings(ctx) if err != nil { return nil, err } code, err := s.GenerateReferralCode() if err != nil { return nil, err } userID, err := strconv.ParseInt(userPhone, 10, 64) if err != nil { return nil, errors.New("invalid phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) if err != nil { return nil, err } if len(wallets) == 0 { _, err = s.walletSvc.CreateWallet(ctx, domain.CreateWallet{ IsWithdraw: true, IsBettable: true, UserID: userID, }) if err != nil { return nil, err } } referral := &domain.Referral{ ReferrerID: userPhone, ReferralCode: code, Status: domain.ReferralPending, RewardAmount: settings.ReferralRewardAmount, ExpiresAt: time.Now().Add(time.Duration(settings.ExpiresAfterDays) * 24 * time.Hour), CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.repo.CreateReferral(ctx, referral); err != nil { return nil, err } return referral, nil } func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string) error { referral, err := s.repo.GetReferralByCode(ctx, referralCode) if err != nil { return err } if referral == nil || referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) { return ErrInvalidReferral } user, err := s.store.GetUserByPhone(ctx, referredPhone) if err != nil { if errors.Is(err, domain.ErrUserNotFound) { return ErrUserNotFound } return err } if !user.PhoneVerified { return ErrInvalidReferralSignup } referral.ReferredID = &referredPhone referral.Status = domain.ReferralCompleted referral.UpdatedAt = time.Now() if err := s.repo.UpdateReferral(ctx, referral); err != nil { return err } referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) if err != nil { return errors.New("invalid referrer phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) if err != nil { return err } if len(wallets) == 0 { return errors.New("referrer has no wallet") } walletID := wallets[0].ID currentBonus := float64(wallets[0].Balance) return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100))) } func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error { settings, err := s.repo.GetSettings(ctx) if err != nil { return err } userID, err := strconv.ParseInt(userPhone, 10, 64) if err != nil { return errors.New("invalid phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) if err != nil { return err } if len(wallets) == 0 { return errors.New("user has no wallet") } walletID := wallets[0].ID bonus := amount * (settings.CashbackPercentage / 100) currentBonus := float64(wallets[0].Balance) return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100))) } func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error { settings, err := s.repo.GetSettings(ctx) if err != nil { return err } referral, err := s.repo.GetReferralByReferredID(ctx, userPhone) if err != nil { return err } if referral == nil || referral.Status != domain.ReferralCompleted { return ErrNoReferralFound } referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64) if err != nil { return errors.New("invalid referrer phone number format") } wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID) if err != nil { return err } if len(wallets) == 0 { return errors.New("referrer has no wallet") } bonusPercentage := settings.BetReferralBonusPercentage if bonusPercentage == 0 { bonusPercentage = 5.0 } bonus := betAmount * (bonusPercentage / 100) walletID := wallets[0].ID currentBalance := float64(wallets[0].Balance) return s.walletSvc.Add(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100))) } func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) { return s.repo.GetReferralStats(ctx, userPhone) } func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error { settings.UpdatedAt = time.Now() return s.repo.UpdateSettings(ctx, settings) } func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) { return s.repo.GetSettings(ctx) }