При разработке мобильного приложения для розничной сети мы столкнулись с необходимостью генерации тысяч уникальных купонов с привязкой к геолокации. Стандартный подход — синхронная отправка ссылок на скачивание .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-обновлений.







