mobile-qaiap

Тестирование In-App Purchase в мобильной игре: 9 разделов и чек-лист

IAP — самый дорогой сегмент кода для бага. Один пропущенный кейс «деньги списались, но товар не пришёл» — это support-тикеты, рефанды, плохие отзывы и удержание под угрозой. И при этом IAP покрытие...

IAP — самый дорогой сегмент кода для бага. Один пропущенный кейс «деньги списались, но товар не пришёл» — это support-тикеты, рефанды, плохие отзывы и удержание под угрозой. И при этом IAP покрытие тестами в моб. играх обычно слабее всего: «работает на моей машине» в режиме sandbox — норма. Полный гайд как тестировать.

Почему IAP сложнее обычного API

  • Транзакция идёт через посредника (App Store / Play Store), а не напрямую в ваш бекенд. Запрос → платформа → ваш код → ваш сервер → подтверждение → выдача товара. Любое звено может сломаться отдельно.
  • Receipt-валидация на сервере — критично, но часто пропускается. Без неё клиент может «подделать» успешную покупку.
  • Восстановление покупок (restore) — отдельная и часто забываемая ветка кода. Юзер сменил девайс, сбросил прогресс, переустановил приложение.
  • Подписки имеют свой жизненный цикл: grace period, billing retry, upgrade/downgrade с проратированием, family sharing, paused subscriptions.
  • Регион и валюта влияют на цену, налоги, доступность. То что работает в US-сторе может не работать в Турции или Индии.

1. Setup: sandbox и тестовые аккаунты

iOS

  • App Store Connect → Users and Access → Sandbox Testers — создайте отдельные тестовые Apple ID. Никогда не используйте свой реальный.
  • На устройстве: Settings → App Store → Sandbox Account → залогиниться тестовым ID. Не выходите из своего основного — sandbox-логин отдельно от Apple ID устройства.
  • Если в приложении используется StoreKit 2 — sandbox-режим работает без сепаратного account-логина при запуске из Xcode. Это удобнее для разработки, но QA-флоу из TestFlight всё равно через Sandbox Tester.

Android

  • Google Play Console → Setup → License testing — добавьте Google-аккаунты тестировщиков. Эти аккаунты увидят тестовые цены (₽0) и могут пройти полный flow.
  • Для тестирования нужны: closed/internal/open testing track в Play Console, тестовый аккаунт добавлен в track, приложение скачано с Play Store через ссылку track’а.
  • Важно: IAP не работают через adb install — только через установку с Play. Без этого Billing вернёт BILLING_UNAVAILABLE.

2. Functional: основные сценарии

Минимальный набор кейсов, который должен пройти каждый IAP в продукте:

  • Покупка успешна: тап → подтверждение → товар добавлен в баланс/инвентарь → событие аналитики ушло → purchase_token / transaction_id залогирован.
  • Пользователь отменил платёжный диалог: возврат в нормальное состояние, никаких частичных эффектов, не списано ни баллов ни денег.
  • Двойной тап на кнопку «Купить»: запрос отправлен только один раз. Многократный grant товара — классический баг.
  • Приложение убито посреди транзакции: после релонча приложение видит pending-транзакцию, завершает её, выдаёт товар.
  • Покупка с offline-стартом: нет сети → кнопка отключена или показывает понятную ошибку. Не пустой диалог StoreKit без объяснений.

3. Edge cases: сетевые сбои

Самый «грязный» класс багов — когда платёж прошёл у платформы, но клиент об этом не узнал. Проверять симуляцией:

  • Network drop после оплаты: покупка ушла в App Store / Play, ваш сервер про неё не знает. Платформа удерживает receipt — приложение должно поднять её при следующем старте и грантнуть товар.
  • Server 500 при receipt-валидации: клиент получил receipt, отправил на ваш бекенд, бекенд упал. Retry-логика? Сколько раз? Через какие интервалы? Сохраняется ли receipt локально для повтора?
  • Slow network (10 sec timeout): platform отдала receipt через 8 секунд после тапа. Loading-индикатор не залип? Не показалась ли ошибка «timeout» раньше времени? Используйте Network Conditioner — 3G / packet loss / high latency.
  • App в фоне во время оплаты: платёжный диалог вылез поверх — юзер свернул приложение. Возврат через минуту — состояние корректное.

4. Restore Purchases

Эта кнопка обязательна для App Store review. Тестируют её редко. Самые частые баги тут.

  • Restore после reset устройства: купили No-Ads → сбросили девайс → переустановили игру → залогинились тем же Apple ID → тап Restore → No-Ads должен включиться. Без покупок повторно.
  • Restore с другим Apple ID: купили на ID-A → залогинились на ID-B → Restore. Не должно ничего восстановиться, должна быть понятная ошибка.
  • Restore без покупок: никогда ничего не покупал → тап Restore → не падает, показывает «No purchases to restore».
  • Двойной Restore: два тапа подряд → один запрос на сторе, не двойной grant.
  • Restore для consumable: consumable-покупки (монеты, gems) не должны восстанавливаться через Restore. Только non-consumable и subscriptions. Логика: купил 100 coins → потратил → Restore не должен вернуть 100 монет обратно.

5. Подписки: свой зоопарк сценариев

Если игра продаёт подписку (Battle Pass, VIP, No-Ads-monthly) — тестировать в разы больше:

  • Auto-renew: активная подписка возобновилась автоматически — клиент это узнал? UI показывает корректный статус? В sandbox iOS подписка возобновляется на ускоренном таймлайне (5 минут вместо месяца).
  • Upgrade / Downgrade: переход с Monthly на Yearly или наоборот. В Play Console — четыре режима proration: ImmediateWithTimeProration, Charge prorated, Without proration, Deferred. Проверьте, какой у вас выставлен, и что юзер получает ровно то что задокументировано.
  • Grace period: подписка экспирировала, оплата провалилась → платформа даёт 16 дней grace period на retry. Юзер должен в это время продолжать иметь доступ ко всему premium.
  • Billing retry: iOS / Android самостоятельно пробуют списать ещё раз. UI должен показать «Payment issue» с deep-link на Subscriptions settings.
  • Cancel mid-period: юзер отменил подписку через Settings App Store → доступ должен сохраниться до конца оплаченного периода, а не сразу пропасть.
  • Refund: юзер получил refund через support → ваш сервер получит REFUND server notification → надо отозвать grant. Часто пропускают.
  • Family Sharing: если включён — подписка одного члена семьи доступна всем. Receipt в этом случае приходит на каждом устройстве, с тем же original_transaction_id.

6. Receipt validation: security

Если ваш сервер не валидирует receipt у Apple / Google — у вас нет IAP. Кто угодно может попросить локальный код «выдай мне товар» и оно сработает. Что должен делать сервер:

  • Получить от клиента receipt (iOS) или purchaseToken (Android).
  • Отправить на verifyReceipt endpoint Apple или purchases.products.get API Google. Apple guide, Google guide.
  • Проверить: status (валидно ли), bundle_id (наше ли приложение), product_id (тот ли товар), transaction_id (не повторный ли — для consumable важно).
  • Только после успешной валидации — гранить товар в БД пользователя. И только тогда отвечать клиенту OK.

QA-проверка: попросите разработчика подменить ответ платформы (через Proxyman Map Local) на «success» с поддельным receipt → ваш бекенд должен отказать в выдаче товара. Если выдал — security-бомба.

7. Локализация цен и регионов

  • Цена с правильным символом валюты и форматом: ₽299 (РФ), 2,99 € (Германия), $2.99 (US), R$ 14,90 (Бразилия). Не хардкодьте «$» — берите localizedPriceString (iOS) / formattedPrice (Android Billing 5).
  • Регион VPN: если юзер залогинен в US App Store, но физически в RU — цена должна показываться по US-стору, не по геолокации. Иначе UX-баг.
  • Доступность: проверьте, что товар активен во всех target-странах. В App Store Connect — Pricing and Availability → list of countries. В Play Console — Pricing → Countries / regions.
  • Tax behaviour: в некоторых регионах налог включён, в некоторых добавляется отдельно. price в API может быть pre-tax или post-tax. Сверяйтесь со сторой, а не считайте сами.

8. Аналитика и серверная сверка

Покупка — это четыре события, которые должны сойтись:

  • Клиент послал purchase_attempted event.
  • Платформа подтвердила платёж → клиент шлёт purchase_completed с suma, валютой, product_id.
  • Сервер валидировал → шлёт со своей стороны purchase_validated (на BI).
  • Apple/Google присылают S2S-нотификацию → сервер сверяет с тем, что было на клиенте.

Если одно из событий пропускается — у вас «дыра» в воронке. Проверяйте через Proxyman + Amplitude / Firebase real-time view одновременно.

9. Common bugs из практики

  • Double-grant — товар выдан дважды (например, после восстановления pending-транзакции). Причина: нет идемпотентности по transaction_id на сервере.
  • Lost purchase — юзер заплатил, товар не пришёл, support вытаскивает руками. Причина: receipt не дошёл до сервера (network drop), pending не обработан при следующем релонче.
  • Stuck loading spinner — после тапа на Buy показывается loading и не сходит. Причина: не повешено timeout на StoreKit-promise, нет error handling.
  • Wrong product price — UI показывает $4.99, а списывается $9.99. Причина: цена хардкоднута, а не берётся из StoreKit SKProduct.price.
  • Grant без оплаты — взломанный клиент шлёт «куплено» на бекенд без receipt → товар выдаётся. Причина: нет server-side валидации.
  • Restore не работает на новом девайсе — переехал на новый iPhone, Restore не возвращает No-Ads. Причина: бекенд хранит entitlement по device-id, а не по Apple ID.
  • Subscription “flicker” — UI на 1 секунду показывает Free, потом Premium, потом снова Free. Причина: гонка между локальным receipt и server-validated state.

Чек-лист на каждый IAP

  • Sandbox-аккаунт работает, покупка проходит, событие аналитики ушло
  • Отмена платёжного диалога не имеет побочных эффектов
  • Double-tap не приводит к double-grant
  • Network drop посреди транзакции — pending обрабатывается при релонче
  • Restore работает: возвращает non-consumables и subscriptions, не возвращает consumables
  • Receipt валидируется на сервере, не на клиенте
  • Цена показывается локализованной (валюта + формат)
  • Для подписок: auto-renew, cancel, grace period, refund — все проверены
  • Server S2S notifications обрабатываются (REFUND, CANCEL, RENEW)
  • Аналитика идёт на все события воронки
  • Идемпотентность по transaction_id на сервере — повторный receipt не даёт повторного гранта

Полезные ссылки