![Le novità dell'API StoreKit 2 e come Apple ha semplificato l'integrazione degli acquisti in-app 8 62fdf0da8b35ff3ba074dbce jp android tutorial 1 configuration 5](png/62fdf0da8b35ff3ba074dbce_jp-android-tutorial-1-configuration-5.png)
Tendenze-insight
Settembre 6, 2022
Updated: Marzo 20, 2023
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:
Prima di iniziare, accertati di:
Ora, mettiamoci al lavoro e creiamo il nostro primo prodotto.
Passa al tuo account sviluppatore e scegli l’app.
Quindi, nel menu a sinistra, cerca la sezione Products, seleziona Subscriptions e fai clic su Create a Subscription.
Ora vedremo il configuratore di abbonamenti. Ecco alcuni punti importanti.
Scorriamo verso il basso e scegliamo il periodo di abbonamento. Nel nostro caso, sarà di una settimana. Impostiamo il prezzo.
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.
Scorriamo verso il basso e scegliamo (se necessario):
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.
Una volta creati i prodotti, lavoriamo sull’architettura per accettare ed elaborare gli acquisti. In generale, il processo è il seguente:
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
)
}
}
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.
Further reading
Tendenze-insight
Settembre 6, 2022
Newsletter a pagamento di Adapty
Suggerimenti, trucchi e idee per aumentare le conversazioni sul paywall della vostra app. Contenuti freschi dai migliori esperti del settore mobile ogni mese.