Реалізація Non-Consumable IAP для iOS
Non-consumable IAP — категорія, де кожна помилка в логіці відновлення покупок перетворюється на скаргу в App Store та чарджбек. Користувач купив «безлімітний режим» або «прибрати рекламу», перевстановив додаток — і не отримав своє. Підтримка Apple не допоможе: відновлення non-consumable це відповідальність розробника. Наш досвід п'яти років та більше двадцяти проектів з IAP показує, що коректна реалізація з першого разу економить до 40% часу на баг-фіксах. Згідно з документацією StoreKit, правильне відновлення критично важливе.
Що найчастіше йде не так
Найпоширеніша помилка — викликати SKPaymentQueue.default().restoreCompletedTransactions() тільки по натисканню кнопки «Відновити». Правильно: при кожному запуску перевіряти originalTransaction через SKReceiptRefreshRequest або серверну валідацію. Без цього користувач, який повернувся через півроку з новим iPhone, опиниться без оплаченого контенту.
Другий кейс — неправильна обробка SKPaymentTransactionObserver. Якщо updatedTransactions не викликає finishTransaction(_:) для всіх станів (.purchased, .restored, .failed), транзакція зависає в черзі і при наступному запуску додатку повторно тригерить observer. Бачили проекти, де це призводило до подвійного відкриття paid-контенту після рестарту.
Третя проблема — відсутність серверної валідації. Локальна перевірка receipt через ASN.1 парсинг складна та вразлива: зловмисник може підмінити receipt у файловій системі. Серверна верифікація через ендпоінт Apple /verifyReceipt (або альтернативний) дає гарантію справжності.
Як влаштована правильна реалізація
Архітектура non-consumable IAP будується навколо StoreKit 2 (iOS 15+) або StoreKit 1 з підтримкою iOS 13–14. Порівняємо два підходи:
| Аспект | StoreKit 2 | StoreKit 1 (Legacy) |
|---|---|---|
| Мінімальна версія iOS | 15.0 | 3.0 |
| API стиль | async/await | Delegate + completion handlers |
| Відновлення покупок | Автоматичне через Transaction.currentEntitlements |
Ручне restoreCompletedTransactions() |
| Перевірка підпису | Вбудована VerificationResult |
Потрібен ReceiptValidator |
| Складність коду | Низька | Середня |
StoreKit 2 радикально спрощує код: він в 2 рази скорочує кількість рядків порівняно з StoreKit 1.
// Запит продуктів 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): // unlock контент 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, але працює). Рекомендуємо серверну: локальна вимагає вбудовування Apple root certificate та коректного ASN.1-парсингу.
Як уникнути проблем з відновленням покупок?
Ключове — робити відновлення автоматичним при кожному запуску, а не тільки по кнопці. В StoreKit 2 це досягається підпискою на Transaction.updates. В StoreKit 1 — викликом restoreCompletedTransactions() та збереженням originalTransaction.transactionIdentifier у UserDefaults або Keychain. Без автоматизації користувачі з новими пристроями залишаться без контенту.
Чому серверна валідація критична?
Для додатків з бекендом: при покупці клієнт відправляє appStoreReceiptURL на сервер, сервер запитує Apple Sandbox/Production та зберігає original_transaction_id в базі. При відновленні на новому пристрої — запит до свого API по apple_id користувача. Це єдиний спосіб гарантувати «покупка на одному пристрої, доступ на іншому» в рамках одного Apple ID. Середня економія від правильної реалізації — до $10,000 на рік на підтримку.
Покроковий план впровадження StoreKit 2
- Налаштуйте продукти в App Store Connect (локалізація, ціни).
- Реалізуйте запит продуктів через
Product.products(for:). - Додайте обробку покупки з перевіркою
VerificationResult. - Підпишіться на
Transaction.updatesдля автоматичного відновлення. - Інтегруйте серверну верифікацію через
/verifyReceipt. - Протестуйте в Sandbox на сценарій «покупка → видалення → відновлення».
Типова помилка відновлення
// НЕПРАВИЛЬНО: відновлення тільки по кнопці func restorePurchases() { SKPaymentQueue.default().restoreCompletedTransactions() } // ПРАВИЛЬНО: автоматичне відновлення при старті Task { for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { // розблокувати контент } } } Процес роботи та що входить
Наша реалізація IAP під ключ включає:
- Налаштування продуктів в App Store Connect (створення, локалізація, ціни — конкретні суми розраховуються індивідуально).
- Інтеграцію StoreKit 2 з fallback на StoreKit 1 для максимальної сумісності.
- Серверну верифікацію receipt зі збереженням
original_transaction_id. - Тестування в Sandbox та на реальних пристроях з чек-листом сценаріїв.
- Документацію по підтримці та доступам.
- Гарантію проходження App Review (дотримання параграфу 3.1.1 App Store Review Guidelines).
Тестування
В 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» в інтерфейсі). Замовте інтеграцію IAP під ключ — гарантуємо коректну роботу на всіх пристроях та версіях iOS.







