О подписках

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

Если вы еще не настроили продукты подписки для своего приложения, см. раздел Создание и настройка продуктов .

Обзор подписок

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

С помощью базовых планов и предложений вы можете создать несколько конфигураций для одного и того же продукта подписки. Например, вы можете создать вводное предложение для пользователей, которые никогда не подписывались на ваше приложение. Аналогичным образом, вы можете создать предложение по обновлению для пользователей, которые уже подписаны.

Подробный обзор подписных продуктов, базовых планов и предложений см. в документации в Справочном центре Play Console .

Библиотека Play Billing поддерживает следующие типы подписок:

  • Подписка на один товар — в этом типе один товар соответствует одному праву. Например, подписка на сервис потоковой музыки.

  • Подписка с дополнениями — в этом случае одна покупка может включать несколько отдельных прав, объединенных в одну. Например, подписка на потоковую музыку и видео. Подробнее о подписке с дополнениями см. в разделе Подписки с дополнениями .

Интеграция предоплаченных планов

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

Для пополнения баланса запустите процесс оплаты так же, как и при первоначальной покупке. Вам не нужно указывать, что покупка является пополнением.

При пополнении предоплаченных тарифов всегда используется режим замены CHARGE_FULL_PRICE , и вам не нужно настраивать этот режим явно. С пользователя сразу списывается плата за полный расчетный период, а его право на использование продлевается на срок, указанный при пополнении.

После пополнения счета следующие поля в объекте результата Purchase обновляются, чтобы отразить последнюю покупку пополнения:

  • Идентификатор заказа
  • Время покупки
  • Подпись
  • Купить токен
  • Признано

Следующие поля Purchase всегда содержат те же данные, что и в исходной покупке:

  • Имя пакета
  • Состояние покупки
  • Продукты
  • Автоматическое продление

Подтверждение предоплаченной покупки

Как и в случае с автоматическим продлением подписки, после покупки необходимо подтвердить предоплаченные тарифы. Необходимо подтвердить как первоначальную покупку, так и любые пополнения. Подробнее см. в разделе «Обработка покупок» .

Ввиду возможного короткого срока действия предоплаченного плана важно подтвердить покупку как можно скорее.

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

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

Интеграция подписок в рассрочку

Подписка в рассрочку — это тип подписки, при котором пользователи платят за подписку несколькими частями в течение определенного периода времени, а не вносят всю стоимость подписки авансом.

Дополнительные соображения по поводу подписки в рассрочку:

  • Доступность в странах : Функция подписки в рассрочку доступна только в Бразилии, Франции, Италии и Испании (уточните информацию о доступности в Консоли).
  • Установка цены : при установке цены на подписку в рассрочку на Консоли цена представляет собой ежемесячный платеж. В сочетании с заданным периодом действия подписки она формирует общую стоимость подписки на экране покупки.
  • Период действия подписки : общая продолжительность действия первоначального обязательства по подписке, в течение которой необходимо вносить ежемесячные платежи. Например, если базовый план имеет 15-месячный период действия, пользователь совершит 15 ежемесячных платежей в течение этого периода.
  • Продление : В контексте подписок с рассрочкой платежа «продление» означает завершение периода действия обязательств, будь то первоначального или последующего. После первоначальной регистрации первое продление происходит по завершении всего первоначального периода действия обязательств. Последующие продления производятся по завершении каждого последующего периода действия обязательств. Типы продления для подписок с рассрочкой платежа могут быть «ежемесячное автоматическое продление» или «автоматическое продление на тот же срок». При ежемесячном автоматическом продлении последующие обязательства отсутствуют, и план функционирует как ежемесячная подписка, где каждая ежемесячная плата за подписку является продлением.
  • Расчетный период : в контексте подписки в рассрочку это относится к повторяющемуся интервалу, с которым производятся отдельные платежи, как указано в базовом плане.
  • Изменение плана и изменение цены : для изменения цены и отмены подписки обязательство является твёрдым. Это означает, что если пользователь хочет отменить подписку или разработчик хочет изменить цену, изменение вступает в силу по окончании периода действия обязательств. Для изменения плана обязательство не является твёрдым. Это означает, что изменение плана не требует ожидания окончания периода действия обязательств, оно вступает в силу либо немедленно, либо на следующую дату платежа в зависимости от установленного способа замены.
  • Изменение плана подписки в рамках одной и той же подписки : изменение плана с рассрочки на базовый план без рассрочки для того же продукта подписки не допускается.
  • Уведомления разработчикам в режиме реального времени (RTDN) : RTDN SUBSCRIPTION_CANCELLATION_SCHEDULED отправляется сразу после отмены подписки пользователем, если платежи по ней остаются на весь период действия обязательств. Отмена находится в режиме ожидания и вступит в силу только по окончании периода действия обязательств. Если пользователь не восстановит подписку, RTDN SUBSCRIPTION_CANCELED и SUBSCRIPTION_EXPIRED отправляются по окончании периода действия обязательств.

  • Выплаты/получение дохода : Выплаты разработчикам будут осуществляться по мере внесения пользователями ежемесячных платежей на тех же условиях, что и для всех остальных подписок. Разработчикам не выплачивается аванс при оформлении пользователем рассрочки.

  • Пропущенные платежи : если пользователь не вносит какие-либо платежи по подписке в рассрочку, ни Google, ни Разработчик не будут пытаться взыскать с пользователя такие пропущенные или непогашенные платежи, за исключением случаев, когда Google может периодически повторять платеж в течение применимого льготного периода или периода блокировки аккаунта в соответствии со своей обычной практикой повторных платежей. Google не несет ответственности перед Разработчиком за любые оставшиеся неоплаченные платежи в рассрочку.

  • Доступность библиотеки Play Billing : поле installmentDetails доступно только для PBL 7 и более поздних версий. Для PBL 5 и более поздних версий информация о подписке в рассрочку возвращается с помощью queryProductDetails() , но подписка не будет содержать подробную информацию о рассрочке, например, количество совершенных платежей по плану.

Используйте глубокие ссылки, чтобы позволить пользователям управлять подпиской

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

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

Используйте следующий URL-адрес, чтобы направить пользователей на страницу, на которой показаны все их подписки, как показано на рисунках 1 и 2:

https://play.google.com/store/account/subscriptions
На экране подписок Play Store отображается статус всех подписок пользователя, оплаченных через Google Play.
Рисунок 1. На экране подписок Play Store отображается статус всех подписок пользователя, оплаченных через Google Play.


Нажмите на подписку, чтобы увидеть дополнительные сведения.
Рисунок 2. Нажмите на подписку, чтобы увидеть дополнительные сведения.

Эта глубокая ссылка может быть полезна, чтобы помочь пользователю восстановить отмененную подписку из центра подписок 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. Это приложение имеет три уровня подписки.

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

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

Режимы замены

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

Режим замены

Описание

Пример использования

Обязательные платежи учтены как оплаченные (для замены подписки в рассрочку)

WITH_TIME_PRORATION

Подписка немедленно повышается или понижается. Оставшееся время корректируется с учетом разницы в цене и засчитывается в счет новой подписки путем переноса даты выставления счета на более ранний срок. Это действие по умолчанию.

Переходите на более дорогой уровень без немедленной дополнительной оплаты.

0

CHARGE_PRORATED_PRICE

Подписка сразу же обновляется, а платёжный цикл остаётся прежним. Разница в цене за оставшийся период списывается с пользователя.

Примечание: эта опция доступна только для обновления подписки, при котором цена за единицу времени увеличивается.

Перейдите на более дорогой тариф, не меняя дату выставления счета.

1

CHARGE_FULL_PRICE

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

Примечание: если новая подписка имеет бесплатную пробную версию или вводное предложение, с пользователя будет взиматься плата в размере 0 долларов США или стоимости вводного предложения (в зависимости от того, что применимо) на момент повышения или понижения уровня подписки.

Переход с более короткого на более длительный расчетный период.

1 (Примечание: 0, если новая подписка имеет бесплатный пробный период.)

WITHOUT_PRORATION

Подписка повышается или понижается немедленно, и новая цена взимается при продлении подписки. Платежный цикл остается прежним.

Перейдите на более высокий уровень подписки, сохранив при этом оставшийся бесплатный период.

0

DEFERRED

Уровень подписки повышается или понижается только при продлении подписки, но новая покупка оформляется немедленно и содержит следующие два товара:

  • Существующий товар с отключенным автоматическим продлением и сроком действия, равным концу текущего расчетного периода.
  • Новый тарифный план, который вступает в силу после истечения срока действия текущего. Вы можете разрешить пользователям вносить дополнительные изменения по их желанию. Например, пользователи могут вернуться к исходному тарифному плану или инициировать новый отложенный тарифный план.

Примечание: для подписок в рассрочку изменение плана происходит в начале следующей даты платежа.

Понижение до менее дорогого уровня.

1

KEEP_EXISTING

График оплаты за подписку при замене остается неизменным.

Добавить или удалить элемент подписки из подписки с дополнениями, когда определенный элемент должен остаться неизменным.

Н/Д

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

Установите режим замены для покупки

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

Повторная подписка или смена плана в пределах одной подписки

Вы можете указать режим замены по умолчанию в консоли 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

Подписка уровня 1 у Samwise действует до истечения срока ее действия 30 апреля. 1 мая вступает в силу подписка уровня 2 , и с Samwise взимается плата в размере 36 долларов США за новый уровень подписки.

Вам следует вызвать слушатель PurchasesUpdatedListener вашего приложения в момент успешного завершения покупки, и вы сможете получить новую покупку в рамках вызова queryPurchasesAsync() . Ваш бэкенд немедленно получит уведомление разработчика в режиме реального времени SUBSCRIPTION_PURCHASED . Вам следует обработать покупку так же, как и любую другую новую покупку в этот момент. В частности, убедитесь, что вы подтвердили новую покупку. Обратите внимание, что startTime новой подписки заполняется в момент вступления в силу замены, то есть по истечении срока действия старой подписки. В этот момент вы получите RTDN SUBSCRIPTION_RENEWED для нового плана подписки. Подробнее о поведении ReplacementMode.DEFERRED читайте в разделе Обработка отложенной замены .

CHARGE_FULL_PRICE

Подписка уровня 1 Сэмвайза немедленно заканчивается. Его подписка уровня 2 начинается сегодня, и с него списывается 36 долларов. Поскольку он оплатил полный месяц, но использовал только половину, к его новой подписке добавляется половина месячной подписки (1 доллар). Поскольку эта новая подписка стоит 36 долларов в год, к сроку подписки (около 10 дней) будет добавлена ​​1/36 часть года. Таким образом, следующий платёж Сэмвайза составит 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)

Ява

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 = ...;

ArrayList newProductDetailsList = 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
   )
// ...

Ява

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 в ресурсе подписки , когда покупка заменяет существующую. Обязательно аннулируйте токен, предоставленный в linkedPurchaseToken , чтобы убедиться, что старый токен не будет использоваться для доступа к вашим сервисам. Подробнее об обработке покупок с повышением и понижением уровня подписки см. в разделе «Обновление, понижение уровня подписки и повторная регистрация» .

Получив новый токен покупки, выполните ту же процедуру проверки, что и при подтверждении нового токена покупки . Обязательно подтвердите эти покупки с помощью BillingClient.acknowledgePurchase() из библиотеки Google Play Billing Library или Purchases.subscriptions:acknowledge из API разработчика Google Play.

Обработка отложенной замены

Режим отложенной замены позволяет пользователю использовать оставшиеся права по старому плану, прежде чем начать использовать новый план.

При использовании ReplacementMode.DEFERRED для новой покупки queryPurchasesAsync() возвращает новый токен покупки после потока покупки, который остается связанным со старым продуктом до тех пор, пока отложенная замена не произойдет в следующую дату продления, после чего новый продукт возвращается.

Раньше такого пользовательского опыта можно было добиться с помощью устаревшего метода ProrationMode.DEFERRED , но ProrationMode.DEFERRED устарел в Play Billing Library 6. Чтобы понять, в чем разница в поведении, см. следующую таблицу:

Время

ProrationMode.DEFERRED (устарело)

ReplacementMode.DEFERRED

Сразу после успешного завершения процесса покупки (приложение)

PurchasesUpdatedListener вызывается после покупки со статусом того, было ли обновление или понижение версии успешным.

Права на старый тариф сохраняются до даты следующего продления. Чтобы приложение предоставило правильные права, queryPurchasesAsync() возвращает объект Purchase с исходным токеном покупки и исходными правами до тех пор, пока не произойдет замена.

Новый токен покупки не обнаружен, поэтому на данном этапе его обработка невозможна.

PurchasesUpdatedListener вызывается после покупки со статусом того, было ли обновление или понижение версии успешным.

queryPurchasesAsync() сразу же возвращает покупку с новым токеном покупки и исходным правом , связанным с ним.

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

Сразу после успешного завершения процесса покупки (бэкэнд)

SUBSCRIPTION_PURCHASED RTDN не отправляется после завершения процесса покупки. Бэкенд ещё не уведомлён о новой покупке.

SUBSCRIPTION_PURCHASED RTDN со старым product_id отправляется сразу после потока покупки для нового токена покупки.

Вызов метода purchases.subscriptionsv2.get с новым токеном покупки возвращает покупку, имеющую «startTime», указывающий время покупки с двумя позициями :

  • Один из них представляет старое право и имеет свойство expiryTime в будущем. Старое право не будет продлено и содержит свойство DeferredItemReplacement, содержащее результат нового права. Это указывает на ожидание замены старого права после истечения его срока действия.
  • Один представляет недавно приобретенный доступ. Значение «expiryTime» для него не задано.

SUBSCRIPTION_EXPIRED отправлено для старого токена покупки. При вызове метода purchases.subscriptionsv2.get со старым токеном покупки он отображается как истёкший (права на подписку по старому плану переносятся на новую покупку на оставшееся время).

При замене — первое продление после покупки (приложение)

queryPurchasesAsync() возвращает новый объект Purchase с новым токеном покупки и правами.

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

queryPurchasesAsync() сразу же возвращает покупку с новым токеном покупки и новым правом , связанным с ним.

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

При замене — первое продление после покупки (бэкэнд)

Новая покупка теперь может быть обработана и подтверждена после отправки первого SUBSCRIPTION_RENEWED RTDN.

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

Новая покупка была обработана и подтверждена, когда SUBSCRIPTION_PURCHASED RTDN был отправлен для нового токена покупки и записан как «startTime».

При использовании ReplacementMode.DEFERRED первые обновления следуют стандартному поведению любого другого обновления, и вам не нужно обрабатывать специальную логику для замен, когда происходит это событие.

При вызове метода purchases.subscriptionsv2.get с новым токеном покупки возвращается покупка с двумя позициями :

  • Один представляет старое право, с `expiryTime` в прошлом и без установленного значения для DeferredItemReplacement .
  • Один представляет новое право с `expiryTime` в будущем и включенным флагом auto_renewing_enabled.

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.

Customer management

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 Подписаться повторно
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 BillingFlowParams for 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.

subscriptions section in the google play store app showing a
            cancelled subscription with a resubscribe button
Figure 8. Account > Subscriptions section in the Google Play Store app showing a cancelled subscription with a Resubscribe button.

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.

subscriptions section in the google play store app showing a
            cancelled and expired subscription with resubscribe and remove
            buttons
Figure 9. Account > Subscriptions section in the Google Play Store app showing a cancelled and expired subscription with Resubscribe and Remove buttons.

Re-subscribing is considered an out-of-app purchase, so be sure to follow best practices for handling purchases made from outside your app .

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 advance the next billing date for an auto-renewing subscriber by using Purchases.subscriptions:defer from the Google Play Developer API. 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

  1. 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.

Snackbar notifying the user to fix their payment
Figure 20. Snackbar notifying the user to fix their payment.

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:

Kotlin

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.
                }
            }
        })

Ява

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.