cashback implementation
This commit is contained in:
parent
ddf55763d2
commit
b84027ea04
|
|
@ -168,6 +168,7 @@ func main() {
|
|||
)
|
||||
|
||||
go httpserver.SetupReportCronJobs(context.Background(), reportSvc)
|
||||
go httpserver.ProcessBetCashback(context.TODO(), betSvc)
|
||||
|
||||
bankRepository := repository.NewBankRepository(store)
|
||||
instSvc := institutions.New(bankRepository)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ CREATE TABLE IF NOT EXISTS bets (
|
|||
is_shop_bet BOOLEAN NOT NULL,
|
||||
outcomes_hash TEXT NOT NULL,
|
||||
fast_code VARCHAR(10) NOT NULL,
|
||||
processed BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
UNIQUE(cashout_id),
|
||||
CHECK (
|
||||
user_id IS NOT NULL
|
||||
|
|
|
|||
|
|
@ -143,6 +143,10 @@ UPDATE bets
|
|||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;
|
||||
-- name: UpdateBetWithCashback :exec
|
||||
UPDATE bets
|
||||
SET processed = $1
|
||||
WHERE id = $2;
|
||||
-- name: DeleteBet :exec
|
||||
DELETE FROM bets
|
||||
WHERE id = $1;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ INSERT INTO bets (
|
|||
fast_code
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
RETURNING 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
|
||||
RETURNING 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
|
||||
`
|
||||
|
||||
type CreateBetParams struct {
|
||||
|
|
@ -78,6 +78,7 @@ func (q *Queries) CreateBet(ctx context.Context, arg CreateBetParams) (Bet, erro
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -119,7 +120,7 @@ func (q *Queries) DeleteBetOutcome(ctx context.Context, betID int64) error {
|
|||
}
|
||||
|
||||
const GetAllBets = `-- name: GetAllBets :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, outcomes
|
||||
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 (
|
||||
branch_id = $1
|
||||
|
|
@ -196,6 +197,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -209,7 +211,7 @@ func (q *Queries) GetAllBets(ctx context.Context, arg GetAllBetsParams) ([]BetWi
|
|||
}
|
||||
|
||||
const GetBetByBranchID = `-- name: GetBetByBranchID :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, outcomes
|
||||
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 branch_id = $1
|
||||
`
|
||||
|
|
@ -240,6 +242,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -253,7 +256,7 @@ func (q *Queries) GetBetByBranchID(ctx context.Context, branchID pgtype.Int8) ([
|
|||
}
|
||||
|
||||
const GetBetByCashoutID = `-- name: GetBetByCashoutID :one
|
||||
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, outcomes
|
||||
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 cashout_id = $1
|
||||
`
|
||||
|
|
@ -278,13 +281,14 @@ func (q *Queries) GetBetByCashoutID(ctx context.Context, cashoutID string) (BetW
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetBetByFastCode = `-- name: GetBetByFastCode :one
|
||||
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, outcomes
|
||||
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 fast_code = $1
|
||||
LIMIT 1
|
||||
|
|
@ -310,13 +314,14 @@ func (q *Queries) GetBetByFastCode(ctx context.Context, fastCode string) (BetWit
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetBetByID = `-- name: GetBetByID :one
|
||||
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, outcomes
|
||||
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 id = $1
|
||||
`
|
||||
|
|
@ -341,13 +346,14 @@ func (q *Queries) GetBetByID(ctx context.Context, id int64) (BetWithOutcome, err
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetBetByUserID = `-- name: GetBetByUserID :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, outcomes
|
||||
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 user_id = $1
|
||||
`
|
||||
|
|
@ -378,6 +384,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
|||
&i.IsShopBet,
|
||||
&i.OutcomesHash,
|
||||
&i.FastCode,
|
||||
&i.Processed,
|
||||
&i.Outcomes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -393,7 +400,7 @@ func (q *Queries) GetBetByUserID(ctx context.Context, userID pgtype.Int8) ([]Bet
|
|||
const GetBetCount = `-- name: GetBetCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM bets
|
||||
where user_id = $1
|
||||
WHERE user_id = $1
|
||||
AND outcomes_hash = $2
|
||||
`
|
||||
|
||||
|
|
@ -623,6 +630,22 @@ func (q *Queries) UpdateBetOutcomeStatusForEvent(ctx context.Context, arg Update
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateBetWithCashback = `-- name: UpdateBetWithCashback :exec
|
||||
UPDATE bets
|
||||
SET processed = $1
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateBetWithCashbackParams struct {
|
||||
Processed bool `json:"processed"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBetWithCashback(ctx context.Context, arg UpdateBetWithCashbackParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateBetWithCashback, arg.Processed, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateCashOut = `-- name: UpdateCashOut :exec
|
||||
UPDATE bets
|
||||
SET cashed_out = $2,
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ type Bet struct {
|
|||
IsShopBet bool `json:"is_shop_bet"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
FastCode string `json:"fast_code"`
|
||||
Processed bool `json:"processed"`
|
||||
}
|
||||
|
||||
type BetOutcome struct {
|
||||
|
|
@ -127,6 +128,7 @@ type BetWithOutcome struct {
|
|||
IsShopBet bool `json:"is_shop_bet"`
|
||||
OutcomesHash string `json:"outcomes_hash"`
|
||||
FastCode string `json:"fast_code"`
|
||||
Processed bool `json:"processed"`
|
||||
Outcomes []BetOutcome `json:"outcomes"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -439,6 +439,24 @@ func (s *Store) UpdateBetOutcomeStatusForEvent(ctx context.Context, eventID int6
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateBetWithCashback(ctx context.Context, betID int64, cashbackStatus bool) error {
|
||||
err := s.queries.UpdateBetWithCashback(ctx, dbgen.UpdateBetWithCashbackParams{
|
||||
ID: betID,
|
||||
Processed: cashbackStatus,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
domain.MongoDBLogger.Error("failed to update bet outcome status for event",
|
||||
zap.Int64("betID", betID),
|
||||
zap.Bool("status", cashbackStatus),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBetSummary returns aggregated bet statistics
|
||||
func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
|
||||
totalStakes domain.Currency,
|
||||
|
|
|
|||
|
|
@ -44,4 +44,6 @@ type BetStore interface {
|
|||
GetSportBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.SportBetActivity, error)
|
||||
GetSportDetails(ctx context.Context, filter domain.ReportFilter) (map[string]string, error)
|
||||
GetSportMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[string]string, error)
|
||||
|
||||
UpdateBetWithCashback(ctx context.Context, betID int64, cashbackStatus bool) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -907,6 +907,80 @@ func (s *Service) SetBetToRemoved(ctx context.Context, id int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
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{})
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to fetch bets",
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := s.betStore.UpdateBetWithCashback(ctx, bet.ID, true); err != nil {
|
||||
s.mongoLogger.Error("failed to process cashback for bet",
|
||||
zap.Int64("betID", bet.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
wallets, err := s.walletSvc.GetCustomerWallet(ctx, bet.UserID.Value)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get wallets of a user",
|
||||
zap.Int64("userID", bet.UserID.Value),
|
||||
zap.Error(err),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
cashbackAmount := calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)
|
||||
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(cashbackAmount), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
domain.PaymentDetails{}, fmt.Sprintf("cashback amount of %f added to users static wallet", cashbackAmount))
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("Failed to update wallet for user",
|
||||
zap.Int64("userID", bet.UserID.Value),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {
|
||||
// should always be in the same order for producing the same hash
|
||||
sort.Slice(outcomes, func(i, j int) bool {
|
||||
|
|
@ -927,3 +1001,29 @@ func generateOutcomeHash(outcomes []domain.CreateBetOutcome) (string, error) {
|
|||
sum := sha256.Sum256([]byte(sb.String()))
|
||||
return hex.EncodeToString(sum[:]), nil
|
||||
}
|
||||
|
||||
func calculateCashbackAmount(amount, total_odds float32) float32 {
|
||||
var multiplier float32
|
||||
|
||||
if total_odds < 18 {
|
||||
multiplier = 0
|
||||
} else if total_odds >= 18 && total_odds <= 35 {
|
||||
multiplier = 1
|
||||
} else if total_odds > 35 && total_odds <= 55 {
|
||||
multiplier = 2
|
||||
} else if total_odds > 55 && total_odds <= 95 {
|
||||
multiplier = 3
|
||||
} else if total_odds > 95 && total_odds <= 250 {
|
||||
multiplier = 5
|
||||
} else if total_odds > 250 && total_odds <= 450 {
|
||||
multiplier = 10
|
||||
} else if total_odds > 450 && total_odds <= 1000 {
|
||||
multiplier = 50
|
||||
} else if total_odds > 1000 && total_odds <= 2000 {
|
||||
multiplier = 100
|
||||
} else {
|
||||
multiplier = 500
|
||||
}
|
||||
|
||||
return amount * multiplier
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
// "time"
|
||||
|
||||
betSvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
|
||||
eventsvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
oddssvc "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||
|
|
@ -148,3 +149,34 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service) {
|
|||
c.Start()
|
||||
log.Println("Cron jobs started for report generation service")
|
||||
}
|
||||
|
||||
func ProcessBetCashback(ctx context.Context, betService *betSvc.Service) {
|
||||
c := cron.New(cron.WithSeconds())
|
||||
|
||||
schedule := []struct {
|
||||
spec string
|
||||
task func()
|
||||
}{
|
||||
{
|
||||
spec: "*/10 * * * * *", // 10 seconds for testing
|
||||
// spec: "0 0 0 * * *", // Daily at midnight
|
||||
task: func() {
|
||||
log.Println("process bet cashbacks...")
|
||||
if err := betService.ProcessBetCashback(ctx); err != nil {
|
||||
log.Printf("Failed to process bet cashbacks: %v", err)
|
||||
} else {
|
||||
log.Printf("Successfully processed bet cashbacks")
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range schedule {
|
||||
if _, err := c.AddFunc(job.spec, job.task); err != nil {
|
||||
log.Fatalf("Failed to schedule cron job: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Start()
|
||||
log.Println("Cron jobs started for bet cashbacks")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user