Cashback Platform Development

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Cashback Platform Development

Cashback platform connects customers, partner stores and payment processing. Customer makes purchase via partner link or card, platform receives commission from store and returns part to customer. Technically it's a system of click/transaction tracking, reward calculation and payout management.

Tracking Models

There are two fundamentally different purchase tracking mechanisms:

Affiliate-tracking — user follows special link, makes purchase, store notifies platform via postback or pixel.

Card-linked — payment card binding, transactions tracked via bank APIs (Visa/Mastercard CLO, SBP). Requires bank partnership.

Most platforms start with affiliate.

Data Schema

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,  -- % of purchase amount
    platform_rate   NUMERIC(5,2) NOT NULL,  -- full commission from partner
    status          VARCHAR(20) NOT NULL DEFAULT 'active'
                    CHECK (status IN ('active','paused','terminated')),
    tracking_url    VARCHAR(500),            -- link template with {click_id}
    network         VARCHAR(50),             -- admitad, cityads, proprietary
    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,  -- passed to partner network
    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),           -- order ID in store
    purchase_amount NUMERIC(15,2),
    commission      NUMERIC(15,2),          -- received from partner
    cashback_amount NUMERIC(15,2),          -- credited to user
    status          VARCHAR(20) NOT NULL DEFAULT 'pending'
                    CHECK (status IN ('pending','confirmed','cancelled','paid')),
    hold_until      DATE,                   -- date when can be paid out
    source          VARCHAR(50),            -- 'postback','pixel','api'
    raw_data        JSONB,                  -- raw data from partner
    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,  -- available for withdrawal
    pending         NUMERIC(15,2) NOT NULL DEFAULT 0,  -- on hold
    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,   -- card number/phone
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',
    processed_at    TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Tracking Links

import hashlib
import base64
from django.conf import settings


def generate_click_id(user_id: str, partner_id: str) -> str:
    """Unique click_id for tracking"""
    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))

    # Save click
    Click.objects.create(
        user=user,
        partner=partner,
        click_id=click_id,
    )

    # Build link with click_id
    tracking_url = partner.tracking_url.replace('{click_id}', click_id)
    # Example: https://ad.admitad.com/g/abc123/?subid={click_id}
    # → https://ad.admitad.com/g/abc123/?subid=xK9mNpQr

    return tracking_url

Redirect 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:
        # Save intent, redirect to login
        request.session['pending_cashback_partner'] = partner_slug
        return redirect('/login/?next=' + request.path)

    url = build_tracking_url(user, partner)

    # Analytics
    track_event.delay('cashback_click', {
        'user_id': str(user.id),
        'partner_id': str(partner.id),
    })

    return HttpResponseRedirect(url)

Postback from Partner Networks

Admitad, CityAds and most CPA networks notify via GET request (postback):

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

def admitad_postback(request):
    # Check signature (each network has its own algorithm)
    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:
        # Update status (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')

Crediting and Paying Out Cashback

@shared_task
def credit_cashback(transaction_id: str):
    """Credit cashback after transaction confirmation"""
    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():
    """Daily: move confirmed cashback from pending to 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()

Withdrawal via SBP

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('Minimum withdrawal amount 100 RUB')

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

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

    # Send to payment gateway (YooKassa, Tinkoff, etc.)
    process_sbp_payout.delay(str(withdrawal.id))
    return withdrawal

Timeline

MVP with affiliate-tracking, Admitad postback, personal account and card withdrawal: 6–8 weeks. Full platform with multiple networks, card-linked offers, referral program and partner analytics dashboard: 4–5 months.