Implementar substituições de assinaturas com o Google Play Faturamento

1. Introdução

Este codelab ensina a usar a Biblioteca Google Play Faturamento (PBL, na sigla em inglês) para gerenciar mudanças no plano de assinatura. Você vai descobrir como vários modos de substituição afetam os preços e os direitos de acesso dos usuários enquanto aprende a processar Notificações do Desenvolvedor em Tempo Real (RTDNs) de back-end.

Público

Criado para desenvolvedores de apps Android, este codelab oferece orientações sobre a implementação de recursos sofisticados de gerenciamento de assinaturas. As orientações ajudam você a oferecer aos usuários uma experiência perfeita para fazer upgrade, downgrade ou transição entre diferentes planos de assinatura.

O que você vai aprender…

  • Como criar assinaturas no Play Console.
  • Como escolher o ReplacementMode correto (por exemplo, WITH_TIME_PRORATION x DEFERRED) para corresponder às políticas de upgrade e downgrade do seu app.
  • Como configurar BillingFlowParams em launchBillingFlow para acionar o fluxo de compra do Google Play para uma substituição de plano.
  • Como usar as notificações do desenvolvedor em tempo real (RTDN) e a API purchases.subscriptionsv2 para revogar com segurança o acesso antigo e conceder um novo acesso no seu back-end

O que é necessário

2. Criar o app de amostra

Este codelab usa um app Android de exemplo para mostrar como implementar substituições de assinaturas na PBL. O app de exemplo foi projetado para ser um app Android totalmente funcional com o código-fonte completo que mostra os seguintes aspectos:

  • Integrar o app com a PBL
  • Implementar substituições de assinatura

Se você já conhece as substituições de assinaturas e o PBL, baixe o app de exemplo e teste.

O vídeo de demonstração a seguir mostra como o app de exemplo vai aparecer e se comportar depois de implantado e executado.

Pré-requisitos

Antes de criar e implantar o app de exemplo, faça o seguinte:

Criar

Para criar o app de exemplo conforme necessário para seguir o codelab:

  1. Faça o download do app de exemplo no GitHub.
  2. Atualize o applicationId no build.gradle do app de exemplo para refletir o ID do aplicativo no Play Console.
  3. Crie o app de exemplo.
    Observação: isso cria o app para testes locais. No entanto, a execução do app não busca produtos e preços porque as assinaturas necessárias ainda não foram criadas no Play Console. A próxima seção vai abordar a criação de assinaturas no Developer Console.

3. Criar assinaturas no Play Console

O sistema de assinaturas do Google Play oferece flexibilidade para criar, gerenciar e vender assinaturas. No Play Console, é possível configurar assinaturas com vários planos básicos, cada um com várias ofertas. As ofertas de assinatura podem ter vários modelos de preços e opções de qualificação. Neste codelab, você vai criar três assinaturas: Plano Premium, Plano básico e Plano Lite, simulando uma oferta de assinatura típica em vários preços. Cada uma delas terá um único plano básico mensal recorrente.

Criar uma nova assinatura

Para criar uma assinatura

  1. Abra o Play Console e acesse a página "Assinaturas" (Monetizar com o Google Play > Produtos > Assinaturas).
  2. Clique em Criar assinatura.
  3. Insira os detalhes da assinatura:
    • ProductID : insira um ID de produto exclusivo. Insira premium_plan.
    • Nome : insira um nome curto para a assinatura. Exemplo: Premium Plan.
  4. Clique em Criar.

Criar o Base Plan

  1. Abra o Play Console e acesse a página "Assinaturas" (Monetizar com o Google Play > Produtos > Assinaturas).
  2. Ao lado da assinatura em que você quer criar um plano básico, clique na seta para a direita para conferir os detalhes.
  3. Clique em Adicionar plano básico.
  4. Insira um ID do plano básico. Por exemplo, monthly-auto-renewing.
  5. Escolha o tipo Renovação automática.
  6. Para o plano básico com renovação automática, defina o seguinte:
    • Período de faturamento: mensal.
    • Período de carência: 7 dias.
    • Mudanças no plano de faturamento e na oferta: Cobrar na data de faturamento.
    • Renovar a assinatura: Permitir.
  7. Na seção Preço e disponibilidade, clique em Definir preços para definir o preço do plano básico.
  8. Selecione todos os países e regiões e clique em Definir preço.
  9. Defina o preço como US$10 para esse plano básico e clique em Atualizar.
  10. Depois de definir o preço do plano básico, clique em Salvar e em Ativar no canto inferior direito.

Criar assinaturas para o app de exemplo

Para este codelab, crie mais duas assinaturas com a seguinte configuração:

  • Plano básico
    • ID do produto: basic_plan
    • Nome: Plano básico
    • ID do plano básico: monthly-auto-renewing
    • Preço: R$5
  • Plano Lite
    • ID do produto: lite_plan
    • Nome: Plano Lite
    • ID do plano básico: monthly-auto-renewing
    • Preço: US$3

O app de exemplo está configurado para usar esses IDs de produto e de plano básico. É possível criar assinaturas diferentes com configurações diferentes. Nesse caso, você terá que modificar o app de exemplo para usar o ID do produto criado.

Vídeo sobre como criar uma assinatura

O vídeo a seguir mostra as etapas descritas anteriormente para criar uma assinatura no Play Console para desenvolvedores.

4. Substituições de assinatura

Os desenvolvedores que integram com a PBL podem oferecer aos assinantes várias opções para mudar o plano de assinatura e atender melhor às necessidades deles:

  • Se você vende diversos níveis de assinatura, como básica e premium, permita que os usuários mudem de nível comprando o plano básico ou uma oferta de assinatura diferente.
  • Você pode permitir que os usuários mudem o período de faturamento atual, por exemplo, passando do plano mensal para o anual.
  • Também é possível permitir que eles alternem entre os planos pré-pago e com renovação automática.

Quando os usuários decidem fazer upgrade, downgrade ou mudar a assinatura, você especifica um modo de substituição que determina como o valor proporcional do período de faturamento atual é aplicado e quando a mudança de direito de acesso ocorre para os usuários.

A Play Billing Library oferece várias opções de ReplacementMode para controlar esse comportamento.

Modos de substituição disponíveis

  • WITH_TIME_PRORATION: o item da assinatura recebe upgrade ou downgrade imediatamente. Qualquer tempo restante é ajustado com base na diferença de preço e creditado à nova assinatura, atualizando a próxima data de faturamento. Esse é o comportamento padrão.
  • CHARGE_PRORATED_PRICE: o item da assinatura recebe upgrade imediatamente, e o ciclo de faturamento permanece o mesmo. A diferença de preço referente ao período restante é cobrada do usuário.
  • CHARGE_FULL_PRICE: o item de assinatura recebe upgrade ou downgrade imediatamente, e o usuário recebe a cobrança imediata do preço total pelo novo direito de acesso. O valor restante da assinatura anterior é transferido para o mesmo direito de acesso ou um valor proporcional ao tempo é transferido ao mudar para outro.
  • WITHOUT_PRORATION: o item de assinatura recebe upgrade ou downgrade imediatamente, e o novo preço é cobrado quando a assinatura é renovada. O ciclo de faturamento permanece o mesmo.
  • DEFERRED: o item da assinatura só recebe upgrade ou downgrade quando ela é renovada.

5. WITH_TIME_PRORATION

Nesse modo, o item da assinatura é atualizado ou recebe um downgrade imediatamente. Qualquer tempo restante é ajustado com base na diferença de preço e creditado à nova assinatura, adiando a próxima data de faturamento. Esse é o comportamento padrão.

Exemplo de cenário

Um usuário muda de um plano Basic (US$ 4,99 por mês) para um plano Premium (US$ 9,99 por mês) em 15 de abril, que é a metade do ciclo de faturamento mensal.

Nesse caso:

  • O usuário recebe acesso ao plano Premium imediatamente.
  • O Google Play calcula automaticamente o período proporcional. Por exemplo, se o Google Play calcular que os 15 dias restantes do plano Basic valem 7 dias do plano Premium, a próxima data de faturamento será antecipada para 21 de abril.
  • O usuário não precisa fazer um pagamento imediato.

Snippet de código

// 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);

Fazer upgrade com WITH_TIME_PRORATION

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Premium.

O direito do usuário é atualizado para o plano Premium imediatamente. O valor a ser pago imediatamente pelo usuário é de US $0,00. O valor restante do plano Basic é proporcional ao tempo do plano Premium, o que antecipa a próxima data de renovação. O usuário vai receber uma cobrança de US $9,99 na nova data de faturamento ajustada.

Fazer downgrade com WITH_TIME_PRORATION

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Lite.

O direito do usuário é rebaixado imediatamente para o plano Lite. O valor a ser pago imediatamente é de US $0,00. O valor restante do plano Basic é proporcional ao tempo do plano Lite, o que estende significativamente a próxima data de renovação. O usuário vai receber uma cobrança de US $2,99 na nova data de faturamento ajustada.

Conclusão

Nesta seção, você aprendeu como o WITH_TIME_PRORATION modifica os direitos do usuário sem cobranças imediatas, ajustando o tempo até a próxima renovação com base na diferença de preço. É uma estratégia padrão eficaz para fazer upgrade ou downgrade de usuários.

6. CHARGE_PRORATED_PRICE

Nesse modo de substituição, o item da assinatura recebe upgrade imediatamente, e o ciclo de faturamento permanece o mesmo. A diferença de preço referente ao período restante é cobrada do usuário.

Observação: essa opção está disponível apenas para um upgrade de item de assinatura, em que o preço por unidade de tempo aumenta.

Exemplo de cenário

Um usuário do plano Básico (R$ 7,99 por mês) decide fazer upgrade para o plano Premium (R$ 39,99 por mês) em 20 de abril, faltando cerca de 10 dias para o fim do ciclo de faturamento mensal.

Nesse caso:

  • O usuário recebe acesso ao plano Premium imediatamente.
  • O usuário recebe imediatamente a cobrança proporcional da diferença pelos 10 dias restantes do ciclo de faturamento atual. Isso equivale a aproximadamente US $2,99, representando 10 dias do plano Premium.
  • A data de faturamento do usuário não muda.

Snippet de código

// 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);

Fazer upgrade com CHARGE_PRORATED_PRICE

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Premium.

O usuário recebe um upgrade imediato para o plano Premium, mantendo a data de renovação original. O valor a ser pago imediatamente é a diferença proporcional entre os preços dos planos Premium e Basic pelos dias restantes do ciclo atual. Na data de renovação, o valor total da renovação do Premium (US $9,99) será cobrado do usuário.

Fazer downgrade com CHARGE_PRORATED_PRICE

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Lite.

Esse modo de substituição resulta em um erro durante um downgrade porque está disponível apenas para upgrades de itens de assinatura em que o preço por unidade de tempo aumenta. O fluxo de faturamento vai falhar e mostrar um erro ao usuário informando que o modo de cálculo proporcional é indisponível para downgrades.

Conclusão

Esta seção explicou como o CHARGE_PRORATED_PRICE permite upgrades imediatos cobrando dos usuários a diferença exata de preço pelo período de faturamento restante, mantendo o ciclo de faturamento intacto. Isso é útil quando os usuários querem fazer upgrade para um nível mais caro sem mudar a data de faturamento.

7. CHARGE_FULL_PRICE

Nesse modo de substituição, o item de assinatura recebe upgrade ou downgrade imediatamente, e o usuário recebe a cobrança imediata do preço total pelo novo direito de acesso. O valor restante da assinatura anterior é transferido para o mesmo direito de acesso ou um valor proporcional ao tempo é transferido ao mudar para outro.

Exemplo de cenário

Um usuário tem o plano Basic (US$ 4,99 por mês a partir de 1º de abril). Em 20 de abril, o usuário quer mudar para o plano Premium (US$ 9,99 por mês).

Nesse caso:

  • O usuário recebe imediatamente a cobrança do preço total do plano Premium (US$ 9,99).
  • O valor restante do plano Basic (por exemplo, 10 dias) é convertido em tempo equivalente para o plano Premium. Neste exemplo, 10 dias de Básico equivalem a 5 dias de Premium.
  • A próxima data de renovação do usuário é ajustada para incluir esse período proporcional. Portanto, a data de renovação passa a ser 25 de maio (20 de abril + 1 mês + 5 dias).
  • As renovações seguintes vão ocorrer mensalmente a partir de 25 de maio.

Snippet de código

// 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);

Fazer upgrade com CHARGE_FULL_PRICE

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Premium.

O usuário recebe um upgrade imediato para o plano Premium. O valor a ser pago imediatamente é o preço total do plano Premium: US$ 9,99. Qualquer valor restante do plano Basic é convertido em tempo no novo plano Premium, estendendo um pouco a primeira data de renovação. Depois disso, o valor da renovação será de US $9,99 por ciclo.

Fazer downgrade com CHARGE_FULL_PRICE

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Lite.

O usuário recebe um downgrade imediato para o plano Lite, e um novo ciclo de faturamento é iniciado. O valor a ser pago imediatamente é o preço total da meta de US $2,99. A parte não utilizada do plano Basic é proporcional ao tempo do novo plano Lite, estendendo a primeira data de renovação. Depois disso, o valor da renovação será de US $2,99 por ciclo.

Conclusão

Nesta seção, explicamos como o CHARGE_FULL_PRICE cobra do usuário o valor total no dia da troca, iniciando um novo ciclo de faturamento imediatamente. Qualquer saldo restante do plano anterior será aplicado linearmente à próxima data de renovação.

8. WITHOUT_PRORATION

Nesse modo de substituição, o item de assinatura recebe upgrade ou downgrade imediatamente, e o novo preço é cobrado quando a assinatura é renovada.

Exemplo de cenário

Um usuário tem o plano Basic (US$ 4,99 por mês a partir de 1º de abril). Em 20 de abril, o usuário quer mudar para o plano Premium (US$ 9,99 por mês).

Nesse caso:

  • O usuário recebe acesso ao plano Premium imediatamente.
  • O usuário não precisa pagar o preço mais alto de US $9,99 até a próxima data de renovação da assinatura (1º de maio).

Snippet de código

// 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);

Fazer upgrade com WITHOUT_PRORATION

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Premium.

O usuário recebe um upgrade imediato para o plano Premium, mantendo a data de renovação atual. O valor a ser pago imediatamente é de US $0,00. O usuário tem acesso ao plano Premium pelo tempo restante do ciclo atual antes de mudar para o novo valor de renovação de US $9, 99 na próxima data de faturamento.

Fazer downgrade com WITHOUT_PRORATION

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Lite.

O usuário faz downgrade para o plano Lite imediatamente e perde os recursos do Basic que pagou. O valor a ser pago imediatamente é de US $0,00. O ciclo de faturamento continua sem alterações, e o usuário vai pagar a nova taxa mais baixa de US $2, 99 na próxima renovação programada.

Conclusão

Esta seção demonstrou como WITHOUT_PRORATION troca imediatamente os direitos do usuário sem uma cobrança de finalização da compra, deixando o ciclo de faturamento intacto.

9. DEFERRED

Nesse modo de substituição, o item de assinatura só recebe upgrade ou downgrade quando a assinatura é renovada, mas a nova compra é emitida imediatamente. O item atual é definido como não renovável e expira no final do ciclo de faturamento atual, enquanto o direito de acesso recém-solicitado começa logo em seguida.

Exemplo de cenário

Um usuário tem o plano Basic (US$ 4,99 por mês a partir de 1º de abril). Em 20 de abril, o usuário quer mudar para o plano Premium (US$ 9,99 por mês).

Nesse caso:

  • O usuário não recebe uma cobrança imediata.
  • O usuário continua recebendo os recursos do Basic até o fim do ciclo de faturamento atual (30 de abril).
  • O plano de assinatura será atualizado automaticamente para o Premium na próxima data de renovação (1º de maio).

Snippet de código

// 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);

Fazer upgrade com DEFERRED

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Premium.

O usuário vai permanecer no plano Basic até o fim do ciclo de faturamento atual. O valor a ser pago imediatamente é de US $0,00. Na data de renovação, o direito de acesso será atualizado para o plano Premium, e o novo valor de renovação de US $9,99 será cobrado.

Downgrade com DEFERRED

Para simular esse cenário:

  • No MainActivity do app de exemplo, atualize o replacementMode no snippet de código para SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Recrie e inicie o aplicativo.
  • Cancele as assinaturas atuais (se houver) na Google Play Store e deixe que elas expirem.
  • Compre o plano Basic.
  • Mude para o plano Lite.

O usuário vai permanecer no plano Basic até o fim do ciclo de faturamento atual. O valor a ser pago imediatamente é de US $0,00. Na data de renovação, o direito será atualizado para o plano Lite, e o novo valor de renovação de US $2,99 será cobrado.

Conclusão

Esta seção explicou como o modo de substituição DEFERRED adia um upgrade ou downgrade até o final do período pago de um usuário ativo. Isso o torna ideal para fazer downgrade e evitar a perda de recursos já comprados.

10. Processamento de back-end e do lado do cliente

Depois que um usuário aciona uma substituição de assinatura bem-sucedida, verifique se o app e o back-end processam corretamente a mudança para evitar problemas como interrupções de serviço ou cobrança dupla.

Exemplo

  • O usuário tem um plano mensal Básico (product_id basic_plan e purchase_token basic_purchase_token_123).
  • O usuário muda para um plano Premium usando um modo de substituição imediata (um de WITHOUT_PRORATION, WITH_TIME_PRORATION, CHARGE_PRORATED_PRICE, CHARGE_FULL_PRICE)
  • Depois que a troca for concluída, o Google vai tratar a assinatura como NOVA e criar um token de compra novo e diferente para o plano Premium (product_id premium_plan e purchase_token premium_purchase_token_123).

Processamento do lado do cliente

onPurchasesUpdated

Quando a compra substituta for concluída, o evento PurchasesUpdatedListener será acionado. Mesmo que tenha sido uma mudança, o Google Play trata o plano Premium como uma compra totalmente nova.

O app vai receber um objeto Purchase que contém o token de compra premium_purchase_token_123 e o product_id premium_plan. Você precisa tratar isso exatamente como um novo assinante: verifique o token e prepare-se para conceder acesso.

@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

O queryPurchasesAsync retorna apenas as assinaturas ativas compradas no seu app. Use esse método para determinar qual direito mostrar ao usuário. Para substituições imediatas, o queryPurchasesAsync() vai parar de retornar o token de compra BASIC antigo e vai retornar apenas o novo token de compra PREMIUM.

Sempre que o app for retomado ou uma compra for concluída, chame esse método. Se o token Premium estiver presente, conceda imediatamente os recursos Premium e remova os recursos básicos.

Processamento de back-end (RTDN)

Quando uma substituição ocorre, o Google Play envia uma Notificação do desenvolvedor em tempo real (RTDN) para o tópico configurado do Pub/Sub.

  • No caso de substituição imediata, o Google envia uma RTDN SUBSCRIPTION_PURCHASED com o novo token de compra.Exemplo de payload de 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
      }
    }
    
  • Quando o servidor receber o novo token de compra da RTDN, chame a API purchases.subscriptionsV2 com o novo token para buscar os detalhes da compra. A resposta da API contém um campo linkedPurchaseToken usado para determinar se o token de compra se refere a uma nova compra de assinatura ou a uma substituição de assinatura.
  • No caso de uma substituição de assinatura, linkedPurchaseToken se refere ao token de compra da assinatura antiga. Nesse cenário, seria basic_purchase_token_123.Exemplo de resposta de 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>"
    }
    
  • Você precisa confirmar a nova compra do Premium. Isso pode ser feito no app ou no back-end. Se a compra não for confirmada em até três dias, ela será reembolsada e o direito será revogado. Para mais detalhes sobre o processamento e a confirmação de compras, consulte a documentação para desenvolvedores.

Conclusão

Esta seção abordou as etapas para lidar com substituições imediatas de assinaturas no cliente e no back-end. Você aprendeu que o Google Play trata o novo plano como uma compra totalmente nova, emitindo um novo token de compra. No cliente, processe essa nova compra usando o PurchasesUpdatedListener e atualize os direitos com base na resposta do queryPurchasesAsync. No back-end, ouça as RTDNs SUBSCRIPTION_PURCHASED para o novo token, use a API purchases.subscriptionsv2 para identificar o linkedPurchaseToken da assinatura antiga e revogue imediatamente o acesso associado ao token antigo ao conceder o novo direito. Não se esqueça de sempre confirmar a nova compra.

11. Processar substituições ADIADAS

Ao contrário dos modos de substituição imediata, o ReplacementMode.DEFERRED adia a mudança de assinatura e a atualização de direitos de acesso até o final do ciclo de faturamento atual. O processamento de substituições adiadas exige uma lógica específica para garantir que os usuários recebam o direito correto no momento adequado.

Exemplo

  • O usuário tem um plano mensal Básico (product_id basic_plan e purchase_token basic_purchase_token_123) que será renovado em 15 de abril.
  • Em 1º de abril, o usuário decide mudar para um plano Premium usando ReplacementMode.DEFERRED.
  • O Google cria um token de compra NOVO para o plano Premium (product_id premium_plan e purchase_token premium_purchase_123) imediatamente, mas o valor a ser cobrado do usuário e o direito são programados para 15 de abril.

Processar substituição adiada

1. Logo após a conclusão do fluxo de compra (app)

  • O PurchasesUpdatedListener é invocado após a conclusão do fluxo de compra. O app vai receber um objeto Purchase contendo o novo token de compra premium_purchase_token_123. No entanto, o product_id ainda se referirá ao basic_plan antigo, já que o usuário tem direito apenas ao plano Básico. Você precisa tratar isso exatamente como uma nova compra e confirmar o token.
    @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);
            }
        }
    }
    
  • O queryPurchasesAsync retorna a compra com o novo token (premium_purchase_token_123) imediatamente e o direito original (basic_plan) associado a ela. Você pode confiar nisso para continuar concedendo o direito ao plano Basic ao usuário.

2. Logo após a conclusão do fluxo de compra (back-end)

  • A RTDN SUBSCRIPTION_PURCHASED é enviada imediatamente após o fluxo de compra do novo token (premium_purchase_token_123).
    {
      "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
      }
    }
    
  • Chame o GET purchases.subscriptionsv2 com o novo token de compra para buscar os detalhes da compra. A resposta contém dois itens de linha.
    • Um representa a assinatura antiga (plano básico) e tem um expiryTime no futuro. A assinatura antiga não será renovada e terá um deferredItemReplacement com a nova assinatura (plano premium). Isso indica uma substituição pendente do direito antigo após a expiração dele.
    • Um representando a assinatura recém-comprada. Ele não tem um valor definido para "expiryTime".
    Exemplo de resposta da 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>"
    }
    
  • Você precisa confirmar o novo token de compra. Isso pode ser feito no app ou no back-end. Para mais detalhes sobre o processamento e a confirmação de compras, consulte a documentação para desenvolvedores.
  • A RTDN SUBSCRIPTION_EXPIRED é enviada para o token de compra antigo (basic_purchase_token_123).Exemplo de payload de 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
      }
    }
    
  • Ao chamar a API GET purchases.subscriptionsv2 com o token de compra antigo, ele aparece como expirado (SUBSCRIPTION_STATE_EXPIRED). O direito de acesso do plano antigo é transferido para a nova compra pelo tempo restante.

3. Na data de substituição: primeira renovação após o fluxo de compra (app)

  • O queryPurchasesAsync retorna a compra com o novo token (premium_purchase_token_123) e a nova assinatura associada a ela (premium_plan).
  • A nova compra já terá sido processada quando o fluxo de compra for concluído. Portanto, não é necessário fazer nada além de garantir que o acesso à assinatura correta seja concedido ao usuário.

4. Na data de substituição: primeira renovação após o fluxo de compra (back-end)

  • Com ReplacementMode.DEFERRED, as primeiras renovações seguem o comportamento padrão de qualquer outra renovação que esteja processando RTDNs SUBSCRIPTION_RENEWED. Não é necessário ter uma lógica especial para substituições quando isso acontece.
  • Chame o GET purchases.subscriptionsv2 com o novo token de compra para buscar os detalhes da compra. A resposta contém dois itens de linha.
    • Um representa a assinatura antiga (plano básico) e tem um expiryTime no passado. A assinatura antiga não terá mais um valor definido para o campo deferredItemReplacement.
    • Um representando a nova assinatura com um expiryTime no futuro e o campo autoRenewEnabled definido como true.
    Exemplo de resposta da 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>"
    }
    

Conclusão

Esta seção detalhou o processamento exclusivo necessário para ReplacementMode.DEFERRED. Você aprendeu que, ao contrário dos modos imediatos, a mudança de direito só ocorre no final do ciclo de faturamento atual. Esta seção abordou as etapas necessárias para que seu app e back-end processem corretamente a compra inicial, confirmem o novo token e gerenciem a troca de direitos quando a assinatura antiga expirar e a nova for ativada.

12. Playground de substituição de assinatura

O recurso Replacement Playground no app de exemplo permite testar upgrades e downgrades de assinaturas para os produtos por assinatura configurados na sua conta do Google Play Console. Esta seção descreve como usar o recurso Replacement Playground.

Configuração

Para usar o recurso Playground de substituição, confira o seguinte:

  • O packageId no arquivo build.gradle corresponde ao aplicativo configurado no Google Play Console.
  • Sua conta de usuário de teste está inscrita como testador de licença no Google Play Console. Para saber mais sobre o teste de licença, consulte Testar a implementação de faturamento do app.

Playground de substituição de assinatura

O app de exemplo inclui uma guia Replacement Playground, que permite simular mudanças de assinatura. É possível consultar as assinaturas definidas no Play Console e testar a troca entre elas usando vários modos de substituição. Esse ambiente de simulação ajuda você a entender como os diferentes modos afetam os ciclos de faturamento e os direitos das suas assinaturas. Assim, você pode determinar quais opções são mais adequadas às necessidades da sua empresa.

Para simular substituições usando o playground, siga estas etapas:

  1. Acesse a guia Playground.
  2. Se você tiver uma assinatura ativa:ela vai ser destacada. Esta é a assinatura que será substituída.

Página inicial do Playground

  1. Se você não tiver uma assinatura ativa:primeiro, compre uma.
    • O Playground lista os planos Basic, Premium e Lite criados para este codelab por padrão.
    • Para testar com outros planos configurados no Play Console, clique em Adicionar plano personalizado e pesquise por productId e basePlanId.
    • Compre a assinatura selecionada.
    • A assinatura ativa recém-comprada do usuário vai aparecer. Adicionar assinatura personalizada
  2. Selecione a assinatura de destino para a qual o usuário quer mudar.
  3. Selecione um Modo de substituição para a transição.

Selecionar modo de substituição

  1. Clique no botão Testar substituição para simular a substituição da assinatura.
  2. Você vai encontrar a página inferior do Google Play Faturamento com os detalhes calculados da substituição da assinatura, como cobranças imediatas e ajustes no ciclo de faturamento.

Carrinho de faturamento de substituição de assinatura

  1. Conclua o pagamento.
  2. Acesse a página Gerenciar assinaturas no app Google Play Store para conferir as mudanças nas assinaturas ativas, além de detalhes sobre as datas e os preços de renovação atualizados.

Gerenciamento de assinaturas da Play Store

13. Próximas etapas

Documentos de referência

14. Parabéns

Parabéns! Você implementou substituições de assinatura com vários modos de rateio e configurou o processamento de back-end para transições de plano.

O que você aprendeu

  • Como configurar SubscriptionProductReplacementParams com modos de substituição específicos.
  • A diferença entre upgrades imediatos e downgrades adiados.
  • Como desativar tokens de assinatura antigos usando linkedPurchaseToken com RTDNs.

Pesquisa

Seu feedback sobre este codelab é muito importante. Reserve alguns minutos para responder à nossa pesquisa.