package bet import ( "context" "crypto/rand" "encoding/json" "errors" "fmt" "log/slog" "math/big" random "math/rand" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "go.uber.org/zap" ) var ( ErrNoEventsAvailable = errors.New("Not enough events available with the given filters") ErrGenerateRandomOutcome = errors.New("Failed to generate any random outcome for events") ErrOutcomesNotCompleted = errors.New("Some bet outcomes are still pending") ErrEventHasBeenRemoved = errors.New("Event has been removed") ) type Service struct { betStore BetStore eventSvc event.Service prematchSvc odds.ServiceImpl walletSvc wallet.Service branchSvc branch.Service logger *slog.Logger mongoLogger *zap.Logger } func NewService(betStore BetStore, eventSvc event.Service, prematchSvc odds.ServiceImpl, walletSvc wallet.Service, branchSvc branch.Service, logger *slog.Logger, mongoLogger *zap.Logger) *Service { return &Service{ betStore: betStore, eventSvc: eventSvc, prematchSvc: prematchSvc, walletSvc: walletSvc, branchSvc: branchSvc, logger: logger, mongoLogger: mongoLogger, } } var ( ErrEventHasNotEnded = errors.New("Event has not ended yet") ErrRawOddInvalid = errors.New("Prematch Raw Odd is Invalid") ErrBranchIDRequired = errors.New("Branch ID required for this role") ErrOutcomeLimit = errors.New("Too many outcomes on a single bet") ) func (s *Service) GenerateCashoutID() (string, error) { const chars = "abcdefghijklmnopqrstuvwxyz0123456789" const length int = 13 charLen := big.NewInt(int64(len(chars))) result := make([]byte, length) for i := 0; i < length; i++ { index, err := rand.Int(rand.Reader, charLen) if err != nil { s.mongoLogger.Error("failed to generate random index for cashout ID", zap.Int("position", i), zap.Error(err), ) return "", err } result[i] = chars[index.Int64()] } return string(result), nil } func (s *Service) GenerateBetOutcome(ctx context.Context, eventID int64, marketID int64, oddID int64) (domain.CreateBetOutcome, error) { eventIDStr := strconv.FormatInt(eventID, 10) marketIDStr := strconv.FormatInt(marketID, 10) oddIDStr := strconv.FormatInt(oddID, 10) event, err := s.eventSvc.GetUpcomingEventByID(ctx, eventIDStr) if err != nil { s.mongoLogger.Error("failed to fetch upcoming event by ID", zap.Int64("event_id", eventID), zap.Error(err), ) return domain.CreateBetOutcome{}, ErrEventHasBeenRemoved } currentTime := time.Now() if event.StartTime.Before(currentTime) { s.mongoLogger.Error("event has already started", zap.Int64("event_id", eventID), zap.Time("event_start_time", event.StartTime), zap.Time("current_time", currentTime), ) return domain.CreateBetOutcome{}, ErrEventHasNotEnded } odds, err := s.prematchSvc.GetRawOddsByMarketID(ctx, marketIDStr, eventIDStr) if err != nil { s.mongoLogger.Error("failed to get raw odds by market ID", zap.Int64("event_id", eventID), zap.Int64("market_id", marketID), zap.Error(err), ) return domain.CreateBetOutcome{}, err } type rawOddType struct { ID string Name string Odds string Header string Handicap string } var selectedOdd rawOddType var isOddFound bool for _, raw := range odds.RawOdds { var rawOdd rawOddType rawBytes, err := json.Marshal(raw) if err != nil { s.mongoLogger.Error("failed to marshal raw odd", zap.Any("raw", raw), zap.Error(err), ) continue } err = json.Unmarshal(rawBytes, &rawOdd) if err != nil { s.mongoLogger.Error("failed to unmarshal raw odd", zap.ByteString("raw_bytes", rawBytes), zap.Error(err), ) continue } if rawOdd.ID == oddIDStr { selectedOdd = rawOdd isOddFound = true break } } if !isOddFound { s.mongoLogger.Error("odd ID not found in raw odds", zap.Int64("odd_id", oddID), zap.Int64("market_id", marketID), zap.Int64("event_id", eventID), ) return domain.CreateBetOutcome{}, ErrRawOddInvalid } parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) if err != nil { s.mongoLogger.Error("failed to parse selected odd value", zap.String("odd", selectedOdd.Odds), zap.Int64("odd_id", oddID), zap.Error(err), ) return domain.CreateBetOutcome{}, err } newOutcome := domain.CreateBetOutcome{ EventID: eventID, OddID: oddID, MarketID: marketID, SportID: int64(event.SportID), HomeTeamName: event.HomeTeam, AwayTeamName: event.AwayTeam, MarketName: odds.MarketName, Odd: float32(parsedOdd), OddName: selectedOdd.Name, OddHeader: selectedOdd.Header, OddHandicap: selectedOdd.Handicap, Expires: event.StartTime, } return newOutcome, nil } func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID int64, role domain.Role) (domain.CreateBetRes, error) { if len(req.Outcomes) > 30 { s.mongoLogger.Error("too many outcomes", zap.Int("count", len(req.Outcomes)), zap.Int64("user_id", userID), ) return domain.CreateBetRes{}, ErrOutcomeLimit } var outcomes []domain.CreateBetOutcome = make([]domain.CreateBetOutcome, 0, len(req.Outcomes)) var totalOdds float32 = 1 for _, outcomeReq := range req.Outcomes { newOutcome, err := s.GenerateBetOutcome(ctx, outcomeReq.EventID, outcomeReq.MarketID, outcomeReq.OddID) if err != nil { s.mongoLogger.Error("failed to generate outcome", zap.Int64("event_id", outcomeReq.EventID), zap.Int64("market_id", outcomeReq.MarketID), zap.Int64("odd_id", outcomeReq.OddID), zap.Int64("user_id", userID), zap.Error(err), ) return domain.CreateBetRes{}, err } totalOdds *= float32(newOutcome.Odd) outcomes = append(outcomes, newOutcome) } cashoutID, err := s.GenerateCashoutID() if err != nil { s.mongoLogger.Error("failed to generate cashout ID", zap.Int64("user_id", userID), zap.Error(err), ) return domain.CreateBetRes{}, err } newBet := domain.CreateBet{ Amount: domain.ToCurrency(req.Amount), TotalOdds: totalOdds, Status: req.Status, FullName: req.FullName, PhoneNumber: req.PhoneNumber, CashoutID: cashoutID, } switch role { case domain.RoleCashier: branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) if err != nil { s.mongoLogger.Error("failed to get branch by cashier", zap.Int64("user_id", userID), zap.Error(err), ) return domain.CreateBetRes{}, err } deductedAmount := req.Amount / 10 err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) if err != nil { s.mongoLogger.Error("failed to deduct from wallet", zap.Int64("wallet_id", branch.WalletID), zap.Float32("amount", deductedAmount), zap.Error(err), ) return domain.CreateBetRes{}, err } newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true} newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true} newBet.UserID = domain.ValidInt64{Value: userID, Valid: true} newBet.IsShopBet = true case domain.RoleBranchManager, domain.RoleAdmin, domain.RoleSuperAdmin: if req.BranchID == nil { s.mongoLogger.Error("branch ID required for admin/manager", zap.Int64("user_id", userID), ) return domain.CreateBetRes{}, ErrBranchIDRequired } branch, err := s.branchSvc.GetBranchByID(ctx, *req.BranchID) if err != nil { s.mongoLogger.Error("failed to get branch by ID", zap.Int64("branch_id", *req.BranchID), zap.Error(err), ) return domain.CreateBetRes{}, err } deductedAmount := req.Amount / 10 err = s.walletSvc.DeductFromWallet(ctx, branch.WalletID, domain.ToCurrency(deductedAmount)) if err != nil { s.mongoLogger.Error("wallet deduction failed", zap.Int64("wallet_id", branch.WalletID), zap.Float32("amount", deductedAmount), zap.Error(err), ) return domain.CreateBetRes{}, err } newBet.BranchID = domain.ValidInt64{Value: branch.ID, Valid: true} newBet.CompanyID = domain.ValidInt64{Value: branch.CompanyID, Valid: true} newBet.UserID = domain.ValidInt64{Value: userID, Valid: true} newBet.IsShopBet = true case domain.RoleCustomer: wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID) if err != nil { s.mongoLogger.Error("failed to get customer wallets", zap.Int64("user_id", userID), zap.Error(err), ) return domain.CreateBetRes{}, err } userWallet := wallets[0] err = s.walletSvc.DeductFromWallet(ctx, userWallet.ID, domain.ToCurrency(req.Amount)) if err != nil { s.mongoLogger.Error("wallet deduction failed for customer", zap.Int64("wallet_id", userWallet.ID), zap.Float32("amount", req.Amount), zap.Error(err), ) return domain.CreateBetRes{}, err } newBet.UserID = domain.ValidInt64{Value: userID, Valid: true} newBet.IsShopBet = false default: s.mongoLogger.Error("unknown role type", zap.String("role", string(role)), zap.Int64("user_id", userID), ) return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type") } bet, err := s.CreateBet(ctx, newBet) if err != nil { s.mongoLogger.Error("failed to create bet", zap.Int64("user_id", userID), zap.Error(err), ) return domain.CreateBetRes{}, err } for i := range outcomes { outcomes[i].BetID = bet.ID } rows, err := s.betStore.CreateBetOutcome(ctx, outcomes) if err != nil { s.mongoLogger.Error("failed to create bet outcomes", zap.Int64("bet_id", bet.ID), zap.Error(err), ) return domain.CreateBetRes{}, err } res := domain.ConvertCreateBet(bet, rows) return res, nil } func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string, sportID int32, HomeTeam, AwayTeam string, StartTime time.Time, numMarkets int) ([]domain.CreateBetOutcome, float32, error) { var newOdds []domain.CreateBetOutcome var totalOdds float32 = 1 markets, err := s.prematchSvc.GetPrematchOddsByUpcomingID(ctx, eventID) if err != nil { s.logger.Error("failed to get odds for event", "event id", eventID, "error", err) s.mongoLogger.Error("failed to get odds for event", zap.String("eventID", eventID), zap.Int32("sportID", sportID), zap.String("homeTeam", HomeTeam), zap.String("awayTeam", AwayTeam), zap.Error(err)) return nil, 0, err } if len(markets) == 0 { s.logger.Error("empty odds for event", "event id", eventID) s.mongoLogger.Warn("empty odds for event", zap.String("eventID", eventID), zap.Int32("sportID", sportID), zap.String("homeTeam", HomeTeam), zap.String("awayTeam", AwayTeam)) return nil, 0, fmt.Errorf("empty odds or event %v", eventID) } var selectedMarkets []domain.Odd numMarkets = min(numMarkets, len(markets)) for i := 0; i < numMarkets; i++ { randomIndex := random.Intn(len(markets)) selectedMarkets = append(selectedMarkets, markets[randomIndex]) markets = append(markets[:randomIndex], markets[randomIndex+1:]...) } for _, market := range selectedMarkets { randomRawOdd := market.RawOdds[random.Intn(len(market.RawOdds))] type rawOddType struct { ID string Name string Odds string Header string Handicap string } var selectedOdd rawOddType rawBytes, err := json.Marshal(randomRawOdd) err = json.Unmarshal(rawBytes, &selectedOdd) if err != nil { s.logger.Error("Failed to unmarshal raw odd", "error", err) s.mongoLogger.Warn("Failed to unmarshal raw odd", zap.String("eventID", eventID), zap.Int32("sportID", sportID), zap.Error(err)) continue } parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32) if err != nil { s.logger.Error("Failed to parse odd", "error", err) s.mongoLogger.Warn("Failed to parse odd", zap.String("eventID", eventID), zap.String("oddValue", selectedOdd.Odds), zap.Error(err)) continue } eventIDInt, err := strconv.ParseInt(eventID, 10, 64) if err != nil { s.logger.Error("Failed to parse eventID", "error", err) s.mongoLogger.Warn("Failed to parse eventID", zap.String("eventID", eventID), zap.Error(err)) continue } oddID, err := strconv.ParseInt(selectedOdd.ID, 10, 64) if err != nil { s.logger.Error("Failed to parse oddID", "error", err) s.mongoLogger.Warn("Failed to parse oddID", zap.String("oddID", selectedOdd.ID), zap.Error(err)) continue } marketID, err := strconv.ParseInt(market.MarketID, 10, 64) if err != nil { s.logger.Error("Failed to parse marketID", "error", err) s.mongoLogger.Warn("Failed to parse marketID", zap.String("marketID", market.MarketID), zap.Error(err)) continue } marketName := market.MarketName newOdds = append(newOdds, domain.CreateBetOutcome{ EventID: eventIDInt, OddID: oddID, MarketID: marketID, SportID: int64(sportID), HomeTeamName: HomeTeam, AwayTeamName: AwayTeam, MarketName: marketName, Odd: float32(parsedOdd), OddName: selectedOdd.Name, OddHeader: selectedOdd.Header, OddHandicap: selectedOdd.Handicap, Expires: StartTime, }) totalOdds *= float32(parsedOdd) } if len(newOdds) == 0 { s.logger.Error("Bet Outcomes is empty for market", "selectedMarkets", len(selectedMarkets)) s.mongoLogger.Error("Bet Outcomes is empty for market", zap.String("eventID", eventID), zap.Int32("sportID", sportID), zap.String("homeTeam", HomeTeam), zap.String("awayTeam", AwayTeam), zap.Int("selectedMarkets", len(selectedMarkets))) return nil, 0, ErrGenerateRandomOutcome } // ✅ Final success log (optional) s.mongoLogger.Info("Random bet outcomes generated successfully", zap.String("eventID", eventID), zap.Int32("sportID", sportID), zap.Int("numOutcomes", len(newOdds)), zap.Float32("totalOdds", totalOdds)) return newOdds, totalOdds, nil } func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID int64, leagueID, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) { // Get a unexpired event id events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx, domain.EventFilter{ SportID: sportID, LeagueID: leagueID, FirstStartTime: firstStartTime, LastStartTime: lastStartTime, }) if err != nil { s.mongoLogger.Error("failed to get paginated upcoming events", zap.Int64("userID", userID), zap.Int64("branchID", branchID), zap.Error(err)) return domain.CreateBetRes{}, err } if len(events) == 0 { s.mongoLogger.Warn("no events available for random bet", zap.Int64("userID", userID), zap.Int64("branchID", branchID)) return domain.CreateBetRes{}, ErrNoEventsAvailable } // TODO: Add the option of passing number of created events var selectedUpcomingEvents []domain.UpcomingEvent numEventsPerBet := min(random.Intn(4)+1, len(events)) //Eliminate the option of 0 for i := 0; i < int(numEventsPerBet); i++ { randomIndex := random.Intn(len(events)) selectedUpcomingEvents = append(selectedUpcomingEvents, events[randomIndex]) events = append(events[:randomIndex], events[randomIndex+1:]...) } // s.logger.Info("Generating random bet events", "selectedUpcomingEvents", len(selectedUpcomingEvents)) // Get market and odds for that var randomOdds []domain.CreateBetOutcome var totalOdds float32 = 1 numMarketsPerBet := random.Intn(2) + 1 for _, event := range selectedUpcomingEvents { newOdds, total, err := s.GenerateRandomBetOutcomes(ctx, event.ID, event.SportID, event.HomeTeam, event.AwayTeam, event.StartTime, numMarketsPerBet) if err != nil { s.logger.Error("failed to generate random bet outcome", "event id", event.ID, "error", err) s.mongoLogger.Error("failed to generate random bet outcome", zap.Int64("userID", userID), zap.Int64("branchID", branchID), zap.String("eventID", event.ID), zap.String("error", fmt.Sprintf("%v", err))) continue } randomOdds = append(randomOdds, newOdds...) totalOdds = totalOdds * total } if len(randomOdds) == 0 { s.logger.Error("Failed to generate random any outcomes for all events") s.mongoLogger.Error("Failed to generate random any outcomes for all events", zap.Int64("userID", userID), zap.Int64("branchID", branchID)) return domain.CreateBetRes{}, ErrGenerateRandomOutcome } // s.logger.Info("Generated Random bet Outcome", "randomOdds", len(randomOdds)) var cashoutID string cashoutID, err = s.GenerateCashoutID() if err != nil { s.mongoLogger.Error("Failed to generate cash out ID", zap.Int64("userID", userID), zap.Int64("branchID", branchID)) return domain.CreateBetRes{}, err } randomNumber := strconv.FormatInt(int64(random.Intn(100000000000)), 10) newBet := domain.CreateBet{ Amount: domain.ToCurrency(123.5), TotalOdds: totalOdds, Status: domain.OUTCOME_STATUS_PENDING, FullName: "test" + randomNumber, PhoneNumber: "0900000000", CashoutID: cashoutID, BranchID: domain.ValidInt64{Valid: true, Value: branchID}, UserID: domain.ValidInt64{Valid: true, Value: userID}, } bet, err := s.CreateBet(ctx, newBet) if err != nil { s.mongoLogger.Error("Failed to create a new random bet", zap.Int64("userID", userID), zap.Int64("branchID", branchID), zap.String("bet", fmt.Sprintf("%+v", newBet))) return domain.CreateBetRes{}, err } for i := range randomOdds { randomOdds[i].BetID = bet.ID } rows, err := s.betStore.CreateBetOutcome(ctx, randomOdds) if err != nil { s.mongoLogger.Error("Failed to create a new random bet outcome", zap.Int64("userID", userID), zap.Int64("branchID", branchID), zap.String("randomOdds", fmt.Sprintf("%+v", randomOdds))) return domain.CreateBetRes{}, err } res := domain.ConvertCreateBet(bet, rows) s.mongoLogger.Info("Random bets placed successfully", zap.Int64("userID", userID), zap.Int64("branchID", branchID), zap.String("response", fmt.Sprintf("%+v", res))) return res, nil } func (s *Service) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) { return s.betStore.CreateBet(ctx, bet) } func (s *Service) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) { return s.betStore.CreateBetOutcome(ctx, outcomes) } func (s *Service) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) { return s.betStore.GetBetByID(ctx, id) } func (s *Service) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) { return s.betStore.GetBetByCashoutID(ctx, id) } func (s *Service) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) { return s.betStore.GetAllBets(ctx, filter) } func (s *Service) GetBetByBranchID(ctx context.Context, branchID int64) ([]domain.GetBet, error) { return s.betStore.GetBetByBranchID(ctx, branchID) } func (s *Service) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) { return s.betStore.GetBetByUserID(ctx, UserID) } func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error { return s.betStore.UpdateCashOut(ctx, id, cashedOut) } func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error { bet, err := s.GetBetByID(ctx, id) if err != nil { s.mongoLogger.Error("failed to update bet status: invalid bet ID", zap.Int64("bet_id", id), zap.Error(err), ) return err } if bet.IsShopBet || status == domain.OUTCOME_STATUS_ERROR || status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_LOSS { return s.betStore.UpdateStatus(ctx, id, status) } customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id) if err != nil { s.mongoLogger.Error("failed to get customer wallet", zap.Int64("bet_id", id), zap.Error(err), ) return err } var amount domain.Currency switch status { case domain.OUTCOME_STATUS_WIN: amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) case domain.OUTCOME_STATUS_HALF: amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) / 2 default: amount = bet.Amount } err = s.walletSvc.AddToWallet(ctx, customerWallet.RegularID, amount) if err != nil { s.mongoLogger.Error("failed to add winnings to wallet", zap.Int64("wallet_id", customerWallet.RegularID), zap.Float32("amount", float32(amount)), zap.Error(err), ) return err } return s.betStore.UpdateStatus(ctx, id, status) } func (s *Service) CheckBetOutcomeForBet(ctx context.Context, betID int64) (domain.OutcomeStatus, error) { betOutcomes, err := s.betStore.GetBetOutcomeByBetID(ctx, betID) if err != nil { s.mongoLogger.Error("failed to get bet outcomes", zap.Int64("bet_id", betID), zap.Error(err), ) return domain.OUTCOME_STATUS_PENDING, err } status := domain.OUTCOME_STATUS_PENDING for _, betOutcome := range betOutcomes { if betOutcome.Status == domain.OUTCOME_STATUS_PENDING { s.mongoLogger.Info("outcome still pending", zap.Int64("bet_id", betID), ) return domain.OUTCOME_STATUS_PENDING, ErrOutcomesNotCompleted } if betOutcome.Status == domain.OUTCOME_STATUS_ERROR { s.mongoLogger.Info("outcome contains error", zap.Int64("bet_id", betID), ) return domain.OUTCOME_STATUS_ERROR, nil } switch status { case domain.OUTCOME_STATUS_PENDING: status = betOutcome.Status case domain.OUTCOME_STATUS_WIN: switch betOutcome.Status { case domain.OUTCOME_STATUS_LOSS: status = domain.OUTCOME_STATUS_LOSS case domain.OUTCOME_STATUS_HALF: status = domain.OUTCOME_STATUS_HALF case domain.OUTCOME_STATUS_VOID: status = domain.OUTCOME_STATUS_VOID case domain.OUTCOME_STATUS_WIN: // remain win default: status = domain.OUTCOME_STATUS_ERROR } case domain.OUTCOME_STATUS_LOSS: // stay as LOSS regardless of others case domain.OUTCOME_STATUS_VOID: switch betOutcome.Status { case domain.OUTCOME_STATUS_LOSS: status = domain.OUTCOME_STATUS_LOSS case domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_HALF, domain.OUTCOME_STATUS_VOID: // remain VOID default: status = domain.OUTCOME_STATUS_ERROR } case domain.OUTCOME_STATUS_HALF: switch betOutcome.Status { case domain.OUTCOME_STATUS_LOSS: status = domain.OUTCOME_STATUS_LOSS case domain.OUTCOME_STATUS_VOID: status = domain.OUTCOME_STATUS_VOID case domain.OUTCOME_STATUS_HALF, domain.OUTCOME_STATUS_WIN: // remain HALF default: status = domain.OUTCOME_STATUS_ERROR } default: status = domain.OUTCOME_STATUS_ERROR } } if status == domain.OUTCOME_STATUS_PENDING || status == domain.OUTCOME_STATUS_ERROR { s.mongoLogger.Info("bet status not updated due to status", zap.Int64("bet_id", betID), zap.String("final_status", string(status)), ) } return status, nil } func (s *Service) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) { betOutcome, err := s.betStore.UpdateBetOutcomeStatus(ctx, id, status) if err != nil { s.mongoLogger.Error("failed to update bet outcome status", zap.Int64("betID", id), zap.Error(err), ) return domain.BetOutcome{}, err } return betOutcome, err } func (s *Service) DeleteBet(ctx context.Context, id int64) error { return s.betStore.DeleteBet(ctx, id) }