Все статьи
Deep dive9 мин чтения

Отложенная покупка выглядит как продажа, но деньги не пришли, а выдача её раньше времени отдаёт продукт даром

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

Кассир передаёт небольшой упакованный свёрток через прилавок магазина в тёплом свете, иллюстрируя отложенную покупку, где товар уходит раньше, чем проходит платёж

Главное

  • Отложенная покупка, это реальный заказ, который магазин принял, но ещё не списал. Ваш код видит новую покупку, но деньги не пришли и могут не прийти никогда.
  • Выдавайте доступ только когда состояние равно PURCHASED в Google Play или когда транзакция завершена в Apple. Никогда не открывайте доступ на состоянии PENDING или на результате pending у Apple.
  • В Google Play оплата наличными в магазине, банковские переводы и часть операторских платежей проходят вне основного потока, поэтому покупка возвращается в PENDING, а не PURCHASED, пока клиент действительно не оплатит.
  • В Apple отложенная покупка, это обычно Ask to Buy, где организатор семьи должен её одобрить. Одобрение может занять часы или дни, а завершённая транзакция приходит позже через Transaction.updates.
  • Откройте доступ по отложенному заказу, который затем отменяется, и вы потратили вычисления, вызовы API, хранилище или выплату расходуемого товара на списание, которое так и не прошло. В отличие от возврата, забирать нечего, потому что ничего и не было получено.
  • Магазин сообщает, когда отложенный заказ срывается. Google отправляет ONE_TIME_PRODUCT_CANCELED, тип 2, или SUBSCRIPTION_PENDING_PURCHASE_CANCELED, тип 20. Apple просто никогда не доставляет завершённую транзакцию.
  • Отложенный заказ может превратиться в реальную продажу, пока ваше приложение закрыто, поэтому перепроверяйте при возврате: вызывайте queryPurchasesAsync() в onResume() в Google Play и продолжайте слушать Transaction.updates в Apple.

Кто-то нажимает «Купить» в вашем приложении. В логах появляется новый заказ, срабатывает слушатель биллинга, и вы выдаёте товар. Для большинства покупок это ровно то, что нужно. Для отложенной покупки это ошибка, потому что заказ есть, а денег нет. Клиент выбрал способ оплаты, который проходит позже, магазин всё ещё ждёт платёж, а вы уже выдали платную функцию за списание, которое может так и не пройти. Это тихий родственник возврата. Здесь ничего не отменяется, потому что ничего и не было получено. Вы просто отдали продукт даром.

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

Что такое отложенная покупка на самом деле

Отложенная покупка, это заказ, который магазин зафиксировал, но ещё не списал деньги. Покупатель начал процесс, магазин его принял, а расчёт идёт где-то, куда вашему приложению не видно. Google Play называет это состоянием PENDING. Apple называет это отложенной, или отсроченной, транзакцией. Названия разные, суть одна: магазин держит заказ открытым, пока ждёт оплату, и он сказал вам не считать этот заказ деньгами.

Google Play: платёж проходит где-то в другом месте

Некоторые способы оплаты проходят вне основного потока. Наличные в физическом магазине, банковские переводы и часть операторских платежей требуют дополнительных шагов между нажатием и списанием. Когда клиент выбирает такой способ, Google возвращает покупку в состоянии PENDING, а не PURCHASED. При оплате наличными клиент получает код в уведомлении и по электронной почте, несёт его в магазин-партнёр и платит кассиру. Пока этого не произошло, Google ничего не получил, и вы тоже. Правило Google в одну строку: используйте getPurchaseState() и выдавайте доступ только тогда, когда состояние равно PURCHASED. Оно также говорит не подтверждать покупку, пока она в состоянии PENDING, потому что подтверждение относится к оплаченному заказу, а не к обещанному.

Apple: покупка ждёт чужого нажатия

Отложенное состояние Apple означает, что транзакции нужно внешнее действие, прежде чем она сможет завершиться. Самое частое, это Ask to Buy, когда ребёнок начинает покупку, а организатор семьи должен её одобрить. В StoreKit 2 вызов покупки возвращает Product.PurchaseResult.pending. В прежнем StoreKit транзакция сообщается как отложенная (deferred). В любом случае Apple никого не списал, а завершённая транзакция, если она придёт, приходит асинхронно через Transaction.updates. Покажите клиенту состояние ожидания и не открывайте ничего, пока не поступит завершённая транзакция.

Почему выдача отложенной покупки стоит вам реальных денег

Здесь потеря не такая, как при возврате, где забирают уже учтённые деньги. Она хуже в одном конкретном смысле: забирать нечего, потому что деньги никогда не были получены.

Вы выдаёте товар, а магазин так и не получает оплату

Когда вы открываете доступ по отложенному заказу, который позже отменяется, вы уже потратились на его обслуживание. Вычисления, отработавшие функцию, оплаченные вызовы сторонних API, выделенное хранилище, а для расходуемого товара, собственно выплата того, что вы продали. Всё это уходит по заказу, который не принёс никакой выручки. Возврат хотя бы начинается со списания, которое состоялось. У ошибочно выданной отложенной покупки списания не было вовсе, поэтому она даже не отображается как ушедшие деньги. Она отображается как ничто, и именно поэтому её легко упустить и легко повторить.

Сигнал об отмене и что он означает

Магазин действительно сообщает, когда отложенный заказ умирает. В Google Play разовый товар, который срывается, отправляет уведомление ONE_TIME_PRODUCT_CANCELED, тип 2, а подписка, которая была отложенной, отправляет SUBSCRIPTION_PENDING_PURCHASE_CANCELED, тип 20. Когда те же заказы, наоборот, проходят, вы получаете ONE_TIME_PRODUCT_PURCHASED, тип 1, или SUBSCRIPTION_PURCHASED, тип 4. У Apple нет события отмены, которое можно поймать, потому что отсроченная транзакция, которую отклонили, просто никогда не становится завершённой транзакцией. Если вы открыли доступ раньше времени, эта тишина и есть счёт.

ВопросGoogle PlayApple
Что запускаетНаличные, банковский перевод, часть операторских платежейОдобрение Ask to Buy или другое необходимое действие
Что вы видитеPurchaseState PENDINGрезультат pending или отсроченная транзакция
Выдавать доступ, когдаСостояние равно PURCHASEDТранзакция завершена
Прошло успешноONE_TIME_PRODUCT_PURCHASED (1), SUBSCRIPTION_PURCHASED (4)Завершённая транзакция через Transaction.updates
СорвалосьONE_TIME_PRODUCT_CANCELED (2), SUBSCRIPTION_PENDING_PURCHASE_CANCELED (20)Завершённая транзакция так и не приходит
Были ли получены деньгиНет, пока не PURCHASEDНет, пока транзакция не завершится

Окно и кто кого ждёт

Отложенная покупка, это не таймер, с которым вы гонитесь. Это таймер, который с вашей стороны ещё не запущен.

Google Play даёт клиенту дни, а не минуты

Платёж наличными или банковским переводом проходит по графику клиента, а не вашему. Заказ лежит в состоянии PENDING, пока клиент не оплатит или пока не истечёт окно и Google не отменит его. Ваше собственное трёхдневное окно подтверждения, то, которое автоматически возвращает деньги за покупку, которую вы не подтвердили, даже не начинается, пока покупка не перейдёт из PENDING в PURCHASED. Так что нет причин спешить обслуживать отложенный заказ. Есть только дисциплина дождаться смены состояния.

Одобрение Apple находится в телефоне организатора семьи

Запрос Ask to Buy приходит на устройство организатора как приглашение, которое он одобряет или отклоняет, когда доберётся до него. Это могут быть минуты, часы или день позже, и ваше приложение не может это ускорить. Единственно правильное поведение, это отражать состояние ожидания и позволить StoreKit передать вам завершённую транзакцию, если и когда одобрение придёт.

Запечатанная картонная коробка рядом с песочными часами на столе, иллюстрирующая отложенную покупку, где товар готов, но платёж ещё не прошёл

Как обрабатывать отложенные покупки без утечек

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

Выдавайте на оплаченном состоянии, никогда на отложенном

В Google Play проверяйте getPurchaseState() и выдавайте доступ только на PURCHASED, и не подтверждайте покупку, пока она в состоянии PENDING. В Apple открывайте доступ только на завершённой транзакции и никогда на результате pending. Одно это правило закрывает всю утечку. Всё остальное, это про то, чтобы действительно заметить, когда придёт оплаченное состояние.

Перепроверяйте, когда приложение возвращается

Переход из отложенного в оплаченное часто происходит, пока ваше приложение не запущено. В Google Play вызывайте queryPurchasesAsync() в обработчике onResume(), чтобы подхватить заказы, которые стали PURCHASED в фоне, и держите слушатель Real-time Developer Notifications как серверный источник истины. В Apple слушайте Transaction.updates в течение всей жизни приложения, потому что одобренная транзакция может прийти намного позже, чем вернулся исходный вызов покупки.

Включите поддержку отложенных покупок и протестируйте оба исхода

Google требует вызвать enablePendingPurchases() при создании BillingClient, и поддержка отложенных транзакций для разовых товаров обязательна, а не опциональна. Протестируйте это до релиза. Тестировщики лицензий получают два дополнительных тестовых инструмента для отложенных форм оплаты, где платёж автоматически завершается или автоматически отменяется через пару минут, так что вы можете отследить и путь оплаты, и путь срыва от начала до конца.

Сообщите клиенту, что заказ не завершён

Покупатель в состоянии ожидания, это реальный клиент посреди покупки, а не провал. Покажите ему, что заказ ждёт его оплаты или одобрения, и дайте понятный путь назад, чтобы её завершить. Молчаливое состояние ожидания теряет продажи, которые хорошо подписанное состояние возвращает, потому что большинство таких покупателей всё ещё хотят товар, и им остался всего один шаг.

Те же потоки Real-time Developer Notifications и App Store Server Notifications, которые RefundHalt уже читает для возвратов и чарджбэков, несут и эти сигналы. Уведомление о покупке, которое говорит, что отложенный заказ наконец прошёл, и уведомление об отмене, которое говорит, что нет, попадают в вашу панель рядом с остальными событиями по выручке, так что сорвавшийся отложенный заказ, это то, что вы можете увидеть, а не то, за что вы случайно заплатили.

Коротко

Отложенная покупка, это заказ без платежа, и оба магазина прямо говорят, что нужно подождать. Google Play возвращает заказы за наличные, банковским переводом и частью операторских платежей в состоянии PENDING и велит выдавать доступ только на PURCHASED. Apple возвращает отложенную или отсроченную транзакцию для Ask to Buy и других необходимых действий и доставляет завершённую транзакцию позже через Transaction.updates. Открывайте доступ на оплаченном состоянии, перепроверяйте, когда приложение возобновляется, включите и протестируйте поддержку отложенных покупок и подпишите ожидающий заказ для клиента. Сделайте так, и отложенная покупка не будет стоить вам ничего. Пропустите это, и вы выдадите платный продукт за списание, которое так и не пришло, а это единственная потеря, на которую нельзя указать чеком.

Частые вопросы

Что такое отложенная покупка?
Отложенная покупка, это заказ, который магазин принял, но ещё не списал. В Google Play это состояние покупки PENDING, используемое для способов оплаты, которые проходят позже, таких как наличные, банковский перевод и часть операторских платежей. В Apple это отложенная или отсроченная транзакция, чаще всего покупка Ask to Buy, ждущая одобрения организатора семьи. В обоих случаях деньги ещё не получены, поэтому доступ выдавать не следует.
Стоит ли выдавать доступ, пока покупка в состоянии ожидания?
Нет. Выдавайте доступ только когда состояние равно PURCHASED в Google Play или когда транзакция завершена в Apple. Если вы откроете функцию, пока заказ ещё в ожидании, а платёж так и не пройдёт, вы выдали продукт бесплатно, и отменять нечего, потому что списания не было.
Какие способы оплаты вызывают отложенную покупку в Google Play?
Способы оплаты, которые проходят вне основного потока. Оплата наличными в физическом магазине, банковские переводы и часть операторских платежей требуют дополнительных шагов между нажатием и списанием, поэтому Google возвращает покупку в состоянии PENDING, а не PURCHASED. При оплате наличными клиент получает код в уведомлении и по электронной почте, затем платит в магазине-партнёре.
Что такое Ask to Buy и как это связано с отложенными покупками?
Ask to Buy, это функция Family Sharing от Apple, которая позволяет ребёнку запросить покупку, которую должен одобрить организатор семьи. Пока запрос ждёт, покупка находится в отложенном состоянии Apple, возвращаемом как Product.PurchaseResult.pending в StoreKit 2 или как отсроченная транзакция в прежнем StoreKit. Завершённая транзакция приходит через Transaction.updates только если и когда организатор её одобрит.
Что происходит, если отложенная покупка так и не оплачена?
Заказ отменяется, и денег не переходит. В Google Play вы получаете уведомление ONE_TIME_PRODUCT_CANCELED, тип 2, для разового товара, или SUBSCRIPTION_PENDING_PURCHASE_CANCELED, тип 20, для подписки. В Apple отсроченная транзакция просто никогда не становится завершённой транзакцией. Если вы уже выдали доступ, именно в этот момент потеря становится реальной.
Отложенная покупка, это то же самое, что возврат?
Нет. Возврат отменяет платёж, который действительно был получен. Отложенная покупка, которая срывается, никогда не была списана, поэтому отменять нечего и в отчёте о возвратах ничего не появляется. Если вы открыли её раньше времени, стоимость, это вычисления, вызовы API, хранилище или расходуемый товар, которые вы потратили на обслуживание заказа, не принёсшего выручки.

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

RefundHalt

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

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

Deep dive9 мин чтения

Продления срываются чаще, чем клиенты отменяют подписку, а этот непроизвольный отток остаётся выручкой, которую вы всё ещё можете вернуть

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

Deep dive8 мин чтения

Несанкционированные встроенные покупки, сделанные детьми, почти всегда возвращаются родителю, а расходы несёте вы

Когда ребёнок покупает набор монет на телефоне родителя, и Apple, и Google возвращают деньги, и никто из них не спрашивает вас заранее. Так это устроили регуляторы. Вот как работают эти возвраты за несанкционированные встроенные покупки в каждом магазине, что за 15-минутное окно, в котором уходят деньги, и во сколько на самом деле обходится одна такая покупка.

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

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