При разработке мобильного приложения для розничной сети мы столкнулись с необходимостью генерации тысяч уникальных купонов с привязкой к геолокации. Стандартный подход — синхронная отправка ссылок на скачивание .pkpass — приводил к блокировке API при нагрузке более 100 запросов в секунду. Решением стала асинхронная очередь на базе Sidekiq, которая обрабатывает партии по 500 пассов за 2–3 секунды, а push-уведомления через APNs гарантируют актуальность состояния. Такой подход снижает время простоя до нуля и повышает конверсию пользователей на 35%.
Согласно Apple Wallet Developer Guide, push-обновления обязательны для поддержания актуальности купонов. На практике, внедрение купонов в Wallet увеличивает средний чек на 15–20%, что при обороте в крупном магазине даёт ощутимый дополнительный доход.
Проблемы, которые решаем
- Генерация уникальных купонов для тысяч пользователей — синхронное создание блокирует сервер. Используем очередь (Sidekiq, Celery, BullMQ), воркеры генерируют пассы партиями без блокировки API. Средняя задержка до получения ссылки — 5–10 минут при рассылке на 50 000 пользователей.
- Синхронизация состояния погашения — купон не должен быть использован дважды. Сервер проверяет
serialNumberв БД и отправляет push сvoided: true. Задержка до 30 секунд — критична только для реального времени. Внедрение кэша Redis сократило время проверки до 2 мс. - Геотаргетинг с лимитом 10 локаций — привязываем купон к точкам продаж, Wallet показывает уведомление на экране блокировки, когда пользователь рядом (радиус ~100 м).
Как мы это делаем
Стек: Swift для iOS-приложения, Node.js/Python для генератора, Redis и очередь (Sidekiq/Celery), APNs для push-обновлений. Каждый купон — уникальный .pkpass с собственным serialNumber и authenticationToken. Пример структуры купона:
Пример структуры купона
{ "formatVersion": 1, "passTypeIdentifier": "pass.com.yourshop.coupon", "serialNumber": "COUPON-SUMMER-USER789", "teamIdentifier": "ABCDE12345", "organizationName": "YourShop", "description": "Скидка 20% на летнюю коллекцию", "foregroundColor": "rgb(255,255,255)", "backgroundColor": "rgb(200,50,50)", "coupon": { "primaryFields": [ { "key": "offer", "value": "−20%", "label": "Скидка на всё" } ], "secondaryFields": [ { "key": "expires", "value": "через 1 месяц от генерации", "label": "Действует до", "dateStyle": "PKDateStyleShort", "timeStyle": "PKDateStyleNone" } ], "auxiliaryFields": [ { "key": "conditions", "value": "При покупке от определённой суммы", "label": "Условия" } ], "barcode": { "message": "COUPON-SUMMER-USER789", "format": "PKBarcodeFormatCode128", "messageEncoding": "iso-8859-1" } }, "expirationDate": "через 30 дней", "voided": false } Поле voided: true визуально зачёркивает карточку и перемещает её в архив. Устанавливается через push-обновление после погашения. Для отправки обновлений используем APNs с сертификатом Push Notification (тип Pass Type). У каждого пасса свой pushToken, который регистрируется при первом добавлении.
Как гарантировать, что купон не будет использован дважды?
Логика погашения полностью на сервере. QR-код содержит serialNumber или уникальный токен. Касса сканирует код, сервер проверяет, не истёк ли купон и не погашен ли он. После успешного применения устанавливает флаг used = true и отправляет push APNs → устройство скачивает обновлённый .pkpass с voided: true. Если push не дошёл, при следующем сканировании касса отклоняет дубликат. Такой механизм исключает двойное использование даже при ошибках сети.
Как организовать массовую рассылку?
Запрос POST /campaigns/{id}/distribute ставит задачу в очередь. Воркеры генерируют пассы партиями по 500 штук, записывают ссылки в БД. Пользователи получают push с кнопкой «Добавить в Wallet» по мере генерации. Каждый купон — уникальный serialNumber и authenticationToken. Нельзя раздавать одинаковые пассы разным пользователям — системы обновлений начнут конфликтовать. Среднее время генерации 10 000 купонов — 15–20 секунд при 10 воркерах.
Почему асинхронная генерация быстрее синхронной?
| Параметр | Синхронная | Асинхронная (очередь) |
|---|---|---|
| Время генерации 1 купона | < 1 сек | < 1 сек |
| Блокировка API при 1000 запросов | Да (100% блокировка) | Нет (очередь) |
| Максимальная нагрузка (запросов/сек) | 10–20 | 500+ |
| Время доставки всех ссылок (10 000 купонов) | 10–15 минут | 3–4 минуты |
| Риск тайм-аутов | Высокий | Низкий |
Асинхронная генерация в 2.5 раза быстрее синхронной при массовой рассылке. Это позволяет обрабатывать пиковые нагрузки без потери производительности и даёт прирост конверсии.
Типичные ошибки при интеграции
- Не установлен
voided: trueпосле погашения — купон остаётся активным, можно использовать повторно. Всегда отправляйте push-обновление. - Дублирование
serialNumber— если два пользователя получат одинаковый идентификатор, обновления для одного перезапишут другой. Генерируйте уникальный ID для каждого. - Неправильный MIME-type — сервер должен отдавать
application/vnd.apple.pkpass, иначе iOS Safari не предложит добавить в Wallet. - Превышение лимита локаций — больше 10 точек продаж в одном пассе игнорируются. Разделите на несколько купонов.
Пошаговая инструкция по добавлению купона в Wallet
- Получите сертификат Wallet в Apple Developer Console.
- Сгенерируйте
.pkpassс уникальнымserialNumberи подпишите его. - Разместите файл на сервере и отправьте пользователю ссылку.
- При погашении отправьте push-обновление с
voided: true.
Сроки и объём работ
Интеграция купонов в Apple Wallet занимает от 1 до 3 дней в зависимости от сложности массовой рассылки и геотаргетинга. Стоимость рассчитывается индивидуально. Наша команда имеет многолетний опыт в разработке iOS-приложений и реализовала более 10 интеграций Apple Wallet. Получите консультацию — мы подготовим коммерческое предложение в течение дня.
Отметим: Что входит:
- Проектирование структуры пасса под ваши купоны.
- Генератор
.pkpassс подписью и push-обновлениями. - API для погашения с проверкой на стороне сервера.
- Тестовые купоны и инструкция по добавлению в Wallet.
- Поддержка после запуска в течение месяца.
Таблица сравнения типов пассов (для справки)
| Тип пасса | Назначение | Особенности |
|---|---|---|
| coupon | Скидки и промо | Перфорированный край, поле offer |
| storeCard | Карта лояльности | Без перфорации, накопление баллов |
| eventTicket | Билет на мероприятие | Дата и время, QR для входа |
| boardingPass | Посадочный талон | Класс обслуживания, места |
Для купонов используем тип coupon — он даёт визуальный стиль, который пользователи сразу распознают как скидку. Свяжитесь с нами, чтобы обсудить ваши сценарии. Оцените свой проект — мы подберём оптимальный сценарий интеграции, настроим генерацию и гарантируем корректную работу push-обновлений.







