Implementa reemplazos de suscripciones con la Facturación Google Play

1. Introducción

En este codelab, aprenderás a usar la Biblioteca de Facturación Google Play (PBL) para administrar los cambios en los planes de suscripción. Descubrirás cómo los distintos modos de reemplazo afectan los precios y los derechos de los usuarios, y aprenderás a procesar las notificaciones para desarrolladores en tiempo real (RTDN) del backend.

Público

Este codelab, diseñado para desarrolladores de apps para Android, proporciona orientación para implementar funciones sofisticadas de administración de suscripciones. Esta guía te ayuda a ofrecer a los usuarios una experiencia fluida para actualizar, cambiar a una versión inferior o realizar la transición entre diferentes planes de suscripción.

Qué aprenderás

  • Cómo crear suscripciones en Play Console
  • Cómo elegir el ReplacementMode correcto (p. ej., WITH_TIME_PRORATION en lugar de DEFERRED) para que coincida con las políticas de actualización y degradación de tu app
  • Cómo configurar BillingFlowParams dentro de launchBillingFlow para activar el flujo de compra de Google Play para un reemplazo de plan
  • Cómo usar las notificaciones para desarrolladores en tiempo real (RTDN) y la API de purchases.subscriptionsv2 para revocar de forma segura el acceso anterior y otorgar acceso nuevo en tu backend

Requisitos

2. Compila la app de ejemplo

En este codelab, se usa una app de ejemplo para Android para mostrarte cómo implementar reemplazos de suscripciones en PBL. La app de ejemplo está diseñada para ser una app para Android completamente funcional que tiene el código fuente completo que muestra los siguientes aspectos:

  • Integra la app con PBL
  • Implementa reemplazos de suscripciones

Si ya conoces los reemplazos de suscripciones y la PBL, puedes descargar la app de ejemplo y probarla.

En el siguiente video de demostración, se muestra cómo se verá y se comportará la app de ejemplo después de que se implemente y ejecute.

Requisitos previos

Antes de compilar e implementar la app de ejemplo, haz lo siguiente:

Compilación

Para compilar la app de ejemplo según sea necesario para seguir el codelab, haz lo siguiente:

  1. Descarga la app de ejemplo desde GitHub.
  2. Actualiza applicationId dentro de build.gradle de la app de ejemplo para que refleje el ID de aplicación de tu app en Play Developer Console.
  3. Compila la app de ejemplo.
    Nota: Esto compila la app correctamente para las pruebas locales. Sin embargo, ejecutar la app no recupera productos ni precios porque aún no se crearon las suscripciones requeridas en Play Console. En la siguiente sección, se explicará cómo crear suscripciones en Developer Console.

3. Crea suscripciones en Play Console

El sistema de suscripciones de Google Play te proporciona flexibilidad en la forma de crear, administrar y vender suscripciones. En Play Console, puedes configurar suscripciones con varios planes básicos, cada uno con múltiples ofertas. Las ofertas de suscripción pueden tener varios modelos de precios y opciones de elegibilidad. En este codelab, crearás tres suscripciones: Plan Premium, Plan Básico y Plan Lite, que simulan una oferta de suscripción típica con varios precios. Cada una de ellas tendrá un solo plan básico mensual recurrente.

Crear una nueva suscripción

Para crear una suscripción nueva, sigue estos pasos:

  1. Abre Play Console y ve a la página Suscripciones (Monetiza con Play > Productos > Suscripciones).
  2. Haz clic en Crear suscripción.
  3. Ingresa los detalles de la suscripción:
    • ProductID : Ingresa un ID de producto único. Ingresa premium_plan.
    • Nombre : Ingresa un nombre corto para la suscripción. Ejemplo: Premium Plan.
  4. Haz clic en Crear.

Crea el plan básico

  1. Abre Play Console y ve a la página Suscripciones (Monetiza con Play > Productos > Suscripciones).
  2. Junto a la suscripción en la que quieras crear un plan básico, haz clic en la flecha hacia la derecha para ver los detalles respectivos.
  3. Haz clic en Agregar plan básico.
  4. Ingresa un ID de plan básico. Ejemplo monthly-auto-renewing
  5. Elige el tipo Renovación automática.
  6. Para el plan básico con renovación automática, configura lo siguiente:
    • Período de facturación: Mensual
    • Período de gracia: 7 días.
    • Cambios en el plan de facturación y en las ofertas: Cargar en la fecha de facturación.
    • Volver a suscribirse: Permitir
  7. En la sección Precio y disponibilidad, haz clic en Establecer precios para definir el precio del plan básico.
  8. Selecciona todos los países y regiones y, luego, haz clic en Establecer precio.
  9. Establece el precio en USD 10 para este plan básico y haz clic en Actualizar.
  10. Una vez que se establezca el precio del plan básico, haz clic en Guardar y, luego, en Activar en la esquina inferior derecha.

Crea suscripciones para la app de ejemplo

Para los fines de este codelab, crea dos suscripciones adicionales con la siguiente configuración:

  • Plan básico
    • ID del producto: basic_plan
    • Nombre: Plan básico
    • ID del plan básico: monthly-auto-renewing
    • Precio: USD 5
  • Plan Lite
    • ID del producto: lite_plan
    • Nombre: Plan Lite
    • ID del plan básico: monthly-auto-renewing
    • Precio: USD 3

La app de ejemplo está configurada para usar estos IDs de producto y de plan básico. Puedes crear diferentes suscripciones con diferentes configuraciones, en cuyo caso, deberás modificar la app de ejemplo para usar el ID de producto que creaste.

Video sobre la creación de suscripciones

En el siguiente video, se muestran los pasos descritos anteriormente para crear una suscripción en Play Console.

4. Reemplazos de suscripciones

Los desarrolladores que se integran con PBL pueden proporcionarles a sus suscriptores existentes varias opciones para cambiar su plan de suscripción y satisfacer mejor sus necesidades:

  • Si vendes varios niveles de suscripción, como suscripciones básicas y premium, puedes permitir que los usuarios cambien de nivel comprando una oferta o un plan básico de suscripción diferente.
  • Puedes permitir que los usuarios cambien su período de facturación actual, por ejemplo, de un plan mensual a uno anual.
  • También puedes permitir que cambien entre planes prepagados y de renovación automática.

Cuando los usuarios deciden actualizar su suscripción, cambiarla o regresar a una versión inferior, deberás especificar un modo de reemplazo que determine cómo se aplicará el valor prorrateado del período de facturación actual y cuándo se producirá el cambio de derecho para los usuarios.

La Biblioteca de Facturación Play proporciona varias opciones de ReplacementMode para controlar este comportamiento.

Modos de reemplazo disponibles

  • WITH_TIME_PRORATION: El elemento de suscripción se actualiza o se cambia a una versión inferior de inmediato. El tiempo restante se ajusta en función de la diferencia de precio y se acredita a la nueva suscripción actualizando la próxima fecha de facturación. Este es el comportamiento predeterminado.
  • CHARGE_PRORATED_PRICE: El elemento de suscripción se actualiza de inmediato, y el ciclo de facturación sigue siendo el mismo. Luego se le cobra al usuario la diferencia de precio del período restante.
  • CHARGE_FULL_PRICE: Se actualiza el elemento de suscripción o cambia a una versión inferior de inmediato, y al usuario se le cobra el precio total por este nuevo derecho de inmediato. El valor restante de la suscripción anterior se transfiere al mismo derecho o se prorratea para cuando se cambie a un derecho diferente.
  • WITHOUT_PRORATION: Se actualiza la suscripción o cambia a una versión inferior de inmediato, y se cobra el precio nuevo cuando esta se renueva. El ciclo de facturación no cambia.
  • DEFERRED: El elemento de suscripción se actualiza o cambia a una versión inferior solo cuando se renueva la suscripción.

5. WITH_TIME_PRORATION

En este modo de reemplazo, el elemento de suscripción se actualiza o se cambia a una versión inferior de inmediato. El tiempo restante se ajusta en función de la diferencia de precio y se atrasa la siguiente fecha de facturación para acreditarlo a la nueva suscripción. Este es el comportamiento predeterminado.

Situación de ejemplo

Un usuario cambia de un plan Básico (USD 4.99 al mes) a un plan Premium (USD 9.99 al mes) el 15 de abril, que es la mitad de su ciclo de facturación mensual.

En este caso, ocurre lo siguiente:

  • El usuario obtiene acceso al plan Premium de inmediato.
  • Google Play calcula automáticamente el período de prorrateo. Por ejemplo, si Play calcula que los 15 días restantes del plan Básico equivalen a 7 días del plan Premium, la próxima fecha de facturación se adelanta al 21 de abril.
  • El usuario no debe realizar ningún pago inmediato.

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

Actualización con WITH_TIME_PRORATION

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Premium.

El derecho del usuario se actualiza de inmediato al plan Premium. El importe que el usuario debe pagar de inmediato es de USD 0.00. El valor restante del plan Básico se prorratea en tiempo para el plan Premium, lo que adelanta la próxima fecha de renovación. Al usuario se le cobrará el importe de renovación de USD 9.99 en la fecha de facturación recién ajustada.

Cambio a una versión inferior con WITH_TIME_PRORATION

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Lite.

El derecho del usuario se cambia de inmediato al plan Lite. El importe que se debe pagar de inmediato es de USD 0.00. El valor restante del plan Básico se prorratea en tiempo para el plan Lite, lo que extiende significativamente la próxima fecha de renovación. Al usuario se le cobrará el importe de renovación de USD 2.99 en la fecha de facturación recién ajustada.

Conclusión

En esta sección, aprendiste cómo WITH_TIME_PRORATION modifica los derechos del usuario sin cargos inmediatos ajustando el tiempo hasta la próxima renovación según la diferencia de precio. Es una estrategia predeterminada eficaz para actualizar o cambiar a una versión inferior los usuarios.

6. CHARGE_PRORATED_PRICE

En este modo de reemplazo, el elemento de suscripción se actualiza de inmediato, y el ciclo de facturación sigue siendo el mismo. Luego se le cobra al usuario la diferencia de precio del período restante.

Nota: Esta opción solo está disponible para una actualización de un elemento de suscripción, en la que el precio por unidad de tiempo aumenta.

Situación de ejemplo

Un usuario con el plan Básico (USD 4.99 al mes) decide actualizar al plan Premium (USD 9.99 al mes) el 20 de abril, cuando le quedan unos 10 días en su ciclo de facturación mensual.

En este caso, ocurre lo siguiente:

  • El usuario obtiene acceso al plan Premium de inmediato.
  • Al usuario se le cobra de inmediato la diferencia prorrateada por los 10 días restantes del ciclo de facturación actual. Esto equivale a aproximadamente USD 2.99, lo que representa 10 días del plan Premium.
  • No cambia la fecha de facturación del usuario.

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

Actualización con CHARGE_PRORATED_PRICE

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Premium.

Se actualiza al usuario al plan Premium de inmediato y se mantiene la fecha de renovación original. El importe que se debe pagar de inmediato es la diferencia prorrateada entre los precios de los planes Premium y Básico para los días restantes del ciclo actual. En la fecha de renovación, se le cobrará al usuario el importe total de la renovación de Premium, que es de USD 9.99.

Cambio a una versión inferior con CHARGE_PRORATED_PRICE

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Lite.

Este modo de reemplazo genera un error durante la actualización a una versión anterior porque solo está disponible para las actualizaciones de elementos de suscripción en las que aumenta el precio por unidad de tiempo. El flujo de facturación fallará y mostrará un error al usuario que indica que el modo de prorrateo no es compatible con las versiones inferiores.

Conclusión

En esta sección, se explicó cómo CHARGE_PRORATED_PRICE permite realizar actualizaciones inmediatas cobrando a los usuarios la diferencia de precio exacta del período de facturación restante y, al mismo tiempo, dejando intacto el ciclo de facturación. Esto es útil cuando el usuario quiere actualizar a un nivel más costoso sin cambiar la fecha de facturación.

7. CHARGE_FULL_PRICE

En este modo de reemplazo, el elemento de suscripción se actualiza o cambia a una versión inferior de inmediato, y al usuario se le cobra el precio total por el nuevo derecho de inmediato. El valor restante de la suscripción anterior se transfiere al mismo derecho o se prorratea para cuando se cambie a un derecho diferente.

Situación de ejemplo

Un usuario tiene el plan Básico (USD 4.99 al mes a partir del 1 de abril). El 20 de abril, el usuario quiere cambiar al plan Premium (USD 9.99 al mes).

En este caso, ocurre lo siguiente:

  • Al usuario se le cobra de inmediato el precio total del plan Premium (USD 9.99).
  • El valor restante del plan Básico (p.ej., el equivalente a 10 días) se convierte en tiempo equivalente para el plan Premium. En este ejemplo, 10 días de Básico equivalen a 5 días de Premium.
  • La próxima fecha de renovación del usuario se ajusta para incluir este tiempo prorrateado. Por lo tanto, la fecha de renovación será el 25 de mayo (20 de abril + 1 mes + 5 días).
  • Las renovaciones posteriores se realizarán mensualmente a partir del 25 de mayo.

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

Actualiza con CHARGE_FULL_PRICE

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Premium.

El usuario se actualiza al plan Premium de inmediato. El importe que se debe pagar de inmediato es el precio completo del plan Premium: USD 9.99. El valor restante del plan Básico se convierte en tiempo para el nuevo plan Premium, lo que extiende ligeramente la primera fecha de renovación. Luego, el importe de renovación será de USD 9.99 por ciclo.

Cambio a una versión inferior con CHARGE_FULL_PRICE

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Lite.

Se cambia al usuario al plan Lite de inmediato y se inicia un nuevo ciclo de facturación. El importe que se debe pagar de inmediato es el precio objetivo completo de USD 2.99. La parte no utilizada del plan Básico se prorratea en tiempo para el nuevo plan Lite, lo que extiende la primera fecha de renovación. Después de eso, el importe de renovación será de USD 2.99 por ciclo.

Conclusión

En esta sección, explicamos cómo CHARGE_FULL_PRICE le cobra al usuario el costo total de su bolsillo el día del cambio, lo que inicia un nuevo ciclo de facturación de inmediato. Cualquier saldo restante del plan anterior se aplica de forma lineal a la próxima fecha de renovación.

8. WITHOUT_PRORATION

En este modo de reemplazo, el elemento de suscripción se actualiza o cambia a una versión inferior de inmediato, y se cobra el precio nuevo cuando se renueva la suscripción.

Situación de ejemplo

Un usuario tiene el plan Básico (USD 4.99 al mes a partir del 1 de abril). El 20 de abril, el usuario quiere cambiar al plan Premium (USD 9.99 al mes).

En este caso, ocurre lo siguiente:

  • El usuario obtiene acceso al plan Premium de inmediato.
  • El usuario no tiene que pagar el precio más alto de USD 9.99 hasta la próxima fecha de renovación de la suscripción (1 de mayo).

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

Actualización con WITHOUT_PRORATION

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Premium.

Se actualiza al usuario al plan Premium de inmediato y se conserva la fecha de renovación existente. El importe que se debe pagar de inmediato es de USD 0.00. El usuario tendrá acceso al plan Premium durante el tiempo restante del ciclo determinado antes de cambiar al nuevo importe de renovación de USD 9.99 en la próxima fecha de facturación.

Cambio a una versión inferior con WITHOUT_PRORATION

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Lite.

Se cambia al usuario al plan Lite de inmediato, por lo que pierde las funciones del plan Basic por las que pagó. El importe que se debe pagar de inmediato es de USD 0.00. El ciclo de facturación continúa sin modificaciones, y el usuario pagará la nueva tarifa más baja de USD 2.99 en la próxima renovación programada.

Conclusión

En esta sección, se demostró cómo WITHOUT_PRORATION intercambia de inmediato los derechos del usuario sin un cargo de confirmación de la compra y sin modificar el ciclo de facturación.

9. DEFERRED

En este modo de reemplazo, el elemento de suscripción se actualiza o se cambia a una versión inferior solo cuando se renueva la suscripción, pero la compra nueva se emite de inmediato. El elemento existente se configura como no renovable y vence al final del ciclo de facturación actual, mientras que el derecho solicitado recientemente comienza inmediatamente después.

Situación de ejemplo

Un usuario tiene el plan Básico (USD 4.99 al mes a partir del 1 de abril). El 20 de abril, el usuario quiere cambiar al plan Premium (USD 9.99 al mes).

En este caso, ocurre lo siguiente:

  • El usuario no incurre en cargos inmediatos.
  • El usuario seguirá recibiendo las funciones de Basic hasta el final del ciclo de facturación actual (30 de abril).
  • El plan de suscripción se actualizará automáticamente a Premium en la próxima fecha de renovación (1 de mayo).

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

Actualización con DEFERRED

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Premium.

El usuario permanecerá en el plan Básico hasta el final de su ciclo de facturación actual. El importe que se debe pagar de inmediato es de USD 0.00. En la fecha de renovación, su derecho se actualizará al plan Premium y se le cobrará el nuevo importe de renovación de USD 9.99.

Cambio a una versión inferior con DEFERRED

Para simular esta situación, haz lo siguiente:

  • En el MainActivity de la app de ejemplo, actualiza replacementMode en el fragmento de código a SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Vuelve a compilar y ejecutar la aplicación.
  • Cancela las suscripciones existentes (si las hay) en Google Play Store y espera a que venzan.
  • Compra el plan Básico.
  • Cámbiate al plan Lite.

El usuario permanecerá en el plan Básico hasta el final de su ciclo de facturación actual. El importe que se debe pagar de inmediato es de USD 0.00. En la fecha de renovación, su derecho se actualizará al plan Lite y se le cobrará el nuevo importe de renovación de USD 2.99.

Conclusión

En esta sección, se explicó cómo el modo de reemplazo DEFERRED pospone una actualización o un cambio a una versión inferior hasta el final del tiempo pagado de un usuario activo. Esto lo hace ideal para las versiones anteriores, ya que evita que se pierdan las funciones que ya se compraron.

10. Procesamiento de backend y del cliente

Después de que un usuario active un reemplazo de suscripción exitoso, asegúrate de que tanto tu app como el backend controlen correctamente el cambio para evitar problemas como interrupciones del servicio o facturación doble.

Situación de ejemplo

  • El usuario tiene un plan mensual Basic (product_id basic_plan y purchase_token basic_purchase_token_123).
  • El usuario cambia a un plan Premium con un modo de reemplazo inmediato (uno de WITHOUT_PRORATION, WITH_TIME_PRORATION, CHARGE_PRORATED_PRICE, CHARGE_FULL_PRICE).
  • Una vez que el cambio de suscripción se realiza correctamente, Google lo considera como una suscripción NUEVA y crea un token de compra nuevo y diferente para el plan Premium (product_id premium_plan y purchase_token premium_purchase_token_123).

Procesamiento del cliente

onPurchasesUpdated

Cuando se completa la compra de reemplazo, se activa PurchasesUpdatedListener. Aunque se trató de un cambio, Google Play considera el plan Premium como una compra completamente nueva.

La app recibirá un objeto Purchase que contiene el token de compra premium_purchase_token_123 y el product_id premium_plan. Debes tratarlo exactamente como un suscriptor nuevo: verifica el token y prepárate para otorgar acceso.

@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 solo devuelve las suscripciones activas compradas desde tu app. Debes usar este método para determinar qué derecho mostrarle al usuario. En el caso de los reemplazos inmediatos, queryPurchasesAsync() dejará de devolver el token de compra BASIC anterior y solo devolverá el token de compra PREMIUM nuevo.

Llama a este método cada vez que se reanude tu app o se complete una compra. Si el token de Premium está presente, otorga inmediatamente las funciones de Premium y quita las funciones básicas.

Procesamiento de backend (RTDN)

Cuando se produce un reemplazo, Google Play envía una notificación para desarrolladores en tiempo real (RTDN) al tema de Pub/Sub que configuraste.

  • En el caso de reemplazo inmediato, Google envía una RTDN de SUBSCRIPTION_PURCHASED con el nuevo token de compra.Carga útil de ejemplo 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
      }
    }
    
  • Cuando tu servidor recibe el nuevo token de compra de la RTDN, llama a la API de purchases.subscriptionsV2 con el nuevo token de compra para recuperar los detalles de la compra. La respuesta de la API contiene un campo linkedPurchaseToken que se usa para determinar si el token de compra hace referencia a una compra de suscripción nueva o a un reemplazo de suscripción.
  • En el caso de un reemplazo de suscripción, linkedPurchaseToken hace referencia al token de compra de la suscripción anterior. En este caso, sería basic_purchase_token_123.Ejemplo de respuesta 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>"
    }
    
  • Debes confirmar la compra nueva de Premium. Esto se puede hacer en tu app o en tu backend. Si no se confirma la compra en un plazo de 3 días, se reembolsará el dinero y se revocará el derecho. Para obtener más detalles sobre el procesamiento y la confirmación de compras, consulta la documentación para desarrolladores.

Conclusión

En esta sección, se describieron los pasos para controlar los reemplazos inmediatos de suscripciones tanto en el cliente como en tu backend. Aprendiste que Google Play trata el plan nuevo como una compra nueva y emite un token de compra nuevo. En el cliente, debes procesar esta nueva compra con PurchasesUpdatedListener y actualizar los derechos según la respuesta de queryPurchasesAsync. En el backend, debes escuchar las RTDN de SUBSCRIPTION_PURCHASED para el token nuevo, usar la API de purchases.subscriptionsv2 para identificar el linkedPurchaseToken de la suscripción anterior y revocar de inmediato el acceso asociado con el token anterior mientras otorgas el derecho nuevo. Recuerda que siempre debes reconocer la compra nueva.

11. Procesa reemplazos DEFERRED

A diferencia de los modos de reemplazo inmediato, ReplacementMode.DEFERRED pospone el cambio de suscripción y la actualización del derecho hasta el final del ciclo de facturación actual. El manejo de reemplazos diferidos requiere una lógica específica para garantizar que los usuarios reciban el derecho correcto en el momento adecuado.

Situación de ejemplo

  • El usuario tiene un plan mensual básico (product_id basic_plan y purchase_token basic_purchase_token_123) que se renueva el 15 de abril.
  • El 1 de abril, el usuario decide cambiar a un plan Premium con ReplacementMode.DEFERRED.
  • Google crea un token de compra NUEVO para el plan Premium (product_id premium_plan y purchase_token premium_purchase_123) de inmediato, pero el importe que se le cobrará al usuario y el derecho se programan para el 15 de abril.

Procesa el reemplazo diferido

1. Inmediatamente después de que el flujo de compra se realiza de manera correcta (app)

  • PurchasesUpdatedListener se invoca después de que se completa el flujo de compra. La app recibirá un objeto Purchase que contiene el nuevo token de compra premium_purchase_token_123. Sin embargo, el product_id seguirá haciendo referencia al basic_plan anterior, ya que el usuario solo tiene derecho al plan Básico. Debes tratarlo exactamente como una compra nueva y confirmar el 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);
            }
        }
    }
    
  • queryPurchasesAsync muestra inmediatamente la compra con el nuevo token de compra (premium_purchase_token_123) y el derecho original (basic_plan) asociado. Puedes confiar en esto para seguir otorgando derechos del plan Básico al usuario.

2. Inmediatamente después de que el flujo de compra se realiza de manera correcta (backend)

  • La RTDN de SUBSCRIPTION_PURCHASED se envía inmediatamente después del flujo de compra del nuevo token de compra (premium_purchase_token_123).Carga útil de ejemplo de la 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
      }
    }
    
  • Llama a GET purchases.subscriptionsv2 con el nuevo token de compra para recuperar los detalles de la compra. La respuesta contiene 2 partidas.
    • Uno que representa la suscripción anterior (plan básico) y tiene un expiryTime en el futuro. La suscripción anterior no se renovará y tendrá un deferredItemReplacement que contiene la suscripción nueva (plan premium). Esto indica que hay un reemplazo pendiente del derecho anterior cuando venza.
    • Uno que representa la suscripción recién comprada. No tiene ningún valor establecido para "expiryTime".
    Ejemplo de respuesta de la 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>"
    }
    
  • Debes confirmar el nuevo token de compra. Esto se puede hacer en tu app o en tu backend. Para obtener más detalles sobre el procesamiento y la confirmación de compras, consulta la documentación para desarrolladores.
  • Se envía la RTDN de SUBSCRIPTION_EXPIRED para el token de compra anterior (basic_purchase_token_123).Carga útil de ejemplo de la 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
      }
    }
    
  • Cuando llamas a la API de GET purchases.subscriptionsv2 con el token de compra anterior, aparece como vencido (SUBSCRIPTION_STATE_EXPIRED). El derecho del plan anterior se transfiere a la compra nueva durante el tiempo restante.

3. En la fecha de reemplazo: Primera renovación después del flujo de compra (app)

  • queryPurchasesAsync muestra la compra con el nuevo token de compra (premium_purchase_token_123) y la nueva suscripción asociada (premium_plan).
  • La compra nueva ya debería haberse procesado cuando el flujo de compra se completó correctamente, por lo que no tienes que realizar ninguna acción especial además de asegurarte de que se otorgue al usuario acceso a la suscripción correcta.

4. En la fecha de reemplazo: Primera renovación después del flujo de compra (backend)

  • Con ReplacementMode.DEFERRED, las primeras renovaciones siguen el comportamiento estándar de cualquier otra renovación que procese RTDN de SUBSCRIPTION_RENEWED. No necesitas tener ninguna lógica especial para los reemplazos cuando esto sucede.
  • Llama a GET purchases.subscriptionsv2 con el nuevo token de compra para recuperar los detalles de la compra. La respuesta contiene 2 partidas.
    • Uno que representa la suscripción anterior (plan básico) y tiene un expiryTime en el pasado. La suscripción anterior ya no tendrá un valor establecido para el campo deferredItemReplacement.
    • Uno que representa la suscripción nueva con un expiryTime en el futuro y el campo autoRenewEnabled establecido en true.
    Ejemplo de respuesta de la 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>"
    }
    

Conclusión

En esta sección, se detalló el manejo único requerido para ReplacementMode.DEFERRED. Aprendiste que, a diferencia de los modos inmediatos, el cambio de derechos solo se produce al final del ciclo de facturación actual. En esta sección, se describieron los pasos necesarios para que tu app y tu backend procesen correctamente la compra inicial, confirmen el nuevo token y administren el cambio de derecho cuando venza la suscripción anterior y se active la nueva.

12. Zona de pruebas de reemplazo de suscripciones

La función Replacement Playground de la app de ejemplo te permite probar las actualizaciones y las degradaciones de suscripciones para los productos de suscripción configurados en tu cuenta de Google Play Console. En esta sección, se describe cómo usar la función Replacement Playground.

Configuración

Para usar la función Replacement Playground, asegúrate de cumplir con los siguientes requisitos:

  • El packageId en tu archivo build.gradle coincide con la aplicación configurada en Google Play Console.
  • Tu cuenta de usuario de prueba está inscrita como verificador de licencias en Google Play Console. Para obtener más información sobre las pruebas de licencias, consulta Cómo probar la implementación de la facturación de tu app.

Zona de pruebas de reemplazo de suscripciones

La app de ejemplo incluye una pestaña Replacement Playground, que te permite simular cambios en las suscripciones. Puedes consultar las suscripciones definidas en Play Console y probar el cambio entre ellas con varios modos de reemplazo. Este entorno de pruebas te ayuda a comprender cómo los diferentes modos afectan los ciclos de facturación y los derechos de tus suscripciones, de modo que puedas determinar qué opciones se adaptan mejor a las necesidades de tu empresa.

Para simular reemplazos con la zona de pruebas, sigue estos pasos:

  1. Navega a la pestaña Playground.
  2. Si tienes una suscripción activa: Se destacará. Esta es la suscripción que se reemplazará.

Página principal de Playground

  1. Si no tienes una suscripción activa, primero debes comprar una.
    • De forma predeterminada, Playground muestra los planes Basic, Premium y Lite creados para este codelab.
    • Para realizar pruebas con otros planes configurados en Play Console, haz clic en Agregar plan personalizado y busca por productId y basePlanId.
    • Compra la suscripción seleccionada.
    • Ahora debería mostrarse la suscripción activa que compró el usuario. Agregar suscripción personalizada
  2. Selecciona la suscripción objetivo a la que el usuario quiere cambiar a.
  3. Selecciona un Modo de reemplazo para la transición.

Selecciona el modo de reemplazo

  1. Haz clic en el botón Test Replacement para simular el reemplazo de la suscripción.
  2. Deberías ver la hoja inferior de la facturación de Google Play con los detalles calculados del reemplazo de la suscripción (como los cargos inmediatos y los ajustes del ciclo de facturación).

Carrito de facturación de reemplazo de suscripción

  1. Completa la transacción.
  2. Ve a la página Administrar suscripciones en la app de Play Store para ver los cambios en las suscripciones activas, junto con los detalles de las fechas y los precios de renovación actualizados.

Administración de suscripciones de Play Store

13. Próximos pasos

Documentos de referencia

14. Felicitaciones

¡Felicitaciones! Implementaste correctamente los reemplazos de suscripciones con varios modos de prorrateo y configuraste el control de backend para las transiciones de planes.

Qué aprendiste

  • Cómo configurar SubscriptionProductReplacementParams con modos de reemplazo específicos
  • La diferencia entre las actualizaciones inmediatas y los cambios a versiones anteriores diferidos
  • Cómo retirar tokens de suscripción antiguos con linkedPurchaseToken a través de RTDN

Encuesta

Valoramos mucho tus comentarios sobre este codelab. Considera dedicar unos minutos a completar nuestra encuesta.