Розробка мобільного додатку для продажу квитків на заходи
Головний технічний біль ticketing-додатку — конкурентний продаж. Коли 500 людей одночасно натискають «Купити» на останні 10 місць, додаток зобов’язаний коректно обробити це без подвійних продажів, зависань і некоректних статусів. Це не frontend-задача — ми проєктуємо серверну частину з песимістичними блокуваннями та резервуванням через чергу. А мобільний клієнт готовий до сценарію «місце вже зайняте» — зі зрозумілим UX та миттєвим зворотним зв’язком.
Ми створюємо ticketing-додатки під ключ: від інтерактивної схеми залу до додатку контролера. Наш досвід у цій сфері — 5+ років та понад 20 успішних проєктів. Гарантуємо відмовостійкість під навантаженням та дотримання App Store Review Guidelines (Section 4.2/5.1).
Резервування місць та race condition
Стандартна схема: користувач обирає місце → сервер створює reservation з TTL 10 хвилин → користувач платить → reservation конвертується в ticket. Якщо оплата не пройшла за 10 хвилин — місце звільняється.
На мобільному клієнті таймер резервування — це CountDownTimer (Android) або Timer.scheduledTimer (iOS), синхронізований із серверним TTL. Не з моменту натискання кнопки на клієнті, а з reservation.expiresAt з відповіді сервера. Різниця часових поясів і дрейф системного годинника пристрою вб’ють UI-таймер, якщо синхронізувати неправильно.
// Android: синхронізація таймера з серверним expiresAt class ReservationViewModel : ViewModel() { private val _timeLeft = MutableStateFlow(0L) val timeLeft: StateFlow<Long> = _timeLeft fun startCountdown(expiresAt: Instant) { viewModelScope.launch { while (true) { val remaining = ChronoUnit.SECONDS.between(Instant.now(), expiresAt) if (remaining <= 0) { _timeLeft.emit(0) onReservationExpired() break } _timeLeft.emit(remaining) delay(1000) } } } } При закінченні резервування — не просто показуємо помилку. Автоматично пропонуємо найближчі доступні місця, якщо потік реального часу (WebSocket або SSE) це підтримує.
Як уникнути double booking?
Double booking — класична проблема при паралельних запитах. Ми вирішуємо її за допомогою SELECT FOR UPDATE в PostgreSQL або distributed lock на рівні reservationId + seatId в Redis. Альтернатива — ON CONFLICT DO NOTHING при вставці резервування. На клієнті дублюємо блокування: кнопка «Купити» деактивується після першого натискання, а повторні запити надсилаються з унікальним idempotency key. Ці методи в 3 рази знижують ймовірність конфлікту при пікових навантаженнях.
Схема місць та інтерактивний зал
Для концертних залів і театрів потрібна інтерактивна схема. На iOS — UICollectionView з кастомним UICollectionViewLayout або Canvas в SwiftUI для залів з нестандартною геометрією. На Android — Canvas + GestureDetector для pinch-to-zoom.
Схема завантажується як JSON з координатами кожного сидіння, секцією та статусом (available, reserved, sold, disabled). Оновлення статусів у реальному часі — через WebSocket: як тільки хтось зарезервував місце, всім підключеним клієнтам летить { type: "seat_reserved", seatId: "A-15" }.
| Платформа | Технологія | Перевага |
|---|---|---|
| iOS | SwiftUI Canvas + UIKit | Нативна продуктивність, підтримка безьє |
| Android | Canvas + GestureDetector | Плавний pinch-to-zoom, кастомна геометрія |
| Cross-platform | react-native-svg / flutter_svg | Єдиний код, швидкий рендеринг SVG |
| Технологія оновлення | Затримка | Навантаження на сервер |
|---|---|---|
| WebSocket | ~100 мс | Низьке (постійне з’єднання) |
| Polling (кожні 5 с) | ~5 с | Високе (часті запити) |
SVG-схеми залів — популярний варіант для крос-платформних рішень. React Native з react-native-svg або Flutter з flutter_svg + GestureDetector рендерять SVG з можливістю дотику по елементах.
Чому важливо оновлення в реальному часі?
Без WebSocket користувач може обрати місце, яке вже зайняте іншим. Це призводить до фрустрації та втрати продажів. Ми впроваджуємо WebSocket-з’єднання з автоматичним перепідключенням (exponential backoff). Як тільки резервування змінюється, клієнт отримує подію та перемальовує схему. Для offline-режиму кешуємо останній стан і показуємо попередження.
Електронні квитки та валідація на вході
Кожен куплений квиток отримує унікальний UUID та QR-код, згенерований на сервері. Зберігати секрет у QR не потрібно — достатньо ticketId, верифікація відбувається на сервері при скануванні. QR не має бути статичним зображенням у PDF — генеруємо його динамічно з ticketId на клієнті через ZXingObjC (iOS) або zxing-android-embedded, щоб не зберігати растрове зображення в базі.
Додаток контролера — окремий екран або окремий додаток зі сканером AVFoundation/CameraX, POST на /tickets/{id}/validate, відповідь: valid / already_used / invalid. Кешуємо валідовані ticketId локально на пристрої контролера на випадок відсутності інтернету — синхронізація після відновлення зв’язку.
Платіжний флоу
Еквайринг — ЮKassa, CloudPayments або Stripe. На iOS додатково Apple Pay (PKPaymentRequest), на Android — Google Pay. Для b2b сегменту — виставлення рахунку на email з оплатою через СБП.
Після оплати негайна відправка PDF-квитка на email через SendGrid/Postmark і push-сповіщення через FCM/APNs з deep link на екран «Мої квитки».
Як ми це робимо: стек і процес
...
Приклад конфігурації кешування контролера
CREATE TABLE ticket_validations ( ticket_id UUID PRIMARY KEY, validated_at TIMESTAMP DEFAULT NOW() ); При відновленні зв’язку надсилаємо всі ticket_id на сервер для синхронізації.
Типові помилки
- Double booking. Без
SELECT FOR UPDATEабо distributed lock два користувачі можуть створити резервування на одне місце. - QR на скріншоті. Не ставте захист від скріншотів. QR має бути одноразовим при валідації.
- Синхронізація часу. Використовуйте серверне
expiresAt, а не локальний час.
Етапи та терміни
Проектування моделі даних → схема місць → резервування з TTL → оплата → електронні квитки → додаток контролера → тестування навантаження (1000+ одночасних користувачів).
6–10 тижнів для повноцінного додатку з інтерактивною схемою залу та real-time оновленнями. Вартість розраховується індивідуально після аналізу вимог.
Отримайте консультацію з архітектури ticketing-рішення. Замовте розробку під ваш проєкт — оцінку зробимо за 1 день.







