Внедрите замену подписок на Google Play Billing.

1. Введение

В этом практическом занятии вы научитесь использовать библиотеку Google Play Billing Library (PBL) для управления изменениями тарифных планов подписки. Вы узнаете, как различные режимы замены влияют на ценообразование и права пользователей, а также научитесь обрабатывать уведомления разработчиков в режиме реального времени (RTDN) на бэкэнде.

Аудитория

Этот практический курс, разработанный для разработчиков Android-приложений, предоставляет рекомендации по реализации сложных функций управления подписками. Он поможет вам обеспечить пользователям удобный процесс обновления, понижения или перехода между различными тарифными планами подписки.

Что вы узнаете...

  • Как создать подписки в консоли разработчика Play .
  • Как выбрать правильный ReplacementMode (например, WITH_TIME_PRORATION или DEFERRED ), соответствующий политике обновления и понижения версии вашего приложения.
  • Как настроить BillingFlowParams в launchBillingFlow для запуска процесса покупки в Google Play при смене тарифного плана.
  • Как использовать уведомления для разработчиков в реальном времени (RTDN) и API purchases.subscriptionsv2 для безопасного отзыва старого доступа и предоставления нового доступа к вашей серверной части.

Что вам понадобится

2. Создайте демонстрационное приложение.

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

  • Интеграция приложения с PBL
  • Внедрить замену подписок

Если вы уже знакомы с заменой подписок и PBL, вы можете скачать демонстрационное приложение и поэкспериментировать с ним.

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

Предварительные требования

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

Строить

Для сборки тестового приложения в соответствии с требованиями практического занятия:

  1. Загрузите пример приложения с GitHub .
  2. Обновите applicationId в build.gradle тестового приложения, указав идентификатор приложения (Application Id) в консоли разработчика Play.
  3. Создайте демонстрационное приложение .
    Примечание : Приложение успешно собрано для локального тестирования. Однако при запуске приложения не отображаются товары и цены, поскольку необходимые подписки еще не созданы в консоли разработчика Play. В следующем разделе будет рассмотрено создание подписок в консоли разработчика.

3. Создайте подписки в Play Console.

Система подписок Google Play предоставляет гибкие возможности для создания, управления и продажи подписок. В консоли Play вы можете настраивать подписки с несколькими базовыми планами, каждый из которых содержит несколько предложений. Предложения подписок могут иметь различные модели ценообразования и варианты участия. В рамках этого практического занятия вы создадите три подписки: Премиум-план , Базовый план и Лайт-план , имитируя типичное предложение подписки по разным ценам. Каждая из них будет иметь один базовый план с ежемесячной оплатой.

Создать новую подписку

Для создания новой подписки

  1. Откройте Play Console и перейдите на страницу «Подписки» ( Монетизировать через Play > Продукты > Подписки ).
  2. Нажмите «Создать подписку» .
  3. Введите данные вашей подписки:
    • ProductID : Введите уникальный идентификатор продукта. Введите premium_plan .
    • Название : Введите краткое название подписки. Пример: Premium Plan .
  4. Нажмите «Создать» .

Создайте базовый план.

  1. Откройте Play Console и перейдите на страницу «Подписки» ( Монетизировать через Play > Продукты > Подписки ).
  2. Рядом с подпиской, для которой вы хотите создать базовый тарифный план, нажмите стрелку вправо, чтобы просмотреть подробную информацию о подписке.
  3. Нажмите «Добавить базовый план» .
  4. Введите идентификатор базового тарифного плана . Например: monthly-auto-renewing .
  5. Выберите тип подписки «Автоматическое продление» .
  6. Для базового тарифного плана с автоматическим продлением установите следующие параметры:
    • Период выставления счетов: ежемесячно .
    • Льготный период: 7 дней .
    • Изменения в тарифном плане и предложении: оплата производится в дату выставления счета .
    • Повторная подписка: Разрешить .
  7. В разделе «Цена и доступность» нажмите «Установить цены» , чтобы задать цену базового тарифного плана.
  8. Выберите все страны и регионы, а затем нажмите «Установить цену» .
  9. Установите цену в 10 долларов за этот базовый тарифный план и нажмите «Обновить» .
  10. После того, как цена базового тарифного плана будет установлена, в правом нижнем углу нажмите «Сохранить» , а затем «Активировать» .

Создайте подписки для демонстрационного приложения.

Для целей данного практического занятия создайте две дополнительные подписки со следующей конфигурацией:

  • Базовый план
    • Идентификатор продукта: basic_plan
    • Название: Базовый тарифный план
    • Идентификатор базового плана: monthly-auto-renew
    • Цена: 5 долларов
  • План "Лайт"
    • Идентификатор продукта: lite_plan
    • Название: План "Лайт"
    • Идентификатор базового плана: monthly-auto-renew
    • Цена: 3 доллара

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

Видео о создании подписки

В следующем видео показаны описанные ранее шаги по созданию подписки в консоли разработчика Play.

4. Замена подписки

Разработчики, интегрирующие PBL, могут предоставить своим существующим подписчикам различные варианты изменения тарифного плана для более точного удовлетворения их потребностей:

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

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

Библиотека Play Billing предоставляет несколько параметров ReplacementMode для управления этим поведением.

Доступные режимы замены

  • WITH_TIME_PRORATION : Подписка немедленно повышается или понижается в статусе. Оставшееся время корректируется в зависимости от разницы в цене и зачисляется на счет новой подписки путем обновления даты следующего выставления счета. Это поведение по умолчанию .
  • CHARGE_PRORATED_PRICE : Подписка обновляется немедленно, а цикл выставления счетов остается прежним. Разница в цене за оставшийся период затем списывается с пользователя.
  • CHARGE_FULL_PRICE : Подписка немедленно повышается или понижается, и с пользователя немедленно взимается полная стоимость новой подписки. Остаток средств от предыдущей подписки либо переносится на ту же подписку, либо рассчитывается пропорционально времени при переходе на другую подписку.
  • WITHOUT_PRORATION : Подписка немедленно повышается или понижается в статусе, и новая цена взимается при продлении подписки. Расчетный цикл остается прежним.
  • DEFERRED : Изменение уровня подписки на более высокий или низкий только при продлении подписки.

5. С_пропорциональным_распределением_по_времени

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

Пример сценария

Пользователь переходит с базового тарифа (4,99 доллара в месяц) на премиум- тариф (9,99 доллара в месяц) 15 апреля, что приходится на середину расчетного периода.

В этом сценарии:

  • Пользователь получает доступ к тарифному плану «Премиум» немедленно.
  • Google Play автоматически рассчитывает период пропорционального распределения. Допустим, если Play подсчитал, что оставшиеся 15 дней базового плана эквивалентны 7 дням премиум- плана, то следующая дата выставления счета переносится на 21 апреля.
  • От пользователя не требуется немедленная оплата.

Фрагмент кода

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Обновить с помощью WITH_TIME_PRORATION

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план «Премиум» .

Пользователь немедленно переходит на тарифный план «Премиум» . Сумма, подлежащая немедленной оплате пользователем, составляет 0,00 долларов США. Остаток стоимости базового плана пропорционально распределяется на период действия тарифного плана «Премиум» , что приводит к переносу даты следующего продления. С пользователя будет списана сумма продления в размере 9,99 долларов США в новую скорректированную дату выставления счета.

Понижение версии с помощью WITH_TIME_PRORATION

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план Lite .

Право пользователя на использование тарифного плана немедленно понижается до тарифного плана Lite . Сумма к немедленной оплате составляет 0,00 долларов США. Оставшаяся стоимость тарифного плана Basic пропорционально пересчитывается на время действия тарифного плана Lite , что значительно продлевает дату следующего продления. С пользователя будет списана сумма продления в размере 2,99 долларов США в новую скорректированную дату выставления счета.

Заключение

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

6. CHARGE_PRORATED_PRICE

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

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

Пример сценария

Пользователь, использующий базовый тариф (4,99 доллара в месяц), решает перейти на премиум- тариф (9,99 доллара в месяц) 20 апреля, когда до конца его расчетного периода остается около 10 дней.

В этом сценарии:

  • Пользователь получает доступ к тарифному плану «Премиум» немедленно.
  • С пользователя немедленно взимается пропорциональная разница за оставшиеся 10 дней текущего расчетного периода. Это составляет приблизительно 2,99 доллара США, что соответствует стоимости 10 дней действия тарифного плана Premium .
  • Дата выставления счета пользователю не меняется.

Фрагмент кода

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Обновите подписку с помощью CHARGE_PRORATED_PRICE

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план «Премиум» .

Пользователь немедленно переходит на тарифный план Premium , при этом сохраняется первоначальная дата продления. Сумма, подлежащая немедленной оплате, представляет собой пропорциональную разницу между ценами тарифных планов Premium и Basic за оставшиеся дни текущего цикла. В дату продления с пользователя будет списана полная стоимость продления тарифного плана Premium в размере 9,99 долларов США.

Понижение уровня с помощью ПРОПОРЦИОНАЛЬНОЙ ЦЕНЫ

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план Lite .

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

Заключение

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

7. ПОЛНАЯ ЦЕНА

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

Пример сценария

Пользователь использует базовый тарифный план (4,99 доллара США в месяц, начиная с 1 апреля). 20 апреля пользователь хочет перейти на премиум-тарифный план (9,99 доллара США в месяц).

В этом сценарии:

  • С пользователя немедленно взимается полная стоимость тарифного плана Premium (9,99 долларов США).
  • Оставшаяся стоимость базового плана (например, 10 дней) конвертируется в эквивалентное время для премиум- плана. В этом примере 10 дней базового плана эквивалентны 5 дням премиум-плана .
  • Следующая дата продления подписки пользователя корректируется с учетом этого пропорционального периода. Таким образом, дата продления становится 25 мая (20 апреля + 1 месяц + 5 дней).
  • Последующие продления будут осуществляться ежемесячно, начиная с 25 мая.

Фрагмент кода

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Обновите подписку, заплатив полную цену.

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план «Премиум» .

Пользователь немедленно переходит на тарифный план Premium . Сумма, подлежащая немедленной оплате, составляет полную стоимость тарифного плана Premium - 9,99 долларов США. Любая оставшаяся сумма с базового плана конвертируется в время действия нового тарифного плана Premium , что немного продлевает дату первого продления. В дальнейшем стоимость продления составит 9,99 долларов США за цикл.

Понижение уровня с оплатой полной цены

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план Lite .

Пользователь немедленно переходит на тарифный план Lite , и начинается новый расчетный цикл. Сумма к оплате составляет полную целевую цену в размере 2,99 доллара США. Неиспользованная часть базового тарифного плана пропорционально пересчитывается на новый тарифный план Lite , продлевая дату первого продления. После этого сумма продления составит 2,99 доллара США за цикл.

Заключение

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

8. БЕЗ ПРОПОРЦИОНАЛЬНОГО РАСПРЕДЕЛЕНИЯ

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

Пример сценария

Пользователь использует базовый тарифный план (4,99 доллара США в месяц, начиная с 1 апреля). 20 апреля пользователь хочет перейти на премиум-тарифный план (9,99 доллара США в месяц).

В этом сценарии:

  • Пользователь получает доступ к тарифному плану «Премиум» немедленно.
  • Пользователю не нужно платить более высокую цену в размере 9,99 долларов США до следующей даты продления подписки (1 мая).

Фрагмент кода

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Обновление с параметром WITHOUT_PRORATION

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план «Премиум» .

Пользователь немедленно переходит на тарифный план Premium , при этом сохраняется существующая дата продления. Сумма к оплате составляет 0,00 долларов США. Пользователь имеет доступ к тарифному плану Premium на оставшееся время в данном цикле, после чего в следующую дату выставления счета будет произведено продление по новой цене в 9,99 долларов США.

Понижение версии с параметром WITHOUT_PRORATION

Для моделирования данного сценария:

  • В MainActivity ) обновите параметр replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план Lite .

Пользователь немедленно переходит на тарифный план Lite , теряя при этом базовые функции, за которые он заплатил. Сумма к оплате составляет 0,00 долларов США. Расчетный период продолжается без изменений, и пользователь будет платить по новой, более низкой ставке в размере 2,99 долларов США при следующем плановом продлении.

Заключение

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

9. ОТЛОЖЕНО

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

Пример сценария

Пользователь использует базовый тарифный план (4,99 доллара США в месяц, начиная с 1 апреля). 20 апреля пользователь хочет перейти на премиум-тарифный план (9,99 доллара США в месяц).

В этом сценарии:

  • С пользователя не взимается никакая плата сразу.
  • Пользователь продолжает получать доступ к базовым функциям до конца текущего расчетного периода (30 апреля).
  • При следующем продлении подписки (1 мая) она автоматически переходит на тарифный план Premium .

Фрагмент кода

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.DEFERRED;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

Обновление с отсрочкой

Для моделирования данного сценария:

  • В MainActivity ) обновите replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.DEFERRED .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план «Премиум» .

Пользователь останется на базовом тарифном плане до конца текущего расчетного периода. Сумма к оплате составляет 0,00 долларов США. В дату продления подписки пользователь переходит на премиум- план, и с него будет списана новая сумма продления в размере 9,99 долларов США.

Понижение статуса с отсрочкой

Для моделирования данного сценария:

  • В MainActivity ) обновите replacementMode в приведенном фрагменте кода, установив значение SubscriptionProductReplacementParams.ReplacementMode.DEFERRED .
  • Пересоберите и запустите приложение.
  • Отмените существующие подписки (если таковые имеются) в Google Play Store, и они истекут.
  • Приобретите базовый тарифный план.
  • Переключитесь на тарифный план Lite .

Пользователь останется на базовом тарифе до конца текущего расчетного периода. Сумма к оплате составляет 0,00 долларов США. В дату продления подписки пользователь переходит на тарифный план Lite , и с него будет списана новая сумма продления в размере 2,99 долларов США.

Заключение

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

10. Обработка на стороне бэкэнда и клиента

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

Пример сценария

  • У пользователя установлен базовый ежемесячный тарифный план (product_id basic_plan и purchase_token basic_purchase_token_123 ).
  • Пользователь переключается на тарифный план Premium , используя режим немедленной замены (один из вариантов: WITHOUT_PRORATION , WITH_TIME_PRORATION , CHARGE_PRORATED_PRICE , CHARGE_FULL_PRICE ).
  • После успешного переключения подписки Google рассматривает её как НОВУЮ подписку и создаёт новый, отличный от предыдущего, токен покупки для Премиум-плана (product_id premium_plan и purchase_token premium_purchase_token_123 ).

Обработка на стороне клиента

onPurchasesUpdated

Когда замена покупки завершается, срабатывает обработчик PurchasesUpdatedListener . Несмотря на то, что это была замена , Google Play рассматривает план Premium как совершенно новую покупку .

Приложение получит объект Purchase , содержащий токен покупки premium_purchase_token_123 и идентификатор продукта premium_plan . Вы должны обрабатывать это точно так же, как и нового подписчика: проверить токен и подготовиться к предоставлению доступа.

@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
        for (Purchase purchase : purchases) {
            // purchase.getPurchaseToken() = premium_purchase_token_123
            // purchase.getProducts() will contain premium_plan
            // Verify the purchase and grant entitlement
            handleNewPurchase(purchase);
        }
    }
}

queryPurchasesAsync

queryPurchasesAsync возвращает только активные подписки, приобретенные в вашем приложении. Вы должны полагаться на этот метод для определения того, какую подписку показывать пользователю. Для немедленной замены queryPurchasesAsync() перестанет возвращать старый токен покупки BASIC и будет возвращать только новый токен покупки PREMIUM .

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

Обработка данных на стороне сервера (RTDN)

При замене Google Play отправляет уведомление для разработчиков в режиме реального времени (RTDN) на настроенную вами тему публикации/подписки.

  • В случае немедленной замены Google отправляет RTDN-сообщение SUBSCRIPTION_PURCHASED с новым токеном покупки. Пример полезной нагрузки RTDN.
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • Когда ваш сервер получит новый токен покупки от RTDN, вызовите API purchases.subscriptionsV2 , используя этот новый токен, чтобы получить подробную информацию о покупке. Ответ API содержит поле linkedPurchaseToken , которое используется для определения того, относится ли токен покупки к новой покупке подписки или к замене подписки.
  • В случае замены подписки linkedPurchaseToken ссылается на токен покупки старой подписки. В этом случае это будет basic_purchase_token_123 . Пример ответа GET purchases.subscriptionsV2
    curl \
    'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/<application_id>/purchases/subscriptionsv2/tokens/premium_purchase_token_123' \
    --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
    --header 'Accept: application/json'
    
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "...",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123", // The purchase token of the subscription that was replaced (Basic Plan in this case)
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // productID of the new subscription (Premium Plan in this case)
          "expiryTime": "....",
          "autoRenewingPlan": {...},
          "offerDetails": {
            "basePlanId": "monthly-auto-renewing" // base plan ID of the new subscription
          },
          "itemReplacement": { // Details about the subscription replacement
            "productId": "subscription_basic", // productID of the old subscription (Basic Plan in this case)
            "replacementMode": "CHARGE_PRORATED_PRICE", // Replacement strategy used for this subscription change
            "basePlanId": "monthly-auto-renewing" // base plan ID of the old subscription 
          },
          "offerPhase": {...}
        }
      ],
      "etag": "<etag_value>"
    }
    
  • Необходимо подтвердить новую покупку Premium. Это можно сделать либо в самом приложении, либо в административной панели. Неподтверждение покупки в течение 3 дней приведет к возврату средств и аннулированию права на использование сервиса. Для получения более подробной информации об обработке и подтверждении покупок обратитесь к документации для разработчиков .

Заключение

В этом разделе описаны шаги по обработке немедленной замены подписки как на стороне клиента, так и на стороне бэкэнда. Вы узнали, что Google Play рассматривает новый план как совершенно новую покупку, выдавая новый токен покупки. На стороне клиента необходимо обработать эту новую покупку с помощью PurchasesUpdatedListener и обновить права доступа на основе ответа от queryPurchasesAsync . На стороне бэкэнда следует отслеживать RTDN-сообщения SUBSCRIPTION_PURCHASED для нового токена, использовать API purchases.subscriptionsv2 для идентификации linkedPurchaseToken старой подписки и незамедлительно отозвать доступ, связанный со старым токеном, одновременно предоставляя новые права доступа. Не забывайте всегда подтверждать новую покупку.

11. Обработка отложенных замен.

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

Пример сценария

  • У пользователя есть базовый ежемесячный тарифный план (product_id basic_plan и purchase_token basic_purchase_token_123 ), который продлевается 15 апреля .
  • 1 апреля пользователь решает перейти на тарифный план Premium , используя ReplacementMode.DEFERRED .
  • Google немедленно создает НОВЫЙ токен покупки для тарифного плана Premium (product_id premium_plan и purchase_token premium_purchase_123 ), но сумма, которая будет списана с пользователя, и право на использование плана запланированы на 15 апреля .

Процесс отложенной замены

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

  • PurchasesUpdatedListener вызывается после завершения процесса покупки. Приложение получит объект Purchase , содержащий новый токен покупки premium_purchase_token_123 , однако product_id по-прежнему будет ссылаться на старый basic_plan , поскольку пользователь имеет право только на базовый план. Необходимо рассматривать это как новую покупку и подтвердить токен.
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                // purchase.getPurchaseToken() = premium_purchase_token_123
                // purchase.getProducts() will contain basic_plan
                // Verify and acknowledge the purchase
                handleNewPurchase(purchase);
            }
        }
    }
    
  • queryPurchasesAsync возвращает покупку с новым токеном покупки ( premium_purchase_token_123 ) сразу же, а также исходное право на использование тарифного плана ( basic_plan ), связанное с ней. Вы можете полагаться на это для дальнейшего предоставления пользователю права на использование тарифного плана Basic .

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

  • RTDN- сообщение SUBSCRIPTION_PURCHASED отправляется сразу после завершения процесса покупки нового токена покупки ( premium_purchase_token_123 ). Пример полезной нагрузки RTDN.
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • Для получения сведений о покупке выполните запрос GET purchases.subscriptionsv2 , используя новый токен покупки. В ответе содержится 2 позиции.
    • Один из элементов представляет собой старую подписку (базовый план) и имеет expiryTime в будущем. Старая подписка не будет продлена и содержит элемент deferredItemReplacement , включающий новую подписку (премиум-план). Это указывает на ожидающую замену старого права доступа по истечении срока его действия.
    • Один из вариантов представляет собой недавно приобретенную подписку. Для параметра 'expiryTime' значение не задано.
    Пример ответа API
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "2026-05-07T15:50:11.383Z",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123",
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // Premium Plan has no expiry time
          "autoRenewingPlan": {...},
          "offerDetails": {...},
          "itemReplacement": {. // Subscription replacement details
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {}
        },
        {
          "productId": "basic_plan", // Subscription to be replaced
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time in the future
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "deferredItemReplacement": { // identifier indicating this subscription will be replaced upon renewal
            "productId": "subscription_premium"
          },
          "latestSuccessfulOrderId": "GPA.<order_id>",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    
  • Необходимо подтвердить новый токен покупки. Это можно сделать либо в приложении, либо на бэкэнде. Для получения более подробной информации об обработке и подтверждении покупок обратитесь к документации для разработчиков .
  • SUBSCRIPTION_EXPIRED RTDN отправляется для старого токена покупки ( basic_purchase_token_123 ). Пример полезной нагрузки RTDN.
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":13, // SUBSCRIPTION_EXPIRED
        "purchaseToken":"basic_purchase_token_123" //purchase token for the old subscription
      }
    }
    
  • При вызове API GET purchases.subscriptionsv2 со старым токеном покупки он отображается как просроченный ( SUBSCRIPTION_STATE_EXPIRED ). Право на использование старого плана переносится на новую покупку на оставшееся время.

3. В дату замены — первое продление после завершения процесса покупки (приложения).

  • queryPurchasesAsync возвращает объект purchase с новым токеном покупки ( premium_purchase_token_123 ) и новой подпиской, связанной с ним ( premium_plan ).
  • Обработка новой покупки должна была завершиться успешно, поэтому вам не нужно предпринимать никаких специальных действий, кроме как убедиться, что пользователю предоставлен доступ к нужной подписке.

4. В дату замены — первое продление после завершения процесса покупки (бэкэнд).

  • При использовании ReplacementMode.DEFERRED первое продление подписки происходит по стандартному сценарию, аналогичному поведению любого другого продления, обрабатывающего RTDN-номера SUBSCRIPTION_RENEWED . В этом случае вам не потребуется специальная логика для замен.
  • Для получения сведений о покупке выполните запрос GET purchases.subscriptionsv2 , используя новый токен покупки. В ответе содержится 2 позиции.
    • Один из вариантов представляет собой старую подписку (базовый план) и имеет expiryTime в прошлом. В старой подписке больше не будет значения, установленного для поля deferredItemReplacement .
    • Один из вариантов представляет собой новую подписку со expiryTime в будущем и полем autoRenewEnabled , установленным в значение true .
    Пример ответа API
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>..0",
      "linkedPurchaseToken": "basic_purchase_token_123", // purchase token of the old subscription
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // New subscription
          "expiryTime": "2026-05-07T16:00:09.437Z", // Expiry time set in the future
          "autoRenewingPlan": {
            "autoRenewEnabled": true, // Auto Renewing Flag set to True
            "recurringPrice": {...}
          },
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {. // Details of the subscription replacement
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {...}
        },
        {
          "productId": "basic_plan", // Old subscription, Does not contains the deferredItemReplacement field
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time set in the past
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    

Заключение

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

12. Площадка для замены подписки

Функция «Замена тестовой площадки» в демонстрационном приложении позволяет тестировать повышение и понижение уровня подписки для продуктов, настроенных в вашей учетной записи Google Play Console. В этом разделе описывается, как использовать функцию «Замена тестовой площадки» .

Настраивать

Для использования функции «Замена игровой площадки» необходимо выполнить следующие действия:

  • Параметр packageId в вашем файле build.gradle соответствует приложению, настроенному в вашей консоли Google Play.
  • Ваша тестовая учетная запись зарегистрирована в консоли Google Play как учетная запись тестировщика лицензий. Чтобы узнать больше о тестировании лицензий, см. раздел «Тестирование реализации оплаты в вашем приложении» .

Игровая площадка для замены подписки

В демонстрационном приложении есть вкладка «Замена в интерактивной среде» , которая позволяет имитировать изменения подписки. Вы можете запрашивать подписки, определенные в вашей консоли Play Console, и тестировать переключение между ними с использованием различных режимов замены. Эта интерактивная среда поможет вам понять, как различные режимы влияют на циклы выставления счетов и права доступа к вашим подпискам, чтобы вы могли определить, какие варианты лучше всего соответствуют потребностям вашего бизнеса.

Для имитации замен с использованием игровой площадки выполните следующие шаги:

  1. Перейдите на вкладку «Игровая площадка» .
  2. Если у вас есть активная подписка: она будет выделена. Это подписка, которая будет заменена.

Домашняя детская площадка

  1. Если у вас нет активной подписки: вам необходимо сначала её приобрести.
    • В разделе «Игровая площадка» по умолчанию отображаются тарифные планы Basic , Premium и Lite , созданные для этого практического занятия.
    • Чтобы протестировать тарифные планы с другими настройками, заданными в консоли разработчика Play, нажмите «Добавить пользовательский план» , выполните поиск по productId и basePlanId .
    • Приобретите выбранную подписку.
    • Теперь должна отображаться недавно приобретенная активная подписка пользователя. Добавить пользовательскую подписку
  2. Выберите целевую подписку, на которую пользователь хочет переключиться.
  3. Выберите режим замены для перехода.

Выберите режим замены

  1. Нажмите кнопку «Тестовая замена» , чтобы имитировать замену подписки.
  2. В нижней части страницы с информацией о платежах в Google Play должны отображаться подробные данные о замене подписки (например, мгновенные списания и корректировки расчетного периода).

Корзина платежей при замене подписки

  1. Завершите транзакцию.
  2. Перейдите на страницу «Управление подписками» в приложении Play Store, чтобы просмотреть изменения активных подписок, а также подробную информацию об обновленных датах и ​​ценах продления.

управление подписками в Play Store

13. Дальнейшие шаги

Справочная документация

14. Поздравляем!

Поздравляем! Вы успешно внедрили замену подписок с различными режимами пропорционального распределения и настроили обработку переходов между тарифными планами на стороне бэкэнда.

Что вы узнали

  • Как настроить SubscriptionProductReplacementParams с указанием конкретных режимов замены.
  • Разница между немедленным повышением уровня обслуживания и отложенным понижением уровня обслуживания.
  • Как вывести из эксплуатации старые подписные токены с помощью linkedPurchaseToken , используя RTDN.

Опрос

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