Разработка кэшбэк-платформы

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кэшбэк-платформы
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кэшбэк-платформы

Кэшбэк-платформа соединяет покупателей, магазины-партнёры и платёжный процессинг. Покупатель делает покупку через партнёрскую ссылку или карту, платформа получает комиссию от магазина и возвращает часть покупателю. Технически это система трекинга кликов/транзакций, расчёта вознаграждений и управления выплатами.

Модели трекинга

Существуют два принципиально разных механизма отслеживания покупок:

Affiliate-трекинг — пользователь переходит по специальной ссылке, совершает покупку, магазин уведомляет платформу через postback или пиксель.

Card-linked — привязка платёжной карты, транзакции отслеживаются через банковские API (Visa/Mastercard CLO, СБП). Требует партнёрства с банком.

Большинство платформ начинает с affiliate.

Схема данных

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),            -- шаблон ссылки с {click_id}
    network         VARCHAR(50),             -- admitad, cityads, собственная
    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),           -- ID заказа в магазине
    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),            -- 'postback','pixel','api'
    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
import base64
from django.conf import settings


def generate_click_id(user_id: str, partner_id: str) -> str:
    """Уникальный click_id для отслеживания"""
    raw = f'{user_id}:{partner_id}:{timezone.now().timestamp()}'
    return base64.urlsafe_b64encode(
        hashlib.sha256(raw.encode()).digest()[:12]
    ).decode().rstrip('=')


def build_tracking_url(user, partner) -> str:
    click_id = generate_click_id(str(user.id), str(partner.id))

    # Сохраняем клик
    Click.objects.create(
        user=user,
        partner=partner,
        click_id=click_id,
    )

    # Строим ссылку с click_id
    tracking_url = partner.tracking_url.replace('{click_id}', click_id)
    # Пример: https://ad.admitad.com/g/abc123/?subid={click_id}
    # → https://ad.admitad.com/g/abc123/?subid=xK9mNpQr

    return tracking_url

Endpoint для редиректа:

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)

Postback от партнёрских сетей

Admitad, CityAds и большинство CPA-сетей уведомляют через GET-запрос (postback):

# GET /postback/admitad/?click_id={click_id}&order_id={order_id}&
#      sale_amount={sale_amount}&commission={commission}&status={status}

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:
        # Обновление статуса (approved → cancelled)
        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: str):
    """Начисляем кэшбэк после подтверждения транзакции"""
    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():
    """Ежедневно: переводим подтверждённый кэшбэк из pending в balance"""
    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()

Выплата через СБП

def request_withdrawal_sbp(user, amount: Decimal, phone: str):
    account = CashbackAccount.objects.select_for_update().get(user=user)

    if account.balance < amount:
        raise InsufficientBalanceError()

    if amount < Decimal('100'):
        raise ValidationError('Минимальная сумма вывода 100 ₽')

    account.balance -= amount
    account.total_withdrawn += amount
    account.save()

    withdrawal = Withdrawal.objects.create(
        user=user,
        amount=amount,
        method='sbp',
        destination=phone,
        status='pending',
    )

    # Отправляем в платёжный шлюз (ЮKassa, Тинькофф, etc.)
    process_sbp_payout.delay(str(withdrawal.id))
    return withdrawal

Сроки

MVP с affiliate-трекингом, постбэком Admitad, личным кабинетом и выплатой на карту: 6–8 недель. Полная платформа с несколькими сетями, card-linked офферами, реферальной программой и аналитическим дашбордом для партнёров: 4–5 месяцев.