Реалізація In-App Purchases (одноразові покупки) для iOS
Non-consumable IAP — категорія, де кожна помилка в логіці відновлення покупок перетворюється на скаргу в App Store та чарджбек. Користувач купив «безлімітний режим» або «убрати рекламу», переустановив додаток — і не отримав своє. Служба підтримки Apple не поможе: відновлення non-consumable це відповідальність розробника.
Що частіше всього йде не так
Найпоширеніша помилка — викликати SKPaymentQueue.default().restoreCompletedTransactions() тільки по нажатию кнопки «Відновити». Правильно: при кожному запуску перевіряти originalTransaction через SKReceiptRefreshRequest або серверну валідацію. Без цього користувач, який повернувся через піл року з новим iPhone, окажется без оплаченого контенту.
Другий кейс — неправильна обробка SKPaymentTransactionObserver. Якщо updatedTransactions не викликує finishTransaction(_:) для всіх станів (.purchased, .restored, .failed), транзакція зависає в черзі та при наступному запуску приложения повторно триггерить observer. Бачив проекти, де це призводило до подвійного відкриття платного контенту після перезавантаження.
Як влаштована правильна реалізація
Архітектура non-consumable IAP в 2024 році будується вокруг StoreKit 2 (iOS 15+) або StoreKit 1 з підтримкою iOS 13–14.
StoreKit 2 радикально спрощує код:
// Запрос продуктів
let products = try await Product.products(for: ["com.app.premium_unlock"])
// Покупка
let result = try await products.first?.purchase()
switch result {
case .success(let verification):
switch verification {
case .verified(let transaction):
// розблокуємо контент
await transaction.finish()
case .unverified:
// receipt підроблений — не розблокуємо
break
}
case .pending:
// SCA або батьківський контроль — чекаємо
break
case .userCancelled:
break
}
Transaction.currentEntitlements — async sequence, який при кожному запуску приложения повертає всі активні покупки. Ітеруємо його в @main або в AppDelegate.applicationDidFinishLaunching та відновлюємо стан без кнопки «Відновити».
Для iOS 13–14 залишається StoreKit 1 з SKPaymentTransactionObserver. Потрібен окремий ReceiptValidator — або локальна верифікація через openssl (складно, але без мережевих запитів), або серверна через Apple /verifyReceipt endpoint (deprecated з 2023, але працює). Рекомендую серверну: локальна вимагає embedding Apple root certificate та коректної ASN.1-парсинга.
Серверна валідація
Для приложений з бекендом: при покупці клієнт відправляє appStoreReceiptURL на сервер, сервер запитує Apple Sandbox/Production та зберігає original_transaction_id в БД. При відновленні на новому пристрої — запит до свого API за apple_id користувача.
Без цього неможливо реалізувати «покупка на одному пристрої, доступ на іншому» в рамках одного Apple ID — а користувачі це очікують.
Тестування
В Xcode Simulator StoreKit працює через локальний .storekit файл — можна тестувати без реальних продуктів. Для device-тестування потрібен Sandbox Account в App Store Connect. Важливо перевіряти сценарій: покупка → видалення → переустановка → відновлення. Цей шлях ломається частіше всього.
Терміни реалізації — 2–3 дні: налаштування продуктів в App Store Connect, інтеграція StoreKit 2 з fallback на StoreKit 1, покриття тестами на Sandbox, прохождення ревю (App Review вимагає кнопку «Restore Purchases» в інтерфейсі).







