Під час інтеграційного тестування IAP перед релізом ми зіткнулися з типовою, але дуже заплутаною проблемою:
- Користувач натискає «Продовжити покупку»
- Кнопка переходить у стан «Обробка»
- Через кілька секунд повертається до «Продовжити покупку»
- Вікно підтвердження покупки App Store не з’являється
У цій статті — повний шлях діагностики, ключові логи, коренева причина та практична стратегія виправлення перед випуском.
1. Симптом
Список товарів на екрані покупки завантажувався нормально, але після натискання кнопки покупки:
- Викликався
purchase() - Системний діалог не з’являвся
- UI зрештою повертався в стан «не куплено»
На перший погляд здавалося, що потік покупки взагалі не стартує, але логи показали іншу картину.
2. Перша перевірка: чи справді виконується потік покупки
Ми додали логи в усі критичні точки потоку:
- Параметри натискання кнопки
- Вхід у
purchaseSelectedProduct()і гілкиguard - До/після
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Результат:
purchase()повертав.success- Верифікація транзакції проходила,
transaction.finish()також виконувався - Але
entitledIDsзалишався порожнім
Це означає: проблема була не в тому, що «покупка не стартувала», а в тому, що «після покупки права не розпізнавалися».
3. Ключові знахідки після поглибленого аналізу
Після розширення логування з’явилися вирішальні сигнали:
- Кількість ітерацій
Transaction.currentEntitlementsбула0 Transaction.latest(for: monthly)містив валідний запис, але вже прострочений- Найважливіше: до покупки в
Transaction.unfinishedбули старі транзакції - Поточний
purchase()повертав транзакцію зі старого ланцюжка, а не з нового інтерактивного потоку покупки
Це пояснює, чому «попап не з’являється»:
- StoreKit спочатку повертає стару оброблювану транзакцію (unfinished / історичний ланцюжок)
- Код завершує її через
finish() - Запит виглядає «успішним», але не проходить через новий шлях підтвердження, який очікує користувач
4. Підсумок кореневої причини
Це не одна точка відмови, а поєднання двох факторів:
- Незавершені транзакції (
unfinished transaction) втручаються в потік покупки - Sandbox-ланцюжок підписки перебуває у простроченому стані (навіть за наявності
latest transactionце не обов’язково активний entitlement)
Фінальна поведінка: попапа немає, видимий успіх, entitlement порожні.
5. Стратегія виправлення (перевірена на практиці)
5.1 Проактивно очищати unfinished transactions перед покупкою
Під час запуску застосунку і перед покупкою скануйте Transaction.unfinished та виконуйте finish() для verified-транзакцій, щоб старі транзакції не перехоплювали новий потік.
5.2 Зберегти й посилити діагностичні логи (рекомендовано до релізу)
До випуску варто тримати увімкненими:
- Snapshot
unfinished transaction subscription status(state, renewal info)- Кількість ітерацій
currentEntitlements - Деталі
transaction/expirationізlatest(for:)
5.3 Використовувати «двоканальну» перевірку прав (опційно)
Якщо в деяких середовищах currentEntitlements тимчасово порожній, можна використовувати latest(for:) як fallback (з обов’язковою перевіркою активного статусу).
6. IAP-чекліст перед релізом
- Перед кожним тестом перевіряйте стан підписки sandbox-акаунта (прострочена чи ні)
- Логуйте кількість
unfinishedна критичних шляхах - Тестуйте три сценарії: перша покупка / продовження після завершення терміну / відновлення покупок
- Перевіряйте узгодженість між
currentEntitlementsіlatest - Якщо спостерігаєте «немає попапа», спершу перевірте потрапляння в старий транзакційний шлях
7. Висновок
«Натиснув купити, але попап не з’явився» — це не обов’язково проблема UI або таймінгу виклику.
У StoreKit 2 стан транзакцій, черга unfinished і життєвий цикл підписки напряму впливають на фінальну поведінку.
Додавання цих логів і кроків очищення до релізу суттєво зменшує кількість IAP-проблем, які важко відтворити в продакшені.