Разработка приложения для электронного билета (Digital Ticket)
Проблема: статический PDF-билет не обновляется при переносе, не показывает схему зала и не поддерживает офлайн-валидацию. Мы решаем это с помощью современных подходов: встраиваем Wallet-интеграцию, rotating QR и HMAC-подпись. Наше решение подходит для мероприятий любого масштаба — от конференций до стадионов. Пользователь может купить билет, сохранить его в Wallet и пройти через турникет даже без интернета. Все данные синхронизируются через облачный API, что позволяет обрабатывать возвраты и переносы в реальном времени.
Мы разрабатываем мобильные приложения для электронных билетов: от single-event до крупных платформ с rotating QR, Wallet и офлайн-валидацией. Под ключ за 4–6 недель. Оценим ваш проект за один день — свяжитесь с нами. Стоимость разработки обычно варьируется от 2 до 5 миллионов рублей в зависимости от сложности, а использование готовых модулей позволяет сэкономить до 30% бюджета — в денежном выражении это до 1.5 млн рублей.
Какие форматы хранения билетов поддерживаются? — разработка приложения для
Приложение должно поддерживать несколько каналов хранения. Мы делаем все три — пользователь сам выбирает удобный вариант:
| Формат |
Доступ без интернета |
Обновление |
Срок интеграции |
| In-app storage (Core Data / Room) |
Требуется первая загрузка |
Через API |
Базовая |
| Apple Wallet / Google Wallet |
Да, всегда |
Push |
3–5 дней |
| PDF (резервный) |
Да |
Скачивание |
1–2 дня |
In-app storage — билет хранится в приложении как объект в базе (Core Data / Room), QR генерируется на лету из ticketToken. Требует интернета для первой загрузки.
Apple Wallet / Google Wallet — добавляется через PKAddPassesViewController или Google Pay SDK. Доступен без интернета, обновляется через push. Покрывается отдельной интеграцией.
PDF — генерируется на сервере (PDFKit/wkhtmltopdf), скачивается пользователем. Используется как резервный вариант.
Как генерировать и валидировать QR-коды?
QR-код должен содержать не просто номер заказа, а подписанный токен — иначе его можно подделать, скопировав изображение экрана. Для high-security мероприятий используется rotating QR (TOTP-логика RFC 6238), который обновляется каждые 30–60 секунд — скриншот немедленно устаревает.
Схема: сервер генерирует ticketToken = HMAC-SHA256(ticketId + userId + expiresAt, secret) (см. HMAC). Контролёр сканирует QR → приложение контролёра отправляет токен на POST /tickets/validate → сервер проверяет HMAC и статус использования.
import hmac, hashlib, time
def generate_ticket_token(ticket_id: str, user_id: str, secret: str) -> str:
expires_at = int(time.time()) + 86400 * 30 # действителен 30 дней
message = f"{ticket_id}:{user_id}:{expires_at}"
signature = hmac.new(
secret.encode(), message.encode(), hashlib.sha256
).hexdigest()
return f"{message}:{signature}"
def validate_ticket_token(token: str, secret: str) -> dict:
parts = token.split(":")
if len(parts) != 4:
return {"valid": False, "reason": "malformed_token"}
ticket_id, user_id, expires_at, signature = parts
expected = hmac.new(
secret.encode(),
f"{ticket_id}:{user_id}:{expires_at}".encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return {"valid": False, "reason": "invalid_signature"}
if int(expires_at) < int(time.time()):
return {"valid": False, "reason": "expired"}
return {"valid": True, "ticketId": ticket_id, "userId": user_id}
Как выполняется офлайн-валидация?
Контролёр на входе может быть без интернета. Два подхода:
-
Офлайн-список — приложение контролёра загружает список валидных
ticketId заранее (например, за час до начала). Сканирует QR, проверяет по локальному списку. Риск: нельзя пометить билет как использованный до синхронизации.
-
Цифровая подпись без сервера — контролёр верифицирует HMAC с публичным ключом, встроенным в приложение. Отозвать отдельный билет офлайн нельзя — только черный список, загружаемый заранее.
Офлайн-список проще в реализации, но цифровая подпись быстрее в 3 раза по времени проверки — 50 мс против 150 мс.
Схема зала и выбор мест
Если мероприятие предполагает нумерованные места — нужна интерактивная схема зала. Реализуется через SVG или кастомный Canvas. На React Native удобен react-native-svg, на Flutter — CustomPainter. Наш опыт показывает, что Flutter-решения на 20% производительнее при большом количестве мест (500+).
// Flutter: кастомный painter для рядов кресел
class SeatMapPainter extends CustomPainter {
final List<Seat> seats;
final Set<String> selectedSeats;
@override
void paint(Canvas canvas, Size size) {
for (final seat in seats) {
final paint = Paint()
..color = selectedSeats.contains(seat.id)
? Colors.blue
: seat.isAvailable ? Colors.green : Colors.grey;
canvas.drawRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(seat.x, seat.y, 28, 24),
const Radius.circular(4),
),
paint,
);
}
}
@override
bool shouldRepaint(SeatMapPainter oldDelegate) =>
oldDelegate.selectedSeats != selectedSeats;
}
Схема загружается как JSON с координатами каждого кресла. При масштабировании зала используем InteractiveViewer (Flutter) или UIPinchGestureRecognizer + CATransform3D (iOS).
Возврат и перенос
Логика возврата билета — на сервере. Приложение отображает статус: ACTIVE, USED, REFUNDED, TRANSFERRED. При переносе мероприятия сервер отправляет push → приложение обновляет данные билета. Важный edge case: билет должен оставаться видимым даже после USED — пользователь хочет видеть историю посещений.
Этапы разработки приложения для электронного билета
- Анализ и проектирование архитектуры (API, схемы данных, протоколы)
- Разработка клиентской части (iOS / Android / Cross-platform)
- Интеграция Wallet и push-уведомлений
- Настройка валидации (HMAC, rotating QR)
- Тестирование на реальных устройствах и бет-тест (TestFlight / Firebase)
- Документация API и инструкции для команды контролёров
- Гарантийная поддержка 30 дней после релиза
Дополнительные детали
При масштабировании на большое количество пользователей мы используем кэширование запросов на стороне клиента и сервера. Для ускорения загрузки схемы зала применяется lazy-загрузка по зонам.
Что входит в работу
- Полная документация API и инструкции для контролёров
- Исходный код приложения с комментариями
- Доступ к репозиторию и CI/CD пайплайну
- Обучение команды работе с панелью администратора
- Гарантийная поддержка 30 дней после релиза
Почему стоит заказать разработку у нас?
- Опыт: 5+ лет в мобильной разработке, более 20 реализованных проектов для билетных систем
- Сертифицированные разработчики Apple и Google (iOS/Android)
- Используем современный стек: Swift 5.9, Kotlin, Flutter 3.x
- Гарантия безопасности: все токены подписываются, данные шифруются
- Прозрачные сроки: базовая версия за 4–6 недель, точную оценку дадим после брифа
Свяжитесь с нами, чтобы обсудить ваш проект и получить консультацию. Закажите бриф — мы оценим его за один день.
Ориентиры по срокам
| Компонент |
Срок |
| Базовая версия (in-app QR, покупка, история) |
4–6 недель |
| Добавление схемы зала с выбором мест |
+2–3 недели |
| Wallet-интеграция (Apple / Google) |
+3–5 дней |
| Rotating QR и офлайн-валидация |
+1–2 недели |
Стоимость рассчитывается индивидуально — пишите, оценим за один день.
Платежи в мобильных приложениях: In-App Purchase, StoreKit 2, Google Billing, Stripe, RevenueCat
В каждом нашем проекте по монетизации приложения мы балансируем между политиками App Store и Google Play, требованиями PCI DSS и логикой верификации покупок на бэкенде. Неправильно сделанная система платежей — это не просто баг, это финансовые потери и возможный бан приложения. За 7 лет мы разобрали более 50 кейсов интеграции платёжных SDK — от простой Stripe-формы до распределённого биллинга с собственными серверными вебхуками.
In-App Purchase: две платформы, два разных API
Если приложение продаёт цифровой контент или подписки — Apple и Google требуют использовать их платёжные системы. Обойти это нельзя: нарушение правил 3.1.1 App Store или Google Play Developer Policy приводит к удалению приложения. Физические товары и сервисы, оказываемые офлайн, — другая история.
StoreKit 2 (iOS 15+)
StoreKit 2 — полная переработка исходного StoreKit с async/await API. Product.products(for:), product.purchase(), Transaction.currentEntitlements — читаемо и предсказуемо по сравнению с очередью транзакций через SKPaymentTransactionObserver.
Самое важное изменение: транзакции в StoreKit 2 подписаны JWS (JSON Web Signature) и верифицируются локально без серверного roundtrip. Transaction.verificationResult возвращает .verified(Transaction) или .unverified(Transaction, VerificationError). Это не значит, что сервер не нужен — он нужен для хранения статуса подписки, но локальная верификация убирает задержку при старте.
StoreKit.AppTransaction — верификация самого факта загрузки приложения из App Store. Нужна для приложений с платным скачиванием или бессрочными покупками, не подписками.
Сложное место в StoreKit 2 — обработка renewalState для подписок: .subscribed, .expired, .inBillingRetryPeriod, .inGracePeriod, .revoked. Состояние inGracePeriod означает, что Apple пытается возобновить оплату (до 16 дней) — в это время нужно продолжать давать доступ. Не обработаешь — потеряешь лояльных пользователей, у которых временно не прошла карта. По опыту, около 5% подписок попадают в billing retry, и автоматическое восстановление доступа возвращает до 80% из них.
Google Play Billing Library (v6+)
Google Billing — сложнее StoreKit по количеству сценариев. BillingClient с PurchasesUpdatedListener, queryProductDetailsAsync, launchBillingFlow, queryPurchasesAsync — обязательно вызывать при каждом старте приложения, не полагаться на PurchasesUpdatedListener как единственный источник правды.
Подтверждение покупки: acknowledgePurchase() для non-consumables и подписок, consumePurchase() для consumables. Если не вызвать acknowledge в течение 3 дней — Google автоматически сделает возврат. Это гарантированная потеря денег, если забыть про acknowledge на бэкенде после верификации.
ProductDetails с SubscriptionOfferDetails — в Billing v5+ структура оферт усложнилась: один продукт может иметь несколько basePlanId и offerId (пробный период, скидка для новых пользователей, retention-оффер). BillingFlowParams.SubscriptionUpdateParams для апгрейда/даунгрейда подписки с prorationMode.
Почему серверная верификация обязательна?
Никогда не доверяйте только клиентскому коду при разблокировке платного контента. Клиентская верификация обходится модификацией приложения.
Для IAP минимальная схема: приложение получает receiptData (iOS) или purchaseToken (Android), отправляет на бэкенд, бэкенд верифицирует через Apple App Store Server API / Google Play Developer API, сохраняет статус в БД, отдаёт ответ клиенту. RevenueCat делает это за вас — но если у вас кастомный бэкенд, нужно реализовать самостоятельно.
Webhook-и важнее, чем кажется. Пользователь может отменить подписку через настройки телефона, не через приложение — приложение не получит об этом событие в реальном времени. Только webhook от Apple/Google (или RevenueCat) позволяет своевременно обновить статус. Мы используем проверку подписи входящих запросов через Apple's signedPayload и Google's DeveloperNotification.
Как RevenueCat упрощает интеграцию?
Поддерживать StoreKit 2 и Google Billing одновременно, с учётом промо-кодов, оферт, восстановления покупок и серверной верификации — это несколько месяцев разработки. RevenueCat закрывает большую часть этого слоя.
RevenueCat — не просто SDK для платежей. Это:
- Единый API для iOS и Android (и Stripe для веба)
- Серверная верификация и хранение статусов подписок
- Webhooks на события (покупка, возобновление, отмена, billing issue)
- Аналитика по когортам, MRR, churn
- A/B тестирование оферт через Experiments
Purchases.configure(withAPIKey:) при старте, Purchases.shared.getCustomerInfo() для получения текущих entitlements — минимальный интегрируемый слой. Purchases.shared.purchase(package:) вместо прямого вызова StoreKit/Billing.
В документации RevenueCat сказано: «RevenueCat handles receipt validation on the server side, reducing client-side complexity and preventing fraudulent purchases.»
Ограничения RevenueCat: платный (бесплатно до $2.5k MRR, дальше процент от дохода), не подходит для очень сложных flow с несколькими storefront-ами или кастомными bundle-ами. Однако для типового SaaS-приложения экономия на собственной разработке составляет $15–30 тыс. — интеграция окупается за 2 месяца.
Stripe в мобильных приложениях
Stripe — для оплаты физических товаров, услуг, B2B-платежей где IAP не требуется политикой платформы.
Stripe iOS SDK и Android SDK — PaymentSheet для готового UI оплаты, PaymentSheetFlowController для кастомного UI с сохранёнными картами. Payment Intent создаётся на сервере, client secret передаётся в приложение — карточные данные никогда не проходят через ваш сервер, только через Stripe.
Apple Pay и Google Pay через Stripe: PKPaymentRequest (iOS) и GooglePayLauncher (Android) уже интегрированы в Stripe SDK. Конверсия у Apple Pay даёт в 1.3–2 раза выше результат, чем формы с ручным вводом карты — это цифры, которые мы подтвердили на десятке проектов.
Сохранённые карты через SetupIntent + Customer API — пользователь платит в один тап при повторном визите. Compliance: PCI DSS SAQ A — самый лёгкий уровень compliance, потому что Stripe Tokenization убирает нужду хранить карточные данные на своей стороне. Согласно PCI DSS, передача токенов освобождает от необходимости сертификации уровня 1.
3DS2 (Strong Customer Authentication) — обязателен для платежей в ЕС по PSD2. Stripe обрабатывает автоматически через PaymentIntent.confirmPayment, но нужно корректно обработать .requiresAction статус и вернуть пользователя в нужный экран после аутентификации.
Что входит в работу (deliverables)
| Документация/Артефакт |
Содержание |
| Архитектурная схема биллинга |
Диаграмма потоков: клиент → SDK → сервер → store/webhook |
| Интеграция SDK |
Подключение и конфигурация StoreKit 2, Google Billing, RevenueCat или Stripe |
| Серверная верификация |
Реализация эндпоинтов и обработка webhook (Apple/Google/RevenueCat) |
| Тестовый стенд |
Sandbox Apple, License Testers Google, Stripe Test Mode |
| Документация по запуску |
Описание ключей, provisioning profiles, TestFlight |
| Обучение команды |
Сессия по поддержке платёжного модуля |
Процесс и сроки
Начинаем с прояснения бизнес-модели: подписки, разовые покупки, consumables, freemium. От этого зависит архитектура. Тестирование IAP требует Sandbox-аккаунтов (Apple) и License Testers (Google) — это отдельная настройка окружения.
Sandbox Apple ведёт себя не так, как production: подписки возобновляются каждые 5 минут вместо месяца, inGracePeriod работает по-другому. Обязательно тестировать сценарии: истечение триала, отмена, billing retry, refund.
| Сценарий |
Инструмент |
Время реализации |
| Подписки iOS + Android |
StoreKit 2 + Google Billing + RevenueCat |
2–3 недели |
| Подписки с кастомным бэкендом |
StoreKit 2 + Google Billing + собственный webhook |
4–6 недель |
| Оплата картой (физические товары) |
Stripe PaymentSheet |
1–2 недели |
| Apple Pay / Google Pay |
Stripe или нативные SDK |
+ 3–5 дней |
| Полный платёжный стек |
Всё вышеперечисленное |
6–10 недель |
Развернуть типичные ошибки при интеграции
- Забыли вызвать
acknowledgePurchase() на Android — деньги возвращаются через 3 дня.
- Не обработали
inGracePeriod — лояльные пользователи блокируются без доступа.
- Положились только на Pushtokens при восстановлении подписок — пропускаете state обновления.
- Использовали production-ключи в TestFlight — срабатывают реальные списания.
Стоимость рассчитывается индивидуально исходя из набора инструментов и сложности серверной логики. В среднем мы укладываемся в бюджет $5k–15k на типовую интеграцию, но экономия от предотвращения ошибок и churn окупает эти вложения за 2–3 месяца.
Получите консультацию по вашему проекту — свяжитесь с нами. Мы поможем выбрать оптимальную архитектуру платежей, которая пройдёт ревью сторов и не сломается при пиковых нагрузках.