Разработка системы фрибетов крипто-казино
Фрибет (free bet) — это бесплатная ставка, которую казино предоставляет игроку. В отличие от депозитного бонуса, фрибет не требует внесения средств. Пользователь получает возможность сыграть бесплатно, но при выигрыше получает только чистую прибыль (net winnings), а не полную сумму ставки + прибыль.
Механика фрибета
Стандартная механика:
- Игрок получает free bet на $10
- Ставит фрибет на событие с коэффициентом 2.0
- При выигрыше получает $10 (прибыль), не $20 (возврат ставки + прибыль)
- При проигрыше — ничего не теряет (ставка была бесплатной)
Free Spin (слоты) — аналог фрибета для слот-машин. Определённое количество прокруток без использования реального баланса.
Модель данных
class FreeBet(BaseModel):
id: str
user_id: str
type: str # 'SPORTS_BET', 'CASINO_BET', 'FREE_SPIN'
status: str # 'AVAILABLE', 'USED', 'EXPIRED', 'WON', 'LOST'
amount: Decimal # сумма фрибета
currency: str
# Для free spins
spin_count: int = 0
spins_used: int = 0
eligible_games: list[str] = [] # game IDs
# Для sports bets
min_odds: Optional[Decimal] # минимальный коэффициент
eligible_markets: list[str] = [] # типы рынков
# Результаты
bet_id: Optional[str] # ID использованной ставки
winnings: Decimal = Decimal(0) # только чистая прибыль
# Wagering на выигрыш
wagering_required: bool = True
wagering_multiplier: int = 1 # 1x для sports, до 30x для casino
expires_at: datetime
issued_at: datetime
source: str # 'WELCOME', 'PROMO', 'REWARD', 'REFERRAL'
Применение фрибета к ставке
class FreeBetService:
async def apply_free_bet(
self,
user_id: str,
free_bet_id: str,
bet_params: BetRequest,
) -> BetResult:
free_bet = await self.freebet_repo.get(free_bet_id)
# Валидация
if free_bet.user_id != user_id:
raise AuthorizationError()
if free_bet.status != "AVAILABLE":
raise InvalidStatusError(f"Free bet status: {free_bet.status}")
if datetime.utcnow() > free_bet.expires_at:
raise ExpiredError()
# Проверяем eligibility
if free_bet.min_odds and bet_params.odds < free_bet.min_odds:
raise ValidationError(f"Minimum odds: {free_bet.min_odds}")
# Помечаем как используемый (атомарно)
async with self.db.transaction():
updated = await self.freebet_repo.claim(free_bet_id, bet_params.bet_id)
if not updated:
raise ConflictError("Free bet already used")
# Создаём ставку без списания с реального баланса
bet = await self.bet_service.place_bet(
user_id=user_id,
amount=free_bet.amount,
is_free_bet=True,
free_bet_id=free_bet_id,
**bet_params.dict(),
)
return bet
async def settle_free_bet(self, bet: Bet, outcome: str):
"""Расчёт выигрыша по фрибету"""
free_bet = await self.freebet_repo.get(bet.free_bet_id)
if outcome == "WIN":
# Выигрыш = прибыль (без возврата суммы ставки)
winnings = bet.potential_profit # (bet.amount * odds) - bet.amount
# Применяем wagering если нужно
if free_bet.wagering_required:
# Зачисляем как заблокированные средства с wagering requirement
await self.bonus_service.create_winnings_bonus(
user_id=free_bet.user_id,
amount=winnings,
wagering_multiplier=free_bet.wagering_multiplier,
reference=f"FREEBET_WIN:{free_bet.id}",
)
else:
# Прямое зачисление
await self.balance_service.credit(
user_id=free_bet.user_id,
amount=winnings,
reference=f"FREEBET_WIN:{free_bet.id}",
)
await self.freebet_repo.update_status(free_bet.id, "WON", winnings=winnings)
else:
await self.freebet_repo.update_status(free_bet.id, "LOST")
Free Spins механика
class FreeSpinService:
async def use_free_spin(self, user_id: str, free_bet_id: str, game_id: str) -> SpinResult:
free_bet = await self.freebet_repo.get(free_bet_id)
if game_id not in free_bet.eligible_games:
raise ValidationError("Game not eligible for this free spin")
if free_bet.spins_used >= free_bet.spin_count:
raise ValidationError("All spins already used")
# Атомарно увеличиваем счётчик использованных спинов
remaining = await self.freebet_repo.consume_spin(free_bet_id)
if remaining < 0:
raise ConflictError("Race condition: spin already consumed")
# Запускаем игровой движок без реальных денег
result = await self.game_engine.spin(
game_id=game_id,
bet_amount=free_bet.amount / free_bet.spin_count,
is_free=True,
)
if result.winnings > 0:
await self.credit_free_spin_winnings(user_id, free_bet, result.winnings)
# Если все спины использованы
if remaining == 0:
await self.freebet_repo.update_status(free_bet_id, "USED")
return result
Маркетинговая аналитика
Фрибеты — маркетинговый инструмент, поэтому нужна аналитика эффективности:
- Conversion rate: % пользователей, получивших фрибет и совершивших реальный депозит
- LTV uplift: сравнение LTV пользователей с фрибетом vs без
- Cost per acquisition: стоимость привлечения через фрибет vs другие каналы
- Abuse rate: % пользователей, злоупотребляющих фрибетами без намерения депонировать







