Интеграция Apple Wallet для билетов на мероприятия в мобильном приложении
Билет на концерт или конференцию — это eventTicket-тип .pkpass. Отличие от boardingPass: нет transitType, структура полей другая, и особое внимание к полю strip.png — оно формирует «шапку» с изображением мероприятия. Без strip.png нужного размера (624×246 @1x или 1248×492 @2x) карточка выглядит голо и безликно.
pass.json для eventTicket
{
"formatVersion": 1,
"passTypeIdentifier": "pass.com.yourtickets.event",
"serialNumber": "EVT-CONCERT-2024-USER42",
"teamIdentifier": "ABCDE12345",
"organizationName": "YourTickets",
"description": "Билет на концерт Иванова",
"foregroundColor": "rgb(255,255,255)",
"backgroundColor": "rgb(20,20,20)",
"labelColor": "rgb(180,180,180)",
"eventTicket": {
"primaryFields": [
{ "key": "event", "value": "Концерт Ивана Иванова", "label": "Мероприятие" }
],
"secondaryFields": [
{ "key": "venue", "value": "Олимпийский", "label": "Место проведения" },
{ "key": "date", "value": "2024-09-21T20:00+03:00", "label": "Дата",
"dateStyle": "PKDateStyleMedium", "timeStyle": "PKDateStyleShort" }
],
"auxiliaryFields": [
{ "key": "seat", "value": "Сектор A, ряд 5, место 12", "label": "Место" },
{ "key": "category", "value": "VIP", "label": "Категория" }
],
"backFields": [
{ "key": "order", "label": "Номер заказа", "value": "ORD-987654" },
{ "key": "rules", "label": "Правила входа", "value": "Вход не ранее чем за 30 минут до начала" }
],
"barcode": {
"message": "EVT-CONCERT-2024-USER42",
"format": "PKBarcodeFormatQR",
"messageEncoding": "iso-8859-1",
"altText": "EVT-CONCERT-2024-USER42"
}
},
"relevantDate": "2024-09-21T19:30+03:00",
"expirationDate": "2024-09-21T23:59+03:00",
"locations": [
{
"latitude": 55.7806,
"longitude": 37.5617,
"relevantText": "Вы рядом с площадкой мероприятия"
}
]
}
expirationDate — после этой даты Wallet автоматически перемещает пасс в архив и предлагает пользователю его удалить.
Валидация QR при сканировании
Содержимое barcode message — это то, что считывает сканер на входе. Логика проверки полностью на стороне сервера: сканер отправляет строку на POST /tickets/validate, сервер проверяет статус билета в базе и возвращает valid/already_scanned/invalid.
Важно: сам .pkpass не содержит информации о том, был ли билет использован. Данные о сканировании хранятся только на сервере.
Защита от скриншотов
QR из Wallet нельзя защитить от скриншота средствами iOS. Для мероприятий с высоким риском перепродажи используют rotating barcode — в pass.json поле barcode.message обновляется каждые 30–60 секунд через push APN. Даже если пользователь сделал скриншот, через минуту скриншот уже недействителен.
Реализация: сервер хранит seed для каждого билета, генерирует TOTP-подобный код, отправляет push в Wallet → устройство запрашивает новый .pkpass → старый QR устаревает.
Групповые билеты
Если один заказ содержит несколько мест — каждый билет отдельный .pkpass со своим serialNumber. Группировать несколько пассов в один архив нельзя. Для пакетной отправки в приложении используется PKAddPassesViewController(passes:):
let passes = ticketDataArray.compactMap { try? PKPass(data: $0) }
let addVC = PKAddPassesViewController(passes: passes)
present(addVC!, animated: true)
Контроллер покажет все билеты списком — пользователь добавляет их одним нажатием.
Сроки
2–3 дня: генерация eventTicket-пассов на сервере, серверный API валидации QR, реализация rotating barcode при необходимости. Стоимость рассчитывается индивидуально.







