В этом документе описывается, как обрабатывать события жизненного цикла подписки, такие как продление и истечение срока действия. Также описываются дополнительные функции подписки, такие как предложение рекламных акций и предоставление пользователям возможности самостоятельно управлять своими подписками.
Если вы еще не настроили продукты по подписке для своего приложения, см. раздел «Создание и настройка продуктов» .
Обзор подписок
Подписка — это повторяющаяся транзакция, предоставляющая пользователям определенные права. Эти права представляют собой набор преимуществ, к которым пользователи могут получить доступ в течение определенного периода времени. Например, подписка может предоставлять пользователю доступ к премиум-классу.
С помощью базовых планов и предложений вы можете создавать несколько конфигураций для одного и того же продукта подписки. Например, вы можете создать ознакомительное предложение для пользователей, которые никогда не подписывались на ваше приложение. Аналогично, вы можете создать предложение по обновлению подписки для пользователей, которые уже подписаны.
Подробный обзор продуктов по подписке, базовых тарифных планов и предложений см. в документации в Справочном центре Play Console .
Библиотека Play Billing поддерживает следующие типы подписки:
Подписка — в этом типе один элемент соответствует одному праву. Например, подписка на сервис потоковой передачи музыки.
Подписка с дополнениями — в этом типе подписки одна покупка может включать в себя несколько различных услуг, объединенных в один пакет. Например, подписка на музыкальный стриминговый сервис и подписка на видеосервис. Для получения информации, касающейся подписки с дополнениями, см. раздел «Подписки с дополнениями» .
Интеграция предоплаченных тарифных планов
Предоплаченные тарифные планы не продлеваются автоматически по истечении срока действия. Для бесперебойного продления подписки пользователю необходимо пополнить баланс предоплаченного тарифного плана на ту же подписку.
Для пополнения счета запустите процесс оплаты так же, как и при первоначальной покупке. Указывать, что покупка является пополнением счета, не требуется.
При пополнении предоплаченных тарифов всегда используется режим замены CHARGE_FULL_PRICE , и вам не нужно явно устанавливать этот режим. С пользователя немедленно списывается сумма за полный расчетный период, и его право на использование услуг продлевается на срок, указанный в пополнении.
После пополнения счета следующие поля в объекте «Результат Purchase обновляются, отражая последнюю покупку пополнения:
- Идентификатор заказа
- Время покупки
- Подпись
- Купить токен
- Признано
В следующих полях раздела Purchase всегда содержатся те же данные, что и в исходной покупке:
- Название пакета
- штат покупки
- Продукты
- Автоматическое продление
Подтверждение предоплаченной покупки
Аналогично подпискам с автоматическим продлением, после покупки необходимо подтвердить предоплаченные тарифные планы. Необходимо подтвердить как первоначальную покупку, так и любые пополнения счета. Для получения дополнительной информации см. раздел «Обработка покупок» .
В связи с возможностью короткого срока действия предоплаченных тарифных планов, важно подтвердить покупку как можно скорее.
Предоплаченные тарифные планы сроком на одну неделю и более должны быть подтверждены в течение трех дней.
Предоплаченные тарифные планы сроком менее одной недели должны быть подтверждены в течение половины срока действия плана. Например, у разработчиков есть 1,5 дня на подтверждение трехдневного предоплаченного тарифного плана.
Интеграция подписок в рассрочку
Подписка в рассрочку — это тип подписки, при котором пользователи оплачивают подписку несколькими частями в течение определенного периода времени, а не вносят всю сумму сразу.
Дополнительные соображения при оформлении подписки в рассрочку:
- Доступность по странам : Функция подписки в рассрочку доступна только в Бразилии, Франции, Италии и Испании (актуальную информацию о доступности смотрите в консоли).
- Установка цены : При установке цены на подписку в рассрочку в консоли, цена представляет собой сумму ежемесячного платежа. Вместе с установленным периодом действия договора это формирует общую сумму подписки на экране покупки.
- Период действия договора : Общая продолжительность первоначального договора на подписку, в течение которой требуются ежемесячные платежи. Например, если базовый тарифный план предусматривает период действия договора в 15 месяцев, пользователь совершит 15 ежемесячных платежей в течение этого периода.
- Продление : В контексте подписок с оплатой в рассрочку «продление» означает завершение периода действия обязательства, будь то первоначального или последующего. После первоначальной регистрации первое продление происходит по истечении всего первоначального периода действия обязательства. Последующие продления происходят после завершения каждого последующего периода действия обязательства. Типы продления для подписок с оплатой в рассрочку могут быть «автоматическое ежемесячное продление» или «автоматическое продление на тот же срок». При «автоматическом ежемесячном продлении» последующих обязательств нет, и план работает как ежемесячная подписка, где каждая ежемесячная плата за подписку представляет собой продление.
- Расчетный период : В контексте подписок с оплатой в рассрочку это относится к периодичному графику осуществления отдельных платежей, как указано в базовом тарифном плане.
- Изменение тарифного плана и изменение цены : В случае изменения цены и отмены заказа обязательство является окончательным. Это означает, что если пользователь хочет отменить заказ или разработчик хочет изменить цену, изменение вступает в силу по истечении срока действия обязательства. В случае изменения тарифного плана обязательство не является окончательным. Это означает, что изменение тарифного плана не обязательно должно ждать окончания срока действия обязательства, оно вступает в силу либо немедленно, либо в следующую дату платежа в зависимости от установленного способа замены.
- Смена тарифного плана в рамках одной подписки : Смена тарифного плана с рассрочкой платежа на тарифный план без рассрочки платежа в рамках одного и того же продукта подписки не допускается.
Уведомления разработчика в режиме реального времени (RTDN) : Уведомление
SUBSCRIPTION_CANCELLATION_SCHEDULEDотправляется немедленно после инициированной пользователем отмены подписки, если платежи остаются в течение периода действия обязательства. Отмена находится в статусе ожидания и вступит в силу только по окончании периода действия обязательства. Затем, если пользователь не восстановит подписку, в конце периода действия обязательства отправляются уведомленияSUBSCRIPTION_CANCELEDиSUBSCRIPTION_EXPIRED.Выплаты / Получение дохода : Выплаты разработчикам будут производиться по мере внесения пользователями ежемесячных платежей на тех же условиях, что и для всех других подписок. Разработчики не получают оплату авансом при оформлении пользователем подписки с рассрочкой платежа.
Взыскание просроченных платежей : Если пользователь не производит какие-либо платежи по подписке в рассрочку, ни Google, ни Разработчик не будут пытаться взыскать с пользователя такие просроченные или непогашенные платежи, за исключением случаев, когда Google может периодически повторять попытку платежа в течение любого применимого льготного периода или периода блокировки учетной записи в соответствии со своей обычной практикой повторных попыток платежей. Google не несет ответственности перед Разработчиком за любые оставшиеся неоплаченные платежи в рассрочку.
Доступность библиотеки Play Billing : Поле
installmentDetailsдоступно только для PBL 7 и более поздних версий. Для PBL 5 и более поздних версий подписка с оплатой в рассрочку возвращается с помощьюqueryProductDetails(), но подписка не будет включать подробную информацию о платежах в рассрочку, такую как количество подтвержденных платежей по плану.
Используйте прямые ссылки, чтобы предоставить пользователям возможность управлять подпиской.
В вашем приложении должна быть ссылка на экране настроек или параметров, позволяющая пользователям управлять своими подписками, которую вы можете органично вписать в общий внешний вид приложения.
Вы можете добавить в своё приложение прямую ссылку на центр подписок Google Play для неистекших подписок, которую можно определить с помощью поля subscriptionState ресурса подписки . Исходя из этого, существует несколько способов создания прямых ссылок на центр подписок Play Store.
Ссылка на центр подписки
Используйте следующий URL-адрес, чтобы перенаправить пользователей на страницу, где отображаются все их подписки, как показано на рисунках 1 и 2:
https://play.google.com/store/account/subscriptions


Эта прямая ссылка может быть полезна для восстановления пользователем отмененной подписки из центра подписок Play Store.
Ссылка на страницу управления конкретной подпиской (рекомендуется)
Чтобы перейти непосредственно на страницу управления непросроченной подпиской, укажите имя пакета и productId связанные с приобретенной подпиской. Для программного определения productId для существующей подписки запросите информацию в бэкэнде вашего приложения или вызовите BillingClient.queryPurchasesAsync() для получения списка подписок, связанных с конкретным пользователем. Каждая подписка содержит соответствующий productId как часть информации о статусе подписки. Каждый объект SubscriptionPurchaseLineItem , связанный с покупкой подписки, содержит значение productId связанное с подпиской, которую пользователь приобрел в этой позиции.
Используйте следующий URL-адрес, чтобы перенаправить пользователей на определенный экран управления подпиской, заменив "your-sub-product-id" и "your-app-package" на productId и имя пакета приложения соответственно:
https://play.google.com/store/account/subscriptions?sku=your-sub-product-id&package=your-app-package
Затем пользователь может управлять способами оплаты и получать доступ к таким функциям, как отмена, повторная подписка и приостановка.
Предоставьте пользователям возможность повышать, понижать уровень или изменять свою подписку.
Вы можете предоставить существующим подписчикам различные варианты изменения тарифного плана, чтобы он лучше соответствовал их потребностям:
- Если вы продаете несколько уровней подписки, например, «базовую» и «премиум», вы можете позволить пользователям переключаться между уровнями, приобретая базовый план или предложение другой подписки.
- Вы можете разрешить пользователям изменять текущий расчетный период, например, переключаться с ежемесячного на годовой тарифный план.
- Вы также можете разрешить пользователям переключаться между автоматическим продлением и предоплаченными тарифными планами.
Вы можете стимулировать любые из этих изменений, предлагая подписки со скидкой для соответствующих пользователей. Например, вы можете создать предложение, предоставляющее 50% скидку на первый год при переходе с ежемесячного плана на годовой, и ограничить это предложение пользователями, подписанными на ежемесячный план, которые еще не приобрели это предложение. Более подробная информация о критериях соответствия предложениям доступна в Центре поддержки.
На рисунке 3 показан пример приложения с тремя различными тарифными планами:

В вашем приложении может отображаться экран, похожий на рисунок 3, предоставляющий пользователям возможность изменить свою подписку. Во всех случаях пользователям должно быть понятно, какой у них текущий тарифный план и какие у них есть возможности для его изменения.
Когда пользователи решают повысить, понизить или изменить свою подписку, вы указываете режим замены , который определяет, как будет применяться пропорциональная стоимость текущего оплаченного расчетного периода и когда произойдут любые изменения в правах доступа.
режимы замены
В таблице ниже перечислены доступные способы замены платежей, примеры их использования и количество платежей, считающихся оплаченными.
режим замены | Описание | Пример использования | Платежи, подтвержденные платежами, зарегистрированы как оплаченные (для замены подписки в рассрочку). |
| Подписка немедленно обновляется или понижается. Оставшееся время корректируется в зависимости от разницы в цене и зачисляется на счет новой подписки путем переноса следующей даты выставления счета. Это поведение по умолчанию. | Перейдите на более дорогой тариф без дополнительной оплаты. | 0 |
| Подписка обновляется немедленно, а цикл выставления счетов остается прежним. Разница в цене за оставшийся период затем списывается с пользователя. Примечание: Эта опция доступна только при обновлении подписки, в этом случае цена за единицу времени увеличивается. | Перейдите на более дорогой тарифный план, не меняя дату выставления счета. | 1 |
| Подписка немедленно обновляется или понижается, и с пользователя немедленно взимается полная стоимость нового права доступа. Остаток средств от предыдущей подписки либо переносится на тот же уровень доступа, либо рассчитывается пропорционально времени при переходе на другой уровень доступа. Примечание: Если новая подписка включает бесплатный пробный период или ознакомительное предложение, с пользователя взимается плата в размере 0 долларов США или стоимость ознакомительного предложения, в зависимости от того, какая из них действует, в момент повышения или понижения уровня подписки. | Перейдите с более короткого расчетного периода на более длительный. | 1 (Примечание: 0, если новая подписка включает бесплатный пробный период.) |
| Уровень подписки мгновенно повышается или понижается, и новая цена взимается при продлении подписки. Цикл выставления счетов остается прежним. | Перейдите на более высокий уровень подписки, сохранив при этом оставшийся бесплатный период. | 0 |
| Уровень подписки повышается или понижается только при продлении подписки, но новая покупка включает в себя сразу же следующие два элемента:
Примечание: При оформлении подписки в рассрочку изменение тарифного плана происходит в начале следующей даты платежа. | Перейдите на более дешевый тарифный план. | 1 |
| В новой версии график платежей за подписку остается без изменений. | Добавляйте или удаляйте элементы подписки с дополнительными модулями, если конкретный элемент должен оставаться без изменений. | Н/Д |
Чтобы узнать больше о различных вариантах повышения или понижения уровня подписки и возвращения клиентов, ознакомьтесь с руководством по предложениям и акциям.
Установите режим замены для покупки.
Для разных типов изменений подписки можно использовать разные режимы замены в зависимости от ваших предпочтений и бизнес-логики. В этом разделе объясняется, как установить режим замены для изменения подписки и какие ограничения применяются.
Продлить подписку или сменить тарифный план в рамках одной подписки.
В консоли Google Play можно указать режим замены по умолчанию. Эта настройка позволяет выбрать, когда следует взимать плату с текущих подписчиков, если они приобретают другой базовый тарифный план или предложение для той же подписки, или повторно подписываются после отмены подписки. Доступные варианты: « Списать немедленно» (эквивалентно CHARGE_FULL_PRICE ) и «Списать в следующую дату выставления счета» (эквивалентно WITHOUT_PRORATION ). Это единственные подходящие режимы замены при смене базовых тарифных планов в рамках одной подписки.
Например, если вы реализуете предложение по возврату пользователя к тому же тарифному плану после отмены подписки, но до ее окончания, вы можете обработать новую покупку как обычную, не указывая никаких значений в параметре SubscriptionUpdateParams . Система использует режим замены по умолчанию, настроенный вами в подписке, и автоматически обрабатывает переход от старого тарифного плана к новому.
Меняйте тарифные планы между подписками или отменяйте режим замены по умолчанию.
Если пользователь меняет продукт подписки — приобретает другую подписку — или если вы хотите по какой-либо причине изменить режим замены по умолчанию, вы указываете коэффициент пропорционального распределения во время выполнения в качестве части параметров процесса покупки.
Для корректного указания ReplacementMode в SubscriptionProductReplacementParams или SubscriptionUpdateParams в рамках конфигурации процесса покупки во время выполнения необходимо учитывать следующие ограничения:
- При переходе на более высокий, более низкий тарифный план или при инициировании перехода на предоплаченный тариф с предоплаченного тарифа, тарифа с автоматическим продлением или тарифа в рассрочку, единственным допустимым режимом замены является
CHARGE_FULL_PRICE. Если вы укажете любой другой режим замены, покупка не удастся, и пользователю будет показано сообщение об ошибке. - При переключении тарифных планов в рамках одной подписки с предоплаченного плана на план с автоматическим продлением на план с автоматическим продлением допустимыми режимами пропорционального распределения являются
CHARGE_FULL_PRICEиWITHOUT_PRORATION. Если вы укажете любой другой режим пропорционального распределения, покупка не удастся, и пользователю будет показано сообщение об ошибке. - Переход на тарифный план без рассрочки в рамках одного и того же продукта подписки не допускается.
- При использовании режима замены
KEEP_EXISTINGвSubscriptionProductReplacementParamsдля сохранения платежа по товару без изменений во время замены, старый идентификатор продукта должен совпадать с новым идентификатором продукта. РежимKEEP_EXISTINGне поддерживается вSubscriptionUpdateParams.
Примеры и модели поведения, заменяющие друг друга.
Чтобы понять, как работает каждый режим пропорционального распределения, рассмотрим следующий сценарий:
У Сэмвайза есть подписка на онлайн-контент из приложения Country Gardener. У него ежемесячная подписка на версию Tier 1 , которая содержит только текст. Эта подписка стоит ему 2 доллара в месяц и продлевается первого числа каждого месяца.
15 апреля Сэмвайз решил перейти на годовую версию подписки Tier 2 , которая включает видеообновления и стоит 36 долларов в год .
При обновлении подписки разработчик выбирает режим пропорционального распределения. Ниже приведено описание того, как каждый режим пропорционального распределения влияет на подписку Samwise:
WITH_TIME_PRORATION
Подписка Сэмвайза на первый уровень заканчивается немедленно. Поскольку он оплатил полный месяц (с 1 по 30 апреля), но перешёл на более дорогой тариф в середине срока действия подписки, половина стоимости месячной подписки (1 доллар) засчитывается в счёт новой подписки. Однако, поскольку новая подписка стоит 36 долларов в год, баланс в 1 доллар покрывает только 10 дней (с 16 по 25 апреля); поэтому 26 апреля с него списывается 36 долларов за новую подписку, и ещё 36 долларов 26 апреля каждого последующего года.
Вам следует вызвать обработчик PurchasesUpdatedListener вашего приложения в момент успешного завершения покупки, и вы сможете получить информацию о новой покупке в рамках вызова queryPurchasesAsync() . Ваш бэкэнд немедленно получит уведомление для разработчиков в режиме реального времени SUBSCRIPTION_PURCHASED .
CHARGE_PRORATED_PRICE
Этот режим можно использовать, потому что стоимость подписки уровня 2 за единицу времени (36 долларов в год = 3 доллара в месяц) выше, чем стоимость подписки уровня 1 за единицу времени (2 доллара в месяц). Подписка Сэмвайза уровня 1 заканчивается немедленно. Поскольку он оплатил полный месяц, но использовал только половину, половина стоимости месячной подписки (1 доллар) применяется к его новой подписке. Однако, поскольку эта новая подписка стоит 36 долларов в год, оставшиеся 15 дней стоят 1,50 доллара; поэтому с него взимается разница в 0,50 доллара за новую подписку. 1 мая с Сэмвайза списывается 36 долларов за новый уровень подписки, и еще 36 долларов 1 мая каждого последующего года.
Вам следует вызвать обработчик PurchasesUpdatedListener вашего приложения в момент успешного завершения покупки, и вы сможете получить информацию о новой покупке в рамках вызова queryPurchasesAsync() . Ваш бэкэнд немедленно получит уведомление для разработчиков в режиме реального времени SUBSCRIPTION_PURCHASED .
WITHOUT_PRORATION
Подписка Сэмвайза уровня 1 немедленно повышается до уровня 2 без дополнительной платы, и 1 мая с него списывается 36 долларов за новый уровень подписки, а затем еще 36 долларов 1 мая каждого последующего года.
Вам следует вызвать обработчик PurchasesUpdatedListener вашего приложения в момент успешного завершения покупки, и вы сможете получить информацию о новой покупке в рамках вызова queryPurchasesAsync() . Ваш бэкэнд немедленно получит уведомление для разработчиков в режиме реального времени SUBSCRIPTION_PURCHASED .
DEFERRED
Подписка Сэмвайза первого уровня действует до 30 апреля. 1 мая вступает в силу подписка второго уровня , и с Сэмвайза будет списана плата в размере 36 долларов за новый уровень подписки.
Вам следует вызвать обработчик PurchasesUpdatedListener вашего приложения в момент успешного завершения покупки, когда вы сможете получить информацию о новой покупке в рамках вызова queryPurchasesAsync() . Ваш бэкэнд немедленно получит уведомление разработчика в реальном времени SUBSCRIPTION_PURCHASED . Вам следует обработать покупку так же, как и любую другую новую покупку в этот момент. В частности, убедитесь, что вы подтвердили новую покупку. Обратите внимание, что startTime новой подписки заполняется в момент вступления замены в силу, что происходит, когда истекает срок действия старой подписки. В этот момент вы получаете RTDN SUBSCRIPTION_RENEWED для нового плана подписки. Подробнее о поведении ReplacementMode.DEFERRED в разделе «Обработка отложенной замены» .
CHARGE_FULL_PRICE
Подписка Сэмвайза на первый уровень заканчивается немедленно. Его подписка на второй уровень начинается сегодня, и с него списывается 36 долларов. Поскольку он оплатил полный месяц, но использовал только половину, половина стоимости месячной подписки (1 доллар) будет учтена в его новой подписке. Поскольку новая подписка стоит 36 долларов в год, к периоду подписки добавится 1/36 года (примерно 10 дней). Таким образом, следующая оплата для Сэмвайза будет произведена через 1 год и 10 дней с сегодняшнего дня и составит 36 долларов. После этого с него будет взиматься 36 долларов каждый последующий год.
При выборе режима пропорционального распределения обязательно ознакомьтесь с нашими рекомендациями по замене .
KEEP_EXISTING
У Сэмвайза есть подписка на онлайн-контент в приложении Country Gardener. У него ежемесячная подписка на План 1, включающий базовый контент. Стоимость этой подписки начинается с 2 долларов в месяц в течение 3 месяцев, а затем составляет 4 доллара в месяц. Сэмвайз оформил подписку 1 апреля. Приложение Country Gardener предлагает План 2 в качестве дополнительного специализированного контента за 3 доллара в месяц. 15 апреля Сэмвайз добавил План 2 к своей подписке на приложение Country Gardener, сохранив при этом План 1. График платежей Сэмвайза выглядит следующим образом:
- Цена за план 2 рассчитывается пропорционально и составляет 1,50 доллара США, оплата производится 15 апреля.
- Цена составляет 5,00 долларов США в месяц в течение последующих 2 месяцев и включает в себя как вводную цену для Плана 1, так и обычную цену для Плана 2.
- Впоследствии — регулярный ежемесячный платеж в размере 7,00 долларов.
Внесение изменений в подписку непосредственно в приложении.
Ваше приложение может предложить пользователям обновление или понижение уровня подписки, используя те же шаги, что и при запуске процесса покупки . Однако при обновлении или понижении уровня подписки вам необходимо указать информацию о текущей подписке, будущей (обновленной или пониженной) подписке и режиме замены.
Для замены используйте SubscriptionProductReplacementParams (предпочтительный вариант).
В следующем примере показано, как обновить подписку с помощью SubscriptionProductReplacementParams .
Объект
BillingFlowParams.ProductDetailsParamsтеперь имеет методsetSubscriptionProductReplacementParams()для указания информации о замене на уровне продукта.У
SubscriptionProductReplacementParamsесть два метода-сеттера:-
setOldProductId:Это старый продукт, который будет заменен продуктом из текущихProductDetails. -
setReplacementMode:Это режим замены на уровне элемента. Режимы по сути те же, что и вSubscriptionUpdateParams, но сопоставление значений было обновлено.
-
Существующие параметры обновления уровня покупки, заданные в
BillingFlowParams.setSubscriptionUpdateParams()следует преобразовать вsetOldPurchaseToken().После вызова
setSubscriptionProductReplacementParams()для любого изProductDetailsParams,SubscriptionUpdateParams.setSubscriptionReplacementMode()не будет иметь никакого эффекта.
Приведенный ниже пример кода демонстрирует, как изменить тарифный план подписки с ( old_product_1 , old_product_2 ) на ( product_1 , product_2 , product_3 ). В этом сценарии product_1 заменяет old_product_1 , product_2 заменяет old_product_2 , а product_3 немедленно добавляется к подписке.
Котлин
val billingClient: BillingClient = ... val replacementModeForBasePlan: Int = ... val replacementModeForAddon: Int = ... val purchaseTokenOfExistingSubscription: String = "your_old_purchase_token" // ProductDetails instances obtained from queryProductDetailsAsync(); val productDetailsParams1 = ProductDetailsParams.newBuilder() .setProductDetails(productDetails1_obj) // Required: Set the ProductDetails object .setSubscriptionProductReplacementParams( SubscriptionProductReplacementParams.newBuilder() .setOldProductId("old_product_id_1") .setReplacementMode(replacementModeForBasePlan) .build() ) .build() val productDetailsParams2 = ProductDetailsParams.newBuilder() .setProductDetails(productDetails2_obj) // Required: Set the ProductDetails object .setSubscriptionProductReplacementParams( SubscriptionProductReplacementParams.newBuilder() .setOldProductId("old_product_id_2") .setReplacementMode(replacementModeForAddon) .build() ) .build() // Example for a third item without replacement params val productDetailsParams3 = ProductDetailsParams.newBuilder() .setProductDetails(productDetails3_obj) // Required: Set the ProductDetails object .build() val newProductDetailsList = listOf( productDetailsParams1, productDetailsParams2, productDetailsParams3 ) val billingFlowParams = BillingFlowParams.newBuilder() .setSubscriptionUpdateParams( SubscriptionUpdateParams.newBuilder() .setOldPurchaseToken(purchaseTokenOfExistingSubscription) .build() ) .setProductDetailsParamsList(newProductDetailsList) .build() // To launch the billing flow: // billingClient.launchBillingFlow(activity, billingFlowParams)
Java
BillingClient billingClient = …; int replacementModeForBasePlan =…; int replacementModeForAddon =…; // ProductDetails obtained from queryProductDetailsAsync(). ProductDetailsParams productDetails1 = ProductDetailsParams.newBuilder() .setSubscriptionProductReplacementParams( SubscriptionProductReplacementParams.newBuilder() .setOldProductId("old_product_id_1") .setReplacementMode(replacementModeForBasePlan)) .build(); ProductDetailsParams productDetails2 = ProductDetailsParams.newBuilder() .setSubscriptionProductReplacementParams( SubscriptionProductReplacementParams.newBuilder() .setOldProductId("old_product_id_2") .setReplacementMode(replacementModeForAddon)) .build(); ProductDetailsParams productDetails3 = ...; ArrayListnewProductDetailsList = new ArrayList<>(); newProductDetailsList.add(productDetails1); newProductDetailsList.add(productDetails2); newProductDetailsList.add(productDetails3); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setSubscriptionUpdateParams( SubscriptionUpdateParams.newBuilder() .setOldPurchaseToken(purchaseTokenOfExistingSubscription) .build()) .setProductDetailsParamsList(productDetailsList) .build(); billingClient.launchBillingFlow(billingFlowParams);
Установите параметр SubscriptionUpdateParams для замены (устарело)
В следующем примере показано, как обновить подписку с помощью SubscriptionUpdateParams .
Котлин
val offerToken = productDetails .getSubscriptionOfferDetails(selectedOfferIndex) .getOfferToken() val billingParams = BillingFlowParams.newBuilder().setProductDetailsParamsList( listOf( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() ) ).setSubscriptionUpdateParams( BillingFlowParams.SubscriptionUpdateParams.newBuilder() .setOldPurchaseToken("old_purchase_token") .setSubscriptionReplacementMode( BillingFlowParams.ReplacementMode.CHARGE_FULL_PRICE ) .build() ).build() billingClient.launchBillingFlow( activity, billingParams ) // ...
Java
String offerToken = productDetails .getSubscriptionOfferDetails(selectedOfferIndex) .getOfferToken(); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList( ImmuableList.of( ProductDetailsParams.newBuilder() // fetched via queryProductDetailsAsync .setProductDetails(productDetails) // offerToken can be found in // ProductDetails=>SubscriptionOfferDetails .setOfferToken(offerToken) .build())) .setSubscriptionUpdateParams( SubscriptionUpdateParams.newBuilder() // purchaseToken can be found in Purchase#getPurchaseToken .setOldPurchaseToken("old_purchase_token") .setSubscriptionReplacementMode(ReplacementMode.CHARGE_FULL_PRICE) .build()) .build(); BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); // ...
Рекомендации по замене
В таблице ниже представлены различные сценарии пропорционального распределения нагрузки, а также наши рекомендации для каждого из них:
| Сценарий | Рекомендуемый режим замены | Результат |
|---|---|---|
| Переход на более дорогой тарифный план | CHARGE_PRORATED_PRICE | Пользователь получает доступ немедленно, при этом период оплаты остается тем же. |
| Переход на более дешевый тарифный план | DEFERRED | Пользователь уже оплатил более дорогой тариф, поэтому доступ к сервису сохраняется до следующей даты выставления счета. |
| Обновление во время бесплатного пробного периода с сохранением пробного периода. | WITHOUT_PRORATION | Пользователь переходит на более высокий тарифный план на оставшийся период пробного периода без дополнительной платы. |
| Обновление во время бесплатного пробного периода — прекращение доступа к бесплатному пробному периоду. | CHARGE_PRORATED_PRICE | Пользователь немедленно получает доступ к новому тарифному плану, оставшаяся стоимость бесплатного пробного периода переносится на следующий период. Перенесенная стоимость рассчитывается на основе стоимости базового тарифного плана. |
| Сохранение неизменного графика платежей по некоторым пунктам подписки при добавлении или удалении других пунктов подписки из раздела «Подписка с дополнениями». | KEEP_EXISTING | Пользователь продолжает платить старую цену за неизмененный товар. Новые товары добавляются немедленно. Другие старые товары можно заменить, указав режим замены, или удалить. |
Обработка покупок, изменяющих подписку.
Изменение тарифного плана представляет собой новую покупку во всех отношениях, и его следует оформить и подтвердить соответствующим образом после успешного завершения процесса выставления счетов. Помимо надлежащей обработки новой покупки, необходимо аннулировать покупку, которая заменяется новой.
Внутри приложения поведение такое же, как и при любой новой покупке. Ваше приложение получает результат новой покупки в вашем PurchasesUpdatedListener , и новая покупка становится доступна в queryPurchasesAsync .
API для разработчиков Google Play возвращает linkedPurchaseToken в ресурсе подписки , когда покупка заменяет существующую. Вы можете проверить itemReplacement в SubscriptionPurchaseLineItem в новой покупке, чтобы понять детали замены на уровне элемента. Обязательно аннулируйте токен, предоставленный в linkedPurchaseToken , чтобы гарантировать, что старый токен не будет использован для доступа к вашим сервисам. См. раздел «Обновления, понижения уровня и повторная регистрация» для получения информации об обработке покупок с обновлением и понижением уровня подписки.
После получения нового токена покупки выполните ту же процедуру проверки, что и при проверке нового токена покупки . Обязательно подтвердите эти покупки с помощью BillingClient.acknowledgePurchase() из библиотеки Google Play Billing или Purchases.subscriptions:acknowledge из API разработчиков Google Play.
Обработка отложенной замены
Режим отложенной замены позволяет пользователю использовать оставшийся лимит средств по старому тарифному плану, прежде чем перейти на новый тарифный план.
При использовании ReplacementMode.DEFERRED для новой покупки, queryPurchasesAsync() возвращает новый токен покупки после завершения процесса покупки, который остается связанным со старым продуктом до тех пор, пока отложенная замена не произойдет в следующую дату продления, после чего возвращается новый продукт.
Ранее подобный пользовательский интерфейс можно было реализовать с помощью устаревшего метода ProrationMode.DEFERRED , но ProrationMode.DEFERRED устарел начиная с Play Billing Library 6. См. следующую таблицу, чтобы понять, в чем заключаются различия в поведении:
Время | ProrationMode.DEFERRED (устарело) | ReplacementMode.DEFERRED |
Сразу после успешного завершения процесса покупки (в приложении) | Право на использование старого тарифного плана сохраняется до следующей даты продления. Чтобы гарантировать предоставление приложению правильных прав, Новый токен для покупки пока не отображен, поэтому его обработка невозможна. | Новый токен для покупки уже доступен, поэтому его следует обработать , учитывая сроки замены. |
Сразу после успешного завершения процесса покупки (в бэкэнде) | SUBSCRIPTION_PURCHASED RTDN не отправляется после завершения процесса покупки. Бэкенд еще не уведомлен о новой покупке. | SUBSCRIPTION_PURCHASED RTDN со старым product_id отправляется сразу после завершения процесса покупки для получения нового токена покупки. Вызов метода purchases.subscriptionsv2.get с новым токеном покупки возвращает покупку, содержащую поле 'startTime', указывающее время покупки, и две позиции в списке покупок:
Сообщение SUBSCRIPTION_EXPIRED отправлено для старого токена покупки. При вызове метода purchases.subscriptionsv2.get со старым токеном покупки он отображается как просроченный (право на использование старого плана переносится на новую покупку на оставшееся время). |
При замене — первое продление после завершения процесса покупки (приложения). | Новый токен для покупки теперь доступен, поэтому его следует обработать . | Новая покупка должна была быть обработана уже на момент успешного завершения процесса покупки, поэтому приложению не следует предпринимать никаких специальных действий, кроме проверки наличия соответствующих прав. |
При замене — первое продление после завершения процесса покупки (бэкэнд). | Теперь новую покупку можно обработать и подтвердить после отправки первого RTDN-сообщения SUBSCRIPTION_RENEWED. Объект | Новая покупка была обработана и подтверждена, когда для нового токена покупки был отправлен RTDN-сообщение SUBSCRIPTION_PURCHASED, которое было записано как 'startTime'. При использовании ReplacementMode.DEFERRED первое продление происходит по стандартному сценарию, аналогичному любому другому продлению, и вам не нужно обрабатывать специальную логику для замен при наступлении этого события. При вызове метода purchases.subscriptionsv2.get с новым токеном покупки возвращается покупка с двумя позициями :
|
ReplacementMode.DEFERRED should be used from now on instead of the deprecated ProrationMode.DEFERRED, as it presents the same behavior regarding entitlement changes, but offers a way to manage the purchase that is more consistent with behaviors for other new purchases.
Управление клиентами
Using Real-time developer notifications, you can detect in real time when a user decides to cancel. When a user cancels, but before their subscription has expired, you can send them push notifications or in-app messages to ask them to resubscribe.
After a user has cancelled their subscription, you can try to win them back either in your app, or through the Play store. The following table describes various subscription scenarios along with associated winback actions and app requirements.
| Before subscription expiration | After subscription expiration | |||
| В приложении | In Play Store | В приложении | In Play Store | |
| Winback feature | In-app subscription | Восстановить | In-app subscription | Resubscribe |
| User goes through checkout flow | Да | Нет | Да | Да |
| User subscription remains associated with the same SKU | User can sign up for same or different SKU | Да | User can sign up for same or different SKU | Да |
| Creates new purchase token | Да | Нет | Да | Да |
| Enabled by default | Нет | Yes, support required for all devs | Нет | Apps without Billing Library 2.0+: No Apps with Billing Library 2.0+: Yes. Devs can opt-out in Console. |
| When user is charged | If using same SKU: end of current billing period. If using different SKU: depends on proration mode. | End of current billing period | Немедленно | Немедленно |
| Implementation required | Provide a re-signup UI in your app | Detect change in subscription state Deep-link to Play Store | Provide a re-signup UI in your app | Handle out-of-app purchases |
Before subscription expiration - in-app
For subscriptions that have been canceled but have not yet expired, you can allow subscribers to restore their subscription within your app by applying the same in-app product purchase flow as for new subscribers. Ensure your UI reflects that the user has an existing subscription. For example, you might want to display the user's current expiration date and recurring price with a Reactivate button.
Most of the time, you will want to offer the user the same price and SKU they were already subscribed to, as follows:
- Initiate a new subscription purchase with the same SKU.
- The new subscription replaces the old one and renews on the same expiration date. The old subscription is immediately marked as expired.
- As an example, Achilles has a subscription to Example Music App, and the subscription is due to expire on August 1. On July 10, he resubscribes to the one-month subscription at the same price per month. The new subscription is prorated with the remaining credit, is immediately active, and still renews on August 1.
If you would like to offer a different price—for example a new free trial or a winback discount—you can instead offer a different SKU to the user:
- Initiate an upgrade or downgrade with the different SKU using the replacement mode
WITHOUT_PRORATION. - The new subscription replaces the old one and renews on the same expiration date. The user is charged the price of the new SKU, including any introductory prices, on the original expiration date. If the old subscription was created using an obfuscated account ID, that same ID should be passed to the
BillingFlowParamsfor upgrades and downgrades. - As an example, Achilles has a subscription to Example Music App, and the subscription is due to expire on August 1. On July 10, he resubscribes to an annual subscription with an introductory price. The new subscription is immediately active, and the user is charged the introductory price on August 1.
- If you decide to include a free trial or intro price in your winback SKU, ensure that the user is eligible by unchecking the Allow one free trial per app box in the Google Play Console, which restricts the user to getting one free trial per app.
When you receive the purchase token, process the purchase just as you would with a new subscription. Additionally, the Google Play Developer API returns a linkedPurchaseToken in the subscription resource. Be sure to invalidate the token provided in the linkedPurchaseToken to ensure that the old token is not used to gain access to your services.
Before subscription expiration - in Play Store
While the subscription is canceled but still active, users can restore the subscription in the Google Play subscriptions center by clicking Resubscribe (previously Restore ). This keeps the same subscription and purchase token.

For more information on restoring subscriptions, see Restorations .
After subscription expiration - in-app
You can allow expired subscribers to resubscribe within your app by applying the same in-app product purchase flow as for new subscribers. Note the following:
- To offer users a discount, you might want to offer a product ID with special pricing for your subscription, also called a winback SKU . You can provide the offer in your app, or you can notify the user of the offer outside of the app, such as in email.
- To start a winback subscription, launch the purchase flow in your Android app using the Google Play Billing Library. This is the same process as with a new subscription, but you can determine the SKU that is available to the user.
- If you decide to include a free trial or intro price in your winback SKU, ensure that the user is eligible by unchecking the Allow one free trial per app box in the Google Play Console, which restricts the user to getting one free trial per app.
- If the user resubscribes to the same SKU, they are no longer eligible for free trials or introductory price. Ensure that your UI reflects this.
When you receive the purchase token, process the purchase just as you would with a new subscription. You won't receive a linkedPurchaseToken in the subscription resource.
After subscription expiration - in Play Store
If enabled, users can resubscribe to the same SKU for up to one year after expiration by clicking Resubscribe in the Google Play subscriptions center. This generates a new subscription and purchase token.

Re-subscribing is considered an out-of-app purchase, so be sure to follow best practices for properly acknowledging them from your backend .
Promote your subscription
You can create promotion codes to give selected users an extended free trial to an existing subscription. To learn more, see Promo codes .
For free trials, Google Play verifies that the user has a valid payment method before starting the free trial. Some users may see this verification as a hold or charge on their payment method. This hold or charge is temporary and is later reversed or refunded.
After the trial period ends, the user's payment method is charged for the full subscription amount.
If a user cancels a subscription at any time during the free trial, the subscription remains active until the end of the trial, and they aren't charged when the free trial period ends.
Cancel or revoke
You can use the Google Play Developer API to cancel or revoke a subscription. This functionality is also available in the Google Play Console .
Cancel : Users can cancel a subscription on Google Play. You can also provide an option for users to cancel in your app or on your website. Your app should handle these cancellations as described in Cancellations .
Revoke : When you revoke, the user immediately loses access to the subscription. This can be used if, for example, there was a technical error that prevented the user from accessing your product, and the user does not want to continue using the product. Your app should handle these cancellations as described in Revocations .
The following table illustrates the differences between cancel and revoke.
| Stops renewal | Отменить доступ | |
| Отмена | Да | Нет |
| Отменить | Да | Да |
Defer billing for a subscriber
You can extend the entitlement period for a subscription by using the subscriptionsv2.defer method. When you defer a subscription with add-ons, all items in the subscription are deferred by the same duration. During the deferral period, the user is subscribed to your content with full access but is not charged. The subscription renewal date is updated to reflect the new date.
For prepaid plans, you can use the defer billing API to defer the expiration time.
Deferred billing lets you do the following:
- Give users free access as a special offer, such as giving one week free for purchasing a movie.
- Give free access to customers as a gesture of goodwill.
Billing can be deferred by as little as one day and by as long as one year per API call. To defer the billing even further, you can call the API again before the new billing date arrives.
As an example, Darcy has a monthly subscription to online content for the Fishing Quarterly app. She is normally billed £1.25 on the first of each month. In March, she participated in an online survey for the app publisher. The publisher rewards her with six free weeks by deferring the next payment until May 15, which is six weeks after her previously scheduled billing date of April
- Darcy is not charged for April or the beginning of May and still has access to the content. On May 15, she is charged the normal £1.25 subscription fee for the month. Her next renewal date is now June 15.
When deferring, you might want to notify the user by email or within the app to notify them that their billing date has changed.
Handling payment declines
If there are payment issues with a subscription renewal, Google will periodically attempt to renew the subscription for some time before canceling. This recovery period can consists of a grace period, followed by an account hold period. During this time, Google sends the user emails and notifications prompting them to update their payment method.
Upon payment decline, the subscription enters a grace period if one is configured. During the grace period, you should ensure the user still has access to the subscription entitlements.
After any grace period has ended, the subscription enters an account hold period. During account hold, you should ensure the user does not have access to the subscription entitlements.
You can specify the length of each auto-renewing base plan's grace period and account hold in the Google Play Console. Specifying lengths less than the default values may reduce the number of subscriptions recovered from payment declines.
To maximize the likelihood of subscription recovery during a payment decline, you can inform your user of a payment issue and ask them to fix it.
You can either do this yourself, as described in the grace period and account hold sections, or you can implement the in-app messaging API, where Google shows a message to users in your app.
In-app messaging
If you've enabled in-app messaging with InAppMessageCategoryId.TRANSACTIONAL , Google Play will show users messaging during grace period and account hold once per day and provide them an opportunity to fix their payment without leaving the app.

We recommend that you call this API whenever the user opens the app to determine whether the message should be shown.
If the user successfully recovered their subscription, you will receive a response code of SUBSCRIPTION_STATUS_UPDATED along with a purchase token. You should then use this purchase token to call the Google Play Developer API and refresh the subscription status in your app.
Integrate in-app messaging
To show in-app messaging to user, use BillingClient.showInAppMessages() .
Here is an example of triggering the in-app messaging flow:
Котлин
val inAppMessageParams = InAppMessageParams.newBuilder() .addInAppMessageCategoryToShow(InAppMessageCategoryId.TRANSACTIONAL) .build() billingClient.showInAppMessages(activity, inAppMessageParams, object : InAppMessageResponseListener() { override fun onInAppMessageResponse(inAppMessageResult: InAppMessageResult) { if (inAppMessageResult.responseCode == InAppMessageResponseCode.NO_ACTION_NEEDED) { // The flow has finished and there is no action needed from developers. } else if (inAppMessageResult.responseCode == InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED) { // The subscription status changed. For example, a subscription // has been recovered from a suspend state. Developers should // expect the purchase token to be returned with this response // code and use the purchase token with the Google Play // Developer API. } } })
Java
InAppMessageParams inAppMessageParams = InAppMessageParams.newBuilder() .addInAppMessageCategoryToShow(InAppMessageCategoryId.TRANSACTIONAL) .build(); billingClient.showInAppMessages(activity, inAppMessageParams, new InAppMessageResponseListener() { @Override public void onInAppMessageResponse(InAppMessageResult inAppMessageResult) { if (inAppMessageResult.responseCode == InAppMessageResponseCode.NO_ACTION_NEEDED) { // The flow has finished and there is no action needed from developers. } else if (inAppMessageResult.responseCode == InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED) { // The subscription status changed. For example, a subscription // has been recovered from a suspend state. Developers should // expect the purchase token to be returned with this response // code and use the purchase token with the Google Play // Developer API. } } });
Handle subscription pending transactions
Pending transactions can happen in initial purchase, top-up, upgrade or downgrade. The subscription purchase starts with the SUBSCRIPTION_STATE_PENDING state before transitioning to SUBSCRIPTION_STATE_ACTIVE . If the transaction is expired or canceled by the user, it goes to SUBSCRIPTION_STATE_PENDING_PURCHASE_EXPIRED . You must and should only update the user's entitlement after the transaction is completed.
Subscription state change for initial purchase with pending transactions is straightforward. Your app receives a Purchase with PENDING state when the user initiates a pending transaction. When the transaction is completed, your app receives the Purchase again with state updated to PURCHASED . A SubscriptionNotification message with type SUBSCRIPTION_PURCHASED is sent to your RTDN client. Follow the normal process to verify the purchase, give the user access to the content and acknowledge the purchase. If the transaction expires or is canceled, a SubscriptionNotification message with type SUBSCRIPTION_PENDING_PURCHASE_CANCELED is sent to your RTDN client. In such cases, the user should never have gained access to the content.
Top-up, upgrade or downgrade with pending transactions involves state changes for both the old and new subscriptions. When the user initiates a pending top-up, upgrade or downgrade transaction, your app receives a Purchase for the old subscription with a PendingPurchaseUpdate object. At this time, the user is still owning the old subscription and has not gained the new subscription yet. Calling getProducts() and getPurchaseToken() on the PendingPurchaseUpdate object returns the product ids and purchase token of the new subscription. When the transaction is completed, your app receives a Purchase with the top-level purchase token set for the new subscription and the state set to PURCHASED . A SubscriptionNotification message with type SUBSCRIPTION_PURCHASED is sent to your RTDN client. Only at this time, you should replace the old purchase token with the new purchase token and update the user's access to the content. If the transaction expires or is canceled, a SubscriptionNotification message with type SUBSCRIPTION_PENDING_PURCHASE_CANCELED is sent to your RTDN client. In such cases, the user should still have access to the content of the old subscription.