BlogRight arrowTutorialRight ArrowAchats intégrés sous Android, partie 1 : configuration et ajout au projet
BlogRight arrowTutorialRight ArrowAchats intégrés sous Android, partie 1 : configuration et ajout au projet

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

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

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. 
⭐️ Download our guide on in-app techniques which will make in-app purchases in your app perfect

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.

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

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

  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.   

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

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.  

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?) {
       // 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.

Start for free

Convenient in-app purchases infrastructure.

Adapty SDK has it all:
— server-side purchase validation,
— all side cases covered,
— simple implementation.

Start for free

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,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

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?) {
       // 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. 

Further reading

How to integrate Mixpanel Analytics into your iOS App
How to integrate Mixpanel Analytics into your iOS App
March 29, 2021
8 min read
How to find a new niche and grow your app
How to find a new niche and grow your app
May 25, 2022
42 min listen
Adapty October updates: comparison in charts, remote config table, and more
Adapty October updates: comparison in charts, remote config table, and more
November 10, 2022
5 min read