Реферальна програма («Запроси друга»)
Метадані
Section titled “Метадані”| Поле | Значення |
|---|---|
| Статус | 🟢 prod — викочено cherry-pick-PR-ами (api-v2#551, frontend-v2#489) без dev-only offers-рефакторингу; обидві міграції виконані на prod БД |
| Поверхні | web |
| Доступність | свій промокод і кабінет — потрібен акаунт; ввід чужого коду на checkout — і гостям (код зберігається локально, валідується при оплаті) |
| Власник | TODO: |
| Останнє підтвердження | 2026-07-27 · live dev e2e (gift-grants legacy bypass: покупці 299/399 без активності → eligible:true, quotaTotal:3) + prod: product→priceUah:200, referralDiscountUah:100. ⚠️ З 27.07 грошові бонуси рефереру вимкнені (REFERRAL_REWARDS_ENABLED=false), діє механіка «200/подарунок/100» |
Призначення
Section titled “Призначення”Органічний канал росту платної «Симуляції вступу»: абітурієнт ділиться своїм промокодом із друзями. Друг отримує знижку 100 грн на симуляцію (env REFERRAL_DISCOUNT_KOPEKS, сума інвойсу Monobank рахується зі знижкою, floor 1 грн), а реферер — 100 грн на внутрішній рахунок за кожного друга-покупця (env REFERRAL_REWARD_KOPEKS, сума фіксується в момент нарахування). Виплата рефереру — вручну, поза системою; v1 веде лише облік нарахувань.
User flow
Section titled “User flow”Точка входу (реферер): https://abitly.org/uk/profile/referral («Запроси друга» в сайдбарі кабінету, мобільному MoreSheet і ⌘K-пошуку).
- Реферер відкриває
/profile/referral— код створюється ліниво при першому запиті (8 символів, алфавіт без I/O/0/1). Дії: скопіювати код, скопіювати посилання (/uk/simulation?ref=CODE), надіслати в Telegram (t.me/share/url). - Друг переходить за посиланням —
?ref=CODEзберігається вlocalStorageна 30 днів (переживає Google quick-auth-редірект і resume-шлях покупки) — або вводить код вручну в полі «Маєш промокод від друга?» на трьох buy-поверхнях: paywall кабінету, лендінг симуляції, гостьова модалка покупки. Авторизованим код валідується одразу (POST /referral/validate: немає такого / власний код), гостям — збережеться і перевіриться при оплаті. Ціна на кнопці/в модалці показується зі знижкою (display-only; авторитетна сума — з бекенда). - При покупці фронт додає
referralCodeуPOST /premium/create-payment; api-v2 резолвить код уpremium_orders.referral_code_id(best-effort, якga_client_id: невалідний/власний код ніколи не блокує оплату) і знижує суму інвойсу наREFERRAL_DISCOUNT_KOPEKS. Pending-інвойс перевикористовується лише при збігу реферальної атрибуції. - Success-вебхук Monobank (загальний рейл — data-flows: оплата Monobank) після видачі entitlement створює запис у ledger
referral_rewardsзі статусомaccrued. Ідемпотентність на рівні БД:unique(order_id)гасить дубль-вебхуки,unique(referred_user_id)— один бонус за одного друга назавжди. - Реферер бачить друга в списку на
/profile/referral: ім’я, дата, сума, статус (Нараховано/Виплачено/Скасовано). Після успішної оплати збережений код очищається зlocalStorage.
Дані та API
Section titled “Дані та API”| Джерело | Ендпоінт / entity | Призначення |
|---|---|---|
| api-v2 | GET /referral/me (JWT) | код + share-URL + статистика + список друзів |
| api-v2 | POST /referral/validate (JWT, throttle 5/хв) | перевірка коду перед checkout; захист від перебору |
| api-v2 | POST /premium/create-payment — опційний referralCode | атрибуція замовлення в момент створення інвойсу |
| api-v2 | referral_codes (1:1 user) · premium_orders.referral_code_id · referral_rewards (ledger: accrued/paid/cancelled) | схема даних; міграція 1783200000000-CreateReferralProgram (manual-only) |
| api-v2 | env REFERRAL_REWARD_KOPEKS · REFERRAL_DISCOUNT_KOPEKS (default 10000) · REFERRAL_CONSULTATION_THRESHOLD (default 10) | бонус рефереру · знижка покупцеві · поріг консультації — назви в environments |
| api-v2 | GET /premium/gift/:token (публічний) · POST /premium/gift/:token/claim (JWT) · premium_orders.is_gift/gift_token/gift_claimed_by | «Подарувати другу»: мінт токена success-вебхуком, first-claim-wins, entitlement клеймеру; міграція 1783300000000-AddGiftToPremiumOrders |
| api-v2 | GET /premium/simulation/product → referralDiscountUah | публічний розмір знижки для buy-поверхонь |
| api-v2 | POST /admin/premium/gift-orders · GET /admin/premium/gift-orders/report (guard x-admin-key ↔ env ADMIN_API_KEY) | адмін-мінт безкоштовних gift-ордерів (amount=0, маркер webhook_data.source='admin_gift' + batch/label) і звіт по клеймах: хто прийняв, його пріоритети симуляції, його реферальний код і нарахування |
Аналітика (Track-events): referral_page_viewed, referral_share_click{method}, referral_code_applied{valid,reason,source} + нове поле referral_code_present у simulation_begin_checkout.
Адмін-подарунки (batch-мінт для акцій/хакатонів)
Section titled “Адмін-подарунки (batch-мінт для акцій/хакатонів)”Адмін може роздати безкоштовний доступ до симуляції персональними одноразовими лінками — без оплати і без міграцій: мінтиться звичайний gift-ордер (amount=0, status=success), далі працює штатний клейм-флоу «Подарувати другу» (entitlement клеймеру + zero-amount рядок ledger гіфтеру, тож отримувачі видимі як «запрошені друзі» адміна на /profile/referral).
KEY=$(aws ssm get-parameter --name /abitly/prod/backend/ADMIN_API_KEY \ --with-decryption --profile abitly --query Parameter.Value --output text)
# Мінт: один подарунок на кожен label (ім'я учасника) → TSV "ім'я <TAB> лінк"curl -s -X POST https://api.abitly.org/admin/premium/gift-orders \ -H "x-admin-key: $KEY" -H 'Content-Type: application/json' \ -d '{"gifterEmail":"<адмін-email>","batch":"hackathon-2026-07","labels":["Учасник 1","Учасник 2"]}' \ | jq -r '.gifts[] | [.label, .giftUrl] | @tsv'
# Звіт: клейми + пріоритети симуляції клеймерів + їхні рефералиcurl -s "https://api.abitly.org/admin/premium/gift-orders/report?gifterEmail=<адмін-email>&batch=hackathon-2026-07" \ -H "x-admin-key: $KEY" | jqgifterEmail — email наявного акаунта, якому атрибутуються подарунки (для хакатонів — акаунт адміна). Ліміт — 200 подарунків на запит.
Gift-pass «3 подарунки» (постійний шер-лінк власника)
Section titled “Gift-pass «3 подарунки» (постійний шер-лінк власника)”Покупець симуляції отримує грант (gift_grants: quota_total/quota_remaining, pass_token) з одним постійним лінком abitly.org/uk/gift-pass/<token>; перші N друзів, що відкриють його, забирають повний доступ безкоштовно (клейм = zero-amount success-ордер webhook_data.source='gift_grant', отримувач у gift_claimed_by). Ендпоінти: GET /premium/gift-grants/me (JWT; грант створюється ліниво при першому запиті), GET /premium/gift-pass/:token (публічний), POST /premium/gift-pass/:token/claim. Квоти й legacy-cutoff — з payload PostHog-флага sim-friend-pricing (235423).
Право на грант (вердикт бекенда, getOrCreateGrant):
- Покупець за стару повну ціну (299/399 ₴,
amount ≥ 29900коп., оплата до legacy-cutoff 2026-07-27 03:00) — безумовно, квотаslots.legacy(3). Рішення власника 27.07: гейти активності відсікали ~30 % саме цієї когорти (291 з 1140 на момент викату). PRapi-v2#632/#633. - Решта платних покупців (промо-199, нові за 200 ₴) — покупка + гейти активності: ≥4 заявки в конструкторі, ≥1 запуск і ≥1 reorder (
simulation_activity); квотаslots.buyer(1 після запуску «200/подарунок/100»).
При вимкненому флагу (fail-closed) діє старий режим: гейти для всіх, квота 3. Вичерпаний пас не глухий кут — віддає відвідувачу реферальний код власника і ціну друга. TODO: повна картка механіки «200/подарунок/100» (ціна друга, платний клейм, relay-драбина) — поки що канонічний опис у PR api-v2#626–#633 і досьє .context/price-200-relay/ (workspace, поза хабом).
Когортна аналітика промокоду (per-user дось’є)
Section titled “Когортна аналітика промокоду (per-user дось’є)”Rerunnable-пайплайн analytics/referral-cohort-report/ (цей репозиторій) будує з одного промокоду інтерактивний standalone-дашборд: сегменти купив/кинув оплату/подарунок, картка на кожну людину (таймлайн дій за Києвом, GA4-сесії з переглянутими сторінками КП/ЗВО, пріоритети симуляції, точні бали НМТ, анкета реєстрації, спроби тестів НМТ, транзакції з time-to-pay), зведена таблиця балів і когортні графіки. Джерела: prod-Postgres (обидві БД split-brain періоду 2026-07) + GA4 BigQuery event-level; збудовано на скілі product-report. Дані та готові артефакти містять ПД і живуть тільки в гітігнорному .context/ — у git лише код (рецепт запуску і гочі — у README пайплайна). Відпрацьовано на коді JKNZKEKE (2026-07-12/13): 79 переходів → 22 покупки, 44 особи в когорті.
Зв’язки з іншими фічами
Section titled “Зв’язки з іншими фічами”- Симуляція вступу — єдиний продукт, за покупку якого нараховується бонус (v1).
- Checkout / оплата Monobank — платіжний рейл; нарахування живе в success-гілці вебхука
premium. - Автентифікація — код у
localStorageпереживає quick-auth-редірект гостьової покупки.
| Репо | Шлях | Примітка |
|---|---|---|
abitly-api-v2 | src/api/referral/** | модуль: controller, service (генерація/валідація/нарахування) |
abitly-api-v2 | src/api/premium/premium.service.ts | атрибуція в createPayment + creditForOrder у вебхуку (try/catch, non-blocking) |
abitly-api-v2 | src/database/entities/referralCode.ts, referralReward.ts | entities + unique-констрейнти ідемпотентності |
abitly-frontend-v2 | src/app/[locale]/(profile)/profile/referral/ + src/components/pages/profile/pages/Referral/** | сторінка кабінету |
abitly-frontend-v2 | src/components/pages/simulation/shared/components/ReferralCodeField.tsx + shared/utils/referralCodeStore.ts | поле промокоду на обох buy-поверхнях + localStorage-стор (30 днів) |
abitly-frontend-v2 | src/api/referral/** | API-клієнт (types/api/queries/server-api) |
Обмеження та блокери
Section titled “Обмеження та блокери”- Юніт-економіка запрошення: знижка покупцеві + бонус рефереру = 200 грн з кожного реферального продажу (при ціні 399 грн — маржа лишається додатною; обидві суми env-конфігуровані).
- Виплата ручна: ledger має статуси
accrued/paid/cancelled, але UI/процесу виплати немає — адмін-операції SQL-ом. ⚠️ Реферери можуть бути неповнолітніми — виплата грошей лишається за людиною (та сама юридична тема, що ADR-0005 для бот-рефералок). - Незалежно від bot-referral: бот-система
telegram.referral_*(deep-links/PDF) — інший продукт, дані не змішуються. - Адмін-подарунки в аналітиці продажів: zero-amount ордери мають
status=success— запити «кількість продажів» поpremium_ordersмають фільтруватиamount > 0(абоwebhook_data->>'source' IS DISTINCT FROM 'admin_gift'), інакше batch-мінт інфлює лічильник. - Деплой: міграції api-v2 manual-only —
CreateReferralProgramтреба запустити на БД до користування фічею; PR-и мержити парою, інакше/profile/referralвіддасть помилку завантаження. - Дизайн-док:
.context/referral-program/DESIGN.md(робочий workspace, поза хабом).
Історія змін
Section titled “Історія змін”| Дата | Подія | Джерело |
|---|---|---|
| 2026-07-03 | Спроєктовано і реалізовано v1, змержено на dev/development, міграція на dev БД | PR api-v2#544, frontend-v2#481 |
| 2026-07-03 | Знижка покупцеві 100 грн + поле промокоду в гостьовій модалці (фідбек) | PR api-v2#546, frontend-v2#483 |
| 2026-07-03 | «Подарувати другу» (gift-ордер → одноразовий клейм-лінк /gift/<token> → entitlement клеймеру) + майлстоун «10 друзів → безкоштовна консультація з командою» (прогрес на «Запроси друга»; клейм подарунка = запрошений друг, zero-amount рядок ledger) | PR api-v2#549, frontend-v2#486 |
| 2026-07-04 | UI-полірування + PII-фікс (знеособлений список друзів) + PROD-реліз cherry-pick-ами (без offers-рефакторингу з dev); міграції на prod БД; live-верифіковано | PR api#550/#551, fe#487/#488/#489 |
| 2026-07-04 | Адмін-мінт подарункових ордерів (amount=0, batch/label) + звіт по клеймах із пріоритетами симуляції та рефералами клеймерів — кейс: безкоштовний доступ учасникам хакатону | PR api-v2#554 |
| 2026-07-13 | Когортна аналітика промокоду: rerunnable-пайплайн per-user дось’є + інтерактивний дашборд (analytics/referral-cohort-report/), відпрацьовано на JKNZKEKE (79 переходів → 22 покупки, 44 особи) | цей репозиторій |
| 2026-07-18 | Gift-pass «3 подарунки»: грант активним покупцям, постійний лінк /gift-pass/<token>, клейм = zero-amount ордер | PR api-v2#602–#604, fe#568–#580 |
| 2026-07-27 | Механіка «200/подарунок/100» на проді: база 200 ₴, перший друг безкоштовно (квота нового покупця 1), далі 100 ₴ за реф-кодом, бонуси рефереру вимкнені (REFERRAL_REWARDS_ENABLED=false) | PR api-v2#624–#631, fe#626–#637 |
| 2026-07-27 | Подарунки всім legacy-покупцям 299/399 ₴ безумовно — гейти активності зняті для цієї когорти (розблоковано 291 з 1140) | PR api-v2#632/#633 |