In-App Purchases під мікроскопом: як тестувати покупки в iOS та Android
Привіт, колеги! Я Настя, QA в Megogo.
Я тут, щоб познайомити вас з дуже об’ємною та цікавою темою — тестування in-app purchases. Про внутрішні покупки в мобільних додатках можна говорити годинами, але в цій статті я спробую вмістити найнеобхідніше, щоб ви могли скласти загальну картину про тестування IAP.
Стаття може бути корисною для QA (як для тих, хто ніколи не чув про внутрішні покупки, так і для досвідчених сенсеїв), а також для девелоперів — щоб подивитися на тестувальницький погляд.
Тож поведу вас за таким планом:
- Що таке IAP, їх бізнес-логіка та маркетингові інструменти;
- Як внутрішні покупки працюють технічно;
- Життєвий цикл підписок;
- Поради по тестуванню.
Що таке In-app purchases (IAP)
Отже, для початку я пропоную хвилинку історії, щоб трошки зануритись в те, навіщо IAP були створені.
У 2008 році App Store стартував як «магазин платних додатків», де монетизація відбувалася за моделлю Premium (або Pay-to-Download). Користувач купував доступ до додатка лише один раз — і отримував можливість безстрокового користування.
Дуже швидко для бізнесу стали очевидними 2 речі:
- Поріг входу для юзерів зависокий — люди не люблять платити наперед за те, що ще не спробували;
- Функціонал додатку потребує постійного оновлення, серверів, контенту, підтримки — а разова покупка це погано фінансувала.
Тому в 2009 році Apple (а потім і Google) перейшли на бізнес-модель Freemium. За цією бізнес-моделлю додаток пропонує базову версію продукту чи послуги безкоштовно, щоб залучити широке коло користувачів, а розширені функції, додатковий контент чи можливості — продає за додаткову плату в преміум-версії.
І тут вже на сцену виходять In-App Purchases.
Це можливість купувати контент, внутрішню валюту додатку, доступ до додаткового функціоналу безпосередньо всередині мобільного застосунку.
Існують різні типи внутрішніх покупок для різних цілей: Consumable, Non-consumable, Subscriptions (Auto-Renewable та Non-Renewing). Лишаю красиву картинку для колег візуалів, щоб краще запам’яталося.
Спойлер: саме про передплати ми будемо говорити сьогодні найбільше.

Бізнес-логіка та маркетингові інструменти IAP
Для того, щоб запрошувати або утримувати користувача — в у внутрішніх покупках існують різні маркетингові інструменти, а саме різні тифи оферів, для різної цільової аудиторії:
- Introductory Offers for subscriptions — це такий собі «привітальний бонус» для залучення нових користувачів.
Цільова аудиторія: тільки ті, хто ніколи не мав підписки в цій групі підписок (або не використовував тріал). Детальніше про різні типи introductory offers можна почитати тут. - Promotional Offers — це інструмент для утримання або повернення користувачів, які вже платили. Детально і технічно — тут.
Цільова аудиторія: існуючі або колишні підписники. Наприклад користувач колись мав підписку в додатку, але потім скасував, і через якийсь час додаток показує банер/пейвол: «Повернись у Premium — 3 місяці зі знижкою 50%» (або «1 місяць за $0.99») - Subscription Offer Codes — це класичні «купони», які можна роздавати поза межами додатка. Детальніше — тут.
Цільовою аудиторією є будь-хто (і нові, і існуючі, і колишні) користувачі, залежно від налаштувань коду. (часто застосовується як «вибачення/комплімент» для юзера) - Apple Family Sharing/Google Family Group — це механізм, що дозволяє ділитися однією покупкою з членами родини (група до
5–6 акаунтів), детальніше: iOS та Android.
Цільова аудиторія: члени родини покупця.
Важливо: не працює для Consumables (витратні покупки не шеряться).
З маркетингом розібрались, тепер спускаємось трошки глибше.
Рівні доступу в підписках
Для того, щоб краще зрозуміти логіку підписок, перейдемо до рівнів доступу.
Різні додатки пропонують різний набір фіч, доступ до яких відкривається після купівлі тієї чи іншої підписки. І на пейволі юзер може обрати, які саме фічі, на який період і за яку ціну він хоче придбати.
Приклад: (увага на картиночку), уявімо мобільний застосунок з категорії health & fitness в якому представлені наступні платні фічі:
- трекер активності,
- тренування в записі,
- AI дієтолог
Наш додаток пропонує різні рівні доступу до цих фіч: в найнижчому базовому — доступ тільки до трекера, в середньому — трекер + тренування, а в найдорожчій передплаті — ще додається AI-дієтолог.
Також юзер може обрати період, на який він хоче купити доступ до фіч (в нашому випадку представлені місяць або рік).

Але уявімо ситуацію:
Юзер купив собі базовий тариф на місяць, і через тиждень йому підвищили зп, і він вирішив перейти на кращий преміум-тариф. Дія попереднього базового тарифу в нього ще не завершилася.
Тут постає логічне питання: куди тоді діваються кошти за решту оплаченого раніше періоду?
По-перше, варто зазначити, що якщо різні підписки знаходяться в одній групі підписок, у користувача може бути лише одна активна передплата з цієї групи.
Що таке ці Subscription groups?
В iOS — це налаштований в App Store Connect (кабінеті розробника) контейнер, який об’єднує кілька варіантів підписок в єдину логічну сутність, гарантуючи, що користувач може мати одночасно лише один активний тариф із цього списку.
- Бізнес-логіка Subscription Groups базується на взаємному виключенні та автоматичній заміні тарифів: тобто якщо клієнт купує нову підписку з тієї ж групи, Apple автоматично скасовує стару, обробляючи це як зміну тарифу, щоб запобігти подвійному списанню коштів за той самий сервіс.
- Крім того група, діє як обмежувач для маркетингових акцій:
Існує правило: «один пробний період на одну сабскрібшен-групу», що означає, що, використавши тріал на «Місячному» плані, користувач більше не отримає його при спробі переключитися на «Річний».
Android використовує схожу структуру «Subscription Object → Base plans», яка не має чіткої прив’язки до групи підписок.
Натомість різні рівні доступу налаштовуються як окремі незалежні підписки, які умовно не знають про існування одна одної.
Ключова відмінність реалізації зміни тарифів в iOS та Android полягає в тому, що в iOS система сама скасовує стару підписку при переході в межах групи, тоді як в Android розробник повинен вручну реалізовувати логіку заміни (надсилаючи старий покуповий токен), щоб уникнути подвійного списання коштів.
Детальніше про реалізацію в iOS та Android .
По-друге, як же ж юзеру все ж таки перейти на дорожчу передплату?
Тут вступає в дію окрема логіка зміни передплат Upgrade/Downgrade/Crossgrade.
Коли юзер хоче перейти на передплату вищого рівня доступу, ніж він має зараз — це називається Upgrade передплати.
- Зміна тарифу відбувається одразу (тобто нові premium-фічі стають доступними).
- Поточна підписка закінчується достроково.
- Невикористана частина оплаченого періоду повертається пропорційно рефанду (prorated refund).
- Нова підписка списується за повною ціною, а дата наступного білінгу стає датою апгрейду.

Коли юзер хоче знизити рівень підписки до нижчого рівня, ніж поточний, — це Downgrade.
- Поточна (дорожча) підписка продовжується до кінця оплаченого циклу.
- Перехід на дешевший план відбудеться на дату наступного renewal.

Якщо користувач змінює підписку на інший план того ж рівня доступу — це Crossgrade. Але логіка переходу відрізняється залежно від тривалості плану.
- Якщо та сама тривалість (наприклад, monthly → monthly): перехід відбувається одразу, з рефандом за попередній план і повним списанням за новий план.
- Якщо різна тривалість (monthly → yearly): новий тариф вступає в силу на наступний renewal date.


В Android свої особливості в логіці обробки переходів між тарифами:
Для виконання зміни підписок, розробнику необхідно надіслати на сервер гугла — ID старого продукту та так називаємий Replacement mode (режим заміни). За замовчуванням він буде встановлений як WITHOUT_PRORATION (без перерахунку).
В документації developer.android. наведені приклади для кожного режиму заміни і рекомендації, який режим заміни краще використовувати в тих чи інших сценаріях.
Ми не будемо детально на цьому зупинятися, головне — зрозуміти, що основна відмінність в тому, що в Android-системі зміни підписки дещо гнучкіші, і розробник сам обирає, яка логіка з рекомендованих буде працювати.
На що потрібно звертати увагу при тестуванні переходу між тарифами:
- Перед тестами варто зафіксувати у якій subscription group які передплати і тарифи знаходиться, і який replacement mode або proration mode буде викоритсаний в Android. Це дасть вам точніше розуміння очікуваного результату.
- При переході не має бути «паузи» в доступі для користувача.
- Якщо юзер змінив план на одному девайсі → на іншому стан синхронізувався без ручного «restore».
- Обов’язково перевірити коректне оновлення дати поновлення передплати.
Повернемося трошки до нашого уявного додатка: в якийсь момент, бізнес приходить і просить реалізувати ще додаткові фічі в додатку, пов’язані з метнальним здоров’ям:
- дихальні практики
- медитації
Але продавати їх треба, окремо від тих передплат, що у нас уже існують.
Тоді створюється окрема група підписок з доступом до нових фіч. І юзер паралельно зможе користуватися фічами з групи підписок BeHealthy та Mentalochka (як на картиночці) , тому що, як ми пам’ятаємо, користувач може мати лише один активний тариф з однієї групи передплат.
Так, чую ваші думки, що з основною бізнес-логікою ми вже розібралися, але як взагалі купівля працює технічно?
Тоді йдемо ще на рівень глибше.
Як внутрішні покупки працюють технічно
Шлях від моменту коли юзер заходить на paywall в додатку, до моменту, коли він успішно може користуватись premium фічею — я умовно поділила на 5 етапів в яких взаємодіють наступні сутності:
- Юзер,
- Мобільний клієнт,
- Наш сервер,
- Сервер стору Apple/Google
І кожен з цих п’яти етапів ми розглянемо окремо з sequence diagram.
1. Конфігурація продуктів на Paywall в додатку
- Юзер переходить на пейвол
- Але для того, щоб показати йому якісь продукти, наш мобільний клієнт йде з запитом id продуктів на наш сервер.
Ліричний відступ: для чого взагалі id продуктів, які ми показуємо юзеру, керує бекенд?
А для того, щоб легко і гнучко проводити A/B-тестування і різні експерименти монетизації без потреби релізити нову збірку в стор (а це, як ми знаємо, досить довгий процес). - Сервер на своїй стороні визначає до якого сегменту входить наш юзер і повертає ідентифікатор ціни (app_product_id)
- Далі наш мобільний клієнт надсилає запит в store, для того, щоб store повернув нам локалізовані ціни та валюти. Після цього наш мобільний клієнт відображає пейвол для юзера.
Важливий момент: так як існує велика кількість гео, валют і ще купа різних умовностей — усім, що стосується цін, загалом списань юзерських грошей, керує store. Це досить закрита і секюрна система, до якої ми не маємо доступу.

Що важливо перевірити на першому етапі:
- що наші юзери взагалі отримують коректні id продуктів для конкретного сегменту
- обробку кейсу, коли сервер недоступний і не може повернути вам продукти
- коли приходять биті id продуктів
- що UI вашого пейволу відповідає усім нормам гайдлайнів для рев’ю в сторах
2. Створення ордеру на нашому сервері
Після того, як юзер тапнув «Купити», мобільний клієнт надсилає запит на створення ордера на наш сервер.
Тут перевіряємо:
- коректне створення ордеру
- особливу увагу приділяємо різним негативним сценаріям, коли сервер недоступний, таймаути, дабл тапи, переривання інтернет з’єднання і т.д

3. Ініціація покупки
Після того, як клієнт отримав від серверу респонз, що ордер створений , наш клієнт ініціює покупку вже на стороні стору
- Юзер підтвердує покупку через системне вікно оплати
- Стор, по своїм внутрішнім махінаціям цю покупку проводить
- І повертає клієнту чек про оплату
При тестуванні цього третього етапу оплати варто звернути увагу саме на обробку помилок, інтерапшени, кейс, якщо юзер не підтвердить покупку і т.д

Що таке цей чек про оплату і з чого він складається?
В мобільних додатках використовуються окремі SDK від Apple та Google, через які додаток працює з платіжним флоу.
В iOS — це StoreKit, в Android — це Google Play Billing Library.
Так от, наш додаток через ці SDK отримує від стору результат транзакції, в якому міститься інформація про покупку.
Ось тут детальніше можна ознайомитись з Unified App Receipt (StoreKit 1), JWS Transaction (StoreKit 2) та Android Purchase Token.
Також додаю класні статті від колег про відмінності StoreKit 1 та StoreKit 2:
Тестування In-App Purchases на iOS: гайд для QA з реального досвіду
StoreKit 1 vs StoreKit від розробника
4. Валідаіця чеку про покупку
Для чого його валідувати? Щоб перевірити, що покупка справжня і актуальна, і тільки тоді видати доступ до преміум-фіч. Робити валідацію на клієнті можна, але не потрібно, тому що це не секюрно і чек можна підтробити.
Отже який флоу валідації:
- Мобільний клієнт надсилає запит з цим чеком на наш сервер, що чек потрібно провалідувати
- Наш сервер через Server to Server нотифікації просить стор розшифрувати і провалідувати ресіпт
- Після отримання успішного статусу від стору, що з ресіптом все ок, наш бекенд на своїй стороні відкриває доступ до преміуму і присилає мобільному клієнту респонз, що доступ надано.
При тестуванні робимо важливий акцент на:
— перевірку на повторне використання чека — сервер не має надавати доступ юзеру, якщо чек був уже використаний для іншого,
— імітацію розриву з’єднання відразу після відправки чека — мобільні мережі нестабільні, і клієнт може надіслати запит на валідацію; він дійде до сервера, але відповідь від сервера не дійде до телефону через умовну відсутність зв’язку. Додаток має надіслати запит ще раз.

Як ми бачимо, на цьому етапі важливий акцент падає саме на спілкування нашого сервера з сервером store. Комунікація відбувається через Server-to-Server notifications. В iOS App Store Server Notifications, в Android — Real Time Developer Notification.
Як їх відстежити?
Оскільки логи бекенду часто недоступні для тестувальника або їх важко читати, найкраща практика — попросити розробників зробити для вас Webhook-канал або використовувати проміжні сервіси RevenueCat, Adapty, Qonversion (якщо ваш проєкт використовує ці системи).
5. Закриття транзакції
Після того, як застосунок отримав success від бекенду, йому потрібно «закрити» покупку, щоб транзакція не залишалася у підвішеному стані.
На iOS
Застосунок викликає системний метод завершення транзакції, який прибирає її з локальної черги та запобігає повторній доставці при наступних запусках.
На Android
Застосунок викликає різні системні методи підтвердження обробки покупки (залежно від типу товару), щоб Google зафіксував, що покупку опрацьовано — інакше вона може залишитися непідтвердженою або бути автоматично скасованою/поверненою через 3 дні.

Життєвий цикл підписок
Згадаємо, що в поновлюваних передплатах store автоматично стягує кошти за новий період.
Але не завжди це відбувається успішно. Протягом свого життєвого циклу підписка може переходити в різні стани. Їх досить багато, але ми поговоримо про основні, щоб зрозуміти загальну логіку:
- Initial Buy (Перша покупка): коли користувач уперше оформлює підписку, і після успішної транзакції йому надається доступ до фічі.
- Renewal (Успішне списання): коли підписка автоматично продовжується, бо черговий платіж пройшов успішно, а доступ лишається без перерви.
- Upgrade/Downgrade (Зміна плану): користувач переходить на інший план у межах групи підписок (вищий/нижчий), і змінюються умови доступу та білінгу.
Далі йдуть стани, в яких у користувача може підти щось не так, наприклад, проблеми з оплатою.
- Billing Retry / Grace Period (Не пройшла оплата, але доступ ще є): оплата не пройшла, але протягом певного часу стор ще намагається списати кошти, а користувач може тимчасово зберігати доступ (якщо ввімкнено grace/retry).
- Price Increase Consent (Погодитись на нову ціну): ціна підписки зросла, і користувач має підтвердити нову вартість, інакше продовження може зупинитися і підписка не оновиться.
Ну і стани, коли юзер з різних причин припиняє користуватися преміум-фічами.
- Тимчасове призупинення (Android Only): на Android користувач або система може «поставити на паузу» підписку на визначений період, після чого вона або відновиться, або перейде в інший стан залежно від статусу оплати.
- Voluntary Cancel (Вимкнув автоподовження): користувач сам скасував автопродовження, але підписка зазвичай лишається активною до кінця вже оплаченого періоду.
- Involuntary Cancel (Закінчилися спроби списання): стор не зміг стягнути оплату після всіх спроб, тому підписка припиняється і доступ зазвичай блокується після завершення поточного періоду/грейс-періоду.
- Refund (Примусове повернення грошей): платіж був повернутий користувачу, і доступ може бути відкликаний або переглянутий відповідно до правил платформи та вашої серверної логіки.
Restore
Уявімо ситуацію, коли юзер купив собі новий девайс, встановив наш додаток і хоче отримати придбаний доступ до фіч.
Для таких випадків існує флоу відновлення покупок.
Restore (відновлення покупок) — це процес, коли застосунок синхронізується зі стором і відновлює раніше придбані права для користувача після перевстановлення додатка.
Як це виглядає:
- Користувач натискає кнопочку Restore Purchases на paywall,
- Після цього мобільний застосунок запитує у Apple/Google історію покупок і отримує список активних чеків.
- Далі застосунок надсилає ці дані на бекенд, бекенд валідує їх через серверні API стору і оновлює доступ на своїй стороні та повертає застосунку статус успіху, після чого користувач бачить повідомлення «Successfully Restored» і преміум контент розблоковується.

На що звертати увагу при тестуванні Restore:
- перевіряти, що відновлюються тільки Non-consumable та підписки, витратні продукти не відновлюються
- обробка різних станів, коли success / nothing to restore / error
- дабл тапи
- переривання з’єднання, таймаути
Як підготуватись до тестування In-app purchases
1. Створення тестового юзера
Для тестування покупок в iOS вам потрібен Sandbox Tester — це спеціальний Apple ID, створений виключно для тестування, який дозволяє проходити purchase flow без реального списання коштів.
Коли ви ініціюєте покупку StoreKit автоматично визначає тип акаунту (Sandbox Apple ID) і перенаправляє запити на sandbox.itunes.apple.com
В Android використовується License Tester — це ваш реальний Gmail, який розробник додав у Allowlist у Google Play Console.
Google Play Billing Library перевіряє, чи знаходиться user ID у списку License Testers.
Якщо так — запит йде в обхід платіжного шлюзу, і покупка проходить, використовуючи тестову картку.
2. Test Subscription Duration
Варто знати, що тестові передплати мають скорочений термін дії та обмежену кількість автопролонгів.
В iOS підписка пролонгується 12 разів і на

В Android ліміт автоподовжень у тестовому середовищі становить 6 разів.

3. Інструменти для тестування та поради з практики
XCode — для симуляції покупок без підключення до серверів Apple (StoreKit Configuration Files, Transaction Manager) та відстеження логів.
Android Studio (Logcat) — відстеження логів.
Sniffers (Charles/Proxyman) — для емуляції повільного інтернету, помилок від сервера, підміни даних у реквестах/респонсах, переривання інтернету, тротлінг.
Google Play Billing Lab — офіційний додаток від Google для розробників та QA. Дозволяє змінювати країну магазину та тестувати складні сценарії (наприклад, зміну ціни).
Debug Menu (Clear Purchases) — попросіть розробників додати в дебаг-меню спосіб примусово очищати локальну чергу незавершених транзакцій (особливо актуально для iOS), так як під час тестування транзакції часто застрягають у черзі і заважають перевірці подальших флоу.
Міні-гайд по відтворенню статусів підписки — тут знайдете короткі підказки, як в iOS та Android відтворити різні статуси передплат, що може допомогти вам при створенні тест-кейсів.
Також, варто пам’ятати, що існує велика кількість різних девайсів з операційними системами, де немає Google Play Services (типу Huawei). В такому випадку бізнес має передбачити використання інших альтернативних способів оплати доступу до контенту.
Отже, процес тестування покупок — досить комплексний та багаторівневий процес, який включає в себе симбіоз досить складної бізнес-логіки та технічної реалізації.
Тому на етапі дизайну тестів, окрім розуміння технічних нюансів, вам дуже допоможе «одягти капці» юзера і подумати про різні життєві ситуації, в які він може потрапити, користуючись вашим застосунком.
Якщо виникнуть питання, чекатиму вас в коментарях!
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівДуже гарна і детальна стаття!
Крутий опис!) Дякую
Чудова стаття, можна використовувати як гайд для роботи з покупками
Ого ого ого 🤩
Графічна частина вражає☝🏼
Дуже крута стаття, дякую. Як раз мені актуально прям зараз.
Дуже гарний допис. Дякую
Ого, дуже детальна стаття. Дякую