Обработка возвратов ломается незаметно, поэтому протестируйте возвраты за встроенные покупки в sandbox раньше, чем это сделает реальный клиент
Ваша обработка возвратов запускается только после того, как клиент уже ушёл, поэтому ошибка в ней остаётся невидимой, пока не начнёт стоить реальных денег. Оба магазина позволяют сначала запустить возврат в тестовой среде. Вот как протестировать возвраты за встроенные покупки в App Store и Google Play до того, как возврат станет реальным.

Главное
- Тестирование StoreKit в Xcode позволяет вернуть покупку локально, нажав стрелку возврата в Transaction Manager. Это запускает слушатель Transaction.updates вашего приложения, но никогда не обращается к Apple, поэтому App Store Server Notification не отправляется.
- Чтобы протестировать серверную сторону у Apple, направьте sandbox-URL App Store Server Notifications V2 на ваш бэкенд: тогда возврат в sandbox доставит настоящий REFUND, а запрос на возврат доставит CONSUMPTION_REQUEST на ваш сервер.
- Эндпоинт Apple Request a Test Notification отправляет уведомление типа TEST на настроенный вами URL и возвращает testNotificationToken, поэтому вы можете убедиться, что ваш вебхук доступен, до того как сработает реальное событие.
- Sandbox у Apple никогда не повторяет неудавшееся уведомление, поэтому вебхук, который недоступен в момент отправки из sandbox, теряет событие без второй попытки. Это тот же класс промаха, который позже стоит вам реального окна возврата.
- Google Play даёт лицензионным тестировщикам способ оплаты Test card, approves then charges back, который запускает PendingRefundReviewNotification через несколько мгновений после покупки, так что вы можете отрепетировать свой 24-часовой ответ orders.reviewrefund.
- Для лицензионного тестировщика в Google Play неподтверждённая покупка автоматически возвращается через 3 минуты вместо 3 дней в продакшене, поэтому сломанный путь подтверждения даёт сбой быстро и заметно в тестировании.
- Обработчик возвратов, который вы никогда не тестировали, это тот, что оставляет платный доступ клиента, получившего возврат, активным. А с 3 августа 2026 года непротестированный ответ на чарджбэк в Google Play может стоить вам цены покупки за вычетом сервисного сбора Play плюс комиссия банка.
Ваша обработка возвратов это единственный путь кода, который запускается только после того, как клиент уже ушёл. Ничто в вашем обычном QA его не затрагивает, потому что чтобы до него добраться, вам нужно реально получить возврат. Поэтому он выходит непротестированным, тихо лежит месяцами, а затем даёт сбой на реальном возврате, где сбой стоит денег, а не красного теста. Решение в том, чтобы перестать считать возврат чем-то, что происходит с вами, и начать запускать его намеренно. И Apple, и Google позволяют запустить возврат в тестовой среде и посмотреть, как реагирует ваш сервер. Вот как протестировать возвраты за встроенные покупки в App Store и Google Play до того, как платящий клиент докажет, что ваш обработчик был сломан.
Три среды, в которых может сработать возврат, и только одна из них продакшн
Есть три отдельных места, где можно запустить возврат Apple или Google, пока вы разрабатываете, и они не взаимозаменяемы. Два из них вы можете запускать по требованию. Третье это продакшн, где вы никогда не хотите впервые встретить ошибку возврата. Ловушка в том, чтобы считать, что лёгкий вариант, локальное тестирование в Xcode, доказывает работу всего конвейера. Он доказывает работу вашего приложения. Он ничего не говорит о вашем сервере.
Тестирование StoreKit в Xcode локально, поэтому оно проверяет ваше приложение и ничего больше
Встроенное тестирование StoreKit в Xcode работает с конфигурационным файлом на вашем Mac, без обращения к Apple. Откройте StoreKit Transaction Manager из панели отладки, выберите оплаченную транзакцию и нажмите изогнутую стрелку возврата. Транзакция переключается на возвращённую, и слушатель Transaction.updates вашего приложения срабатывает точно так же, как это было бы в реальности. Вы также можете вызвать beginRefundRequest, чтобы показать настоящий лист возврата, и в среде Xcode выбранная вами проблема сопоставляется один к одному с RevocationReason, а возврат применяется немедленно. Это самый быстрый способ доказать, что ваш клиент отрезает доступ в тот момент, когда revocationDate становится не-nil. Это же и всё, что может сказать локальное тестирование, потому что здесь ничто никогда не достигает серверов Apple, поэтому App Store Server Notification не отправляется. Ваш бэкенд не узнаёт ничего.
Sandbox это место, где ваш сервер наконец узнаёт о возврате
Чтобы протестировать ту половину вашей интеграции, которая решает про деньги, ваш сервер, вам нужен sandbox Apple. Настройте sandbox-URL App Store Server Notifications V2 в App Store Connect, войдите под sandbox-тестировщиком на устройстве и совершите покупку. Теперь возврат в sandbox доставляет настоящее уведомление REFUND на ваш бэкенд, а запрос на возврат по расходуемой или автообновляемой покупке доставляет CONSUMPTION_REQUEST, тот же подписанный payload, который получит ваш продакшн-сервер. Прежде чем что-либо запускать, вызовите эндпоинт Request a Test Notification. Он говорит серверу App Store отправить уведомление типа TEST на настроенный вами URL и выдаёт вам testNotificationToken, который вы передаёте в Get Test Notification Status, чтобы подтвердить доставку. Если этот цикл не работает, реальное уведомление тоже не сработает.
| Среда | Что она может запустить | Что она доказывает | Что она не может |
|---|---|---|---|
| Тестирование StoreKit в Xcode | Возврат через Transaction Manager или лист beginRefundRequest | Ваше приложение реагирует на возврат локально, за секунды | Никогда не обращается к Apple, поэтому серверное уведомление не отправляется |
| Sandbox | Настоящие REFUND и CONSUMPTION_REQUEST на ваш сервер, плюс уведомление TEST по требованию | Ваш бэкенд получает, проверяет и действует по подписанному payload | Не повторяет уведомление, которое ваш эндпоинт не смог получить |
| Продакшн | Каждый возврат, за реальные деньги | Ничего, что вы хотели бы узнать здесь первым | Вы не можете отменить стоимость ошибки |
Как протестировать возвраты за встроенные покупки в App Store
Выполняйте в таком порядке, от дешёвой проверки клиента до полного серверного цикла. Каждый шаг проверяет свою часть, и именно за поздние шаги продакшн реально выставляет вам счёт.
- Создайте ключ In-App Purchase в разделе Users and Access, Integrations, In-App Purchase в App Store Connect и используйте его для подписи ваших вызовов App Store Server API.
- Направьте ваш sandbox-URL App Store Server Notifications V2 на ваш бэкенд, затем вызовите Request a Test Notification и убедитесь, что payload
TESTприходит и проверяется по цепочке сертификатов Apple. - В Transaction Manager Xcode верните покупку и убедитесь, что ваше приложение снимает право доступа в тот момент, когда установлен
revocationDate. - Войдите под sandbox-тестировщиком, купите расходуемый товар, запросите возврат и убедитесь, что ваш сервер получает
CONSUMPTION_REQUESTи может собрать и отправить ответ Send Consumption Information с хорошим запасом внутри 12-часового окна. - Верните покупку в sandbox и убедитесь, что уведомление
REFUNDдостигает вашего сервера, что вы отзываете доступ или списываете баланс расходуемого товара и что повторная доставка того же уведомления не применяется дважды.

Как отрепетировать возврат и чарджбэк в Google Play
У Google Play нет локального режима, как у Xcode. Всё работает с серверами Google, но лицензионные тестировщики делают это бесплатно и безопасно. Добавьте ваши тестовые аккаунты Google как лицензионных тестировщиков в Play Console, и они получат набор тестовых способов оплаты, которые никогда не списывают реальные деньги. Google помечает каждую тестовую покупку уведомлением посреди диалога покупки, и налоги не рассчитываются. Для тестирования возвратов важно, какой тестовый инструмент вы выберете, потому что каждый из них ведёт к своему исходу.
| Тестовый способ оплаты | Что он имитирует | Зачем его использовать |
|---|---|---|
| Test instrument, always approves | Чистая успешная покупка | Создать заказ, который затем можно вернуть или отозвать |
| Test instrument, always declines | Неудавшийся платёж | Убедиться, что при отклонении вы ничего не выдаёте |
| Slow test card, approves after a few minutes | Ожидающая покупка, которая позже проходит успешно | Проверить обработку PENDING до выдачи доступа |
| Slow test card, declines after a few minutes | Ожидающая покупка, которая позже проваливается | Убедиться, что ожидающее отклонение никогда не выдаёт право доступа |
| Test card, approves then charges back | Чарджбэк, инициированный пользователем | Запустить PendingRefundReviewNotification и отрепетировать ваш 24-часовой ответ |
Запустите возврат, чарджбэк и автовозврат по подтверждению
- Купите с помощью тестовой карты approve-then-charge-back, и
PendingRefundReviewNotificationпридёт в вашу тему Real-time Developer Notifications мгновениями позже. Ответьте на него одним вызовомorders.reviewrefund, потому что Google хранит только ваш первый ответ. - Верните и отзовите тестовый заказ во вкладке Orders в Play Console, чтобы запустить
VoidedPurchaseNotification, и убедитесь, что ваш сервер снимает право доступа. - Намеренно оставьте покупку лицензионного тестировщика неподтверждённой. Google автоматически возвращает её через 3 минуты вместо 3 дней, которые допускает продакшн, и присылает вам письмо об отмене, так что сломанный путь подтверждения проявляется за минуты, а не на четвёртый день в продакшене.
Во что на самом деле обходится непротестированный путь возврата
Обработчик возвратов это не украшение. Это код, который останавливает трату на обслуживание того, кто вам больше не платит. Когда он даёт сбой тихо, возврат всё равно проходит, но доступ, баланс и траты за ними не прекращаются.
Проследите за деньгами. Когда Apple или Google возвращает покупку, вы возвращаете цену продажи, а магазин возвращает свою комиссию, и пока что баланс ровный. Что не возвращается, так это всё, что вы уже потратили на доставку продукта: вычисления за сгенерированным результатом, вызовы model API, хранилище для того, что сохранил пользователь, выплата, которую вы уже отправили автору. Обработчик возвратов, который никогда не отзывает доступ, позволяет получившему возврат пользователю продолжать тратить это за ваш счёт, и в системе не остаётся ничего, что бы его отрезало.
Два окна доказательств делают это резче. CONSUMPTION_REQUEST, который вы никогда не отрабатывали в sandbox, это ответ, который вы отправите некорректным или с опозданием, и Apple часто предоставляет возврат по умолчанию, когда ваш ответ не приходит внутри 12 часов. Ответ на чарджбэк в Google Play, который вы никогда не запускали с тестовой картой, это 24-часовое окно, которое вы упускаете вживую, и с 3 августа 2026 года проигранный чарджбэк в Play стоит вам цены покупки за вычетом сервисного сбора Play плюс комиссия банка за чарджбэк. Каждый из этих сбоев воспроизводим бесплатно в тестовой среде сначала. Ни один из них не дёшев в продакшене.
| Непротестированный путь | Как он даёт сбой в продакшене | Во что он вам обходится |
|---|---|---|
| Обработчик REFUND | Получивший возврат пользователь сохраняет доступ | Вычисления, вызовы API, хранилище и выплаты, которые вы продолжаете на него тратить |
| Ответ CONSUMPTION_REQUEST | Некорректный или отправленный после 12 часов | Apple предоставляет возврат по умолчанию, поэтому вы теряете продажу и траты |
| Ответ orders.reviewrefund | Пропущен или неверен внутри 24 часов | С 3 августа 2026 года, цена покупки за вычетом сервисного сбора Play плюс комиссия банка за чарджбэк |
Короткий чек-лист перед тем, как выпускать обработку возвратов
Вам не нужна лаборатория. Вам нужно один раз увидеть, как каждое событие доходит до вашего кода.
- Ваше приложение снимает доступ в тот момент, когда транзакция StoreKit показывает
revocationDate, подтверждено в Transaction Manager Xcode. - Ваш sandbox-URL сервера получает уведомление
TESTи проверяет его по сертификатам Apple. - Sandbox
REFUNDотзывает доступ или списывает баланс, а повторная доставка не считается дважды. - Sandbox
CONSUMPTION_REQUESTпорождает валидный ответ Send Consumption Information с хорошим запасом внутри 12 часов. PendingRefundReviewNotificationот Google с тестовой карты чарджбэка порождает ровно один вызовorders.reviewrefund.- Неподтверждённая тестовая покупка в Google Play автоматически возвращается за 3 минуты, и ваша сверка это замечает.
Пройдите этот список один раз, и обработка возвратов перестанет быть кодом, который вы надеетесь работает. Она станет кодом, который вы видели работающим.
Частые вопросы
- Могу ли я протестировать возврат в App Store без реальной покупки?
- Да. Тестирование StoreKit в Xcode позволяет вернуть покупку локально через Transaction Manager, без реальных денег и без аккаунта App Store, что запускает слушатель Transaction.updates вашего приложения. Оно не отправляет серверное уведомление, поэтому тестирует только ваше приложение, а не ваш бэкенд.
- Отправляет ли локальное тестирование StoreKit App Store Server Notifications?
- Нет. Тестирование StoreKit в Xcode работает целиком на вашем Mac с локальной конфигурацией и никогда не обращается к серверам Apple, поэтому App Store Server Notification, включая REFUND или CONSUMPTION_REQUEST, никогда не отправляется. Используйте sandbox, чтобы протестировать ваш сервер.
- Как мне протестировать ответ на чарджбэк в Google Play?
- Используйте способ оплаты лицензионного тестировщика Test card, approves then charges back. Он запускает PendingRefundReviewNotification через мгновения после покупки, то же уведомление, что отправляет настоящий чарджбэк банка, так что вы можете отрепетировать ваш 24-часовой ответ orders.reviewrefund.
- Почему моя тестовая покупка в Google Play возвращается через несколько минут?
- Для лицензионных тестировщиков Google автоматически возвращает покупку через 3 минуты, если ваше приложение её не подтвердило, и присылает вам письмо об отмене. Продакшн ждёт 3 дня, но тестировщики получают ускоренную версию, так что сломанный путь подтверждения проявляется быстро.
- Повторяет ли sandbox Apple неудавшееся уведомление о возврате?
- Нет. Sandbox не повторяет App Store Server Notifications, поэтому если ваш эндпоинт недоступен, когда срабатывает возврат в sandbox, уведомление теряется без второй попытки. Сначала убедитесь, что ваш URL доступен, с помощью Request a Test Notification.
Источники и дополнительное чтение
- Apple Developer: Testing refund requests
- Apple Developer: Testing App Store server notifications
- Apple Developer: Request a Test Notification (App Store Server API)
- Apple Developer: Testing In-App Purchases with the sandbox
- Android Developers: Test your Google Play Billing Library integration
- Android Developers: Help Google dispute chargebacks (orders.reviewrefund)
- Play Console Help: Updates to refund protection and chargeback cost responsibility
RefundHalt
Автопилот возвратов для App Store и Google Play
Читайте дальше
Возврат средств обходится вашему приложению дороже, чем цена, которую вы возвращаете
Возвращённая цена, это самая маленькая строка в счёте. Возврат отменяет и комиссию магазина, поэтому вы теряете свою долю, а вычисления, вызовы API, хранилище и выплаты, которые вы уже потратили, не вернуть. Возвратный платёж в Google Play после 3 августа 2026 года добавляет сверху банковскую комиссию. Вот полный счёт.
Возвраты по подпискам работают не так, как возвраты за разовые покупки, и магазин, в котором вы находитесь, решает, сколько у вас будет права голоса
Возврат по подписке отменяет целый расчётный период, а не отдельную продажу. В App Store решение принимает Apple, а ваш сервер лишь узнаёт результат. В Google Play вы сами выбираете полный или пропорциональный возврат. Вот как каждый магазин обрабатывает возвраты по подпискам и во что вам обходится один такой возврат.