215 lines
5.5 KiB
Go
215 lines
5.5 KiB
Go
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)
|
|
}
|