Affiliate Program: How to Earn a % on Referrals
In short
The partner program is a classic affiliate setup: you bring people in through a referral link and earn a percentage of every order they pay for (cash per sale, not give-get coupons). The mechanics are transparent and idempotent: joining issues a unique code with no look-alike characters, the link drops a cookie for 30 days, attribution is first-touch and freezes the moment an order is created, and self-referral is blocked. Commission is accrued in a single SQL statement with replay protection — a duplicate webhook cannot double-credit you by construction. Below: how to join, a worked commission calculation, and an honest section on payouts and their limits.
What it is. You bring users to qvib.pro through your referral link — and for every order they PAY for, you earn a percentage. This is an affiliate model (cash per sale), not give-get coupons.
Why it works this way. Engines aren't cheap and are bought once, so a percentage of the sale is a clear, fair incentive for the people who recommend the product (agencies, creators, community leads).
How it works, step by step (in code).
- Joining:
POST /api/partner/enrollcreates apartner_accountsrow with a unique referral code (an alphabet without 0/O/1/I/l, so nothing gets confused). Idempotent — a repeat call returns the same account. - The link:
GET /api/partner/r/<code>(public) records the click, drops anes_refcookie for 30 days, and 302s to the root. An unknown code is silently ignored. - FIRST-touch attribution: on signup,
bindReferralwritespartner_referrals(user_id)withON CONFLICT (user_id) DO NOTHING— the FIRST partner wins, and a later code won't overwrite the binding. There's a guard against self-referral (a partner can't refer themselves). - Locked in on the order: when an order is created,
ref_partner_idis filled in from the binding — attribution "freezes" at the moment of purchase. - Accrual: once an order becomes
paid,accrueForOrdercredits the commission in ONE SQL statement:INSERT ... ON CONFLICT (order_id) DO NOTHING, then bumpstotal_earnedviaFROM insonly for the row that was actually inserted. A repeated webhook is a no-op: double accrual is impossible by construction.
How much you get paid, and when
- Rate: 25% by default. Capped by engine tier: Simple ≤25%, Standard ≤30%, Pro ≤40% (
rate = min(partner rate, tier cap)). Configurable in theaffiliateadmin setting. - 15% reserve — insurance against refunds.
- Hold: 45 days for card payments (
hold_until), after whichreleaseHeldCommissionsmoves the commission frompendingtoavailableand credits the balance (under an account lock, so there's no race). - Refunded purchase:
clawbackrolls the commission back according to its lifecycle stage (pending / available / locked / paid); if the money was already paid out, the balance can go negative BY DESIGN and gets flagged — the debt is real and should be visible, not hidden.
Worked example: selling a Pro engine for ₽3900
Input: a partner with the code K7MNPQ2A shares the link /api/partner/r/K7MNPQ2A; someone clicks it, signs up a week later, and a day after that buys a Pro engine for ₽3900 (390,000 kopecks). The partner's rate is 40%, and the Pro tier cap is 40%.
What the engine does:
- The click is recorded, the
es_ref=K7MNPQ2Acookie lives for 30 days. - On signup,
bindReferralbinds the user to the partner (first touch). - The order is created with
ref_partner_idset to that partner. - The
paidwebhook fires →accrueForOrder:rate = min(0.40, 0.40) = 0.40;commission = round(390000 * 0.40) = 156000kopecks. - The commission is created as
pendingwithhold_until = now + 45 days;total_earnedis bumped by 156000.
Result: the partner dashboard (GET /api/partner/me) shows clicks, signups, sales, balance, reserve, and the history of commissions and payouts. After 45 days, 1 560 ₽ moves to available.
Honest talk about payouts (an important limitation)
Payouts are MANUAL. The partner requests a withdrawal (POST /api/partner/payout), the system creates a pending payout, reserves the money from the balance, and locks the relevant commissions (oldest first). An ADMIN then executes the transaction by hand and records the transaction_id. There are no automatic payouts yet. Plus one hard requirement: to withdraw, a partner MUST have an INN on file (the Russian taxpayer ID for self-employed individuals or sole proprietors) — otherwise you get 403 INN_REQUIRED.