Разработка мобильного приложения для продажи билетов на мероприятия
Главная техническая боль 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 с возможностью tap по элементам.
Почему важно обновление в реальном времени?
Без 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 день.







