Розробка реферальної системи
Реферальна програма — це механізм, при якому існуючий користувач отримує вознаграждення за привлечення нового. На практиці це набір технічних завдань: генерація унікальних посилань, атрибуція переходів, облік виконаних умов, нарахування й виплата вознаграждень.
Модель даних
CREATE TABLE referral_codes (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
code VARCHAR(32) UNIQUE NOT NULL,
type VARCHAR(32) DEFAULT 'personal', -- personal/promo/partner
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE referral_clicks (
id BIGSERIAL PRIMARY KEY,
code_id BIGINT REFERENCES referral_codes(id),
ip INET,
user_agent TEXT,
landed_at TIMESTAMPTZ DEFAULT NOW(),
converted BOOLEAN DEFAULT FALSE
);
CREATE TABLE referrals (
id BIGSERIAL PRIMARY KEY,
referrer_id BIGINT REFERENCES users(id), -- хто привів
referred_id BIGINT REFERENCES users(id), -- кого привели
code_id BIGINT REFERENCES referral_codes(id),
status VARCHAR(32) DEFAULT 'pending', -- pending/qualified/rewarded/cancelled
qualified_at TIMESTAMPTZ, -- виконано умова
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE referral_rewards (
id BIGSERIAL PRIMARY KEY,
referral_id BIGINT REFERENCES referrals(id),
recipient_id BIGINT REFERENCES users(id), -- реферер або реферал
type VARCHAR(32), -- 'cashback', 'bonus_points', 'discount'
amount DECIMAL(14,2),
currency CHAR(3) DEFAULT 'RUB',
status VARCHAR(32) DEFAULT 'pending', -- pending/paid/cancelled
paid_at TIMESTAMPTZ
);
Генерація унікального коду
class ReferralCodeService {
public function generateForUser(User $user): ReferralCode {
// Перевіряємо, немає ли вже коду
if ($existing = ReferralCode::where('user_id', $user->id)->first()) {
return $existing;
}
do {
// Читаємий код на основі імені користувача + випадковий суфікс
$base = strtoupper(substr(preg_replace('/[^a-z]/i', '', $user->name), 0, 4));
$code = $base . strtoupper(Str::random(4));
} while (ReferralCode::where('code', $code)->exists());
return ReferralCode::create([
'user_id' => $user->id,
'code' => $code,
]);
}
}
Реферальне посилання та cookie
Реферальний параметр передається через URL: https://example.com/register?ref=IVAN4X2K. Потрібно зберегти атрибуцію навіть якщо користувач не зареєструється одразу:
// Middleware: ReferralTracker
class ReferralTrackerMiddleware {
public function handle(Request $request, Closure $next): Response {
$code = $request->query('ref');
if ($code && !session()->has('referral_code')) {
$referralCode = ReferralCode::where('code', $code)->first();
if ($referralCode) {
session(['referral_code' => $code]);
// Логуємо клік
ReferralClick::create([
'code_id' => $referralCode->id,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
}
}
return $next($request);
}
}
Атрибуція при реєстрації
// У UserRegistrationService
public function register(array $data): User {
$user = User::create($data);
$referralCode = session()->pull('referral_code');
if ($referralCode) {
$code = ReferralCode::where('code', $referralCode)->first();
if ($code && $code->user_id !== $user->id) {
Referral::create([
'referrer_id' => $code->user_id,
'referred_id' => $user->id,
'code_id' => $code->id,
'status' => 'pending',
]);
// Позначаємо клік як сконвертований
ReferralClick::where('code_id', $code->id)
->where('converted', false)
->latest('landed_at')
->first()
?->update(['converted' => true]);
}
}
return $user;
}
Умови начисління вознаграждення
Реферал вважається "кваліфікованим" тільки при виконанні певної умови: перша оплата, досягнення порога покупок, закінчення trial-періоду. Реалізується через Events:
// Подія: перша оплата нового користувача
class FirstPurchaseMade {
public function __construct(public Order $order) {}
}
// Listener
class QualifyReferralOnFirstPurchase {
public function handle(FirstPurchaseMade $event): void {
$referral = Referral::where('referred_id', $event->order->user_id)
->where('status', 'pending')
->first();
if (!$referral) return;
DB::transaction(function() use ($referral, $event) {
$referral->update([
'status' => 'qualified',
'qualified_at' => now(),
]);
$program = ReferralProgram::active()->first();
// Вознаграждення рефереру
ReferralReward::create([
'referral_id' => $referral->id,
'recipient_id' => $referral->referrer_id,
'type' => $program->reward_type,
'amount' => $this->calculateReward($program, $event->order),
'status' => 'pending',
]);
// Двостороння програма: бонус й новому користувачу
if ($program->reward_referred) {
ReferralReward::create([
'referral_id' => $referral->id,
'recipient_id' => $referral->referred_id,
'type' => $program->referred_reward_type,
'amount' => $program->referred_reward_amount,
'status' => 'pending',
]);
}
});
}
private function calculateReward(ReferralProgram $program, Order $order): float {
return match($program->reward_type) {
'fixed' => $program->reward_amount,
'percentage' => round($order->total * $program->reward_percent / 100, 2),
default => 0,
};
}
}
Виплата вознаграждень
Накопичені вознаграждення виплачуються пакетно (раз на тиждень) або одразу — залежить від типу:
// Бонусні бали — мгновенно
class CreditBonusPoints implements ShouldQueue {
public function handle(ReferralRewardCreated $event): void {
$reward = $event->reward;
if ($reward->type !== 'bonus_points') return;
BonusAccount::firstOrCreate(['user_id' => $reward->recipient_id])
->increment('balance', $reward->amount);
$reward->update(['status' => 'paid', 'paid_at' => now()]);
}
}
Особистий кабінет реферера
Страница статистики реферальної програми показує коди, посилання, статистику конверсій та вознаграждень.
Захист від фрода
Базові перевірки:
// Самореферал (користувач ввів власний код)
if ($referral->referrer_id === $event->order->user_id) {
$referral->update(['status' => 'cancelled']);
return;
}
// Один IP зареєстрував кілька аккаунтів
$sameIpUsers = DB::table('user_registrations')
->where('ip', $event->order->user->registration_ip)
->where('created_at', '>', now()->subDays(7))
->count();
if ($sameIpUsers > 3) {
$referral->update(['status' => 'fraud_suspect']);
return;
}
Терміни реалізації
Базова реферальна система з кодами, атрибуцією й нарахуванням фіксованого вознаграждення: 1–1,5 тижні. Двостороння програма з процентними бонусами, особистим кабінетом і захистом від фрода: 2–2,5 тижні. Багаторівнева (MLM-подібна) реферальна система з деревом рефералів: плюс 1–2 тижні.







