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

Главное
- Voided Purchases API, метод purchases.voidedpurchases.list, возвращает заказы, которые Google Play отменил, вернул или по которым прошёл чарджбэк, чтобы вы могли построить систему отзыва, обрезающую доступ к тому, чем клиент больше не владеет.
- Появляются только отозванные заказы. Возврат, оформленный разработчиком без опции отзыва, невидим для этого API, поэтому, если вы хотите забрать доступ, нужно оформлять возврат с включённым отзывом.
- Окно составляет 30 дней. startTime не может быть старше, чем 30 дней назад, поэтому сервер, простаивающий дольше месяца, теряет эти аннулированные заказы навсегда. Опрашивайте по расписанию.
- voidedSource сообщает, кто аннулировал заказ: 0 это пользователь, 1 это разработчик, 2 это Google. voidedReason сообщает причину, от 0 Other до 7 Chargeback и 8 Unacknowledged_purchase.
- Real-time developer notifications присылают VoidedPurchaseNotification в тот момент, когда покупка аннулируется, но относитесь к этому как к сигналу. Вызовите Voided Purchases API за авторитетным списком, прежде чем отзывать.
- Определяйте продления подписки по orderId, а не по purchaseToken. Один purchaseToken охватывает каждое продление подписки, поэтому по одному токену нельзя отличить два продления.
- Квоты составляют 6000 запросов в день и 30 запросов в любом 30-секундном окне, поэтому проходите по результатам с токеном продолжения и запрашивайте по временному окну, а не по одному вызову на заказ.
Возврат в Google Play не стучится к вам в дверь. Деньги уходят, клиент продолжает держать приложение открытым, и, пока вы не пойдёте искать, с вашей стороны ничего не меняется. Voided Purchases API и есть то место, куда вы идёте искать. Он выдаёт вам список заказов, которые были отменены, возвращены или оспорены, чтобы вы могли отозвать доступ к тому, за что клиент больше не платит. Направьте на него запланированную задачу, прочитайте список, обрежьте право доступа. Вот и весь цикл.
Есть одна загвоздка, на которой спотыкается большинство команд, и она не в коде. Здесь появляются только заказы, которые были отозваны. Если вы вернёте покупку в Play Console, не отметив опцию отзыва, этот заказ никогда не попадёт в этот API, и ваша задача отработает вчистую, пока клиент с возвратом сохраняет всё, что вы ему продали. Эта статья проходит по API поле за полем, по числам, которые его ограничивают, и по тому, где утекают деньги, когда вы им пренебрегаете.
Что на самом деле возвращает Voided Purchases API
API отвечает на один вопрос: какие заказы этого приложения были аннулированы недавно. Аннулирование охватывает три исхода, каждый из которых заканчивается тем, что клиент получает деньги обратно. Отмена, возврат или чарджбэк. Оно применяется к разовым внутренним продуктам приложения и к подпискам, а объём вы выбираете одним параметром. Установите type в 0, и вы получите только аннулированные покупки разовых продуктов, это значение по умолчанию. Установите его в 1, и вы получите аннулированные разовые покупки и аннулированные покупки подписок вместе.
Каждая запись в списке представляет собой объект аннулированной покупки. Полей немного, и каждое из них имеет значение.
Поля аннулированной покупки
| Поле | Что оно содержит |
|---|---|
| orderId | Идентификатор заказа, который однозначно определяет разовую покупку, покупку подписки или отдельное продление подписки. Это ваш ключ соединения |
| purchaseToken | Токен, который идентифицирует разовую покупку или подписку. Он не различает продления, поэтому для них используйте orderId |
| purchaseTimeMillis | Когда была совершена покупка, в миллисекундах от эпохи |
| voidedTimeMillis | Когда покупка была отменена, возвращена или оспорена, в миллисекундах от эпохи |
| voidedSource | Кто инициировал аннулирование: 0 пользователь, 1 разработчик, 2 Google |
| voidedReason | Почему покупка была аннулирована, целое число от 0 до 8 |
| voidedQuantity | Аннулированное количество из частичного возврата по количеству, возвращается только когда includeQuantityBasedPartialRefund равно true |
Прочитайте voidedReason, прежде чем действовать
Именно voidedReason превращает сырой список в решение. Возврат из-за передумавшего покупателя и банковский чарджбэк попадают в один и тот же список, но это не одно и то же событие, и августовские расценки делают одно из них дорогим. Вот полный набор.
| voidedReason | Label | Что это значит для вас |
|---|---|---|
| 0 | Other | Категория не была присвоена. Отзовите и двигайтесь дальше |
| 1 | Remorse | Покупатель передумал. Обычный возврат |
| 2 | Not_received | Клиент говорит, что так и не получил продукт. Стоит проверить вашу доставку |
| 3 | Defective | Продукт не работал. Сигнал о качестве, зафиксируйте его |
| 4 | Accidental_purchase | Непреднамеренная покупка, часто на общем устройстве |
| 5 | Fraud | Google пометил транзакцию как мошенническую |
| 6 | Friendly_fraud | Чарджбэк, когда законный держатель карты оспаривает списание, которое сам же совершил |
| 7 | Chargeback | Банк клиента отменил платёж. Окончательно на стороне банка, и теперь выставлено вам |
| 8 | Unacknowledged_purchase | Google автоматически вернул покупку, которую ваше приложение так и не подтвердило |
Окно в 30 дней: ловушка, которая опустошает ваш список
Voided Purchases API может показать только аннулированные покупки за последние 30 дней. Параметр startTime по умолчанию равен текущему времени минус 30 дней, и его нельзя установить старше этого. endTime по умолчанию равен текущему моменту. Так что эндпойнт представляет собой скользящее окно в один месяц, а не архив.
Последствие прямолинейно. Если ваша задача опроса сломается и никто не заметит этого пять недель, аннулирования первой недели устареют и выпадут из API. Нет вызова, который вернул бы их обратно. Вы не отзовёте эти заказы и даже не узнаете, что они существовали, если только не зафиксировали их каким-то другим способом. API представляет собой страховочную сеть с дырой размером с ваш худший сбой.
Опция отзыва решает, появится ли заказ вообще
Это самая частая причина, по которой команда сообщает, что API сломан. Возвращаются только отозванные заказы. Возвраты, инициированные пользователем, отмены, чарджбэки и возвраты, инициированные Google, всегда отзываются, поэтому всегда появляются. Возврат, инициированный разработчиком, отличается. Когда вы возвращаете заказ сами, через Play Console или Orders API, вы выбираете, отзывать ли его также. Верните без отзыва, и заказ будет улажен с клиентом, но никогда не всплывёт в Voided Purchases API.
Правило, которое из этого следует, простое. Если ваше намерение состоит в том, чтобы забрать доступ, возвращайте с включённой опцией отзыва. В противном случае вы вернули деньги и оставили дверь открытой, и ваша задача отзыва, как бы хорошо она ни была написана, не имеет ничего, над чем работать.
Как опрашивать его, не превышая квоту
У эндпойнта есть ограничение частоты, и лимиты достаточно низкие, чтобы наивный цикл их достиг. Вам доступно 6000 запросов в день, отсчитываемых по тихоокеанскому времени, и не более 30 запросов в любой 30-секундный период. Этого бюджета достаточно для оконного опроса, и он враждебен схемам с одним запросом на заказ.
Окна запросов и токен продолжения
maxResults по умолчанию равен 1000, что также является потолком. Когда окно содержит больше одной страницы аннулирований, ответ несёт объект tokenPagination с nextPageToken. Передайте этот токен обратно в следующем вызове, чтобы пройти по страницам. Установите startTime и endTime, чтобы ограничить нужное вам окно, листайте страницы, пока токен не закончится, затем сдвиньте окно. Эта схема удерживает вас внутри как лимита всплеска в 30 секунд, так и суточного потолка.
Real-time developer notifications закрывают разрыв
Опрос каждый день всё равно оставляет до суток слепоты, а окно в 30 дней наказывает за долгие перерывы. Real-time developer notifications убирают задержку. Google публикует VoidedPurchaseNotification в топик Cloud Pub/Sub, которым вы владеете, в тот момент, когда покупка аннулируется, и ваш бэкенд обрабатывает её за секунды. Сообщение небольшое.
| Поле RTDN | Что оно содержит |
|---|---|
| purchaseToken | Токен из исходной покупки |
| orderId | Идентификатор заказа аннулированной транзакции, новый для каждого продления подписки |
| productType | 1 для подписки, 2 для разовой покупки |
| refundType | 1 для полного возврата, 2 для частичного возврата по количеству |

Во что это обходится вам в деньгах
API является всего лишь инфраструктурой, но подключают его ради счёта. Каждое аннулирование в том списке соответствует реальной сумме, и две из них становятся дороже.
Счёт за чарджбэк ложится на вас с 3 августа 2026 года
Начиная с 3 августа 2026 года Google перекладывает стоимость чарджбэка на разработчика. Вы теряете цену покупки и вдобавок платите банковскую комиссию за чарджбэк. voidedReason, равный 7, больше не просто потерянная продажа, это строка расходов с прикреплённой комиссией. Отменить чарджбэк нельзя, он окончателен на стороне банка, но вы можете остановить кровотечение после него. Быстрый перехват аннулирования позволяет отозвать право доступа и, для всего, что вы ещё поставляете, перестать тратиться на клиента, которому вернули деньги, а затем откатили платёж.
Вы продолжаете платить за обслуживание клиента с возвратом
Цена покупки потеряна в тот момент, когда появляется аннулирование. То, что вы всё ещё контролируете, это стоимость продолжения поставки. Каждый час, пока возвращённое право доступа остаётся активным, вы продолжаете платить за то, что клиент больше не финансирует: вычисления, вызовы API моделей, хранилище и любые выплаты авторам или партнёрам, привязанные к его использованию. Система отзыва, управляемая этим API, это то, чем вы выключаете этот счётчик. Пропустите её, и вы финансируете продукт для людей, с которыми магазин уже рассчитался.
Дружественное мошенничество: паттерн, который стоит отслеживать
voidedReason, равный 5 или 6, не единичный случай. Мошенничество и дружественное мошенничество группируются по аккаунту, по устройству, а иногда по акции. API даёт вам voidedSource и voidedReason по каждому аннулированию, и этого достаточно, чтобы отслеживать злоупотребления по аккаунту, а не рассматривать каждый откат как отдельную статью расходов. Клиент, который делает чарджбэк дважды, говорит вам то, чего не сказал первый возврат.
Как связать это по методу RefundHalt
Модель невелика, когда вы держите все части. Слушайте VoidedPurchaseNotification в реальном времени, чтобы ничто не ждало целые сутки. Вызывайте Voided Purchases API как источник истины, с ключом по orderId, чтобы продления подписки никогда не путались. Читайте voidedSource и voidedReason, чтобы чарджбэк обрабатывался иначе, чем возврат из-за передумавшего покупателя. Опрашивайте по расписанию, достаточно плотному, чтобы окно в 30 дней никогда не укусило, и возвращайте с включённой опцией отзыва всякий раз, когда ваше намерение состоит в том, чтобы обрезать доступ.
Это та часть, которую RefundHalt выполняет за вас. Он обрабатывает уведомления в реальном времени, сверяет каждое аннулирование с API, отзывает именно тот заказ, а не весь продукт, и отделяет банковский чарджбэк от обычного возврата, чтобы дорогие были помечены, а не погребены. Вы получаете отозванный доступ за секунды и запись о том, кто, что и почему аннулировал, без необходимости самим поднимать конвейер Pub/Sub и задачу опроса.
Частые вопросы
- Почему мои возвращённые заказы не отображаются в Voided Purchases API?
- Потому что возвращаются только отозванные заказы. Возвраты пользователей, отмены, чарджбэки и возвраты, инициированные Google, всегда отзываются и всегда появляются. Возврат, инициированный разработчиком, появляется только если вы также выбрали опцию отзыва. Если вы вернули заказ, не отозвав его, заказ улажен, но невидим для этого API, поэтому возвращайте с включённым отзывом всякий раз, когда намерены забрать доступ.
- Как далеко назад заглядывает Voided Purchases API?
- На тридцать дней. Параметр startTime по умолчанию равен текущему времени минус 30 дней и не может быть установлен старше этого, поэтому эндпойнт представляет собой скользящее окно в один месяц, а не архив. Аннулированный заказ, который старше 30 дней, исчезает из API без возможности его получить, и именно поэтому вы опрашиваете по расписанию и подкрепляете это уведомлениями в реальном времени.
- Что использовать для отзыва доступа, Real-time developer notifications или Voided Purchases API?
- Используйте и то, и другое. VoidedPurchaseNotification приходит за секунды и говорит вам посмотреть, но собственная рекомендация Google состоит в том, чтобы относиться к нему как к сигналу, а не к источнику истины. Вызовите Voided Purchases API, чтобы подтвердить текущее состояние, затем отзывайте. Уведомление убирает задержку, а API даёт вам авторитетные voidedSource и voidedReason, по которым можно действовать.
- Как отличить чарджбэк от обычного возврата в API?
- Прочитайте поле voidedReason. Значение 7 это чарджбэк, то есть банк клиента отменил платёж, а 6 это дружественное мошенничество. Значение 1 это возврат из-за передумавшего покупателя. Это важно, потому что с 3 августа 2026 года Google перекладывает цену покупки по чарджбэку и банковскую комиссию на разработчика, поэтому voidedReason, равный 7, обходится вам дороже обычного возврата.
- Охватывает ли Voided Purchases API подписки?
- Да. Установите параметр type в 1, чтобы получить и аннулированные разовые покупки, и аннулированные покупки подписок. Значение по умолчанию, type 0, возвращает только покупки разовых продуктов. Для подписок определяйте точный аннулированный период по orderId, потому что один purchaseToken охватывает каждое продление, а новый orderId генерируется для каждой транзакции продления.
Источники и дополнительное чтение
- Google Play Developer API: Voided Purchases API guide
- Google Play Developer API: purchases.voidedpurchases.list method
- Google Play Developer API: purchases.voidedpurchases resource (voidedSource and voidedReason)
- Android Developers: Real-time developer notifications reference (VoidedPurchaseNotification)
- Android Developers: Fight fraud and abuse with Play Billing
RefundHalt
Автопилот возвратов для App Store и Google Play
Читайте дальше
Три уведомления App Store о возврате приходят после решения Apple, а REFUND_REVERSED возвращает продажу вам
Apple отправляет четыре сообщения о возврате через App Store Server Notifications V2, и большинство приложений обрабатывают только два. REFUND говорит отозвать доступ, REFUND_DECLINED означает сохранить продажу, а REFUND_REVERSED возвращает продажу и просит восстановить то, что вы отняли. Вот что требует каждое из них.
Теперь каждый запрос на возврат от Apple приходит с причиной, и consumptionRequestReason показывает, как её прочитать
Начиная с WWDC24, каждый Apple CONSUMPTION_REQUEST несёт consumptionRequestReason, собственную заявленную причину клиента, по которой он хочет возврат. Значений пять, от UNINTENDED_PURCHASE до LEGAL, и каждое должно менять то, что вы отправляете в ответ в течение вашего 12-часового окна. Вот как прочитать каждое из них.