Report: State of in-app subscriptions in the US 2023 Get a report

Achats intégrés sous Android, partie 1 : configuration et ajout au projet

Vlad Guriev

Updated: mars 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

Dans cet article, nous vous expliquerons comment :  

  • Créez un produit dans la Google Play Console ;
  • Configurer les abonnements : comment spécifier la durée, le prix, les essais ; 
  • Ajouter une liste de produits dans une application. 

Création d’un abonnement

Avant de commencer, assurez-vous que vous :

  1. Disposez d’un compte de développeur pour Google Play
  2. Avez signé tous les accords et êtes prêts à commencer à travailler.  

Maintenant, mettons-nous au travail et créons notre premier produit.  

Accédez à votre compte de développeur choisissez l’application.

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

Ensuite, dans le menu de gauche, trouvez la section Produits, sélectionnez Subscriptions et cliquez sur Create a Subscription.

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

Ensuite, nous verrons le configurateur d’abonnement. Voici quelques points importants.

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb
  1. Créez l’ID (identifiant) qui sera utilisé dans l’application. C’est une bonne idée d’ajouter une période d’abonnement ou d’autres informations utiles à l’identifiant(ID). Ainsi, vous pouvez créer des produits dans un seul style et faciliter l’analyse des statistiques de vente.
  2. Le nom de l’abonnement que l’utilisateur verra sur la page du magasin (store).  
  3. Description de l’abonnement. L’utilisateur le verra également.   
60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

Faites défiler la liste et choisissez la période d’abonnement. Dans notre cas, c’est une semaine. Fixez le prix.  

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

En général, vous fixez le prix dans la devise du compte de base, et le système convertit le prix automatiquement. Mais vous pouvez également modifier manuellement le prix pour un pays spécifique.  

Veuillez noter que Google affiche la taxe pour chaque pays. C’est génial, App Store Connect ne le fait pas.  

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

Faites défiler et choisissez (si nécessaire) : 

  1. Période d’essai gratuite (free trial period).
  2. Prix de lancement, qui désigne une offre pour les premières périodes de paiement.  
  3. Délai de grâce (grace period). Si un utilisateur a des problèmes de paiement, vous pouvez toujours lui fournir un accès premium pendant un certain nombre de jours.
  4. Une possibilité de se réabonner à partir de Play Store, et non de l’application, après annulation. 

Comparaison du processus d’achat dans Play Console et App Store Connect

Indépendamment du fait que les abonnements sont monétisés plus efficacement sur iOS, le tableau d’administration de Play Console est plus pratique, il est mieux organisé et localisé, et il fonctionne plus rapidement.

Le processus de création du produit est simplifié autant que possible. Ici nous avons expliqué  comment créer des produits sur iOS.

Obtenir une liste de produits dans une application

Une fois les produits créés, travaillons sur l’architecture pour accepter et traiter les achats. En général, le processus se présente comme suit :   

  1. Ajouter un service de facturation.
  2. Développez une catégorie pour l’interaction avec les produits de Google Play.
  3. Mettre en œuvre toutes les méthodes pour traiter les achats.  
  4. Ajouter la validation par le serveur d’un achat.  
  5. Recueillir des données analytiques.

Dans cette partie, nous allons examiner de plus près les deux premiers points.  

Ajout d’un service de facturation à un projet :

implementation "com.android.billingclient:billing:4.0.0"

Au moment où nous écrivons ces lignes, la dernière version est la 4.0.0. Vous pouvez la remplacer par toute autre version à tout moment.  

Créons une classe enveloppante qui couvrira la logique de l’interaction avec Google Play et initialisons BillingClient du Service de facturation dans cette classe. Appelons cette classe BillingClientWrapper.

Cette classe mettra en œuvre l’interface PurchasesUpdatedListener. Nous allons surcharger une méthode pour elle maintenant – onPurchasesUpdated(billingResult : BillingResult, purchaseList: MutableList<Purchase>?) – il est nécessaire juste après un achat, mais nous décrirons le processus d’implémentation dans le prochain article. 

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

Google recommande d’éviter d’avoir plus d’une connexion active entre BillingClient et Google Play pour éviter qu’un rappel concernant un achat effectué ne soit exécuté plusieurs fois. Ainsi, vous devez avoir un BillingClient unique dans une classe singleton. La classe dans l’exemple n’est pas un singleton, mais nous pouvons utiliser l’injection de dépendance (par exemple, avec l’aide de Dagger ou de Koin) de cette manière, permettant à une seule instance d’exister à un moment donné. 

Pour effectuer une demande auprès de Billing Library, BillingClient doit avoir une connexion active avec Google Play au moment où la demande est effectuée, mais la connexion peut être perdue à un moment donné. Pour des raisons de commodité, écrivons un wrapper nous permettant d’effectuer des requêtes uniquement lorsque la connexion est active.   

Pour obtenir les produits, nous avons besoin de leurs identifiants que nous avons définis sur le marché. Mais il ne suffit pas d’une requête, nous avons également besoin du type de produit (abonnements ou achats uniques) ; c’est pourquoi nous pouvons obtenir une liste générale de produits en «combinant» les résultats de deux requêtes.

La demande de produits est asynchrone, nous avons donc besoin de la fonction de rappel qui nous fournira une liste de produits ou nous renverra un modèle d’erreur. Lorsqu’une erreur se produit, le service de facturation renvoie l’un de ses BillingResponseCodes, ainsi qu’un debugMessage. Créons une interface de rappel ainsi qu’un modèle pour une erreur :

interface OnQueryProductsListener {
  fun onSuccess(products: List < SkuDetails > )
  fun onFailure(error: Error)
}

class Error(val responseCode: Int, val debugMessage: String)

Voici le code d’une méthode privée permettant d’obtenir des données sur un type spécifique de produits et d’une méthode publique qui «combinera» les résultats de deux requêtes et fournira à l’utilisateur la liste finale des produits ou affichera un message d’erreur.  

fun queryProducts(listener: OnQueryProductsListener) {
   val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

   queryProductsForType(
       skusList,
       BillingClient.SkuType.SUBS
   ) { billingResult, skuDetailsList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           val products = skuDetailsList ?: mutableListOf()
           queryProductsForType(
               skusList,
               BillingClient.SkuType.INAPP
           ) { billingResult, skuDetailsList ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                   products.addAll(skuDetailsList ?: listOf())
                   listener.onSuccess(products)
               } else {
                   listener.onFailure(
                       Error(billingResult.responseCode, billingResult.debugMessage)
                   )
               }
           }
       } else {
           listener.onFailure(
               Error(billingResult.responseCode, billingResult.debugMessage)
           )
       }
   }
}

private fun queryProductsForType(
   skusList: List<String>,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

Ainsi, nous avons obtenu des informations précieuses sur les produits (SkuDetails) où nous pouvons voir les noms localisés, les prix, le type de produit, ainsi que la période de facturation et des informations sur le prix de lancement et la période d’essai (si elle est disponible pour cet utilisateur) pour les abonnements. Voici à quoi ressemble la classe finale :   

import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
   interface OnQueryProductsListener {
       fun onSuccess(products: List<SkuDetails>)
       fun onFailure(error: Error)
   }
   class Error(val responseCode: Int, val debugMessage: String)
   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()
   fun queryProducts(listener: OnQueryProductsListener) {
       val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
       queryProductsForType(
           skusList,
           BillingClient.SkuType.SUBS
       ) { billingResult, skuDetailsList ->
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               val products = skuDetailsList ?: mutableListOf()
               queryProductsForType(
                   skusList,
                   BillingClient.SkuType.INAPP
               ) { billingResult, skuDetailsList ->
                   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                       products.addAll(skuDetailsList ?: listOf())
                       listener.onSuccess(products)
                   } else {
                       listener.onFailure(
                           Error(billingResult.responseCode, billingResult.debugMessage)
                       )
                   }
               }
           } else {
               listener.onFailure(
                   Error(billingResult.responseCode, billingResult.debugMessage)
               )
           }
       }
   }
   private fun queryProductsForType(
       skusList: List<String>,
       @BillingClient.SkuType type: String,
       listener: SkuDetailsResponseListener
   ) {
       onConnected {
           billingClient.querySkuDetailsAsync(
               SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
               listener
           )
       }
   }
   private fun onConnected(block: () -> Unit) {
       billingClient.startConnection(object : BillingClientStateListener {
           override fun onBillingSetupFinished(billingResult: BillingResult) {
               block()
           }
           override fun onBillingServiceDisconnected() {
               // Try to restart the connection on the next request to
               // Google Play by calling the startConnection() method.
           }
       })
   }
   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

C’est tout pour aujourd’hui. Dans les prochains articles, nous vous parlerons de la mise en œuvre des achats, des tests et de la gestion des erreurs.