Перейти до вмісту

Кампанійні промокоди (−200 грн через бота)

ПолеЗначення
Статус🟢 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.

  1. Юзер тисне «Старт» у боті — бот викликає 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.
  2. Мінт ідемпотентний: UNIQUE(campaign, telegram_user_id) — повторний тап повертає той самий код; використаний код не перевидається (бот відповідає «уже використано»); API недоступний — вибачення з fallback-посиланням.
  3. Альтернатива без Telegram: згадки Симуляції в пості — клікабельні лінки /uk/simulation?claim=blog — лендінг сам мінтить читачу персональний код по куці (POST /promo-codes/claim) і зберігає його як введений вручну; hero одразу показує «Промокод застосовано: 199 ₴ замість 399 ₴» (?ref= з бота має пріоритет над ?claim=).
  4. ?ref= авто-зберігається фронтендом (localStorage, 30 днів) — той самий рейл, що й реферальні коди; або юзер вводить код руками в полі «Промокод». POST /referral/validate тепер публічний (гості теж) і повертає per-code discountUah + kind — UI показує реальні −200 грн і не рендерить «друг отримає бонус» для kind:"promo".
  5. POST /premium/create-payment: код резолвиться referral-first (колізія неможлива — мінт перевіряє обидві таблиці), інакше — активний promo-код → сума інвойсу = max(ціна − discount_kopecks, 100), premium_orders.promo_code_id фіксує атрибуцію; reuse pending-інвойса враховує promo-атрибуцію.
  6. 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-v2promo_codes (таблиця)код, campaign, discount_kopecks, telegram_user_id, expires_at (мінт +30 днів), used_*
api-v2POST /admin/promo-codes/mint (x-admin-key ↔ env ADMIN_API_KEY)мінт: ідемпотентно per telegramUserId або batch count до 500
api-v2POST /promo-codes/claim (public, throttle)web-claim без бота: код прив’язується до httpOnly-куки promo_vid (UNIQUE(campaign, visitor_id)), allowlist з env PROMO_CLAIMABLE_CAMPAIGNS
api-v2GET /admin/promo-codes/report[?campaign=] (x-admin-key)воронка по кожній кампанії: minted (bot/web), used, sales, revenueUah
api-v2POST /referral/validate (optional JWT)спільна валідація referral+promo: valid, reason (not_found/own_code/used), discountUah, kind
api-v2premium_orders.promo_code_idатрибуція замовлення; вебхук гасить код
tg-bot/start promo_blogservices/promo_codes.pyaiohttp-виклик мінта; env ABITLY_API_URL, ABITLY_ADMIN_API_KEY (secret), PROMO_ENABLED, PROMO_LANDING_URL
Strapiarticle abitly-promokod (категорія vstup2026)SEO-посадкова + канонічна відповідь «чому так дорого» для коментарів

Зв’язки з іншими фічами

Section titled “Зв’язки з іншими фічами”
  • Реферальна програма — спільне поле вводу, спільний validate-ендпоінт, referral-first резолв; формат кодів однаковий (8 символів, алфавіт без I/O/0/1 — проходить клієнтський патерн).
  • Симуляція вступу — знижка діє тільки на неї.
  • Checkout Monobank — інвойс створюється вже зі знижкою; floor 1 грн.
РепоШляхПримітка
abitly-api-v2src/api/promo-codes/ · src/database/entities/promoCode.ts · src/database/migrations/1784800000000-CreatePromoCodes.tsмодуль + міграція; інтеграція в premium.service.ts (create-payment + webhook)
abitly-frontend-v2ReferralCodeField.tsx · useReferralDiscount.tsper-code знижка, гостьова валідація, копі used
abitly-tg-bot-v3handlers/start.py · services/promo_codes.pydeep-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-14Hero pill «застосовано: 199 ₴ замість 399 ₴» на першому екрані (fe#537); web-claim по кукі + клікабельні claim-лінки в пості (api#581, fe#539, міграція AddPromoVisitorId на всіх БД); prod verified browsePR-и
2026-07-14Per-campaign трекінг: generic promo_<slug> deeplink + env-allowlist-и + GET /admin/promo-codes/report (api#583, bot#74, fe#542); prod smoke: звіт віддає реальні цифри, невідомий slug → welcome/400PR-и
2026-07-14Коди стали ШЕРОВАНИМИ: використання не гасить код (гейт лише expiry), бот шле кнопку «Поділитись кодом» (t.me/share з ?ref=-лінком), звіт рахує redemption-леджер DISTINCT-агрегатами (api#587/#588, bot#76/#77); dev E2E: 2 юзери × 1 код = 2 інвойси по 199 ₴; блог-копі оновленоPR-и