| Поле | Значення |
|---|
| Статус | 🟢 prod — api-v2#578/#579/#580, frontend-v2#534/#535/#536, tg-bot-v3#72/#73; міграція CreatePromoCodes1784800000000 виконана на обох prod-БД (live prod-backup-for-stage + заморожена studsearch-prod) і на abitly_dev |
| Поверхні | web (checkout симуляції + claim по кукі) + bot (роздача кодів) + блог (посадкова з клікабельними claim-лінками) |
| Доступність | отримання коду — будь-хто в Telegram; ввід коду на checkout — і гостям |
| Власник | TODO: |
| Останнє підтвердження | 2026-07-13 · browse + curl (live prod): бот видав код, /referral/validate → {valid, discountUah:200, kind:"promo"}, /uk/simulation?ref=<код> показує «на 200 ₴ меншою»; stage/dev E2E — інвойс 199 грн у premium_orders |
Кампанія «Абітлі промокод»: займаємо порожню видачу Google за запитом «Абітлі промокод» власним блог-постом і конвертуємо цей трафік (та цінові заперечення в коментарях соцмереж) у продажі «Симуляції вступу» через особисту знижку 200 грн (399 → 199 грн). На відміну від реферальних кодів (−100 грн, бонус рефереру) кампанійний код несе власну суму знижки і нікому нічого не нараховує; він шерований — перешли код у чат класу, і знижка спрацює для кожного покупця (леджер використань = premium_orders.promo_code_id).
Точка входу: блог-пост «Промокод Abitly: мінус 200 гривень на Симуляцію вступу» → https://t.me/abitlybot?start=promo_blog.
- Юзер тисне «Старт» у боті — бот викликає
POST /admin/promo-codes/mint (x-admin-key, campaign blog, telegramUserId) і надсилає особистий 8-символьний код + кнопку «Застосувати знижку» → /uk/simulation?ref=<код>&utm_source=telegram&utm_medium=bot&utm_campaign=promo_blog.
- Мінт ідемпотентний:
UNIQUE(campaign, telegram_user_id) — повторний тап повертає той самий код; використаний код не перевидається (бот відповідає «уже використано»); API недоступний — вибачення з fallback-посиланням.
- Альтернатива без Telegram: згадки Симуляції в пості — клікабельні лінки
/uk/simulation?claim=blog — лендінг сам мінтить читачу персональний код по куці (POST /promo-codes/claim) і зберігає його як введений вручну; hero одразу показує «Промокод застосовано: 199 ₴ замість 399 ₴» (?ref= з бота має пріоритет над ?claim=).
?ref= авто-зберігається фронтендом (localStorage, 30 днів) — той самий рейл, що й реферальні коди; або юзер вводить код руками в полі «Промокод». POST /referral/validate тепер публічний (гості теж) і повертає per-code discountUah + kind — UI показує реальні −200 грн і не рендерить «друг отримає бонус» для kind:"promo".
POST /premium/create-payment: код резолвиться referral-first (колізія неможлива — мінт перевіряє обидві таблиці), інакше — активний promo-код → сума інвойсу = max(ціна − discount_kopecks, 100), premium_orders.promo_code_id фіксує атрибуцію; reuse pending-інвойса враховує promo-атрибуцію.
- Success-вебхук Monobank штампує ПЕРШЕ використання (used_at, стата); леджер = premium_orders.promo_code_id — код лишається робочим (шерований) (
used_at, used_by_user_id, order_id; ідемпотентно WHERE used_at IS NULL, non-blocking для оплати/entitlement).
| Джерело | Ендпоінт / entity | Призначення |
|---|
| api-v2 | promo_codes (таблиця) | код, campaign, discount_kopecks, telegram_user_id, expires_at (мінт +30 днів), used_* |
| api-v2 | POST /admin/promo-codes/mint (x-admin-key ↔ env ADMIN_API_KEY) | мінт: ідемпотентно per telegramUserId або batch count до 500 |
| api-v2 | POST /promo-codes/claim (public, throttle) | web-claim без бота: код прив’язується до httpOnly-куки promo_vid (UNIQUE(campaign, visitor_id)), allowlist з env PROMO_CLAIMABLE_CAMPAIGNS |
| api-v2 | GET /admin/promo-codes/report[?campaign=] (x-admin-key) | воронка по кожній кампанії: minted (bot/web), used, sales, revenueUah |
| api-v2 | POST /referral/validate (optional JWT) | спільна валідація referral+promo: valid, reason (not_found/own_code/used), discountUah, kind |
| api-v2 | premium_orders.promo_code_id | атрибуція замовлення; вебхук гасить код |
| tg-bot | /start promo_blog → services/promo_codes.py | aiohttp-виклик мінта; env ABITLY_API_URL, ABITLY_ADMIN_API_KEY (secret), PROMO_ENABLED, PROMO_LANDING_URL |
| Strapi | article abitly-promokod (категорія vstup2026) | SEO-посадкова + канонічна відповідь «чому так дорого» для коментарів |
- Реферальна програма — спільне поле вводу, спільний validate-ендпоінт, referral-first резолв; формат кодів однаковий (8 символів, алфавіт без I/O/0/1 — проходить клієнтський патерн).
- Симуляція вступу — знижка діє тільки на неї.
- Checkout Monobank — інвойс створюється вже зі знижкою; floor 1 грн.
| Репо | Шлях | Примітка |
|---|
abitly-api-v2 | src/api/promo-codes/ · src/database/entities/promoCode.ts · src/database/migrations/1784800000000-CreatePromoCodes.ts | модуль + міграція; інтеграція в premium.service.ts (create-payment + webhook) |
abitly-frontend-v2 | ReferralCodeField.tsx · useReferralDiscount.ts | per-code знижка, гостьова валідація, копі used |
abitly-tg-bot-v3 | handlers/start.py · services/promo_codes.py | deep-link гілка + HTTP-клієнт мінта |
- Запуск кампанії під івент/рекламу = додати slug у env: бот
PROMO_CAMPAIGNS (deeplink t.me/abitlybot?start=promo_<slug>) та/або API PROMO_CLAIMABLE_CAMPAIGNS (лінк /uk/simulation?claim=<slug>) — без релізів; кнопка бота несе utm_campaign=promo_<slug>.
- Код гаситься тільки success-вебхуком: два pending-інвойси з одним кодом теоретично можуть обидва отримати знижку (перший success гасить код; другий, якщо оплатять пізніше, збереже стару суму інвойсу) — прийнятий компроміс.
- ECS task-def бота (dev/prod) розширені руками (env + secret + IAM на SSM-парам) —
TODO: віддзеркалити в Terraform cloud-infrastructure.
| Дата | Подія | Джерело |
|---|
| 2026-07-13 | Фіча повністю на prod: API (реліз #580) + бот (@abitlybot, td :30) + FE (реліз #536) + блог-пост опубліковано; E2E dev (інвойс 199 грн) і smoke prod пройдені | PR-и вище |
| 2026-07-14 | Hero pill «застосовано: 199 ₴ замість 399 ₴» на першому екрані (fe#537); web-claim по кукі + клікабельні claim-лінки в пості (api#581, fe#539, міграція AddPromoVisitorId на всіх БД); prod verified browse | PR-и |
| 2026-07-14 | Per-campaign трекінг: generic promo_<slug> deeplink + env-allowlist-и + GET /admin/promo-codes/report (api#583, bot#74, fe#542); prod smoke: звіт віддає реальні цифри, невідомий slug → welcome/400 | PR-и |
| 2026-07-14 | Коди стали ШЕРОВАНИМИ: використання не гасить код (гейт лише expiry), бот шле кнопку «Поділитись кодом» (t.me/share з ?ref=-лінком), звіт рахує redemption-леджер DISTINCT-агрегатами (api#587/#588, bot#76/#77); dev E2E: 2 юзери × 1 код = 2 інвойси по 199 ₴; блог-копі оновлено | PR-и |