optimizations for cashback

This commit is contained in:
Asher Samuel 2025-07-10 15:21:34 +03:00
parent b84027ea04
commit 96ea2c8af4
5 changed files with 84 additions and 24 deletions

View File

@ -99,6 +99,11 @@ SELECT *
FROM bet_with_outcomes
WHERE fast_code = $1
LIMIT 1;
-- name: GetBetsForCashback :many
SELECT *
FROM bet_with_outcomes
WHERE status = 2
AND processed = false;
-- name: GetBetOutcomeByEventID :many
SELECT *
FROM bet_outcomes

View File

@ -512,6 +512,52 @@ func (q *Queries) GetBetOutcomeByEventID(ctx context.Context, arg GetBetOutcomeB
return items, nil
}
const GetBetsForCashback = `-- name: GetBetsForCashback :many
SELECT id, amount, total_odds, status, full_name, phone_number, company_id, branch_id, user_id, cashed_out, cashout_id, created_at, updated_at, is_shop_bet, outcomes_hash, fast_code, processed, outcomes
FROM bet_with_outcomes
WHERE status = 2
AND processed = false
`
func (q *Queries) GetBetsForCashback(ctx context.Context) ([]BetWithOutcome, error) {
rows, err := q.db.Query(ctx, GetBetsForCashback)
if err != nil {
return nil, err
}
defer rows.Close()
var items []BetWithOutcome
for rows.Next() {
var i BetWithOutcome
if err := rows.Scan(
&i.ID,
&i.Amount,
&i.TotalOdds,
&i.Status,
&i.FullName,
&i.PhoneNumber,
&i.CompanyID,
&i.BranchID,
&i.UserID,
&i.CashedOut,
&i.CashoutID,
&i.CreatedAt,
&i.UpdatedAt,
&i.IsShopBet,
&i.OutcomesHash,
&i.FastCode,
&i.Processed,
&i.Outcomes,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const UpdateBetOutcomeStatus = `-- name: UpdateBetOutcomeStatus :one
UPDATE bet_outcomes
SET status = $1

View File

@ -293,6 +293,22 @@ func (s *Store) GetBetByFastCode(ctx context.Context, fastcode string) (domain.G
return convertDBBetWithOutcomes(bet), nil
}
func (s *Store) GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error) {
bets, err := s.queries.GetBetsForCashback(ctx)
var res []domain.GetBet
if err != nil {
return nil, err
}
for _, bet := range bets {
cashbackBet := convertDBBetWithOutcomes(bet)
res = append(res, cashbackBet)
}
return res, nil
}
func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
UserID: pgtype.Int8{Int64: UserID, Valid: true},

View File

@ -45,5 +45,6 @@ type BetStore interface {
GetSportDetails(ctx context.Context, filter domain.ReportFilter) (map[string]string, error)
GetSportMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[string]string, error)
GetBetsForCashback(ctx context.Context) ([]domain.GetBet, error)
UpdateBetWithCashback(ctx context.Context, betID int64, cashbackStatus bool) error
}

View File

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"log/slog"
"math"
"math/big"
random "math/rand"
"sort"
@ -908,9 +909,7 @@ func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error {
}
func (s *Service) ProcessBetCashback(ctx context.Context) error {
// TODO: get filterd data from db instead of filtering it here
// get all bets (status not pending) (processed not true)
bets, err := s.GetAllBets(ctx, domain.BetFilter{})
bets, err := s.betStore.GetBetsForCashback(ctx)
if err != nil {
s.mongoLogger.Error("failed to fetch bets",
zap.Error(err),
@ -919,35 +918,26 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error {
}
for _, bet := range bets {
if bet.Status == domain.OUTCOME_STATUS_PENDING {
continue
}
loseCount := 0
outcomes, err := s.GetBetOutcomeByBetID(ctx, bet.ID)
if err != nil {
s.mongoLogger.Info("failed to fetch outcomes for a best",
zap.Int64("betID", bet.ID),
zap.Error(err),
)
continue
}
// bet meets criteria
shouldProcess := true
for _, outcome := range outcomes {
loseCount := 0
for _, outcome := range bet.Outcomes {
// stop if other outcomes exists in bet outcomes
if outcome.Status != domain.OUTCOME_STATUS_LOSS && outcome.Status != domain.OUTCOME_STATUS_WIN {
shouldProcess = false
break
}
if outcome.Status == domain.OUTCOME_STATUS_LOSS {
loseCount++
// only process caseback if bet is lost by one
if loseCount > 1 {
shouldProcess = false
break
}
}
}
if loseCount != 1 || !shouldProcess {
if !shouldProcess || loseCount != 1 {
continue
}
@ -968,8 +958,10 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error {
continue
}
cashbackAmount := calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(cashbackAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
// TODO: get cashback amount cap (currently 1000) from settings in the db
cashbackAmount := math.Min(10, float64(calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)))
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(cashbackAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
domain.PaymentDetails{}, fmt.Sprintf("cashback amount of %f added to users static wallet", cashbackAmount))
if err != nil {