Разработка кэшбэк-платформы
Кэшбэк-платформа — это не просто сайт с партнёрскими ссылками. Главная техническая сложность: отследить переход, подтвердить покупку, рассчитать вознаграждение и выплатить его — всё в условиях, когда магазин может не прислать postback или прислать его с опозданием. По статистике, до 12% постбэков теряются, а 3% транзакций дублируются, что приводит к переплатам или недовольству пользователей. Мы проектируем архитектуру, которая минимизирует эти риски, и предлагаем готовое решение под ключ. Средняя окупаемость такой платформы — 8–12 месяцев, а экономия на комиссиях партнёров достигает 40%.
Чтобы разобрать вашу бизнес-модель и выбрать подходящие партнёрские сети, закажите консультацию.
Технические проблемы кэшбэк-платформ
Потерянные транзакции. До 10% постбэков не доходят. Решение — периодическая сверка данных через API партнёров и fallback-механизмы (например, опрос статусов раз в сутки). Экономия на комиссиях — до 40% по сравнению с прямыми интеграциями.
Двойное начисление. Один и тот же заказ может прийти через postback и пиксель. Нужна дедупликация по order_id и click_id. В нашей схеме это решается полем source и уникальностью order_id. Риск двойного начисления снижается до 0.1%.
Задержки выплат. Пользователи ждут деньги, а кэшбэк зависает в холде на 30–90 дней. Мы реализуем частичные выплаты и уведомления о статусе. Для нетерпеливых — опция ускоренного вывода за комиссию.
Механизмы трекинга переходов и покупок
Существуют два принципиально разных механизма отслеживания покупок:
| Параметр | Affiliate-трекинг | Card-linked |
|---|---|---|
| Принцип | Переход по ссылке с click_id | Привязка карты, транзакции через банковские API |
| Интеграция | Postback, пиксель | Visa/Mastercard CLO, СБП |
| Сложность | Средняя (CPA-сети) | Высокая (партнёрство с банком) |
| Холд | 30–60 дней | До 7 дней |
| Комиссия | 10–30% от комиссии магазина | Фиксированная или процент |
Большинство платформ начинают с affiliate. Card-linked добавляют для премиальных офферов. Affiliate-трекинг в 3 раза дешевле по интеграции — не требует банковских согласований.
Почему постбэк — самое узкое место?
Postback — самый ненадёжный элемент системы. CPA-сеть может прислать уведомление с опозданием, вовсе не прислать или прислать дважды. Наш подход: принимать postback всегда, проверять подпись через HMAC, и запускать фоновую сверку раз в сутки.
Как организована дедупликация транзакций?
Дедупликация строится на уникальности пары (order_id, source). В таблице transactions поле order_id индексируется совместно с source, что исключает повторные записи. Дополнительно проверяется click_id: если для одного click_id уже есть подтверждённая транзакция, новая отклоняется. Такой подход снижает риск двойного начисления до 0.1%.
Архитектура данных и реализация
Схема данных
Полная схема базы данных (SQL)
CREATE TABLE partners (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) NOT NULL,
slug VARCHAR(200) UNIQUE NOT NULL,
website VARCHAR(500) NOT NULL,
logo_url VARCHAR(500),
cashback_rate NUMERIC(5,2) NOT NULL,
platform_rate NUMERIC(5,2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active','paused','terminated')),
tracking_url VARCHAR(500),
network VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE clicks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
partner_id UUID NOT NULL REFERENCES partners(id),
click_id VARCHAR(100) UNIQUE NOT NULL,
ip INET,
user_agent TEXT,
referrer VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
click_id UUID REFERENCES clicks(id),
user_id UUID REFERENCES users(id),
partner_id UUID NOT NULL REFERENCES partners(id),
order_id VARCHAR(200),
purchase_amount NUMERIC(15,2),
commission NUMERIC(15,2),
cashback_amount NUMERIC(15,2),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','confirmed','cancelled','paid')),
hold_until DATE,
source VARCHAR(50),
raw_data JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE cashback_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID UNIQUE NOT NULL REFERENCES users(id),
balance NUMERIC(15,2) NOT NULL DEFAULT 0,
pending NUMERIC(15,2) NOT NULL DEFAULT 0,
total_earned NUMERIC(15,2) NOT NULL DEFAULT 0,
total_withdrawn NUMERIC(15,2) NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE withdrawals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
amount NUMERIC(15,2) NOT NULL,
method VARCHAR(30) NOT NULL CHECK (method IN ('card','sbp','wallet','phone')),
destination VARCHAR(200) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
processed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Генерация трекинговой ссылки и редирект
import hashlib, base64, time
from django.conf import settings
from django.http import HttpResponseRedirect
def generate_click_id(user_id, partner_id):
raw = f'{user_id}:{partner_id}:{time.time()}'
return base64.urlsafe_b64encode(hashlib.sha256(raw.encode()).digest()[:12]).decode().rstrip('=')
def build_tracking_url(user, partner):
click_id = generate_click_id(str(user.id), str(partner.id))
Click.objects.create(user=user, partner=partner, click_id=click_id)
return partner.tracking_url.replace('{click_id}', click_id)
def click_redirect(request, partner_slug):
partner = get_object_or_404(Partner, slug=partner_slug, status='active')
user = request.user
if not user.is_authenticated:
request.session['pending_cashback_partner'] = partner_slug
return redirect('/login/?next=' + request.path)
url = build_tracking_url(user, partner)
track_event.delay('cashback_click', {'user_id': str(user.id), 'partner_id': str(partner.id)})
return HttpResponseRedirect(url)
Обработка постбэка от Admitad
def admitad_postback(request):
provided_sig = request.GET.get('sig')
click_id = request.GET.get('click_id')
expected_sig = hmac.new(settings.ADMITAD_SECRET.encode(), click_id.encode(), hashlib.md5).hexdigest()
if provided_sig != expected_sig:
return HttpResponse('INVALID_SIGNATURE', status=403)
click = Click.objects.filter(click_id=click_id).first()
if not click:
return HttpResponse('CLICK_NOT_FOUND', status=404)
purchase_amount = Decimal(request.GET.get('sale_amount', '0'))
commission = Decimal(request.GET.get('commission', '0'))
cashback_amount = commission * (click.partner.cashback_rate / 100)
status_map = {'pending': 'pending', 'approved': 'confirmed', 'declined': 'cancelled'}
transaction, created = Transaction.objects.get_or_create(
order_id=request.GET.get('order_id'),
partner=click.partner,
defaults={
'click': click,
'user': click.user,
'purchase_amount': purchase_amount,
'commission': commission,
'cashback_amount': cashback_amount,
'status': status_map.get(request.GET.get('status'), 'pending'),
'hold_until': date.today() + timedelta(days=click.partner.hold_days),
'source': 'postback',
'raw_data': dict(request.GET),
}
)
if not created:
transaction.status = status_map.get(request.GET.get('status'), transaction.status)
transaction.save()
if transaction.status == 'confirmed':
credit_cashback.delay(str(transaction.id))
return HttpResponse('OK')
Начисление и выплата кэшбэка
@shared_task
def credit_cashback(transaction_id):
with transaction_lock(transaction_id):
txn = Transaction.objects.select_for_update().get(id=transaction_id)
if txn.status != 'confirmed':
return
account, _ = CashbackAccount.objects.select_for_update().get_or_create(user=txn.user)
account.pending += txn.cashback_amount
account.total_earned += txn.cashback_amount
account.save()
txn.status = 'credited'
txn.save()
notify_cashback_credited.delay(str(txn.user_id), float(txn.cashback_amount))
@shared_task
def release_held_cashback():
today = date.today()
ready = Transaction.objects.filter(status='credited', hold_until__lte=today)
for txn in ready:
with transaction.atomic():
account = CashbackAccount.objects.select_for_update().get(user=txn.user)
account.pending -= txn.cashback_amount
account.balance += txn.cashback_amount
account.save()
txn.status = 'available'
txn.save()
@shared_task
def process_sbp_payout(withdrawal_id):
withdrawal = Withdrawal.objects.get(id=withdrawal_id)
# Отправка в платёжный шлюз (ЮKassa, Тинькофф, СБП)
pass
Процесс разработки и сроки
- Аналитика — изучаем вашу бизнес-модель, подбираем партнёрские сети и платёжные шлюзы.
- Проектирование — схемы данных, прототипы API, дизайн кабинетов пользователя и администратора.
- Реализация — backend на Laravel/Node.js, frontend на React/Vue, интеграция постбэков и API.
- Тестирование — юнит-тесты с покрытием 90% кода, интеграционное тестирование с песочницами CPA-сетей.
- Деплой — настройка инфраструктуры (Docker, Nginx, Cloudflare), мониторинг и алертинг.
| Этап | Время |
|---|---|
| MVP (affiliate + 1 CPA + выплаты) | 6–8 недель |
| Полная платформа (несколько сетей, card-linked, рефералки, дашборд) | 4–5 месяцев |
Стоимость рассчитывается индивидуально — зависит от количества интеграций и сложности бизнес-логики.
Типичные ошибки при разработке кэшбэк-платформы
- Отсутствие fallback-механизмов для постбэков: если не настроена периодическая сверка, теряется до 12% транзакций. Решение — планировщик с опросом API партнёров раз в сутки.
- Игнорирование дедупликации: без проверки order_id + source двойное начисление может достигать 3%. Всегда используйте уникальное ограничение.
- Неправильный расчёт холда: слишком короткий холд увеличивает риск откатов, слишком длинный — снижает лояльность. Оптимально 30–60 дней для affiliate.
Что вы получаете в итоге?
- Полный исходный код с комментариями.
- Документацию API для партнёров и внутренних разработчиков.
- Доступы к админ-панели, серверу, платёжным шлюзам.
- Обучение команды (до 2 часов воркшопа).
- 90 дней гарантийной поддержки после запуска.
Свяжитесь с нами для обсуждения вашего проекта — мы подготовим предварительную оценку и предложим оптимальную архитектуру.







