fix: refactoring bonus
This commit is contained in:
parent
5595600ede
commit
215eb5a1d8
|
|
@ -142,7 +142,7 @@ func main() {
|
|||
ticketSvc := ticket.NewService(store, eventSvc, *oddsSvc, domain.MongoDBLogger, *settingSvc, notificationSvc)
|
||||
betSvc := bet.NewService(store, eventSvc, *oddsSvc, *walletSvc, *branchSvc, *companySvc, *settingSvc, *userSvc, notificationSvc, logger, domain.MongoDBLogger)
|
||||
resultSvc := result.NewService(store, cfg, logger, domain.MongoDBLogger, *betSvc, *oddsSvc, eventSvc, leagueSvc, notificationSvc, messengerSvc, *userSvc)
|
||||
bonusSvc := bonus.NewService(store)
|
||||
bonusSvc := bonus.NewService(store, walletSvc, settingSvc, domain.MongoDBLogger)
|
||||
referalRepo := repository.NewReferralRepository(store)
|
||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||
recommendationRepo := repository.NewRecommendationRepository(store)
|
||||
|
|
|
|||
|
|
@ -86,7 +86,10 @@ VALUES ('sms_provider', 'afro_message'),
|
|||
('default_winning_limit', '5000000'),
|
||||
('referral_reward_amount', '10000'),
|
||||
('cashback_percentage', '0.2'),
|
||||
('default_max_referrals', '15') ON CONFLICT (key) DO NOTHING;
|
||||
('default_max_referrals', '15'),
|
||||
('minimum_bet_amount', '100'),
|
||||
('send_email_on_bet_finish', 'true'),
|
||||
('send_sms_on_bet_finish', 'false') ON CONFLICT (key) DO NOTHING;
|
||||
-- Users
|
||||
INSERT INTO users (
|
||||
id,
|
||||
|
|
@ -341,5 +344,4 @@ SET name = EXCLUDED.name,
|
|||
profit_percent = EXCLUDED.profit_percent,
|
||||
is_active = EXCLUDED.is_active,
|
||||
created_at = EXCLUDED.created_at,
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
|
|
@ -453,10 +453,17 @@ CREATE TABLE IF NOT EXISTS company_settings (
|
|||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (company_id, key)
|
||||
);
|
||||
CREATE TABLE bonus (
|
||||
multiplier REAL NOT NULL,
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
balance_cap BIGINT NOT NULL DEFAULT 0
|
||||
CREATE TABLE user_bonuses (
|
||||
id BIGINT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
bonus_code TEXT NOT NULL UNIQUE,
|
||||
reward_amount BIGINT NOT NULL,
|
||||
is_claimed BOOLEAN NOT NULL DEFAULT false,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE flags (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
|
|
@ -723,4 +730,4 @@ ADD CONSTRAINT fk_event_settings_company FOREIGN KEY (company_id) REFERENCES com
|
|||
ADD CONSTRAINT fk_event_settings_event FOREIGN KEY (event_id) REFERENCES events (id) ON DELETE CASCADE;
|
||||
ALTER TABLE company_odd_settings
|
||||
ADD CONSTRAINT fk_odds_settings_company FOREIGN KEY (company_id) REFERENCES companies (id) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE;
|
||||
ADD CONSTRAINT fk_odds_settings_odds_market FOREIGN KEY (odds_market_id) REFERENCES odds_market (id) ON DELETE CASCADE;
|
||||
|
|
@ -1,17 +1,52 @@
|
|||
-- name: CreateBonusMultiplier :exec
|
||||
INSERT INTO bonus (multiplier, balance_cap)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: GetBonusMultiplier :many
|
||||
SELECT id, multiplier
|
||||
FROM bonus;
|
||||
|
||||
-- name: GetBonusBalanceCap :many
|
||||
SELECT id, balance_cap
|
||||
FROM bonus;
|
||||
|
||||
-- name: UpdateBonusMultiplier :exec
|
||||
UPDATE bonus
|
||||
SET multiplier = $1,
|
||||
balance_cap = $2
|
||||
WHERE id = $3;
|
||||
-- name: CreateUserBonus :one
|
||||
INSERT INTO user_bonuses (
|
||||
name,
|
||||
description,
|
||||
user_id,
|
||||
bonus_code,
|
||||
reward_amount,
|
||||
expires_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
-- name: GetAllUserBonuses :many
|
||||
SELECT *
|
||||
FROM user_bonuses;
|
||||
-- name: GetUserBonusByID :one
|
||||
SELECT *
|
||||
FROM user_bonuses
|
||||
WHERE id = $1;
|
||||
-- name: GetBonusesByUserID :many
|
||||
SELECT *
|
||||
FROM user_bonuses
|
||||
WHERE user_id = $1;
|
||||
-- name: GetBonusStats :one
|
||||
SELECT COUNT(*) AS total_bonuses,
|
||||
COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned,
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN is_claimed = true THEN 1
|
||||
END
|
||||
) AS claimed_bonuses,
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN expires_at > now() THEN 1
|
||||
END
|
||||
) AS expired_bonuses
|
||||
FROM user_bonuses
|
||||
JOIN users ON users.id = user_bonuses.user_id
|
||||
WHERE (
|
||||
company_id = sqlc.narg('company_id')
|
||||
OR sqlc.narg('company_id') IS NULL
|
||||
)
|
||||
AND (
|
||||
user_id = sqlc.narg('user_id')
|
||||
OR sqlc.narg('user_id') IS NULL
|
||||
);
|
||||
-- name: UpdateUserBonus :exec
|
||||
UPDATE user_bonuses
|
||||
SET is_claimed = $2
|
||||
WHERE id = $1;
|
||||
-- name: DeleteUserBonus :exec
|
||||
DELETE FROM user_bonuses
|
||||
WHERE id = $1;
|
||||
|
|
@ -30,6 +30,19 @@ WHERE id = $1;
|
|||
SELECT *
|
||||
FROM wallet_transfer_details
|
||||
WHERE reference_number = $1;
|
||||
-- name: GetTransferStats :one
|
||||
SELECT COUNT(*) AS total_transfers, COUNT(*) FILTER (
|
||||
WHERE type = 'deposit'
|
||||
) AS total_deposits,
|
||||
COUNT(*) FILTER (
|
||||
WHERE type = 'withdraw'
|
||||
) AS total_withdraw,
|
||||
COUNT(*) FILTER (
|
||||
WHERE type = 'wallet'
|
||||
) AS total_wallet_to_wallet
|
||||
FROM wallet_transfer
|
||||
WHERE sender_wallet_id = $1
|
||||
OR receiver_wallet_id = $1;
|
||||
-- name: UpdateTransferVerification :exec
|
||||
UPDATE wallet_transfer
|
||||
SET verified = $1,
|
||||
|
|
|
|||
|
|
@ -7,43 +7,93 @@ package dbgen
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const CreateBonusMultiplier = `-- name: CreateBonusMultiplier :exec
|
||||
INSERT INTO bonus (multiplier, balance_cap)
|
||||
VALUES ($1, $2)
|
||||
const CreateUserBonus = `-- name: CreateUserBonus :one
|
||||
INSERT INTO user_bonuses (
|
||||
name,
|
||||
description,
|
||||
user_id,
|
||||
bonus_code,
|
||||
reward_amount,
|
||||
expires_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, name, description, user_id, bonus_code, reward_amount, is_claimed, expires_at, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateBonusMultiplierParams struct {
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
type CreateUserBonusParams struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
BonusCode string `json:"bonus_code"`
|
||||
RewardAmount int64 `json:"reward_amount"`
|
||||
ExpiresAt pgtype.Timestamp `json:"expires_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateBonusMultiplier(ctx context.Context, arg CreateBonusMultiplierParams) error {
|
||||
_, err := q.db.Exec(ctx, CreateBonusMultiplier, arg.Multiplier, arg.BalanceCap)
|
||||
func (q *Queries) CreateUserBonus(ctx context.Context, arg CreateUserBonusParams) (UserBonuse, error) {
|
||||
row := q.db.QueryRow(ctx, CreateUserBonus,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.UserID,
|
||||
arg.BonusCode,
|
||||
arg.RewardAmount,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
var i UserBonuse
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.UserID,
|
||||
&i.BonusCode,
|
||||
&i.RewardAmount,
|
||||
&i.IsClaimed,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const DeleteUserBonus = `-- name: DeleteUserBonus :exec
|
||||
DELETE FROM user_bonuses
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteUserBonus(ctx context.Context, id int64) error {
|
||||
_, err := q.db.Exec(ctx, DeleteUserBonus, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const GetBonusBalanceCap = `-- name: GetBonusBalanceCap :many
|
||||
SELECT id, balance_cap
|
||||
FROM bonus
|
||||
const GetAllUserBonuses = `-- name: GetAllUserBonuses :many
|
||||
SELECT id, name, description, user_id, bonus_code, reward_amount, is_claimed, expires_at, created_at, updated_at
|
||||
FROM user_bonuses
|
||||
`
|
||||
|
||||
type GetBonusBalanceCapRow struct {
|
||||
ID int64 `json:"id"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBonusBalanceCap(ctx context.Context) ([]GetBonusBalanceCapRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetBonusBalanceCap)
|
||||
func (q *Queries) GetAllUserBonuses(ctx context.Context) ([]UserBonuse, error) {
|
||||
rows, err := q.db.Query(ctx, GetAllUserBonuses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetBonusBalanceCapRow
|
||||
var items []UserBonuse
|
||||
for rows.Next() {
|
||||
var i GetBonusBalanceCapRow
|
||||
if err := rows.Scan(&i.ID, &i.BalanceCap); err != nil {
|
||||
var i UserBonuse
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.UserID,
|
||||
&i.BonusCode,
|
||||
&i.RewardAmount,
|
||||
&i.IsClaimed,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -54,26 +104,82 @@ func (q *Queries) GetBonusBalanceCap(ctx context.Context) ([]GetBonusBalanceCapR
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const GetBonusMultiplier = `-- name: GetBonusMultiplier :many
|
||||
SELECT id, multiplier
|
||||
FROM bonus
|
||||
const GetBonusStats = `-- name: GetBonusStats :one
|
||||
SELECT COUNT(*) AS total_bonuses,
|
||||
COALESCE(SUM(reward_amount), 0)::bigint AS total_reward_earned,
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN is_claimed = true THEN 1
|
||||
END
|
||||
) AS claimed_bonuses,
|
||||
COUNT(
|
||||
CASE
|
||||
WHEN expires_at > now() THEN 1
|
||||
END
|
||||
) AS expired_bonuses
|
||||
FROM user_bonuses
|
||||
JOIN users ON users.id = user_bonuses.user_id
|
||||
WHERE (
|
||||
company_id = $1
|
||||
OR $1 IS NULL
|
||||
)
|
||||
AND (
|
||||
user_id = $2
|
||||
OR $2 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetBonusMultiplierRow struct {
|
||||
ID int64 `json:"id"`
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
type GetBonusStatsParams struct {
|
||||
CompanyID pgtype.Int8 `json:"company_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]GetBonusMultiplierRow, error) {
|
||||
rows, err := q.db.Query(ctx, GetBonusMultiplier)
|
||||
type GetBonusStatsRow struct {
|
||||
TotalBonuses int64 `json:"total_bonuses"`
|
||||
TotalRewardEarned int64 `json:"total_reward_earned"`
|
||||
ClaimedBonuses int64 `json:"claimed_bonuses"`
|
||||
ExpiredBonuses int64 `json:"expired_bonuses"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetBonusStats(ctx context.Context, arg GetBonusStatsParams) (GetBonusStatsRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetBonusStats, arg.CompanyID, arg.UserID)
|
||||
var i GetBonusStatsRow
|
||||
err := row.Scan(
|
||||
&i.TotalBonuses,
|
||||
&i.TotalRewardEarned,
|
||||
&i.ClaimedBonuses,
|
||||
&i.ExpiredBonuses,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetBonusesByUserID = `-- name: GetBonusesByUserID :many
|
||||
SELECT id, name, description, user_id, bonus_code, reward_amount, is_claimed, expires_at, created_at, updated_at
|
||||
FROM user_bonuses
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBonusesByUserID(ctx context.Context, userID int64) ([]UserBonuse, error) {
|
||||
rows, err := q.db.Query(ctx, GetBonusesByUserID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetBonusMultiplierRow
|
||||
var items []UserBonuse
|
||||
for rows.Next() {
|
||||
var i GetBonusMultiplierRow
|
||||
if err := rows.Scan(&i.ID, &i.Multiplier); err != nil {
|
||||
var i UserBonuse
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.UserID,
|
||||
&i.BonusCode,
|
||||
&i.RewardAmount,
|
||||
&i.IsClaimed,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
|
@ -84,20 +190,42 @@ func (q *Queries) GetBonusMultiplier(ctx context.Context) ([]GetBonusMultiplierR
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateBonusMultiplier = `-- name: UpdateBonusMultiplier :exec
|
||||
UPDATE bonus
|
||||
SET multiplier = $1,
|
||||
balance_cap = $2
|
||||
WHERE id = $3
|
||||
const GetUserBonusByID = `-- name: GetUserBonusByID :one
|
||||
SELECT id, name, description, user_id, bonus_code, reward_amount, is_claimed, expires_at, created_at, updated_at
|
||||
FROM user_bonuses
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateBonusMultiplierParams struct {
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
ID int64 `json:"id"`
|
||||
func (q *Queries) GetUserBonusByID(ctx context.Context, id int64) (UserBonuse, error) {
|
||||
row := q.db.QueryRow(ctx, GetUserBonusByID, id)
|
||||
var i UserBonuse
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.UserID,
|
||||
&i.BonusCode,
|
||||
&i.RewardAmount,
|
||||
&i.IsClaimed,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBonusMultiplier(ctx context.Context, arg UpdateBonusMultiplierParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateBonusMultiplier, arg.Multiplier, arg.BalanceCap, arg.ID)
|
||||
const UpdateUserBonus = `-- name: UpdateUserBonus :exec
|
||||
UPDATE user_bonuses
|
||||
SET is_claimed = $2
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateUserBonusParams struct {
|
||||
ID int64 `json:"id"`
|
||||
IsClaimed bool `json:"is_claimed"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateUserBonus(ctx context.Context, arg UpdateUserBonusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateUserBonus, arg.ID, arg.IsClaimed)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,12 +79,6 @@ type BetWithOutcome struct {
|
|||
Outcomes []BetOutcome `json:"outcomes"`
|
||||
}
|
||||
|
||||
type Bonu struct {
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
ID int64 `json:"id"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -752,6 +746,19 @@ type User struct {
|
|||
Suspended bool `json:"suspended"`
|
||||
}
|
||||
|
||||
type UserBonuse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
BonusCode string `json:"bonus_code"`
|
||||
RewardAmount int64 `json:"reward_amount"`
|
||||
IsClaimed bool `json:"is_claimed"`
|
||||
ExpiresAt pgtype.Timestamp `json:"expires_at"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
||||
type UserGameInteraction struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
|
|
|
|||
|
|
@ -182,6 +182,40 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
|||
return i, err
|
||||
}
|
||||
|
||||
const GetTransferStats = `-- name: GetTransferStats :one
|
||||
SELECT COUNT(*) AS total_transfers, COUNT(*) FILTER (
|
||||
WHERE type = 'deposit'
|
||||
) AS total_deposits,
|
||||
COUNT(*) FILTER (
|
||||
WHERE type = 'withdraw'
|
||||
) AS total_withdraw,
|
||||
COUNT(*) FILTER (
|
||||
WHERE type = 'wallet'
|
||||
) AS total_wallet_to_wallet
|
||||
FROM wallet_transfer
|
||||
WHERE sender_wallet_id = $1
|
||||
OR receiver_wallet_id = $1
|
||||
`
|
||||
|
||||
type GetTransferStatsRow struct {
|
||||
TotalTransfers int64 `json:"total_transfers"`
|
||||
TotalDeposits int64 `json:"total_deposits"`
|
||||
TotalWithdraw int64 `json:"total_withdraw"`
|
||||
TotalWalletToWallet int64 `json:"total_wallet_to_wallet"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTransferStats(ctx context.Context, senderWalletID pgtype.Int8) (GetTransferStatsRow, error) {
|
||||
row := q.db.QueryRow(ctx, GetTransferStats, senderWalletID)
|
||||
var i GetTransferStatsRow
|
||||
err := row.Scan(
|
||||
&i.TotalTransfers,
|
||||
&i.TotalDeposits,
|
||||
&i.TotalWithdraw,
|
||||
&i.TotalWalletToWallet,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||
FROM wallet_transfer_details
|
||||
|
|
|
|||
136
internal/domain/bonus.go
Normal file
136
internal/domain/bonus.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type UserBonus struct {
|
||||
ID int64
|
||||
Name string
|
||||
Description string
|
||||
UserID int64
|
||||
BonusCode string
|
||||
RewardAmount Currency
|
||||
IsClaimed bool
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type UserBonusRes struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
BonusCode string `json:"bonus_code"`
|
||||
RewardAmount float32 `json:"reward_amount"`
|
||||
IsClaimed bool `json:"is_claimed"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func ConvertToBonusRes(bonus UserBonus) UserBonusRes {
|
||||
return UserBonusRes{
|
||||
ID: bonus.ID,
|
||||
Name: bonus.Name,
|
||||
Description: bonus.Description,
|
||||
UserID: bonus.UserID,
|
||||
BonusCode: bonus.BonusCode,
|
||||
RewardAmount: bonus.RewardAmount.Float32(),
|
||||
IsClaimed: bonus.IsClaimed,
|
||||
ExpiresAt: bonus.ExpiresAt,
|
||||
CreatedAt: bonus.CreatedAt,
|
||||
UpdatedAt: bonus.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
type CreateBonus struct {
|
||||
Name string
|
||||
Description string
|
||||
UserID int64
|
||||
BonusCode string
|
||||
RewardAmount Currency
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
type CreateBonusReq struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserID int64 `json:"user_id"`
|
||||
BonusCode string `json:"bonus_code"`
|
||||
RewardAmount float32 `json:"reward_amount"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
func ConvertCreateBonusReq(bonus CreateBonusReq, companyID int64) CreateBonus {
|
||||
return CreateBonus{
|
||||
Name: bonus.Name,
|
||||
Description: bonus.Description,
|
||||
UserID: bonus.UserID,
|
||||
BonusCode: bonus.BonusCode,
|
||||
RewardAmount: ToCurrency(bonus.RewardAmount),
|
||||
ExpiresAt: bonus.ExpiresAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertCreateBonus(bonus CreateBonus) dbgen.CreateUserBonusParams {
|
||||
return dbgen.CreateUserBonusParams{
|
||||
Name: bonus.Name,
|
||||
Description: bonus.Description,
|
||||
UserID: bonus.UserID,
|
||||
BonusCode: bonus.BonusCode,
|
||||
RewardAmount: int64(bonus.RewardAmount),
|
||||
ExpiresAt: pgtype.Timestamp{
|
||||
Time: bonus.ExpiresAt,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBBonus(bonus dbgen.UserBonuse) UserBonus {
|
||||
return UserBonus{
|
||||
ID: bonus.ID,
|
||||
Name: bonus.Name,
|
||||
Description: bonus.Description,
|
||||
UserID: bonus.UserID,
|
||||
BonusCode: bonus.BonusCode,
|
||||
RewardAmount: Currency(bonus.RewardAmount),
|
||||
IsClaimed: bonus.IsClaimed,
|
||||
ExpiresAt: bonus.ExpiresAt.Time,
|
||||
CreatedAt: bonus.CreatedAt.Time,
|
||||
UpdatedAt: bonus.UpdatedAt.Time,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertDBBonuses(bonuses []dbgen.UserBonuse) []UserBonus {
|
||||
result := make([]UserBonus, len(bonuses))
|
||||
for i, bonus := range bonuses {
|
||||
result[i] = ConvertDBBonus(bonus)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type BonusFilter struct {
|
||||
UserID ValidInt64
|
||||
CompanyID ValidInt64
|
||||
}
|
||||
|
||||
type BonusStats struct {
|
||||
TotalBonus int64
|
||||
TotalRewardAmount Currency
|
||||
ClaimedBonuses int64
|
||||
ExpiredBonuses int64
|
||||
}
|
||||
|
||||
func ConvertDBBonusStats(stats dbgen.GetBonusStatsRow) BonusStats {
|
||||
return BonusStats{
|
||||
TotalBonus: stats.TotalBonuses,
|
||||
TotalRewardAmount: Currency(stats.TotalRewardEarned),
|
||||
ClaimedBonuses: stats.ClaimedBonuses,
|
||||
ExpiredBonuses: stats.ExpiredBonuses,
|
||||
}
|
||||
}
|
||||
|
|
@ -16,17 +16,26 @@ var (
|
|||
)
|
||||
|
||||
type SettingList struct {
|
||||
SMSProvider SMSProvider `json:"sms_provider"`
|
||||
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
|
||||
BetAmountLimit Currency `json:"bet_amount_limit"`
|
||||
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
|
||||
TotalWinningLimit Currency `json:"total_winning_limit"`
|
||||
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
|
||||
CashbackAmountCap Currency `json:"cashback_amount_cap"`
|
||||
DefaultWinningLimit int64 `json:"default_winning_limit"`
|
||||
ReferralRewardAmount Currency `json:"referral_reward_amount"`
|
||||
CashbackPercentage float32 `json:"cashback_percentage"`
|
||||
DefaultMaxReferrals int64 `json:"default_max_referrals"`
|
||||
SMSProvider SMSProvider `json:"sms_provider"`
|
||||
MaxNumberOfOutcomes int64 `json:"max_number_of_outcomes"`
|
||||
BetAmountLimit Currency `json:"bet_amount_limit"`
|
||||
DailyTicketPerIP int64 `json:"daily_ticket_limit"`
|
||||
TotalWinningLimit Currency `json:"total_winning_limit"`
|
||||
AmountForBetReferral Currency `json:"amount_for_bet_referral"`
|
||||
CashbackAmountCap Currency `json:"cashback_amount_cap"`
|
||||
DefaultWinningLimit int64 `json:"default_winning_limit"`
|
||||
ReferralRewardAmount Currency `json:"referral_reward_amount"`
|
||||
CashbackPercentage float32 `json:"cashback_percentage"`
|
||||
DefaultMaxReferrals int64 `json:"default_max_referrals"`
|
||||
MinimumBetAmount Currency `json:"minimum_bet_amount"`
|
||||
BetDuplicateLimit int64 `json:"bet_duplicate_limit"`
|
||||
SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"`
|
||||
SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"`
|
||||
WelcomeBonusActive bool `json:"welcome_bonus_active"`
|
||||
WelcomeBonusMultiplier float32 `json:"welcome_bonus_multiplier"`
|
||||
WelcomeBonusCap Currency `json:"welcome_bonus_cap"`
|
||||
WelcomeBonusCount int64 `json:"welcome_bonus_count"`
|
||||
WelcomeBonusExpire int64 `json:"welcome_bonus_expiry"`
|
||||
}
|
||||
|
||||
type SettingListRes struct {
|
||||
|
|
@ -41,6 +50,10 @@ type SettingListRes struct {
|
|||
ReferralRewardAmount float32 `json:"referral_reward_amount"`
|
||||
CashbackPercentage float32 `json:"cashback_percentage"`
|
||||
DefaultMaxReferrals int64 `json:"default_max_referrals"`
|
||||
MinimumBetAmount float32 `json:"minimum_bet_amount"`
|
||||
BetDuplicateLimit int64 `json:"bet_duplicate_limit"`
|
||||
SendEmailOnBetFinish bool `json:"send_email_on_bet_finish"`
|
||||
SendSMSOnBetFinish bool `json:"send_sms_on_bet_finish"`
|
||||
}
|
||||
|
||||
func ConvertSettingListRes(settings SettingList) SettingListRes {
|
||||
|
|
@ -56,6 +69,10 @@ func ConvertSettingListRes(settings SettingList) SettingListRes {
|
|||
ReferralRewardAmount: settings.ReferralRewardAmount.Float32(),
|
||||
CashbackPercentage: settings.CashbackPercentage,
|
||||
DefaultMaxReferrals: settings.DefaultMaxReferrals,
|
||||
MinimumBetAmount: settings.MinimumBetAmount.Float32(),
|
||||
BetDuplicateLimit: settings.BetDuplicateLimit,
|
||||
SendEmailOnBetFinish: settings.SendEmailOnBetFinish,
|
||||
SendSMSOnBetFinish: settings.SendSMSOnBetFinish,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,6 +88,10 @@ type SaveSettingListReq struct {
|
|||
ReferralRewardAmount *float32 `json:"referral_reward_amount"`
|
||||
CashbackPercentage *float32 `json:"cashback_percentage"`
|
||||
DefaultMaxReferrals *int64 `json:"default_max_referrals"`
|
||||
MinimumBetAmount *float32 `json:"minimum_bet_amount"`
|
||||
BetDuplicateLimit *int64 `json:"bet_duplicate_limit"`
|
||||
SendEmailOnBetFinish *bool `json:"send_email_on_bet_finish"`
|
||||
SendSMSOnBetFinish *bool `json:"send_sms_on_bet_finish"`
|
||||
}
|
||||
|
||||
type ValidSettingList struct {
|
||||
|
|
@ -85,6 +106,10 @@ type ValidSettingList struct {
|
|||
ReferralRewardAmount ValidCurrency
|
||||
CashbackPercentage ValidFloat32
|
||||
DefaultMaxReferrals ValidInt64
|
||||
MinimumBetAmount ValidCurrency
|
||||
BetDuplicateLimit ValidInt64
|
||||
SendEmailOnBetFinish ValidBool
|
||||
SendSMSOnBetFinish ValidBool
|
||||
}
|
||||
|
||||
func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList {
|
||||
|
|
@ -100,6 +125,10 @@ func ConvertSaveSettingListReq(settings SaveSettingListReq) ValidSettingList {
|
|||
ReferralRewardAmount: ConvertFloat32PtrToCurrency(settings.ReferralRewardAmount),
|
||||
CashbackPercentage: ConvertFloat32Ptr(settings.CashbackPercentage),
|
||||
DefaultMaxReferrals: ConvertInt64Ptr(settings.DefaultMaxReferrals),
|
||||
MinimumBetAmount: ConvertFloat32PtrToCurrency(settings.MinimumBetAmount),
|
||||
BetDuplicateLimit: ConvertInt64Ptr(settings.BetDuplicateLimit),
|
||||
SendEmailOnBetFinish: ConvertBoolPtr(settings.SendEmailOnBetFinish),
|
||||
SendSMSOnBetFinish: ConvertBoolPtr(settings.SendSMSOnBetFinish),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +146,10 @@ func (vsl *ValidSettingList) ToSettingList() SettingList {
|
|||
ReferralRewardAmount: vsl.ReferralRewardAmount.Value,
|
||||
CashbackPercentage: vsl.CashbackPercentage.Value,
|
||||
DefaultMaxReferrals: vsl.DefaultMaxReferrals.Value,
|
||||
MinimumBetAmount: vsl.MinimumBetAmount.Value,
|
||||
BetDuplicateLimit: vsl.BetDuplicateLimit.Value,
|
||||
SendEmailOnBetFinish: vsl.SendEmailOnBetFinish.Value,
|
||||
SendSMSOnBetFinish: vsl.SendSMSOnBetFinish.Value,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +167,7 @@ func (vsl *ValidSettingList) GetInt64SettingsMap() map[string]*ValidInt64 {
|
|||
"daily_ticket_limit": &vsl.DailyTicketPerIP,
|
||||
"default_winning_limit": &vsl.DefaultWinningLimit,
|
||||
"default_max_referrals": &vsl.DefaultMaxReferrals,
|
||||
"bet_duplicate_limit": &vsl.BetDuplicateLimit,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +178,7 @@ func (vsl *ValidSettingList) GetCurrencySettingsMap() map[string]*ValidCurrency
|
|||
"amount_for_bet_referral": &vsl.AmountForBetReferral,
|
||||
"cashback_amount_cap": &vsl.CashbackAmountCap,
|
||||
"referral_reward_amount": &vsl.ReferralRewardAmount,
|
||||
"minimum_bet_amount": &vsl.MinimumBetAmount,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +189,10 @@ func (vsl *ValidSettingList) GetStringSettingsMap() map[string]*ValidString {
|
|||
}
|
||||
|
||||
func (vsl *ValidSettingList) GetBoolSettingsMap() map[string]*ValidBool {
|
||||
return map[string]*ValidBool{}
|
||||
return map[string]*ValidBool{
|
||||
"send_email_on_bet_finish": &vsl.SendEmailOnBetFinish,
|
||||
"send_sms_on_bet_finish": &vsl.SendSMSOnBetFinish,
|
||||
}
|
||||
}
|
||||
|
||||
func (vsl *ValidSettingList) GetFloat32SettingsMap() map[string]*ValidFloat32 {
|
||||
|
|
@ -167,7 +205,6 @@ func (vsl *ValidSettingList) GetTimeSettingsMap() map[string]*ValidTime {
|
|||
return map[string]*ValidTime{}
|
||||
}
|
||||
|
||||
|
||||
// Setting Functions
|
||||
|
||||
func (vsl *ValidSettingList) GetTotalSettings() int {
|
||||
|
|
|
|||
|
|
@ -105,3 +105,10 @@ type CreateTransfer struct {
|
|||
Status string `json:"status"`
|
||||
CashierID ValidInt64 `json:"cashier_id"`
|
||||
}
|
||||
|
||||
type TransferStats struct {
|
||||
TotalTransfer int64
|
||||
TotalDeposits int64
|
||||
TotalWithdraws int64
|
||||
TotalWalletToWallet int64
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
|
@ -220,6 +221,46 @@ func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.Outcom
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *Store) SettleWinningBet(ctx context.Context, betID int64, userID int64, amount domain.Currency, status domain.OutcomeStatus) error {
|
||||
tx, err := s.conn.BeginTx(ctx, pgx.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qtx := s.queries.WithTx(tx)
|
||||
|
||||
wallet, err := qtx.GetCustomerWallet(ctx, userID)
|
||||
if err != nil {
|
||||
tx.Rollback(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Update wallet
|
||||
newAmount := wallet.RegularBalance + int64(amount)
|
||||
if err := qtx.UpdateBalance(ctx, dbgen.UpdateBalanceParams{
|
||||
Balance: newAmount,
|
||||
ID: wallet.RegularID,
|
||||
}); err != nil {
|
||||
tx.Rollback(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. Update bet
|
||||
if err := qtx.UpdateStatus(ctx, dbgen.UpdateStatusParams{
|
||||
Status: int32(status),
|
||||
ID: betID,
|
||||
}); err != nil {
|
||||
tx.Rollback(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Commit both together
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||
|
||||
outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, dbgen.GetBetOutcomeByEventIDParams{
|
||||
|
|
|
|||
|
|
@ -4,27 +4,75 @@ import (
|
|||
"context"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
func (s *Store) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error {
|
||||
return s.queries.CreateBonusMultiplier(ctx, dbgen.CreateBonusMultiplierParams{
|
||||
Multiplier: multiplier,
|
||||
BalanceCap: balance_cap,
|
||||
func (s *Store) CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) {
|
||||
newBonus, err := s.queries.CreateUserBonus(ctx, domain.ConvertCreateBonus(bonus))
|
||||
|
||||
if err != nil {
|
||||
return domain.UserBonus{}, err
|
||||
}
|
||||
|
||||
return domain.ConvertDBBonus(newBonus), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAllUserBonuses(ctx context.Context) ([]domain.UserBonus, error) {
|
||||
bonuses, err := s.queries.GetAllUserBonuses(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain.ConvertDBBonuses(bonuses), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBonusesByUserID(ctx context.Context, userID int64) ([]domain.UserBonus, error) {
|
||||
bonuses, err := s.queries.GetBonusesByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain.ConvertDBBonuses(bonuses), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error) {
|
||||
bonus, err := s.queries.GetUserBonusByID(ctx, bonusID)
|
||||
if err != nil {
|
||||
return domain.UserBonus{}, err
|
||||
}
|
||||
return domain.ConvertDBBonus(bonus), nil
|
||||
}
|
||||
|
||||
|
||||
func (s *Store) GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error) {
|
||||
bonus, err := s.queries.GetBonusStats(ctx, dbgen.GetBonusStatsParams{
|
||||
CompanyID: filter.CompanyID.ToPG(),
|
||||
UserID: filter.UserID.ToPG(),
|
||||
})
|
||||
if err != nil {
|
||||
return domain.BonusStats{}, err
|
||||
}
|
||||
return domain.ConvertDBBonusStats(bonus), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) {
|
||||
return s.queries.GetBonusMultiplier(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) {
|
||||
return s.queries.GetBonusBalanceCap(ctx)
|
||||
}
|
||||
|
||||
func (s *Store) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error {
|
||||
return s.queries.UpdateBonusMultiplier(ctx, dbgen.UpdateBonusMultiplierParams{
|
||||
ID: id,
|
||||
Multiplier: mulitplier,
|
||||
BalanceCap: balance_cap,
|
||||
func (s *Store) UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) (error) {
|
||||
err := s.queries.UpdateUserBonus(ctx, dbgen.UpdateUserBonusParams{
|
||||
ID: bonusID,
|
||||
IsClaimed: IsClaimed,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (s *Store) DeleteUserBonus(ctx context.Context, bonusID int64) (error) {
|
||||
err := s.queries.DeleteUserBonus(ctx, bonusID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,6 +148,24 @@ func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.TransferD
|
|||
return convertDBTransferDetail(transfer), nil
|
||||
}
|
||||
|
||||
func (s *Store) GetTransferStats(ctx context.Context, walletID int64) (domain.TransferStats, error) {
|
||||
stats, err := s.queries.GetTransferStats(ctx, pgtype.Int8{
|
||||
Int64: walletID,
|
||||
Valid: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return domain.TransferStats{}, err
|
||||
}
|
||||
|
||||
return domain.TransferStats{
|
||||
TotalTransfer: stats.TotalTransfers,
|
||||
TotalDeposits: stats.TotalDeposits,
|
||||
TotalWithdraws: stats.TotalWithdraw,
|
||||
TotalWalletToWallet: stats.TotalWalletToWallet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
||||
err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{
|
||||
ID: id,
|
||||
|
|
|
|||
|
|
@ -31,20 +31,20 @@ import (
|
|||
)
|
||||
|
||||
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")
|
||||
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")
|
||||
|
||||
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")
|
||||
ErrTotalBalanceNotEnough = errors.New("Total Wallet balance is insufficient to create bet")
|
||||
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")
|
||||
ErrTotalBalanceNotEnough = errors.New("total Wallet balance is insufficient to create bet")
|
||||
|
||||
ErrInvalidAmount = errors.New("Invalid amount")
|
||||
ErrBetAmountTooHigh = errors.New("Cannot create a bet with an amount above limit")
|
||||
ErrBetWinningTooHigh = errors.New("Total Winnings over set limit")
|
||||
ErrInvalidAmount = errors.New("invalid amount")
|
||||
ErrBetAmountTooHigh = errors.New("cannot create a bet with an amount above limit")
|
||||
ErrBetWinningTooHigh = errors.New("total Winnings over set limit")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
|
|
@ -221,7 +221,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
if err != nil {
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
if req.Amount < 1 {
|
||||
if req.Amount < settingsList.MinimumBetAmount.Float32() {
|
||||
return domain.CreateBetRes{}, ErrInvalidAmount
|
||||
}
|
||||
|
||||
|
|
@ -284,9 +284,8 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
// TODO: Make this a setting
|
||||
if role == domain.RoleCustomer && count >= 10 {
|
||||
return domain.CreateBetRes{}, fmt.Errorf("max user limit for single outcome")
|
||||
if role == domain.RoleCustomer && count >= settingsList.BetDuplicateLimit {
|
||||
return domain.CreateBetRes{}, fmt.Errorf("max user limit for duplicate bet")
|
||||
}
|
||||
|
||||
fastCode := helpers.GenerateFastCode()
|
||||
|
|
@ -387,7 +386,7 @@ func (s *Service) PlaceBet(ctx context.Context, req domain.CreateBetReq, userID
|
|||
zap.String("role", string(role)),
|
||||
zap.Int64("user_id", userID),
|
||||
)
|
||||
return domain.CreateBetRes{}, fmt.Errorf("Unknown Role Type")
|
||||
return domain.CreateBetRes{}, fmt.Errorf("unknown role type")
|
||||
}
|
||||
|
||||
bet, err := s.CreateBet(ctx, newBet)
|
||||
|
|
@ -588,25 +587,21 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
var newOdds []domain.CreateBetOutcome
|
||||
var totalOdds float32 = 1
|
||||
|
||||
eventLogger := s.mongoLogger.With(
|
||||
zap.String("eventID", eventID),
|
||||
zap.Int32("sportID", sportID),
|
||||
zap.String("homeTeam", HomeTeam),
|
||||
zap.String("awayTeam", AwayTeam),
|
||||
)
|
||||
markets, err := s.prematchSvc.GetOddsByEventID(ctx, eventID, domain.OddMarketWithEventFilter{})
|
||||
|
||||
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))
|
||||
eventLogger.Error("failed to get odds for event", 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))
|
||||
eventLogger.Warn("empty odds for event")
|
||||
return nil, 0, fmt.Errorf("empty odds or event %v", eventID)
|
||||
}
|
||||
|
||||
|
|
@ -635,19 +630,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
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))
|
||||
eventLogger.Warn("Failed to unmarshal raw odd", 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),
|
||||
eventLogger.Warn("Failed to parse odd",
|
||||
zap.String("oddValue", selectedOdd.Odds),
|
||||
zap.Error(err))
|
||||
continue
|
||||
|
|
@ -655,17 +644,13 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
|
||||
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))
|
||||
eventLogger.Warn("Failed to parse 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",
|
||||
eventLogger.Warn("Failed to parse oddID",
|
||||
zap.String("oddID", selectedOdd.ID),
|
||||
zap.Error(err))
|
||||
continue
|
||||
|
|
@ -673,8 +658,7 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
|
||||
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",
|
||||
eventLogger.Warn("Failed to parse marketID",
|
||||
zap.String("marketID", market.MarketID),
|
||||
zap.Error(err))
|
||||
continue
|
||||
|
|
@ -701,22 +685,12 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
}
|
||||
|
||||
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)))
|
||||
eventLogger.Error("Bet Outcomes is empty for market", 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))
|
||||
eventLogger.Info("Random bet outcomes generated successfully", zap.Int("numOutcomes", len(newOdds)), zap.Float32("totalOdds", totalOdds))
|
||||
|
||||
return newOdds, totalOdds, nil
|
||||
}
|
||||
|
|
@ -724,7 +698,15 @@ func (s *Service) GenerateRandomBetOutcomes(ctx context.Context, eventID string,
|
|||
func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyID int64, leagueID domain.ValidInt64, sportID domain.ValidInt32, firstStartTime, lastStartTime domain.ValidTime) (domain.CreateBetRes, error) {
|
||||
|
||||
// Get a unexpired event id
|
||||
|
||||
randomBetLogger := s.mongoLogger.With(
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int64("branchID", branchID),
|
||||
zap.Int64("companyID", companyID),
|
||||
zap.Any("leagueID", leagueID),
|
||||
zap.Any("sportID", sportID),
|
||||
zap.Any("firstStartTime", firstStartTime),
|
||||
zap.Any("lastStartTime", lastStartTime),
|
||||
)
|
||||
events, _, err := s.eventSvc.GetPaginatedUpcomingEvents(ctx,
|
||||
domain.EventFilter{
|
||||
SportID: sportID,
|
||||
|
|
@ -734,17 +716,12 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
})
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get paginated upcoming events",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int64("branchID", branchID),
|
||||
zap.Error(err))
|
||||
randomBetLogger.Error("failed to get paginated upcoming events", 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))
|
||||
randomBetLogger.Warn("no events available for random bet")
|
||||
return domain.CreateBetRes{}, ErrNoEventsAvailable
|
||||
}
|
||||
|
||||
|
|
@ -770,12 +747,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
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)))
|
||||
s.mongoLogger.Error("failed to generate random bet outcome", zap.String("eventID", event.ID), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -784,10 +756,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
|
||||
}
|
||||
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))
|
||||
randomBetLogger.Error("Failed to generate random any outcomes for all events")
|
||||
return domain.CreateBetRes{}, ErrGenerateRandomOutcome
|
||||
}
|
||||
|
||||
|
|
@ -795,20 +764,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
|
||||
outcomesHash, err := generateOutcomeHash(randomOdds)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to generate outcome hash",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
randomBetLogger.Error("failed to generate outcome hash", zap.Error(err))
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
count, err := s.GetBetCountByUserID(ctx, userID, outcomesHash)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get bet count",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.String("outcome_hash", outcomesHash),
|
||||
zap.Error(err),
|
||||
)
|
||||
randomBetLogger.Error("failed to get bet count", zap.String("outcome_hash", outcomesHash), zap.Error(err))
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
|
|
@ -830,10 +792,7 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
|
||||
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)))
|
||||
randomBetLogger.Error("Failed to create a new random bet", zap.Error(err))
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
|
|
@ -843,19 +802,13 @@ func (s *Service) PlaceRandomBet(ctx context.Context, userID, branchID, companyI
|
|||
|
||||
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)))
|
||||
randomBetLogger.Error("Failed to create a new random bet outcome", zap.Any("randomOdds", randomOdds))
|
||||
return domain.CreateBetRes{}, err
|
||||
}
|
||||
|
||||
res := domain.ConvertCreateBetRes(bet, rows)
|
||||
|
||||
s.mongoLogger.Info("Random bets placed successfully",
|
||||
zap.Int64("userID", userID),
|
||||
zap.Int64("branchID", branchID),
|
||||
zap.String("response", fmt.Sprintf("%+v", res)))
|
||||
randomBetLogger.Info("Random bets placed successfully")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
|
@ -902,53 +855,73 @@ func (s *Service) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) e
|
|||
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)
|
||||
func (s *Service) UpdateStatus(ctx context.Context, betId int64, status domain.OutcomeStatus) error {
|
||||
|
||||
updateLogger := s.mongoLogger.With(
|
||||
zap.Int64("bet_id", betId),
|
||||
zap.String("status", status.String()),
|
||||
)
|
||||
bet, err := s.GetBetByID(ctx, betId)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to update bet status: invalid bet ID",
|
||||
zap.Int64("bet_id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
updateLogger.Error("failed to update bet status: invalid bet ID", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, bet.CompanyID)
|
||||
if err != nil {
|
||||
updateLogger.Error("failed to get settings", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if status == domain.OUTCOME_STATUS_ERROR || status == domain.OUTCOME_STATUS_PENDING {
|
||||
s.SendAdminErrorAlertNotification(ctx, status, "")
|
||||
s.SendErrorStatusNotification(ctx, status, bet.UserID, "")
|
||||
s.mongoLogger.Error("Bet Status is error",
|
||||
zap.Int64("bet_id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
if err := s.SendAdminAlertNotification(ctx, betId, status, "", bet.CompanyID); err != nil {
|
||||
updateLogger.Error("failed to send admin notification", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.SendErrorStatusNotification(ctx, betId, status, bet.UserID, ""); err != nil {
|
||||
updateLogger.Error("failed to send error notification to user", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
updateLogger.Error("bet entered error/pending state")
|
||||
return s.betStore.UpdateStatus(ctx, betId, status)
|
||||
}
|
||||
|
||||
if bet.IsShopBet {
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
return s.betStore.UpdateStatus(ctx, betId, status)
|
||||
}
|
||||
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, id)
|
||||
// After this point the bet is known to be a online customer bet
|
||||
|
||||
customerWallet, err := s.walletSvc.GetCustomerWallet(ctx, bet.UserID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to get customer wallet",
|
||||
zap.Int64("bet_id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
updateLogger.Error("failed to get customer wallet", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
resultNotification := SendResultNotificationParam{
|
||||
BetID: betId,
|
||||
Status: status,
|
||||
UserID: bet.UserID,
|
||||
SendEmail: settingsList.SendEmailOnBetFinish,
|
||||
SendSMS: settingsList.SendSMSOnBetFinish,
|
||||
}
|
||||
var amount domain.Currency
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_LOSS:
|
||||
s.SendLosingStatusNotification(ctx, status, bet.UserID, "")
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
err := s.SendLosingStatusNotification(ctx, resultNotification)
|
||||
if err != nil {
|
||||
updateLogger.Error("failed to send notification", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return s.betStore.UpdateStatus(ctx, betId, status)
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds)
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
amount = domain.CalculateWinnings(bet.Amount, bet.TotalOdds) / 2
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
amount = bet.Amount
|
||||
s.SendWinningStatusNotification(ctx, status, bet.UserID, amount, "")
|
||||
default:
|
||||
updateLogger.Error("invalid outcome status")
|
||||
return fmt.Errorf("invalid outcome status")
|
||||
}
|
||||
|
||||
|
|
@ -956,7 +929,7 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
domain.TRANSFER_DIRECT, domain.PaymentDetails{}, fmt.Sprintf("Added %v to wallet by system for winning a bet", amount.Float32()))
|
||||
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("failed to add winnings to wallet",
|
||||
updateLogger.Error("failed to add winnings to wallet",
|
||||
zap.Int64("wallet_id", customerWallet.RegularID),
|
||||
zap.Float32("amount", float32(amount)),
|
||||
zap.Error(err),
|
||||
|
|
@ -964,179 +937,211 @@ func (s *Service) UpdateStatus(ctx context.Context, id int64, status domain.Outc
|
|||
return err
|
||||
}
|
||||
|
||||
return s.betStore.UpdateStatus(ctx, id, status)
|
||||
if err := s.betStore.UpdateStatus(ctx, betId, status); err != nil {
|
||||
updateLogger.Error("failed to update bet status",
|
||||
zap.String("status", status.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
resultNotification.WinningAmount = amount
|
||||
if err := s.SendWinningStatusNotification(ctx, resultNotification); err != nil {
|
||||
|
||||
updateLogger.Error("failed to send winning notification",
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendWinningStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, winningAmount domain.Currency, extra string) error {
|
||||
func newBetResultNotification(userID int64, level domain.NotificationLevel, channel domain.DeliveryChannel, headline, message string, metadata any) *domain.Notification {
|
||||
raw, _ := json.Marshal(metadata)
|
||||
return &domain.Notification{
|
||||
RecipientID: userID,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: level,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: channel,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: raw,
|
||||
}
|
||||
}
|
||||
|
||||
type SendResultNotificationParam struct {
|
||||
BetID int64
|
||||
Status domain.OutcomeStatus
|
||||
UserID int64
|
||||
WinningAmount domain.Currency
|
||||
Extra string
|
||||
SendEmail bool
|
||||
SendSMS bool
|
||||
}
|
||||
|
||||
func (p SendResultNotificationParam) Validate() error {
|
||||
if p.BetID == 0 {
|
||||
return errors.New("BetID is required")
|
||||
}
|
||||
if p.UserID == 0 {
|
||||
return errors.New("UserID is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool {
|
||||
switch {
|
||||
case channel == domain.DeliveryChannelEmail && sendEmail:
|
||||
return true
|
||||
case channel == domain.DeliveryChannelSMS && sendSMS:
|
||||
return true
|
||||
case channel == domain.DeliveryChannelInApp:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) SendWinningStatusNotification(ctx context.Context, param SendResultNotificationParam) error {
|
||||
if err := param.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
switch param.Status {
|
||||
case domain.OUTCOME_STATUS_WIN:
|
||||
headline = "You Bet Has Won!"
|
||||
headline = fmt.Sprintf("Bet #%v Won!", param.BetID)
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
"Congratulations! Your bet #%v has won. %.2f has been credited to your wallet.",
|
||||
param.BetID,
|
||||
param.WinningAmount.Float32(),
|
||||
)
|
||||
case domain.OUTCOME_STATUS_HALF:
|
||||
headline = "You have a half win"
|
||||
headline = fmt.Sprintf("Bet #%v Half-Win", param.BetID)
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
"Your bet #%v resulted in a half-win. %.2f has been credited to your wallet.",
|
||||
param.BetID,
|
||||
param.WinningAmount.Float32(),
|
||||
)
|
||||
case domain.OUTCOME_STATUS_VOID:
|
||||
headline = "Your bet has been refunded"
|
||||
headline = fmt.Sprintf("Bet #%v Refunded", param.BetID)
|
||||
message = fmt.Sprintf(
|
||||
"You have been awarded %.2f",
|
||||
winningAmount.Float32(),
|
||||
"Your bet #%v has been voided. %.2f has been refunded to your wallet.",
|
||||
param.BetID,
|
||||
param.WinningAmount.Float32(),
|
||||
)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported status: %v", param.Status)
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"winning_amount":%.2f,
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, winningAmount.Float32(), status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
for _, channel := range []domain.DeliveryChannel{
|
||||
domain.DeliveryChannelInApp,
|
||||
domain.DeliveryChannelEmail,
|
||||
domain.DeliveryChannelSMS,
|
||||
} {
|
||||
if !shouldSend(channel, param.SendEmail, param.SendSMS) {
|
||||
continue
|
||||
}
|
||||
n := newBetResultNotification(param.UserID, domain.NotificationLevelSuccess, channel, headline, message, map[string]any{
|
||||
"winning_amount": param.WinningAmount.Float32(),
|
||||
"status": param.Status,
|
||||
"more": param.Extra,
|
||||
})
|
||||
if err := s.notificationSvc.SendNotification(ctx, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendLosingStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error {
|
||||
func (s *Service) SendLosingStatusNotification(ctx context.Context, param SendResultNotificationParam) error {
|
||||
if err := param.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
switch param.Status {
|
||||
case domain.OUTCOME_STATUS_LOSS:
|
||||
headline = "Your bet has lost"
|
||||
message = "Better luck next time"
|
||||
headline = fmt.Sprintf("Bet #%v Lost", param.BetID)
|
||||
message = "Unfortunately, your bet did not win this time. Better luck next time!"
|
||||
default:
|
||||
return fmt.Errorf("unsupported status: %v", param.Status)
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
for _, channel := range []domain.DeliveryChannel{
|
||||
domain.DeliveryChannelInApp,
|
||||
domain.DeliveryChannelEmail,
|
||||
domain.DeliveryChannelSMS,
|
||||
} {
|
||||
if !shouldSend(channel, param.SendEmail, param.SendSMS) {
|
||||
continue
|
||||
}
|
||||
n := newBetResultNotification(param.UserID, domain.NotificationLevelWarning, channel, headline, message, map[string]any{
|
||||
"status": param.Status,
|
||||
"more": param.Extra,
|
||||
})
|
||||
if err := s.notificationSvc.SendNotification(ctx, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendErrorStatusNotification(ctx context.Context, status domain.OutcomeStatus, userID int64, extra string) error {
|
||||
func (s *Service) SendErrorStatusNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, userID int64, extra string) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING:
|
||||
headline = "There was an error with your bet"
|
||||
message = "We have encounter an error with your bet. We will fix it as soon as we can"
|
||||
headline = fmt.Sprintf("Bet #%v Processing Issue", betID)
|
||||
message = "We encountered a problem while processing your bet. Our team is working to resolve it as soon as possible."
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported status: %v", status)
|
||||
}
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
RecipientID: userID,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 1,
|
||||
ErrorSeverity: domain.NotificationErrorSeverityHigh,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
}
|
||||
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
return err
|
||||
for _, channel := range []domain.DeliveryChannel{
|
||||
domain.DeliveryChannelInApp,
|
||||
domain.DeliveryChannelEmail,
|
||||
} {
|
||||
n := newBetResultNotification(userID, domain.NotificationLevelError, channel, headline, message, map[string]any{
|
||||
"status": status,
|
||||
"more": extra,
|
||||
})
|
||||
if err := s.notificationSvc.SendNotification(ctx, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status domain.OutcomeStatus, extra string) error {
|
||||
func (s *Service) SendAdminAlertNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, extra string, companyID int64) error {
|
||||
|
||||
var headline string
|
||||
var message string
|
||||
|
||||
switch status {
|
||||
case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING:
|
||||
headline = "There was an error processing bet"
|
||||
message = "We have encounter an error with bet. We will fix it as soon as we can"
|
||||
}
|
||||
headline = fmt.Sprintf("Processing Error for Bet #%v", betID)
|
||||
message = "A processing error occurred with this bet. Please review and take corrective action."
|
||||
|
||||
betNotification := &domain.Notification{
|
||||
ErrorSeverity: domain.NotificationErrorSeverityHigh,
|
||||
DeliveryStatus: domain.DeliveryStatusPending,
|
||||
IsRead: false,
|
||||
Type: domain.NOTIFICATION_TYPE_BET_RESULT,
|
||||
Level: domain.NotificationLevelSuccess,
|
||||
Reciever: domain.NotificationRecieverSideCustomer,
|
||||
DeliveryChannel: domain.DeliveryChannelEmail,
|
||||
Payload: domain.NotificationPayload{
|
||||
Headline: headline,
|
||||
Message: message,
|
||||
},
|
||||
Priority: 2,
|
||||
Metadata: fmt.Appendf(nil, `{
|
||||
"status":%v
|
||||
"more": %v
|
||||
}`, status, extra),
|
||||
default:
|
||||
return fmt.Errorf("unsupported status: %v", status)
|
||||
}
|
||||
|
||||
super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
|
||||
|
|
@ -1153,6 +1158,10 @@ func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status do
|
|||
|
||||
admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{
|
||||
Role: string(domain.RoleAdmin),
|
||||
CompanyID: domain.ValidInt64{
|
||||
Value: companyID,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -1166,23 +1175,17 @@ func (s *Service) SendAdminErrorAlertNotification(ctx context.Context, status do
|
|||
users := append(super_admin_users, admin_users...)
|
||||
|
||||
for _, user := range users {
|
||||
betNotification.RecipientID = user.ID
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send admin notification",
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
}
|
||||
betNotification.DeliveryChannel = domain.DeliveryChannelEmail
|
||||
if err := s.notificationSvc.SendNotification(ctx, betNotification); err != nil {
|
||||
s.mongoLogger.Error("failed to send email admin notification",
|
||||
zap.Int64("admin_id", user.ID),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return err
|
||||
for _, channel := range []domain.DeliveryChannel{
|
||||
domain.DeliveryChannelInApp,
|
||||
domain.DeliveryChannelEmail,
|
||||
} {
|
||||
n := newBetResultNotification(user.ID, domain.NotificationLevelError, channel, headline, message, map[string]any{
|
||||
"status": status,
|
||||
"more": extra,
|
||||
})
|
||||
if err := s.notificationSvc.SendNotification(ctx, n); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1366,6 +1369,14 @@ func (s *Service) ProcessBetCashback(ctx context.Context) error {
|
|||
}
|
||||
|
||||
settingsList, err := s.settingSvc.GetOverrideSettingsList(ctx, bet.CompanyID)
|
||||
if err != nil {
|
||||
s.mongoLogger.Error("Failed to get settings",
|
||||
zap.Int64("userID", bet.UserID),
|
||||
zap.Error(err))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cashbackAmount := math.Min(float64(settingsList.CashbackAmountCap.Float32()), float64(calculateCashbackAmount(bet.Amount.Float32(), bet.TotalOdds)))
|
||||
|
||||
_, err = s.walletSvc.AddToWallet(ctx, wallets.StaticID, domain.ToCurrency(float32(cashbackAmount)), domain.ValidInt64{}, domain.TRANSFER_DIRECT,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ package bonus
|
|||
import (
|
||||
"context"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type BonusStore interface {
|
||||
CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error
|
||||
GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error)
|
||||
GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error)
|
||||
UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error
|
||||
CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error)
|
||||
GetAllUserBonuses(ctx context.Context) ([]domain.UserBonus, error)
|
||||
GetBonusesByUserID(ctx context.Context, userID int64) ([]domain.UserBonus, error)
|
||||
GetBonusByID(ctx context.Context, bonusID int64) (domain.UserBonus, error)
|
||||
GetBonusStats(ctx context.Context, filter domain.BonusFilter) (domain.BonusStats, error)
|
||||
UpdateUserBonus(ctx context.Context, bonusID int64, IsClaimed bool) error
|
||||
DeleteUserBonus(ctx context.Context, bonusID int64) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,32 +2,112 @@ package bonus
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/pkgs/helpers"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
bonusStore BonusStore
|
||||
bonusStore BonusStore
|
||||
walletSvc *wallet.Service
|
||||
settingSvc *settings.Service
|
||||
mongoLogger *zap.Logger
|
||||
}
|
||||
|
||||
func NewService(bonusStore BonusStore) *Service {
|
||||
func NewService(bonusStore BonusStore, walletSvc *wallet.Service, settingSvc *settings.Service, mongoLogger *zap.Logger) *Service {
|
||||
return &Service{
|
||||
bonusStore: bonusStore,
|
||||
bonusStore: bonusStore,
|
||||
walletSvc: walletSvc,
|
||||
settingSvc: settingSvc,
|
||||
mongoLogger: mongoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) CreateBonusMultiplier(ctx context.Context, multiplier float32, balance_cap int64) error {
|
||||
return s.bonusStore.CreateBonusMultiplier(ctx, multiplier, balance_cap)
|
||||
var (
|
||||
ErrWelcomeBonusNotActive = errors.New("welcome bonus is not active")
|
||||
ErrWelcomeBonusCountReached = errors.New("welcome bonus max deposit count reached")
|
||||
)
|
||||
|
||||
func (s *Service) ProcessWelcomeBonus(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))
|
||||
|
||||
_, err = s.CreateUserBonus(ctx, domain.CreateBonus{
|
||||
Name: "Welcome Bonus",
|
||||
Description: "Awarded when the user logged in for the first time",
|
||||
UserID: userID,
|
||||
BonusCode: helpers.GenerateFastCode(),
|
||||
RewardAmount: domain.Currency(newBalance),
|
||||
ExpiresAt: time.Now().Add(time.Duration(settingsList.WelcomeBonusExpire) * 24 * time.Hour),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Add a claim function that adds to the static wallet when the user inputs his bonus code
|
||||
// _, err = s.walletSvc.AddToWallet(ctx, wallet.StaticID, domain.ToCurrency(float32(newBalance)), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||
// fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", newBalance, settingsList.WelcomeBonusMultiplier),
|
||||
// )
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) GetBonusMultiplier(ctx context.Context) ([]dbgen.GetBonusMultiplierRow, error) {
|
||||
return s.bonusStore.GetBonusMultiplier(ctx)
|
||||
func (s *Service) CreateUserBonus(ctx context.Context, bonus domain.CreateBonus) (domain.UserBonus, error) {
|
||||
return s.bonusStore.CreateUserBonus(ctx, bonus)
|
||||
}
|
||||
|
||||
func (s *Service) GetBonusBalanceCap(ctx context.Context) ([]dbgen.GetBonusBalanceCapRow, error) {
|
||||
return s.bonusStore.GetBonusBalanceCap(ctx)
|
||||
func (s *Service) GetAllUserBonuses(ctx context.Context) ([]domain.UserBonus, error) {
|
||||
return s.bonusStore.GetAllUserBonuses(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateBonusMultiplier(ctx context.Context, id int64, mulitplier float32, balance_cap int64) error {
|
||||
return s.bonusStore.UpdateBonusMultiplier(ctx, id, mulitplier, balance_cap)
|
||||
func (s *Service) GetBonusesByUserID(ctx context.Context, userID int64) ([]domain.UserBonus, error) {
|
||||
return s.bonusStore.GetBonusesByUserID(ctx, userID)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type TransferStore interface {
|
|||
GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.TransferDetail, error)
|
||||
GetTransferByReference(ctx context.Context, reference string) (domain.TransferDetail, error)
|
||||
GetTransferByID(ctx context.Context, id int64) (domain.TransferDetail, error)
|
||||
GetTransferStats(ctx context.Context, walletID int64) (domain.TransferStats, error)
|
||||
UpdateTransferVerification(ctx context.Context, id int64, verified bool) error
|
||||
UpdateTransferStatus(ctx context.Context, id int64, status string) error
|
||||
// InitiateTransfer(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error)
|
||||
|
|
@ -47,9 +48,9 @@ type ApprovalStore interface {
|
|||
}
|
||||
|
||||
type DirectDepositStore interface {
|
||||
CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error)
|
||||
UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error)
|
||||
GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error)
|
||||
CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error)
|
||||
UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error)
|
||||
GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error)
|
||||
GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ func (s *Service) GetTransfersByWallet(ctx context.Context, walletID int64) ([]d
|
|||
return s.transferStore.GetTransfersByWallet(ctx, walletID)
|
||||
}
|
||||
|
||||
func (s *Service) GetTransferStats(ctx context.Context, walletID int64) (domain.TransferStats, error) {
|
||||
return s.transferStore.GetTransferStats(ctx, walletID)
|
||||
}
|
||||
func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
||||
return s.transferStore.UpdateTransferVerification(ctx, id, verified)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,6 +215,9 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.
|
|||
return newTransfer, err
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Directly Refilling wallet without
|
||||
// func (s *Service) RefillWallet(ctx context.Context, transfer domain.CreateTransfer) (domain.Transfer, error) {
|
||||
// receiverWallet, err := s.GetWalletByID(ctx, transfer.ReceiverWalletID)
|
||||
|
|
|
|||
|
|
@ -1,98 +1,98 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"time"
|
||||
// import (
|
||||
// "time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
// "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
// "github.com/gofiber/fiber/v2"
|
||||
// "go.uber.org/zap"
|
||||
// )
|
||||
|
||||
func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
}
|
||||
// func (h *Handler) CreateBonusMultiplier(c *fiber.Ctx) error {
|
||||
// var req struct {
|
||||
// Multiplier float32 `json:"multiplier"`
|
||||
// BalanceCap int64 `json:"balance_cap"`
|
||||
// }
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("failed to parse bonus multiplier request", "error", err)
|
||||
h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
}
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// h.logger.Error("failed to parse bonus multiplier request", "error", err)
|
||||
// h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
// }
|
||||
|
||||
// currently only one multiplier is allowed
|
||||
// we can add an active bool in the db and have mulitple bonus if needed
|
||||
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("failed to get bonus multiplier", "error", err)
|
||||
h.mongoLoggerSvc.Info("Failed to get bonus multiplier",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
}
|
||||
// // currently only one multiplier is allowed
|
||||
// // we can add an active bool in the db and have mulitple bonus if needed
|
||||
// multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
// if err != nil {
|
||||
// h.logger.Error("failed to get bonus multiplier", "error", err)
|
||||
// h.mongoLoggerSvc.Info("Failed to get bonus multiplier",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
// }
|
||||
|
||||
if len(multipliers) > 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "only one multiplier is allowed")
|
||||
}
|
||||
// if len(multipliers) > 0 {
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "only one multiplier is allowed")
|
||||
// }
|
||||
|
||||
if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier, req.BalanceCap); err != nil {
|
||||
h.mongoLoggerSvc.Error("failed to create bonus multiplier",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to create bonus multiplier"+err.Error())
|
||||
}
|
||||
// if err := h.bonusSvc.CreateBonusMultiplier(c.Context(), req.Multiplier, req.BalanceCap); err != nil {
|
||||
// h.mongoLoggerSvc.Error("failed to create bonus multiplier",
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "failed to create bonus multiplier"+err.Error())
|
||||
// }
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Create bonus multiplier successfully", nil, nil)
|
||||
}
|
||||
// return response.WriteJSON(c, fiber.StatusOK, "Create bonus multiplier successfully", nil, nil)
|
||||
// }
|
||||
|
||||
func (h *Handler) GetBonusMultiplier(c *fiber.Ctx) error {
|
||||
multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
if err != nil {
|
||||
h.mongoLoggerSvc.Info("failed to get bonus multiplier",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
|
||||
}
|
||||
// func (h *Handler) GetBonusMultiplier(c *fiber.Ctx) error {
|
||||
// multipliers, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
// if err != nil {
|
||||
// h.mongoLoggerSvc.Info("failed to get bonus multiplier",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
|
||||
// }
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Fetched bonus multiplier successfully", multipliers, nil)
|
||||
}
|
||||
// return response.WriteJSON(c, fiber.StatusOK, "Fetched bonus multiplier successfully", multipliers, nil)
|
||||
// }
|
||||
|
||||
func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
ID int64 `json:"id"`
|
||||
Multiplier float32 `json:"multiplier"`
|
||||
BalanceCap int64 `json:"balance_cap"`
|
||||
}
|
||||
// func (h *Handler) UpdateBonusMultiplier(c *fiber.Ctx) error {
|
||||
// var req struct {
|
||||
// ID int64 `json:"id"`
|
||||
// Multiplier float32 `json:"multiplier"`
|
||||
// BalanceCap int64 `json:"balance_cap"`
|
||||
// }
|
||||
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
|
||||
zap.Int("status_code", fiber.StatusBadRequest),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
}
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// h.mongoLoggerSvc.Info("failed to parse bonus multiplier",
|
||||
// zap.Int("status_code", fiber.StatusBadRequest),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
||||
// }
|
||||
|
||||
if err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier, req.BalanceCap); err != nil {
|
||||
h.logger.Error("failed to update bonus multiplier", "error", err)
|
||||
h.mongoLoggerSvc.Error("failed to update bonus multiplier",
|
||||
zap.Int64("id", req.ID),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "failed to update bonus multiplier:"+err.Error())
|
||||
}
|
||||
// if err := h.bonusSvc.UpdateBonusMultiplier(c.Context(), req.ID, req.Multiplier, req.BalanceCap); err != nil {
|
||||
// h.logger.Error("failed to update bonus multiplier", "error", err)
|
||||
// h.mongoLoggerSvc.Error("failed to update bonus multiplier",
|
||||
// zap.Int64("id", req.ID),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "failed to update bonus multiplier:"+err.Error())
|
||||
// }
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Updated bonus multiplier successfully", nil, nil)
|
||||
}
|
||||
// return response.WriteJSON(c, fiber.StatusOK, "Updated bonus multiplier successfully", nil, nil)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package handlers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
|
@ -53,36 +52,39 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
// get static wallet of user
|
||||
wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
Message: "Failed to initiate Chapa deposit",
|
||||
})
|
||||
}
|
||||
// wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
// Error: err.Error(),
|
||||
// Message: "Failed to initiate Chapa deposit",
|
||||
// })
|
||||
// }
|
||||
|
||||
var multiplier float32 = 1
|
||||
bonusMultiplier, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
if err == nil {
|
||||
multiplier = bonusMultiplier[0].Multiplier
|
||||
}
|
||||
// var multiplier float32 = 1
|
||||
// bonusMultiplier, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
||||
// if err == nil {
|
||||
// multiplier = bonusMultiplier[0].Multiplier
|
||||
// }
|
||||
|
||||
var balanceCap int64 = 0
|
||||
bonusBalanceCap, err := h.bonusSvc.GetBonusBalanceCap(c.Context())
|
||||
if err == nil {
|
||||
balanceCap = bonusBalanceCap[0].BalanceCap
|
||||
}
|
||||
// var balanceCap int64 = 0
|
||||
// bonusBalanceCap, err := h.bonusSvc.GetBonusBalanceCap(c.Context())
|
||||
// if err == nil {
|
||||
// balanceCap = bonusBalanceCap[0].BalanceCap
|
||||
// }
|
||||
|
||||
capedBalanceAmount := domain.Currency((math.Min(req.Amount, float64(balanceCap)) * float64(multiplier)) * 100)
|
||||
// capedBalanceAmount := domain.Currency((math.Min(req.Amount, float64(balanceCap)) * float64(multiplier)) * 100)
|
||||
|
||||
_, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, capedBalanceAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||
fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", capedBalanceAmount, multiplier),
|
||||
)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to add bonus to static wallet", "walletID", wallet.StaticID, "user id", userID, "error", err)
|
||||
return err
|
||||
}
|
||||
// _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, capedBalanceAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
||||
// fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", capedBalanceAmount, multiplier),
|
||||
// )
|
||||
// if err != nil {
|
||||
// h.logger.Error("Failed to add bonus to static wallet", "walletID", wallet.StaticID, "user id", userID, "error", err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if err := h.bonusSvc.ProcessWelcomeBonus(c.Context(), domain.ToCurrency(float32(req.Amount)), 0, userID); err != nil {
|
||||
// return err
|
||||
// }
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Chapa deposit process initiated successfully",
|
||||
Data: checkoutURL,
|
||||
|
|
|
|||
|
|
@ -206,9 +206,9 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Get("/raffle-ticket/unsuspend/:id", a.authMiddleware, h.UnSuspendRaffleTicket)
|
||||
|
||||
// Bonus Routes
|
||||
groupV1.Get("/bonus", a.authMiddleware, h.GetBonusMultiplier)
|
||||
groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier)
|
||||
groupV1.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier)
|
||||
// groupV1.Get("/bonus", a.authMiddleware, h.GetBonusMultiplier)
|
||||
// groupV1.Post("/bonus/create", a.authMiddleware, h.CreateBonusMultiplier)
|
||||
// groupV1.Put("/bonus/update", a.authMiddleware, h.UpdateBonusMultiplier)
|
||||
|
||||
groupV1.Get("/cashiers", a.authMiddleware, h.GetAllCashiers)
|
||||
groupV1.Get("/cashiers/:id", a.authMiddleware, h.GetCashierByID)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user