Ваше приложение может показать лист запроса возврата прямо внутри, и вот что Apple делает после того, как клиент нажимает «Отправить»
Встроенный запрос возврата Apple позволяет клиенту попросить возврат, не выходя из приложения, на листе, который Apple создаёт и рассматривает. Вот что возвращает beginRefundRequest, какие часы CONSUMPTION_REQUEST и 48 часов он запускает на вашем сервере, и стоит ли выпускать эту кнопку.

Главное
- beginRefundRequest от Apple, это метод StoreKit 2, который показывает собственный лист возврата Apple внутри вашего приложения. Клиент видит детали своей покупки и список кодов причин, выбирает один, и запрос уходит в Apple. Вы не создаёте форму и не решаете исход.
- Вызов возвращает статус success или userCancelled, либо бросает duplicateRequest или failed. Статус success означает, что App Store получил запрос, а не одобрил его. Никогда не показывайте подтверждённый возврат в своём интерфейсе при success.
- После того как клиент отправляет запрос, Apple берёт до 48 часов на одобрение или отказ. Для расходуемых покупок она сначала присылает CONSUMPTION_REQUEST на ваш сервер, и у вас есть обычные 12 часов, чтобы ответить данными об использовании, если клиент дал согласие.
- Исход приходит на ваш сервер как App Store Server Notification, тот же поток, который вы уже получаете. Одобрение, это уведомление REFUND, отказ, это REFUND_DECLINED. Встроенный запрос попадает в этот поток ровно так же, как возврат, начатый на странице reportaproblem от Apple.
- Кнопка доступна начиная с iOS 15 и iPadOS 15, Mac Catalyst 15 и visionOS 1, так что любое приложение, нацеленное на эти версии, может показать её уже сегодня.
- Финансовый довод в том, что возврат, который вы можете оспорить, лучше chargeback, который вы оспорить не можете. Удерживая клиента внутри процесса Apple, вы запускаете CONSUMPTION_REQUEST, на который можно ответить, вместо банковского chargeback, который окончателен и несёт комиссию.
- Рекомендация Apple по размещению, это вызывать её из настроек аккаунта или меню помощи, а не с экрана покупки, чтобы недовольный клиент нашёл её, а вы не рекламировали возвраты всем остальным.
Apple позволяет клиенту запросить возврат, ни разу не выходя из вашего приложения. Один вызов StoreKit, beginRefundRequest, показывает собственный лист возврата Apple прямо внутри вашего интерфейса, клиент выбирает причину, и запрос уходит в Apple на рассмотрение. Вы не создаёте форму, вы не касаетесь денег и вы не решаете исход. Что вы получаете, это способ разместить путь к возврату там, где расстроенный клиент уже находится, вместо того чтобы потерять его в пользу его банка. Это встроенный запрос возврата, и его стоит понять, прежде чем решать, выпускать ли кнопку.
Вот часть, которая важна для вашей выручки. Кнопка сама по себе ничего не возвращает. Она открывает запрос, Apple берёт до 48 часов на одобрение или отказ, а для расходуемых покупок сначала отправляет CONSUMPTION_REQUEST на ваш сервер. Так что этот лист не подарок. Это воронка в тот самый разбор возврата, на который вы уже можете влиять, и он способен вытащить спор из платёжной системы до того, как тот станет chargeback, который вы оспорить не сможете.
Что на самом деле представляет собой лист встроенного запроса возврата
beginRefundRequest, это метод StoreKit 2, который показывает лист запроса возврата для транзакции в оконной сцене. Сигнатура короткая: func beginRefundRequest(in scene: UIWindowScene) async throws -> Transaction.RefundRequestStatus. Когда вы вызываете его, система показывает лист с деталями покупки клиента и списком кодов причин на выбор. Этот интерфейс создаёт и контролирует Apple. Вы передаёте сцену и транзакцию, больше ничего.
Рекомендация Apple о том, куда её поместить, дана прямо. Вызывайте эту функцию из настроек аккаунта или меню помощи, чтобы клиент, желающий возврата, нашёл её там, где стал бы искать поддержку. Она вышла в iOS 15 и iPadOS 15, Mac Catalyst 15 и visionOS 1, так что любое приложение, нацеленное на эти версии, может показать её уже сегодня.
Два способа открыть лист
Есть две точки входа. Вы можете вызвать beginRefundRequest(in:) для конкретной транзакции, которая уже у вас есть, что ограничивает лист этой одной покупкой. Вы также можете открыть лист по идентификатору продукта, когда хотите, чтобы клиент вернул покупку конкретного продукта. В любом случае лист, список причин и решение принадлежат Apple. Ваша работа заканчивается на показе листа и чтении результата.
Что возвращает вызов и что может пойти не так
Метод помечен async throws, так что он либо возвращает статус, либо бросает ошибку. Оба списка короткие, и оба стоит обработать, чтобы ваш интерфейс говорил правду после закрытия листа.
| Результат | Тип | Что это значит |
|---|---|---|
| success | RefundRequestStatus | App Store получил запрос на возврат. Он отправлен, а не одобрен |
| userCancelled | RefundRequestStatus | Клиент закрыл лист, не отправив запрос. Ничего не ушло |
| duplicateRequest | RefundRequestError | У App Store уже есть запрос на возврат по этой покупке |
| failed | RefundRequestError | Сама отправка не удалась. Дайте клиенту попробовать снова |
Что происходит на вашем сервере после того, как клиент нажимает «Отправить»
Закрытие листа, это начало процесса, а не конец. Apple рассматривает запрос и берёт до 48 часов на одобрение или отказ. Для расходуемой встроенной покупки, прежде чем решить, App Store отправляет уведомление CONSUMPTION_REQUEST на ваш сервер, запрашивая данные об использовании. Если клиент согласился делиться этими данными, вы отвечаете через эндпоинт Send Consumption Information. Если он не согласился, собственная инструкция Apple, это вообще не отвечать на уведомление.
Как только Apple выносит решение, исход приходит на ваш сервер как App Store Server Notification. Это тот же поток, который вы уже получаете, и встроенный запрос попадает в него ровно так же, как возврат, который клиент начинает на странице reportaproblem от Apple. В обработке ничего не меняется только оттого, что запрос начался внутри вашего приложения.
| Этап | Что срабатывает | Ваше действие | Часы |
|---|---|---|---|
| Клиент отправляет лист | beginRefundRequest возвращает success | Зафиксируйте это, покажите «в ожидании», а не «возвращено» | Мгновенно |
| Только расходуемые, Apple спрашивает первой | Уведомление CONSUMPTION_REQUEST | Отправьте данные о потреблении, если клиент согласился, иначе молчите | 12 часов на ответ |
| Apple одобряет | Уведомление REFUND | Отзовите право доступа по этой транзакции | До 48 часов на решение |
| Apple отказывает | Уведомление REFUND_DECLINED | Сохраните продажу, ничего не меняйте | До 48 часов на решение |

Во что вам обходится эта кнопка и что она может сберечь
Возврат, который вы можете оспорить, лучше chargeback, который вы оспорить не можете
Клиент, который не может найти путь к возврату внутри вашего приложения, не сдаётся. Он идёт в свой банк. Chargeback по карте окончателен с банком, несёт комиссию за спор и забирает решение из ваших рук и из рук Apple. Встроенный запрос возврата удерживает того же клиента внутри системы Apple, где расходуемая покупка запускает CONSUMPTION_REQUEST, на который можно ответить, и решение, на которое можно повлиять. Обмен неоспоримого chargeback на оспоримый разбор Apple, это и есть весь финансовый довод в пользу кнопки.
Вы снижаете трение на пути к возврату
Честный противовес в том, что заметный путь к возврату в одно нажатие порождает больше запросов на возврат, чем закопанный адрес поддержки. Часть из них никогда бы не случилась. Это реальная цена, и именно поэтому Apple советует размещать точку входа в настройках аккаунта или меню помощи, а не на экране покупки. Вы хотите, чтобы её нашёл уже недовольный клиент, а не тот, кому просто любопытно.
Цена, которая идёт всё это время, это обслуживание возвращённого аккаунта
Как бы ни решилось дело, счётчик доставки продолжает крутиться, пока вы не отреагируете на исход. Каждый час, что возвращённое право доступа остаётся активным, вы продолжаете платить реальные издержки за ним: вычисления, вызовы API моделей, хранилище и любые выплаты автору или партнёру, привязанные к использованию этого клиента. Встроенный запрос этого не меняет. Быстрый отзыв по уведомлению REFUND меняет. Кнопка дёшева ровно настолько, насколько хороша ваша обработка уведомления, которое она в итоге порождает.
Стоит ли выпускать встроенный запрос возврата
Поместите её туда, где живёт поддержка, а не туда, где живут продажи
Следуйте рекомендации Apple по размещению. Настройки аккаунта и меню помощи, это правильные дома. Ссылка на возврат рядом с пейволом приучает людей ждать возврата денег и приглашает тот любопытный возврат, который вам вовсе не нужно было предлагать.
Проверьте весь путь в песочнице, прежде чем ему доверять
Вы можете смоделировать весь путь в песочнице и в тестировании StoreKit в Xcode, переводя запрос из «в ожидании» в «одобрено» или «отклонено». Одобрение доставляет уведомление REFUND на ваш сервер, а отказ доставляет REFUND_DECLINED, так что вы можете доказать, что ваш обработчик реагирует правильно, ещё до того, как реальный клиент нажмёт «Отправить».
Обрабатывайте каждый результат и никогда не преувеличивайте
Показывайте «в ожидании» при success, предлагайте повтор при failed, говорите, что ничего не изменилось, при userCancelled и относитесь к duplicateRequest как к тихой заметке о том, что прежний запрос клиента ещё в силе. Единственная ошибка, которая вредит, это сказать клиенту, что его возврат завершён, когда всё, что у вас есть, это отправленный запрос.
Как RefundHalt разбирается с последствиями
Встроенный лист принадлежит Apple. То, что идёт после него, принадлежит вам, и это та часть, которую ведёт RefundHalt. Когда клиент отправляет возврат изнутри вашего приложения, RefundHalt перехватывает CONSUMPTION_REQUEST для расходуемых покупок и отвечает на него в окне 12 часов теми доказательствами использования, которые помогают Apple решить. Когда Apple выносит решение, он отзывает право доступа по REFUND и оставляет доступ нетронутым по REFUND_DECLINED, каждое привязано к конкретной транзакции. Вы получаете возможность предложить более дружелюбный встроенный путь к возврату, не оставляя разбор, доказательства и отзыв на ручную суету.
Частые вопросы
- Что делает beginRefundRequest?
- Он показывает лист запроса возврата от Apple внутри вашего приложения для конкретной транзакции. Клиент видит детали своей покупки и список кодов причин, выбирает один, и запрос уходит в Apple. Метод возвращает статус success или userCancelled, либо бросает duplicateRequest или failed. Саму покупку он не возвращает, потому что Apple рассматривает запрос и берёт до 48 часов на решение.
- Возвращает ли встроенный запрос деньги сразу?
- Нет. Результат success означает, что App Store получил запрос, а не одобрил его. Apple берёт до 48 часов на одобрение или отказ, а для расходуемых сначала запрашивает у вашего сервера данные об использовании через уведомление CONSUMPTION_REQUEST. Показывайте клиенту состояние «в ожидании» при success, никогда не подтверждённый возврат.
- Какая версия iOS поддерживает встроенный запрос возврата?
- iOS 15 и iPadOS 15, Mac Catalyst 15 и visionOS 1. Метод StoreKit 2 beginRefundRequest(in:) доступен начиная с этих версий, так что любое приложение, нацеленное на iOS 15 или новее, может показать лист возврата Apple изнутри приложения.
- Куда мне поместить встроенную кнопку возврата?
- Рекомендация Apple, это вызывать её из настроек аккаунта или меню помощи, а не с экрана покупки или пейвола. Так путь к возврату оказывается там, где недовольный клиент ищет поддержку, и при этом вы не рекламируете возвраты клиентам, которые и не собирались о нём просить.
- Встроенный возврат лучше, чем обращение клиента в его банк?
- Обычно да, для вашей выручки. Chargeback банка окончателен и несёт комиссию, и он убирает из решения и Apple, и вас. Встроенный запрос возврата удерживает клиента в процессе Apple, где расходуемая покупка запускает CONSUMPTION_REQUEST, на который можно ответить, и разбор, на который можно повлиять. Оспоримый возврат лучше неоспоримого chargeback.
Источники и дополнительное чтение
- Apple Developer: beginRefundRequest(in:)
- Apple Developer: Transaction.RefundRequestStatus
- Apple Developer: Transaction.RefundRequestError
- Apple Developer: App Store Server Notifications V2 notificationType
- Apple Developer: Send Consumption Information
- Apple Developer: Support customers and handle refunds (WWDC21)
RefundHalt
Автопилот возвратов для App Store и Google Play
Читайте дальше
Когда покупка в Google Play возвращается или оспаривается, узнать об этом можно через Voided Purchases API
Google Play тихо аннулирует покупку, когда её возвращают или оспаривают. Voided Purchases API даёт список таких заказов, чтобы вы могли отозвать доступ. Здесь разобраны все поля, окно в 30 дней, опция отзыва, которая скрывает заказы, и во что это обходится.
Три уведомления App Store о возврате приходят после решения Apple, а REFUND_REVERSED возвращает продажу вам
Apple отправляет четыре сообщения о возврате через App Store Server Notifications V2, и большинство приложений обрабатывают только два. REFUND говорит отозвать доступ, REFUND_DECLINED означает сохранить продажу, а REFUND_REVERSED возвращает продажу и просит восстановить то, что вы отняли. Вот что требует каждое из них.