package bonus import ( "context" "errors" "fmt" "math" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/ports" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notification" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "go.uber.org/zap" ) type Service struct { bonusStore ports.BonusStore walletSvc *wallet.Service settingSvc *settings.Service notificationSvc *notificationservice.Service mongoLogger *zap.Logger } func NewService( bonusStore ports.BonusStore, walletSvc *wallet.Service, settingSvc *settings.Service, notificationSvc *notificationservice.Service, mongoLogger *zap.Logger, ) *Service { return &Service{ bonusStore: bonusStore, walletSvc: walletSvc, settingSvc: settingSvc, notificationSvc: notificationSvc, mongoLogger: mongoLogger, } } var ( ErrWelcomeBonusNotActive = errors.New("welcome bonus is not active") ErrWelcomeBonusCountReached = errors.New("welcome bonus max deposit count reached") ) func (s *Service) CreateWelcomeBonus(ctx context.Context, amount domain.Currency, companyID int64, userID int64) error { settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, companyID) if err != nil { s.mongoLogger.Error("Failed to get settings", zap.Int64("companyID", companyID), zap.Error(err)) return err } if !settingsList.WelcomeBonusActive { return ErrWelcomeBonusNotActive } wallet, err := s.walletSvc.GetCustomerWallet(ctx, userID) if err != nil { return err } stats, err := s.walletSvc.GetTransferStats(ctx, wallet.ID) if err != nil { return err } if stats.TotalDeposits > settingsList.WelcomeBonusCount { return ErrWelcomeBonusCountReached } newBalance := math.Min(float64(amount)*float64(settingsList.WelcomeBonusMultiplier), float64(settingsList.WelcomeBonusCap)) bonus, err := s.CreateUserBonus(ctx, domain.CreateBonus{ Name: "Welcome Bonus", Description: fmt.Sprintf("Awarded for deposit number (%v / %v)", stats.TotalDeposits, settingsList.WelcomeBonusCount), UserID: userID, Type: domain.WelcomeBonus, RewardAmount: domain.Currency(newBalance), ExpiresAt: time.Now().Add(time.Duration(settingsList.WelcomeBonusExpire) * 24 * time.Hour), }) if err != nil { return err } err = s.SendBonusNotification(ctx, SendBonusNotificationParam{ BonusID: bonus.ID, UserID: userID, Type: domain.DepositBonus, Amount: domain.Currency(newBalance), SendEmail: true, SendSMS: false, }) if err != nil { return err } return nil } var ( ErrBonusIsAlreadyClaimed = errors.New("bonus is already claimed") ErrBonusUserIDNotMatch = errors.New("bonus user id is not a match") ) func (s *Service) ProcessBonusClaim(ctx context.Context, bonusID, userID int64) error { bonus, err := s.GetBonusByID(ctx, bonusID) if err != nil { return err } if bonus.UserID != userID { } if bonus.IsClaimed { return ErrBonusIsAlreadyClaimed } wallet, err := s.walletSvc.GetCustomerWallet(ctx, bonus.UserID) if err != nil { return err } _, err = s.walletSvc.AddToWallet( ctx, wallet.StaticID, bonus.RewardAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to bonus wallet due to %v", bonus.RewardAmount, bonus.Type), ) if err != nil { return err } if err := s.UpdateUserBonus(ctx, bonusID, true); err != nil { return err } return nil } func (s *Service) CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) { return s.bonusStore.CreateUserBonus(ctx, bonus) } func (s *Service) GetAllUserBonuses(ctx context.Context, filter domain.BonusFilter) ([]domain.UserBonus, error) { return s.bonusStore.GetAllUserBonuses(ctx, filter) } func (s *Service) GetBonusCount(ctx context.Context, filter domain.BonusFilter) (int64, error) { return s.bonusStore.GetBonusCount(ctx, filter) } func (s *Service) GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error) { return s.bonusStore.GetBonusByID(ctx, bonusID) } func (s *Service) GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error) { return s.bonusStore.GetBonusStats(ctx, filter) } func (s *Service) UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) error { return s.bonusStore.UpdateUserBonus(ctx, bonusID, IsClaimed) } func (s *Service) DeleteUserBonus(ctx context.Context, bonusID int64) error { return s.bonusStore.DeleteUserBonus(ctx, bonusID) }