Implémenter des remplacements d'abonnement avec Google Play Billing

1. Introduction

Cet atelier de programmation vous apprend à utiliser la bibliothèque Google Play Billing (PBL) pour gérer les modifications de forfaits. Vous découvrirez l'impact des différents modes de remplacement sur les tarifs et les droits d'accès des utilisateurs, tout en apprenant à traiter les notifications en temps réel pour les développeurs (RTDN) côté backend.

Audience

Conçu pour les développeurs d'applications Android, cet atelier de programmation fournit des conseils sur l'implémentation de fonctionnalités sophistiquées de gestion des abonnements. Ces consignes vous aident à offrir aux utilisateurs une expérience fluide pour passer à un forfait supérieur ou inférieur, ou pour changer d'abonnement.

Ce que vous allez apprendre...

  • Découvrez comment créer des abonnements dans la Play Console.
  • Comment choisir le bon ReplacementMode (par exemple, WITH_TIME_PRORATION ou DEFERRED) pour qu'il corresponde aux règles de mise à niveau et de rétrogradation de votre application.
  • Comment configurer BillingFlowParams dans launchBillingFlow pour déclencher le processus d'achat Google Play pour le remplacement d'un forfait.
  • Utiliser les notifications en temps réel pour les développeurs (RTDN) et l'API purchases.subscriptionsv2 pour révoquer l'ancien accès et accorder un nouvel accès de manière sécurisée sur votre backend

Prérequis

2. Créer l'application exemple

Cet atelier de programmation utilise une application Android exemple pour vous montrer comment implémenter des remplacements d'abonnements dans PBL. L'application exemple est conçue pour être une application Android entièrement fonctionnelle. Elle dispose d'un code source complet qui montre les aspects suivants :

  • Intégrer l'application à PBL
  • Implémenter les remplacements d'abonnement

Si vous connaissez déjà les remplacements d'abonnement et PBL, vous pouvez télécharger l'application exemple et l'essayer.

La vidéo de démonstration suivante montre à quoi ressemblera l'application exemple et comment elle se comportera une fois déployée et exécutée.

Prérequis

Avant de créer et de déployer l'application exemple, procédez comme suit :

Créer

Pour compiler l'application exemple comme requis pour suivre l'atelier de programmation :

  1. Téléchargez l'application exemple depuis GitHub.
  2. Mettez à jour le applicationId dans le build.gradle de l'application exemple pour refléter l'ID d'application de votre application dans la Play Console.
  3. Créez l'application exemple.
    Remarque : Cette opération permet de créer l'application pour les tests locaux. Toutefois, l'exécution de l'application ne récupère pas les produits ni les prix, car les abonnements requis n'ont pas encore été créés dans la Play Console. La section suivante explique comment créer des abonnements dans la console pour les développeurs.

3. Créer des abonnements dans la Play Console

Le système d'abonnements de Google Play vous aide à créer, gérer et vendre des abonnements de manière flexible. Dans la Play Console, vous pouvez configurer des abonnements avec plusieurs forfaits de base, chacun contenant plusieurs offres. Les offres d'abonnement peuvent présenter différentes options de tarification et d'éligibilité. Pour cet atelier de programmation, vous allez créer trois abonnements : Forfait Premium, Forfait de base et Forfait Lite. Vous simulerez ainsi une offre d'abonnement typique à différents prix. Chacune d'elles disposera d'un forfait de base mensuel récurrent unique.

Créer un abonnement

Pour créer un abonnement

  1. Ouvrez la Play Console, puis accédez à la page Abonnements (Monétiser avec Play > Produits > Abonnements).
  2. Cliquez sur Créer un abonnement.
  3. Saisissez les détails de votre abonnement :
    • ProductID : saisissez un ID produit unique. Saisissez premium_plan.
    • Nom : saisissez un nom court pour l'abonnement. Exemple : Premium Plan.
  4. Cliquez sur Créer.

Créer le forfait de base

  1. Ouvrez la Play Console, puis accédez à la page Abonnements (Monétiser avec Play > Produits > Abonnements).
  2. À côté de l'abonnement pour lequel vous souhaitez créer un forfait de base, cliquez sur la flèche vers la droite pour afficher les détails.
  3. Cliquez sur Ajouter un forfait de base.
  4. Saisissez un ID de forfait de base. Exemple : monthly-auto-renewing.
  5. Sélectionnez le type Renouvellement automatique.
  6. Pour le forfait de base à renouvellement automatique, définissez les éléments suivants :
    • Période de facturation : mensuelle.
    • Délai de grâce : sept jours.
    • Changements de forfait et d'offre : Facturer à la date de facturation.
    • Se réabonner : Autoriser.
  7. Dans la section Prix et disponibilité, cliquez sur Définir les prix pour définir le prix du forfait de base.
  8. Sélectionnez tous les pays et régions, puis cliquez sur Définir le prix.
  9. Définissez le prix de ce forfait de base sur 10 $, puis cliquez sur Mettre à jour.
  10. Une fois le prix du forfait de base défini, cliquez sur Enregistrer, puis sur Activer en bas à droite.

Créer des abonnements pour l'application exemple

Pour les besoins de cet atelier de programmation, créez deux abonnements supplémentaires avec la configuration suivante :

  • Forfait de base
    • ID produit : basic_plan
    • Nom : Forfait de base
    • ID du forfait de base : monthly-auto-renewing
    • Prix : 5 $
  • Forfait Lite
    • ID produit : lite_plan
    • Nom : Forfait Lite
    • ID du forfait de base : monthly-auto-renewing
    • Prix : 3 $

L'application exemple est configurée pour utiliser ces ID de produit et de forfait de base. Vous pouvez créer différentes souscriptions avec différentes configurations. Dans ce cas, vous devrez modifier l'application exemple pour utiliser l'ID de produit que vous avez créé.

Vidéo sur la création d'un abonnement

La vidéo suivante montre comment créer un abonnement dans la Play Console.

4. Remplacements d'abonnement

Les développeurs qui intègrent PBL peuvent proposer à leurs abonnés existants diverses options pour modifier leur abonnement afin de mieux répondre à leurs besoins :

  • Si vous vendez plusieurs niveaux d'abonnement, tels que des abonnements de base et premium, vous pouvez permettre aux utilisateurs de changer de niveau en achetant un autre forfait de base ou une autre offre.
  • Vous pouvez autoriser les utilisateurs à modifier leur période de facturation actuelle, par exemple en passant d'un forfait mensuel à un forfait annuel.
  • Vous pouvez également autoriser les utilisateurs à basculer entre le forfait à renouvellement automatique et le forfait prépayé.

Lorsque les utilisateurs décident de changer d'abonnement, ou de passer à un forfait supérieur ou inférieur, vous spécifiez un mode de remplacement qui détermine le montant au prorata de la période de facturation en cours et le moment où le droit d'accès est modifié pour les utilisateurs.

La bibliothèque Play Billing fournit plusieurs options ReplacementMode pour contrôler ce comportement.

Modes de remplacement disponibles

  • WITH_TIME_PRORATION : l'abonnement passe immédiatement à un forfait supérieur ou inférieur. Le temps restant est ajusté en fonction de la différence de prix, puis crédité sur le nouvel abonnement en modifiant la date de facturation suivante. Il s'agit du comportement par défaut.
  • CHARGE_PRORATED_PRICE : l'abonnement passe à un niveau supérieur immédiatement, et le cycle de facturation reste le même. La différence de prix pour la période restante est ensuite facturée à l'utilisateur.
  • CHARGE_FULL_PRICE : l'abonnement passe immédiatement au forfait supérieur ou inférieur, et l'utilisateur paie immédiatement le tarif complet du nouveau forfait. La valeur restante de l'abonnement précédent est transférée sur le nouveau forfait ou calculée au prorata de la durée restante au moment du changement de forfait.
  • WITHOUT_PRORATION : l'abonnement passe immédiatement à un forfait supérieur ou inférieur, et le nouveau tarif est facturé lors du renouvellement de l'abonnement. Le cycle de facturation reste le même.
  • DEFERRED : l'abonnement ne passe à un forfait supérieur ou inférieur qu'au moment du renouvellement.

5. WITH_TIME_PRORATION

Dans ce mode de remplacement, l'abonnement passe immédiatement à un forfait supérieur ou inférieur. Le temps restant est ajusté en fonction de la différence de prix, puis crédité sur le nouvel abonnement en repoussant la date de facturation suivante. Il s'agit du comportement par défaut.

Exemple de scénario

Un utilisateur passe d'un forfait Basic (4,99 € par mois) à un forfait Premium (9,99 € par mois) le 15 avril, soit à mi-parcours de son cycle de facturation mensuel.

Dans ce cas, on a :

  • L'utilisateur a immédiatement accès au forfait Premium.
  • Google Play calcule automatiquement la période de prorata. Par exemple, si Play calcule que les 15 jours restants du forfait Basic correspondent à 7 jours du forfait Premium, la prochaine date de facturation est avancée au 21 avril.
  • Aucun paiement immédiat n'est requis de la part de l'utilisateur.

Extrait de code

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

Mise à niveau avec WITH_TIME_PRORATION

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Premium.

Les droits d'accès de l'utilisateur sont immédiatement mis à niveau vers le forfait Premium. Le montant à payer immédiatement par l'utilisateur est de 0 $. La valeur restante du forfait Basic est proratisée en temps pour le forfait Premium, ce qui avance la prochaine date de renouvellement. Le montant du renouvellement de 9,99 $ sera facturé à l'utilisateur à la nouvelle date de facturation.

Rétrogradation avec WITH_TIME_PRORATION

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Lite.

Les droits d'accès de l'utilisateur sont immédiatement rétrogradés au forfait Lite. Le montant à payer immédiatement est de 0,00 $. La valeur restante du forfait Basic est proratisée en temps pour le forfait Lite, ce qui repousse considérablement la prochaine date de renouvellement. Le montant du renouvellement de 2,99 $ sera facturé à la nouvelle date de facturation.

Conclusion

Dans cette section, vous avez appris comment WITH_TIME_PRORATION modifie les droits d'accès des utilisateurs sans frais immédiats en ajustant le délai avant le prochain renouvellement en fonction de la différence de prix. Il s'agit d'une stratégie par défaut efficace pour mettre à niveau ou rétrograder les utilisateurs.

6. CHARGE_PRORATED_PRICE

Dans ce mode de remplacement, l'abonnement passe à un niveau supérieur immédiatement, et le cycle de facturation reste le même. La différence de prix pour la période restante est ensuite facturée à l'utilisateur.

Remarque : Cette option n'est disponible que pour le passage à un abonnement de niveau supérieur, où le prix par unité de temps augmente.

Exemple de scénario

Un utilisateur du forfait Basic (4,99 € par mois) décide de passer au forfait Premium (9,99 € par mois) le 20 avril, alors qu'il reste environ 10 jours dans son cycle de facturation mensuel.

Dans ce cas, on a :

  • L'utilisateur a immédiatement accès au forfait Premium.
  • La différence au prorata pour les 10 jours restants du cycle de facturation en cours est immédiatement facturée à l'utilisateur. Cela équivaut à environ 2,99 $, soit 10 jours du forfait Premium.
  • La date de facturation de l'utilisateur ne change pas.

Extrait de code

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

Passer à un forfait supérieur avec CHARGE_PRORATED_PRICE

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Premium.

L'utilisateur passe immédiatement au forfait Premium, en conservant la date de renouvellement d'origine. Le montant à payer immédiatement correspond à la différence au prorata entre les prix des forfaits Premium et Basic pour les jours restants du cycle en cours. À la date de renouvellement, le montant total de 9,99 $ sera facturé à l'utilisateur pour le renouvellement de son abonnement Premium.

Passer à un forfait inférieur avec CHARGE_PRORATED_PRICE

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Lite.

Ce mode de remplacement génère une erreur lors d'un changement de forfait à un niveau inférieur, car il n'est disponible que pour les changements de forfait à un niveau supérieur où le prix par unité de temps augmente. Le flux de facturation échouera et affichera une erreur indiquant à l'utilisateur que le mode de calcul au prorata n'est pas compatible avec les changements d'abonnement à un forfait inférieur.

Conclusion

Cette section expliquait comment CHARGE_PRORATED_PRICE permet aux utilisateurs de passer immédiatement à un forfait supérieur en leur facturant la différence de prix exacte pour la période de facturation restante, tout en conservant le cycle de facturation. Cela est utile lorsque l'utilisateur souhaite passer à un niveau plus cher sans modifier sa date de facturation.

7. CHARGE_FULL_PRICE

Dans ce mode de remplacement, l'abonnement passe immédiatement au forfait supérieur ou inférieur, et l'utilisateur paie immédiatement le tarif complet du nouveau forfait. La valeur restante de l'abonnement précédent est transférée sur le nouveau forfait ou calculée au prorata de la durée restante au moment du changement de forfait.

Exemple de scénario

Un utilisateur dispose du forfait Basic (4,99 $ par mois à partir du 1er avril). Le 20 avril, l'utilisateur souhaite passer au forfait Premium (9,99 $ par mois).

Dans ce cas, on a :

  • Le prix complet du forfait Premium (9,99 $) est immédiatement facturé à l'utilisateur.
  • La valeur restante du forfait Basic (par exemple, 10 jours) est convertie en temps équivalent pour le forfait Premium. Dans cet exemple, 10 jours de Basic équivalent à 5 jours de Premium.
  • La prochaine date de renouvellement de l'utilisateur est ajustée pour inclure cette période au prorata. La nouvelle date de renouvellement devient donc le 25 mai (20 avril + 1 mois + 5 jours).
  • Les renouvellements suivants auront lieu tous les mois à partir du 25 mai.

Extrait de code

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

Mettre à niveau avec CHARGE_FULL_PRICE

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Premium.

L'utilisateur passe immédiatement au forfait Premium. Le montant à payer immédiatement correspond au prix total du forfait Premium, soit 9,99 $. Toute valeur restante du forfait Basic est convertie en temps sur le nouveau forfait Premium, ce qui repousse légèrement la première date de renouvellement. Le montant du renouvellement sera ensuite de 9, 99 € par période.

Rétrogradation avec CHARGE_FULL_PRICE

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Lite.

L'utilisateur passe immédiatement au forfait Lite et un nouveau cycle de facturation commence. Le montant à payer immédiatement correspond au prix cible total de 2,99 $. La partie inutilisée du forfait Basic est proratisée en temps pour le nouveau forfait Lite, ce qui repousse la date du premier renouvellement. Le montant du renouvellement sera ensuite de 2, 99 € par période.

Conclusion

Dans cette section, nous avons vu comment CHARGE_FULL_PRICE facture à l'utilisateur la totalité du montant à payer le jour du changement, en commençant immédiatement un nouveau cycle de facturation. Tout solde restant de l'ancien forfait s'applique de manière linéaire à la prochaine date de renouvellement.

8. WITHOUT_PRORATION

Dans ce mode de remplacement, l'abonnement passe immédiatement à un forfait supérieur ou inférieur, et le nouveau tarif est facturé lors du renouvellement de l'abonnement.

Exemple de scénario

Un utilisateur dispose du forfait Basic (4,99 $ par mois à partir du 1er avril). Le 20 avril, l'utilisateur souhaite passer au forfait Premium (9,99 $ par mois).

Dans ce cas, on a :

  • L'utilisateur a immédiatement accès au forfait Premium.
  • L'utilisateur n'aura pas à payer le nouveau prix de 9,99 $ avant la prochaine date de renouvellement de l'abonnement (1er mai).

Extrait de code

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

Mise à niveau avec WITHOUT_PRORATION

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Premium.

L'utilisateur passe immédiatement au forfait Premium tout en conservant sa date de renouvellement existante. Le montant à payer immédiatement est de 0,00 $. L'utilisateur a accès au forfait Premium pendant la durée restante du cycle en cours, avant de passer au nouveau montant de renouvellement de 9,99 $ à la prochaine date de facturation.

Rétrograder avec WITHOUT_PRORATION

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Lite.

L'utilisateur est immédiatement rétrogradé au forfait Lite et perd les fonctionnalités Basic pour lesquelles il a payé. Le montant à payer immédiatement est de 0,00 $. Le cycle de facturation se poursuit sans modification, et l'utilisateur paiera le nouveau tarif réduit de 2, 99 $ lors de son prochain renouvellement prévu.

Conclusion

Cette section a montré comment WITHOUT_PRORATION échange immédiatement les droits d'accès de l'utilisateur sans frais de paiement, tout en laissant le cycle de facturation intact.

9. DEFERRED

Dans ce mode de remplacement, l'abonnement passe à un forfait supérieur ou inférieur uniquement au moment du renouvellement, mais le nouvel achat est immédiatement émis. L'élément existant est défini comme non renouvelable et expire à la fin du cycle de facturation en cours, tandis que le droit d'accès nouvellement demandé commence juste après.

Exemple de scénario

Un utilisateur dispose du forfait Basic (4,99 $ par mois à partir du 1er avril). Le 20 avril, l'utilisateur souhaite passer au forfait Premium (9,99 $ par mois).

Dans ce cas, on a :

  • L'utilisateur n'est pas facturé immédiatement.
  • L'utilisateur continue de bénéficier des fonctionnalités de base jusqu'à la fin du cycle de facturation en cours (30 avril).
  • L'abonnement passera automatiquement à Premium à la prochaine date de renouvellement (1er mai).

Extrait de code

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

Mettre à niveau avec DEFERRED

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Premium.

L'utilisateur conservera le forfait Basic jusqu'à la fin de son cycle de facturation actuel. Le montant à payer immédiatement est de 0,00 $. À la date de renouvellement, leur abonnement passe au forfait Premium et le nouveau montant de renouvellement de 9,99 $ leur est facturé.

Rétrograder avec DEFERRED

Pour simuler ce scénario :

  • Dans le MainActivity de l'application exemple, remplacez replacementMode dans l'extrait de code par SubscriptionProductReplacementParams.ReplacementMode.DEFERRED.
  • Recompilez et lancez l'application.
  • Résiliez les abonnements existants (le cas échéant) sur le Google Play Store. Ils expireront.
  • Souscrivez la formule Basic.
  • Passez au forfait Lite.

L'utilisateur conservera le forfait Basic jusqu'à la fin de son cycle de facturation actuel. Le montant à payer immédiatement est de 0,00 $. À la date de renouvellement, leur abonnement passera au forfait Lite et le nouveau montant de renouvellement de 2,99 $ leur sera facturé.

Conclusion

Cette section explique comment le mode de remplacement DEFERRED reporte une mise à niveau ou une rétrogradation jusqu'à la fin de la période payée d'un utilisateur actif. Cette option est particulièrement idéale pour les rétrogradations, car elle permet de ne pas perdre les fonctionnalités déjà achetées.

10. Traitement côté serveur et côté client

Lorsqu'un utilisateur remplace un abonnement, assurez-vous que votre application et votre backend gèrent correctement le changement pour éviter des problèmes tels que des interruptions de service ou une double facturation.

Exemple de scénario

  • L'utilisateur dispose d'un forfait mensuel Basic (product_id basic_plan et purchase_token basic_purchase_token_123).
  • L'utilisateur passe à un forfait Premium en utilisant un mode de remplacement immédiat (WITHOUT_PRORATION, WITH_TIME_PRORATION, CHARGE_PRORATED_PRICE ou CHARGE_FULL_PRICE).
  • Une fois le changement d'abonnement effectué, Google le considère comme un NOUVEL abonnement et crée un nouveau jeton d'achat différent pour le forfait Premium (product_id premium_plan et purchase_token premium_purchase_token_123).

Traitement côté client

onPurchasesUpdated

Une fois l'achat de remplacement effectué, PurchasesUpdatedListener est déclenché. Même s'il s'agit d'un changement, Google Play considère le Forfait Premium comme un nouvel achat.

L'application recevra un objet Purchase contenant le jeton d'achat premium_purchase_token_123 et l'ID de produit premium_plan. Vous devez traiter cette demande exactement comme celle d'un nouvel abonné : validez le jeton et préparez-vous à accorder l'accès.

@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 ne renvoie que les abonnements actifs achetés depuis votre application. Vous devez vous appuyer sur cette méthode pour déterminer quel droit d'accès afficher à l'utilisateur. Pour les remplacements immédiats, queryPurchasesAsync() ne renverra plus l'ancien jeton d'achat BASIC et ne renverra plus que le nouveau jeton d'achat PREMIUM.

Appelez cette méthode chaque fois que votre application reprend ou qu'un achat est finalisé. Si le jeton Premium est présent, accordez immédiatement les fonctionnalités Premium et supprimez les fonctionnalités de base.

Traitement backend (RTDN)

Lorsqu'un remplacement a lieu, Google Play envoie une notification en temps réel pour les développeurs (NTRD) au sujet Pub/Sub que vous avez configuré.

  • En cas de remplacement immédiat, Google envoie une notification en temps réel pour les développeurs SUBSCRIPTION_PURCHASED avec le nouveau jeton d'achat.Exemple de charge utile de notification en temps réel pour les développeurs
    {
      "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
      }
    }
    
  • Lorsque votre serveur reçoit le nouveau jeton d'achat de la RTDN, appelez l'API purchases.subscriptionsV2 avec le nouveau jeton d'achat pour récupérer les détails de l'achat. La réponse de l'API contient un champ linkedPurchaseToken qui permet de déterminer si le jeton d'achat fait référence à un nouvel achat d'abonnement ou à un remplacement d'abonnement.
  • En cas de remplacement d'un abonnement, linkedPurchaseToken fait référence au jeton d'achat de l'ancien abonnement. Dans ce scénario, il s'agit de basic_purchase_token_123.Exemple de réponse 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>"
    }
    
  • Vous devez confirmer le nouvel achat Premium. Vous pouvez effectuer cette opération dans votre application ou sur votre backend. Si vous ne confirmez pas l'achat dans un délai de trois jours, l'utilisateur sera remboursé et le droit d'accès sera révoqué. Pour en savoir plus sur le traitement et la confirmation des achats, consultez la documentation pour les développeurs.

Conclusion

Cette section a abordé les étapes à suivre pour gérer les remplacements d'abonnement immédiats sur le client et votre backend. Vous avez appris que Google Play traitait le nouveau forfait comme un tout nouvel achat et émettait un nouveau jeton d'achat. Sur le client, vous devez traiter ce nouvel achat à l'aide de PurchasesUpdatedListener et mettre à jour les droits d'accès en fonction de la réponse de queryPurchasesAsync. Dans le backend, vous devez écouter les RTDN SUBSCRIPTION_PURCHASED pour le nouveau jeton, utiliser l'API purchases.subscriptionsv2 pour identifier le linkedPurchaseToken de l'ancien abonnement et révoquer rapidement l'accès associé à l'ancien jeton tout en accordant le nouveau droit d'accès. N'oubliez pas de toujours confirmer le nouvel achat.

11. Traiter les remplacements DIFFÉRÉS

Contrairement aux modes de remplacement immédiat, ReplacementMode.DEFERRED diffère la modification de l'abonnement et la mise à jour des droits d'accès jusqu'à la fin du cycle de facturation en cours. La gestion des remplacements différés nécessite une logique spécifique pour s'assurer que les utilisateurs reçoivent le bon droit d'accès au bon moment.

Exemple de scénario

  • L'utilisateur dispose d'un forfait mensuel Basic (product_id basic_plan et purchase_token basic_purchase_token_123) qui se renouvelle le 15 avril.
  • Le 1er avril, l'utilisateur décide de passer à un forfait Premium à l'aide de ReplacementMode.DEFERRED.
  • Google crée immédiatement un NOUVEAU jeton d'achat pour le forfait Premium (product_id premium_plan et purchase_token premium_purchase_123), mais le montant à facturer à l'utilisateur et le droit d'accès sont prévus pour le 15 avril.

Traiter un remplacement différé

1. Immédiatement après la réussite du parcours d'achat (application)

  • PurchasesUpdatedListener est appelé une fois le parcours d'achat terminé. L'application recevra un objet Purchase contenant le nouveau jeton d'achat premium_purchase_token_123. Toutefois, product_id fera toujours référence à l'ancien basic_plan, car l'utilisateur n'a droit qu'au forfait Basic. Vous devez traiter cette demande exactement comme un nouvel achat et confirmer le jeton.
    @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 renvoie immédiatement l'achat avec le nouveau jeton d'achat (premium_purchase_token_123) et le droit d'accès d'origine (basic_plan) qui lui est associé. Vous pouvez vous appuyer sur cette méthode pour continuer à accorder le droit d'accès au forfait Basic à l'utilisateur.

2. Immédiatement après la réussite du parcours d'achat (backend)

  • La notification en temps réel pour les développeurs SUBSCRIPTION_PURCHASED est envoyée immédiatement après le parcours d'achat pour le nouveau jeton d'achat (premium_purchase_token_123).Exemple de charge utile de notification en temps réel
    {
      "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
      }
    }
    
  • Appelez GET purchases.subscriptionsv2 avec le nouveau jeton d'achat pour récupérer les détails de l'achat. La réponse contient deux lignes.
    • L'une représentant l'ancien abonnement (forfait de base) et comportant un expiryTime dans le futur. L'ancien abonnement ne sera pas renouvelé et comporte un deferredItemReplacement contenant le nouvel abonnement (forfait Premium). Cela indique qu'un remplacement de l'ancien droit d'accès est en attente à son expiration.
    • Un représentant l'abonnement nouvellement souscrit. Aucune valeur n'est définie pour "expiryTime".
    Exemple de réponse de l'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>"
    }
    
  • Vous devez confirmer le nouveau jeton d'achat. Vous pouvez effectuer cette opération dans votre application ou sur votre backend. Pour en savoir plus sur le traitement et la confirmation des achats, consultez la documentation pour les développeurs.
  • La notification en temps réel pour les développeurs SUBSCRIPTION_EXPIRED est envoyée pour l'ancien jeton d'achat (basic_purchase_token_123).Exemple de charge utile de notification en temps réel pour les développeurs
    {
      "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
      }
    }
    
  • Lorsque vous appelez l'API GET purchases.subscriptionsv2 avec l'ancien jeton d'achat, il apparaît comme expiré (SUBSCRIPTION_STATE_EXPIRED). Les droits d'accès de l'ancien forfait sont transférés vers le nouveau pour le temps restant.

3. À la date de remplacement : premier renouvellement après le parcours d'achat (application)

  • queryPurchasesAsync renvoie l'achat avec le nouveau jeton d'achat (premium_purchase_token_123) et le nouvel abonnement qui lui est associé (premium_plan).
  • Le nouvel achat devrait déjà avoir été traité au moment de la réussite du parcours d'achat. Vous n'avez donc aucune action spéciale à effectuer, à part vous assurer que l'utilisateur a accès à l'abonnement approprié.

4. À la date de remplacement : premier renouvellement après le parcours d'achat (backend)

  • Avec ReplacementMode.DEFERRED, les premiers renouvellements suivent le comportement standard de tout autre renouvellement qui traite les RTDN SUBSCRIPTION_RENEWED. Vous n'avez pas besoin de gérer une logique spéciale pour les remplacements lorsque cet événement se produit.
  • Appelez GET purchases.subscriptionsv2 avec le nouveau jeton d'achat pour récupérer les détails de l'achat. La réponse contient deux lignes.
    • L'une représentant l'ancien abonnement (forfait de base) et comportant un expiryTime dans le passé. L'ancienne souscription n'aura plus de valeur définie pour le champ deferredItemReplacement.
    • L'autre représentant le nouvel abonnement avec un expiryTime à venir et le champ autoRenewEnabled défini sur true.
    Exemple de réponse de l'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>"
    }
    

Conclusion

Cette section décrit le traitement spécifique requis pour ReplacementMode.DEFERRED. Vous avez appris que, contrairement aux modes immédiats, la modification des droits d'accès ne se produit qu'à la fin du cycle de facturation en cours. Cette section a abordé les étapes nécessaires pour que votre application et votre backend traitent correctement l'achat initial, accusent réception du nouveau jeton et gèrent le changement de droits d'accès lorsque l'ancien abonnement expire et que le nouveau devient actif.

12. Bac à sable de remplacement d'abonnement

La fonctionnalité Replacement Playground de l'application exemple vous permet de tester les changements d'abonnement (passage à un forfait supérieur ou inférieur) pour les produits d'abonnement configurés dans votre compte Google Play Console. Cette section explique comment utiliser la fonctionnalité Replacement Playground (Bac à sable de remplacement).

Configuration

Pour utiliser la fonctionnalité Replacement Playground, assurez-vous de disposer des éléments suivants :

  • Le packageId de votre fichier build.gradle correspond à l'application configurée dans votre Google Play Console.
  • Votre compte utilisateur test est enregistré en tant que testeur de licence dans la Google Play Console. Pour en savoir plus sur les tests de licence, consultez Tester l'implémentation de la facturation de votre application.

Bac à sable de remplacement d'abonnement

L'application exemple inclut un onglet Replacement Playground (Bac à sable de remplacement) qui vous permet de simuler des modifications d'abonnement. Vous pouvez interroger les abonnements définis dans la Play Console et tester le passage de l'un à l'autre à l'aide de différents modes de remplacement. Ce terrain de jeu vous aide à comprendre comment les différents modes affectent les cycles de facturation et les droits d'accès pour vos abonnements. Vous pouvez ainsi déterminer les options qui répondent le mieux aux besoins de votre entreprise.

Pour simuler des remplacements à l'aide de l'atelier, procédez comme suit :

  1. Accédez à l'onglet Playground.
  2. Si vous disposez d'un abonnement actif, il sera mis en évidence. Il s'agit de l'abonnement qui sera remplacé.

Accueil de Playground

  1. Si vous n'avez pas d'abonnement actif, vous devez d'abord en souscrire un.
    • Le terrain de jeu liste les forfaits Basic, Premium et Lite créés par défaut pour cet atelier de programmation.
    • Pour effectuer des tests avec d'autres forfaits configurés dans votre Play Developer Console, cliquez sur Ajouter un forfait personnalisé, puis recherchez-les par productId et basePlanId.
    • Souscrivez l'abonnement sélectionné.
    • L'abonnement actif que l'utilisateur vient d'acheter devrait maintenant s'afficher. Ajouter un abonnement personnalisé
  2. Sélectionnez l'abonnement cible vers lequel l'utilisateur souhaite passer .
  3. Sélectionnez un mode de remplacement pour la transition.

Sélectionner le mode de remplacement

  1. Cliquez sur le bouton Tester le remplacement pour simuler le remplacement de l'abonnement.
  2. La bottom sheet Google Play Billing doit s'afficher avec les détails calculés du remplacement de l'abonnement (tels que les frais immédiats et les ajustements du cycle de facturation).

Panier de facturation pour le remplacement d&#39;un abonnement

  1. Effectuez la transaction.
  2. Accédez à la page Gérer les abonnements dans l'application Play Store pour afficher les modifications apportées à l'abonnement actif, ainsi que les nouvelles dates de renouvellement et les nouveaux prix.

Gestion des abonnements Play Store

13. Étapes suivantes

Documents de référence

14. Félicitations

Félicitations ! Vous avez correctement implémenté les remplacements d'abonnement avec différents modes de prorata et configuré la gestion du backend pour les transitions de forfait.

Connaissances acquises

  • Configurer SubscriptionProductReplacementParams avec des modes de remplacement spécifiques.
  • Différence entre les mises à niveau immédiates et les rétrogradations différées.
  • Comment retirer les anciens jetons d'abonnement à l'aide de linkedPurchaseToken et des numéros RTDN

Enquête

Vos commentaires sur cet atelier de programmation sont très importants. Prenez quelques minutes pour répondre à notre enquête.