Розробка кешбек-платформи

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, 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, Tinkoff, etc.)
    process_sbp_payout.delay(str(withdrawal.id))
    return withdrawal

Строк

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