Реалізація In-App Purchases для мобільної гри
IAP в мобільній грі — це не просто «додати кнопку купити». Apple та Google передбачають вимоги до верифікації чеків, архітектури серверної частини та обробки відновлення. Пропуск будь-якої вимоги — або втрата грошей, або відклонення додатку.
StoreKit 2 vs StoreKit 1
StoreKit 1 (до iOS 15) має SKPaymentQueue, SKProduct, SKPaymentTransactionObserver. Legacy: callbacks через делегат, ручне управління транзакціями, verbose код.
StoreKit 2 (iOS 15+) — принципово інший API: async/await, Product.products(for:), product.purchase(), Transaction.updates AsyncSequence. Код чистіший у два рази.
Якщо орієнтуєтесь на iOS 14 та нижче — пишіть на StoreKit 1 або використовуйте кросплатформенну абстракцію. iOS 15+ — StoreKit 2 однозначно.
Android: BillingClient (Play Billing 6+). Ключові методи: launchBillingFlow(), queryProductDetailsAsync(), acknowledgePurchase(). Google вимагає підтверджувати кожну покупку протягом 3 днів, інакше автоматично рефандить. Це не баг — це політика. Викликайте consumeAsync() для витратних товарів (валюта гри, жизні) та acknowledgePurchase() для невитратних (убрати рекламу, premium).
Серверна верифікація чеків — обов'язкова
Клієнтська верифікація небезпечна. Jailbroken пристрій + iap-receipt-generator = фейкові чеки. Правильна схема:
- Додаток отримує чек/токен від StoreKit/BillingClient
- Надсилає на ваш сервер:
POST /api/purchases/verify - Сервер верифікує через Apple
/verifyReceiptабо Google Play Developer API - Сервер зачисляє товар користувачу
- Повертає результат клієнту
Apple застаріває /verifyReceipt на користь JWS-транзакцій — на сервері декодуємо signedTransactionInfo (JWT), верифікуємо підпис Apple Root CA. Бібліотеки: appstore-connect-sdk (Node.js), apple-receipt-verifier (Python/Go).
Типи покупок у грі
| Тип | Приклад | Витратний | Логіка |
|---|---|---|---|
| Ігрова валюта | 1000 монет | Так | consume після зачислення |
| Жизні/енергія | +5 жизні | Так | consume, не дублювати |
| Убрати рекламу | Без реклами | Ні | acknowledge, restore |
| Сезонний пропуск | Battle Pass | Subscription | перевірка expiry |
| Однорозовий контент | Скин персонажа | Ні | acknowledge, restore |
Для підписок (autoRenewableSubscription) — окрема логіка: renewalInfo, grace period, billing retry state. Якщо закінчилась, але в grace period — не позбавляємо доступ відразу.
Відновлення покупок
StoreKit 2: for await transaction in Transaction.currentEntitlements — всі активні невитратні покупки та підписки. Кнопка «Відновити покупки» — обов'язковий елемент App Store Review Guideline 3.1.1. Без неї — відклонять.
Android: queryPurchasesAsync(QueryPurchasesParams) для INAPP та SUBS — всі активні покупки offline.
Offline та edge-cases
Користувач купив, сервер недоступен для верифікації. Правильна схема: зберігаємо pending-транзакцію локально (Core Data / Room), повторюємо верифікацію при наступному запуску з exponential backoff. Не завершуємо транзакцію (finish()) до успішної серверної верифікації.
Процес
Налаштування продуктів в App Store Connect та Google Play Console → серверна верифікація → клієнтський IAP модуль → тестування в Sandbox/Test → тестування restore → QA edge-cases (переривання оплати, дублювання покупки) → сабмит.
Тривалість
Базова інтеграція (витратні + невитратні, серверна верифікація): 3–4 дні. Підписки з grace period, billing retry, receipt migration: +2 дні. Кросплатформа Flutter/React Native з абстракцією над StoreKit та BillingClient: +1–2 дні.







