Все статьи
Playbook8 мин чтения

Обработка возвратов ломается незаметно, поэтому протестируйте возвраты за встроенные покупки в sandbox раньше, чем это сделает реальный клиент

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

iPhone и телефон на Android под увеличительным стеклом на тёмном столе, символизирующие тестирование возвратов за встроенные покупки до того, как они станут реальными

Главное

  • Тестирование 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.

Источники и дополнительное чтение

RefundHalt

Автопилот возвратов для App Store и Google Play

Читайте дальше

Deep dive7 мин чтения

Возврат средств обходится вашему приложению дороже, чем цена, которую вы возвращаете

Возвращённая цена, это самая маленькая строка в счёте. Возврат отменяет и комиссию магазина, поэтому вы теряете свою долю, а вычисления, вызовы API, хранилище и выплаты, которые вы уже потратили, не вернуть. Возвратный платёж в Google Play после 3 августа 2026 года добавляет сверху банковскую комиссию. Вот полный счёт.

Deep dive8 мин чтения

Возвраты по подпискам работают не так, как возвраты за разовые покупки, и магазин, в котором вы находитесь, решает, сколько у вас будет права голоса

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

Следующий запрос на возврат уже в пути.

Настройте RefundHalt за то время, что уходит на чтение очередного письма в поддержку о возврате, который вы не успели оспорить.