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

Acquisti in-app per Android, parte 1: configurazione e aggiunta al progetto

Vlad Guriev

Updated: Marzo 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

Gli acquisti in-app (in-app purchases), in particolare gli abbonamenti (subscriptions), sono i metodi più diffusi per guadagnare con un’app. Da un lato, l’abbonamento consente allo sviluppatore di investire nello sviluppo dei contenuti e del prodotto, dall’altro aiuta gli utenti a ottenere un’app, nel complesso, di qualità superiore. Gli acquisti in-app sono soggetti a una commissione del 30%, ma se un utente è abbonato da più di un anno o un’app  guadagna meno di $1М all’anno, la commissione si riduce al 15%.

In questo articolo spiegheremo come:  

  • Creare un prodotto nella Console Google Play;
  • Configurare gli abbonamenti: come specificare durata, prezzo, periodi di prova; 
  • Aggiungere un elenco di prodotti in un’app. 

Creazione degli abbonamenti

Prima di iniziare, accertati di:

  1. Possedere un account sviluppatore per Google Play.
  2. Aver firmato tutti gli accordi ed essere pronto a iniziare a lavorare.  

Ora, mettiamoci al lavoro e creiamo il nostro primo prodotto.  

Passa al tuo account sviluppatore e scegli l’app.

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

Quindi, nel menu a sinistra, cerca la sezione Products, seleziona Subscriptions e fai clic su Create a Subscription

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

Ora vedremo il configuratore di abbonamenti. Ecco alcuni punti importanti.

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb
  1. Creazione dell’ID che verrà usata nell’app. È una buona idea aggiungere all’ID un periodo di abbonamento o altre informazioni utili, in modo da poter creare prodotti in un unico stile e facilitare l’analisi delle statistiche di vendita.
  2. Il nome dell’abbonamento che l’utente vedrà nel negozio.  
  3. Descrizione dell’abbonamento. L’utente vedrà anche questa informazione.  
60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

Scorriamo verso il basso e scegliamo il periodo di abbonamento. Nel nostro caso, sarà di una settimana. Impostiamo il prezzo.  

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

Di solito, si imposta il prezzo nella valuta dell’account di base e il sistema lo converte automaticamente. Ma è anche possibile modificare il prezzo per un paese specifico in modalità manuale.  

Nota che Google mostra la tassa per ciascun paese. È fantastico, App Store Connect non lo fa.  

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

Scorriamo verso il basso e scegliamo (se necessario): 

  1. Periodo di prova gratuito (Free trial).
  2. Prezzo introduttivo, cioè un’offerta per i primi periodi di pagamento.  
  3. Periodo di tolleranza (Grace period). Se un utente ha problemi di pagamento, puoi comunque fornirgli l’accesso premium per un certo numero di giorni.
  4. L’opportunità di iscriversi nuovamente dal Play Store, non dall’app, in seguito all’annullamento (cancellation). 

Confronto tra il processo di acquisto nella Play Console e in App Store Connect

Benché gli abbonamenti siano monetizzati in modo più efficace su iOS, il pannello di amministrazione della Play Console è più comodo, è organizzato e localizzato meglio ed è più rapido.

Il processo di creazione del prodotto è reso quanto più semplice possibile. Qui spieghiamo come creare prodotti in iOS.

Come ottenere un elenco di prodotti in un’app

Una volta creati i prodotti, lavoriamo sull’architettura per accettare ed elaborare gli acquisti. In generale, il processo è il seguente:   

  1. Aggiungere una Libreria Fatturazione.
  2. Sviluppare una classe per l’interazione con i prodotti di Google Play.
  3. Implementare tutti i metodi per elaborare gli acquisti.  
  4. Aggiungere la convalida di un acquisto (purchase validation) da parte del server.  
  5. Raccogliere le analisi.

In questa parte, esaminiamo più da vicino i primi due punti.  

Aggiunta della Libreria Fatturazione a un progetto:

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

Al momento della stesura di questo articolo, l’ultima versione è la 4.0.0. È possibile sostituirla con qualsiasi altra versione in qualsiasi momento.  

Creiamo una classe wrapper che copra la logica di interazione con Google Play e inizializziamo BillingClient dalla Libreria Fatturazione. Chiamiamo questa classe BillingClientWrapper.

Questa classe implementerà l’interfaccia PurchasesUpdatedListener. Ora sovrascriviamo un metodo per farlo: onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) , è necessario subito dopo aver effettuato un acquisto, ma descriveremo il processo di implementazione nel prossimo articolo. 

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 raccomanda di evitare di avere più di una connessione attiva tra BillingClient e Google Play, per evitare che un callback relativo a un acquisto effettuato venga eseguito più volte. Pertanto, si dovrebbe avere un unico BillingClient in una classe singleton. La classe dell’esempio non è un singleton, ma possiamo usare dependency injection (per esempio, con l’aiuto di Dagger o Koin) in questo modo, consentendo l’esistenza di una sola istanza in un dato momento. 

Per effettuare qualsiasi richiesta con la Libreria Fatturazione, BillingClient deve avere una connessione attiva con Google Play nel momento in cui viene effettuata la richiesta, ma, a un certo punto, la connessione può essere persa. Per comodità, scriviamo un wrapper che ci permetta di fare richieste solo quando la connessione è attiva.  

Per ottenere i prodotti, abbiamo bisogno dei loro ID impostati sul mercato. Ma non basta una richiesta, serve anche il tipo di prodotto (abbonamenti o acquisti una tantum), per questo possiamo ottenere un elenco generale di prodotti “combinando” i risultati di due richieste.   

La richiesta di prodotti è asincrona, quindi abbiamo bisogno di un callback che ci fornisca un elenco di prodotti o restituisca un modello di errore. Quando si verifica un errore, la Libreria Fatturazione restituisce un BillingResponseCodes, e un debugMessage. Creiamo un’interfaccia di callback e un modello per un errore:  

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

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

Ecco il codice di un metodo privato per ottenere dati su un tipo specifico di prodotti e di un metodo pubblico che “combinerà” i risultati di due richieste e fornirà all’utente l’elenco finale dei prodotti o mostrerà un messaggio di errore.  

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!

Abbiamo così ottenuto informazioni preziose sui prodotti (SkuDetails) che ci indicano i nomi, i prezzi e il tipo di prodotto localizzati, così come il periodo di fatturazione e le informazioni sul prezzo di lancio e sul periodo di prova (se disponibile per questo utente) per gli abbonamenti. La classe definitiva verrà visualizzata in questo modo:   

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
   }
}

È tutto, per oggi. Nei prossimi articoli parleremo dell’implementazione degli acquisti (purchase implementation), dei test e della gestione degli errori.