Skip to main content
Назад до блогу
7 хв читання

Архітектура квиткової платформи на 12K квитків за подію: всередині E7

Повний стек івент-тикетингу — інтерактивні карти залів, Stripe-чекаут, WebSocket у реальному часі, QR-чекін. 12 тижнів від нуля до продакшену.

case-studynext.jsstripe

E7 Entertainment проводить живі заходи в Центральній Європі — концерти, корпоративи, театр. Коли знайшли мене, вони продавали квитки через платформу-реселера, яка забирала 15% з квитка, не давала даних про покупців, і мобільний чекаут був настільки зламаний, що 40% покупців кидали на екрані вибору місць.

Вони хотіли свою платформу. Я зібрав її за 12 тижнів.

Проблема в цифрах

Три больових точки, кожна коштує реальних грошей:

15% комісія реселера. На 1,000-місному заході по €35/квиток — це €5,250 йде до початку першого акту. За рік заходів реселер був другим за величиною витратою після оренди площадки.

Немає даних про клієнтів. Квитки, продані через реселера, належали реселеру. E7 не могли написати попереднім покупцям про нові заходи, не бачили, на яких подіях повторні відвідувачі, не могли робити маркетинг, крім платної реклами.

40% покинутих мобільних чекаутів. Мобільний вибір місць реселера — десктопний інтерфейс, запхнутий в екран телефону. Pinch-to-zoom на SVG-карті залу, потім спробуй тапнути по кружечку в 12px. Воронка: перегляд → вибір місць → дані → оплата. 40% йшли між кроками 1 і 2 на мобілці. Це не UX-проблема — це витік виручки.

Обмеження

12 тижнів, жорсткий дедлайн. У E7 найбільший захід сезону через 14 тижнів. Нам потрібно 12 на розробку і 2 на тестування з реальними подіями на менших площадках.

Повинен витримувати 12K квитків за подію. Найбільший зал — 3,000 місць. Чотири заходи на місяць. Система повинна обробляти одночасний вибір місць без перепродажу — якщо двоє людей дивляться на одне місце, тільки один отримує його.

Тільки Stripe. E7 вже використовував Stripe для продажу мерчу. Без нового платіжного провайдера. Про граничні випадки Stripe-вебхуків написав окремо — частина уроків прийшла прямо з цього проєкту.

Що відвантажили

Стек

Фронтенд: Next.js 15, React 19, TypeScript, Tailwind CSS. Публічний сайт (список заходів, вибір місць, чекаут) та адмін-панель (управління подіями, аналітика, сканер) — один і той самий Next.js-застосунок із маршрутизацією за ролями.

Бекенд: Node.js із Prisma ORM на PostgreSQL. Redis для стану сесій, блокувань місць і rate limiting. API-шар — це API-маршрути Next.js, без окремого бекенд-сервісу.

Платежі: Stripe Checkout для потоку покупки. Вебхуки для фулфілменту (випуск квитків, відправка QR-кодів, оновлення інвентарю). Stripe Connect готовий для майбутніх мультивендорних подій.

Реальний час: WebSocket через легкий Node-сервер. Оновлення карти місць транслюються всім підключеним клієнтам за <100ms. Коли хтось обирає місце, всі інші бачать, що воно стало сірим, миттєво.

Інфраструктура: Vercel для Next.js-застосунку. Supabase для PostgreSQL + реальний час. Cloudflare R2 для PDF-квитків та QR-зображень. Sentry для трекінгу помилок.

Вибір місць — складна частина

Інтерактивна карта залу — ядро продукту та найскладніший інженерний виклик.

У кожної площадки є SVG-карта залу — векторний креслення, де кожне місце — елемент із унікальним ID. Карта зберігається як структуровані дані (ряди, секції, координати місць) у Postgres, і SVG рендериться клієнтськи з цих даних.

Складна частина — конкурентність. Коли 200 людей переглядають один захід, вони повинні бачити актуальну доступність місць у реальному часі. Наша модель:

  1. Стани місць: доступне, заблоковане, продане. Заблоковане — значить «хтось додав у кошик».
  2. Тривалість блокування: 8 хвилин. Якщо не завершиш чекаут за 8 хвилин — місце повертається в доступні. Це Redis TTL — блокування буквально закінчується.
  3. Трансляція: При зміні стану місця WebSocket-повідомлення йде всім підключеним клієнтам цього заходу. Карта перемальовує тільки зачеплені місця — без повного перезавантаження.
  4. Обробка перегонів: Двоє користувачів клікають на одне місце одночасно. Блокування — Redis SET NX (встановити, якщо не існує) — атомарна операція, перший записав — переміг. Програвший отримує тост «місце щойно забрали» протягом 100ms.

Нуль інцидентів перепродажу за шість місяців продакшену. Це метрика, яка важливіша за все.

Потік чекауту

Карта залу → зведення кошика → Stripe Checkout → сторінка підтвердження з QR-кодами.

Весь потік вкладається в 30 секунд для користувача, що повертається. Ключові рішення:

QR-чекін

Сканер для персоналу — окрема сторінка в тому ж застосунку, за авторизацією. Співробітник відкриває на телефоні, наводить камеру на QR-код і отримує:

Час відповіді — менше 200ms. Сканер працює й офлайн — кешує список квитків заходу локально та синхронізується при появі зв'язку. Детальніше про офлайн-архітектуру.

Пропускна спроможність персоналу зросла з ~120 відвідувачів/год (ручна перевірка по паперових списках) до ~500/год на сканер. На 3,000-місному заході з 4 сканерами — повний чекін за 25 хвилин замість 2+ годин.

Цифри

Шість місяців продакшн-даних:

МетрикаЗначення
Продано квитків12,400+
Аптайм99.95%
Завершення чекауту (мобілка)78%
Час чекауту (медіана)24s
Латенсі API p95180ms
Інцидентів перепродажу0
Економія на реселері~€18K/рік

78% завершень мобільного чекауту — заголовкова цифра — замість 60% на старій платформі реселера. Ще не 100%, але решта 22% — здебільшого чутливість до ціни та покинутий перегляд, а не UX-тертя.

€18K/рік економії на комісіях реселера окупили всю розробку за перший рік.

Що здивувало

Продуктивність SVG-карти місць на дешевих Android. SVG на 3,000 місць — це 3,000+ DOM-вузлів. На Pixel 4a перший рендер був 1.8 секунди, а зум смикався. Вирішили віртуалізацією — тільки місця в зоні видимості рендеряться як інтерактивні елементи. Місця поза межами — один елемент <path>, що показує загальну компоновку. Час взаємодії впав до 200ms.

Порядок Stripe-вебхуків. Вебхуки не приходять по порядку. checkout.session.completed приходив раніше за payment_intent.succeeded приблизно в 12% випадків. Наша логіка фулфілменту спочатку залежала від payment_intent.succeeded — а значить, 12% покупців чекали до 30 секунд свої квитки. Виправили: тригер фулфілменту на checkout.session.completed, а payment_intent.succeeded — як підтвердження, не тригер.

Тривалість блокування в 8 хвилин — це третя спроба. Почали з 15 хвилин (задовго — на популярних заходах більшість місць «заблоковані» браузерами, що пішли), спробували 3 хвилини (замало — повільно набираючі на мобілці не встигали завершити чекаут), зупинились на 8 після A/B-тестування на реальному трафіку.

Що б зробив інакше

Використав би Stripe Embedded Checkout замість хостингового. На момент розробки вбудований варіант Stripe був новим і слабо документованим. Шість місяців потому він стабільний і прибирає редирект. Більш плавний UX, той самий рівень PCI-відповідності. Переключив би при наступному великому оновленні.

Зібрав би адмінку як окремий застосунок. Зараз це той самий Next.js-застосунок із маршрутизацією за ролями через middleware. Працює, але бандл адмінки включає компоненти карти місць, які не потрібні публічним користувачам, і навпаки. Два застосунки, що діляться Prisma-схемою, дали б чистіші збірки та краще розділення коду.

Додав би навантажувальне тестування раніше. Ми навантажувально тестували на 11-му тижні й виявили, що WebSocket карти місць витримує ~400 одночасних підключень, перш ніж Redis pub/sub починає гальмувати. Достатньо для поточних розмірів заходів, але знадобився день оптимізації, який пройшов би плавніше на ранішій стадії.

Бізнес-результат

E7 тепер володіє своїм тикетинг-стеком. Дані клієнтів ідуть у CRM. Маркетингові розсилки йдуть попереднім покупцям (30% відкриттів на анонсах заходів). Мобільний чекаут працює. Комісія реселера — в минулому.

Платформа обробила 50+ заходів без жодного інциденту перепродажу чи пропущеного вебхука. Чекін персоналу перетворився з паперового вузького горлечка на 25-хвилинну операцію.

І критично: платформа — актив, а не витрата. E7 зараз розглядає white-label для інших промоутерів у регіоні — лінія доходу, якої раніше не існувало.


Якщо ви платите реселеру 10–15% і у вас достатньо заходів, щоб математика виправдовувала — зібрати власну квиткову платформу реально за 12 тижнів, а не за 12 місяців. Давайте обговоримо ваші цифри.