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

Android In-App-Köufe, Teil 1: Konfiguration und Hinzufügung zum Projekt

Vlad Guriev

Updated: März 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

In-App-Käufe (In-app purchases), insbesondere Abonnements, sind die beliebtesten Methoden, um mit Apps Geld zu verdienen. Auf der einen Seite erlauben Abonnements den Entwicklern, in den Content und das Produkt zu investieren. Auf der anderen Seite helfen sie Nutzern, qualitativ hochwertige Apps zu erhalten. In-App-Käufe unterliegen einer Provision in Höhe von 30%, doch wenn ein Nutzer für mindestens ein Jahr lang abonniert war oder die App weniger als 1 Million USD pro Jahr einbringt, gilt eine reduzierte Provision in Höhe von 15%.

In diesem Artikel erklären wir, wie Sie:  

  • Ein Produkt in Google Play Console erstellen;
  • Abonnements konfigurieren: Festlegung von Dauer, Preis, Testversionen; 
  • Eine Produktliste in einer App hinzufügen.

Die Erstellung des Abonnements

Stellen Sie bitte zunächst sicher, dass Sie:

  1. Einen Developer-Account für Google Play haben.
  2. Alle Vereinbarungen unterschrieben haben und bereit für die Arbeit sind.

Lassen Sie uns nun unser erstes Produkt erstellen.

Gehen Sie zu Ihrem Developer-Account und wählen Sie die App aus.

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

Finden Sie anschließend im linken Menü den Produktbereich und wählen Sie Abonnements aus. Klicken Sie auf Ein Abonnement erstellen.

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

Wir sehen nun den Konfigurator des Abonnements. Hier gibt es einige wichtige Feinheiten zu beachten.

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb
  1. Erstellen Sie die ID, die in der App verwendet werden wird. Fügen Sie eine Abonnement-Dauer oder andere nützliche Informationen zur ID hinzu, um Produkte gemäß eines Stils zu erstellen und die Analysen der Statistiken zu einem späteren Zeitpunkt zu vereinfachen.
  2. Der Name des Abonnements, den der Nutzer im Store sehen wird.  
  3. Abonnement-Beschreibung, die der Nutzer ebenfalls sehen wird.
60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

Scrollen Sie weiter nach unten und wählen Sie die Abonnement-Dauer aus. In unserem Fall eine Woche. Legen Sie den Preis fest.

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

In der Regel legen Sie den Preis in der Kontowährung fest. Das System wandelt diesen dann automatisch um. Sie können den Preis jedoch auch per Hand an ein bestimmtes Land und dessen Währung anpassen.

Bitte beachten Sie, dass Google die Steuern für jedes Land anzeigt. Das ist äußerst praktisch, da dies bei App Store Connect nicht der Fall ist.

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

Scrollen Sie nach unten und wählen Sie bei Bedarf Folgendes aus:

  1. Kostenlose Testversion.
  2. Einführungspreis (ein Angebotspreis für erstmalige Abonnement-Zeiträume).  
  3. Gnadenfrist. Falls ein Nutzer über Zahlungsschwierigkeiten verfügt, können Sie ihm für einige Tage Premium-Zugang gewähren.
  4. Die Möglichkeit, nach der Kündigung ein erneutes Abonnement über den Play Store und nicht über die App abzuschließen. 

Vergleich des Kaufvorgangs in Play Console und App Store Connect

Obwohl Abonnements auf iOS erfolgreicher sind, ist die Admin-Oberfläche von Play Console deutlich praktischer, besser organisiert, schneller und richtig übersetzt.

Der Vorgang der Produkterstellung ist denkbar einfach. Hier haben wir Ihnen gezeigt, wie sie Produkte auf iOS erstellen.

Hinzufügung einer Produktliste in einer App

Sobald die Produkte erstellt wurden, sollten Sie an deren Architektur feilen, um Käufe zu akzeptieren und zu verarbeiten. Normalerweise sieht dieser Vorgang wie folgt aus:   

  1. Fügen Sie eine Billing Library hinzu.
  2. Entwickeln Sie eine Klasse zur Interaktion mit Produkten aus Google Play.
  3. Implementieren Sie alle Methoden zur Verarbeitung von Käufen.  
  4. Fügen Sie die Servervalidierung eines Kaufs hinzu.  
  5. Sammeln Sie Analytics.

Lassen Sie uns besonders die ersten beiden Punkte genauer betrachten.  

Die Hinzufügung einer Billing Library zu einem Projekt:

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

Zum Zeitpunkt der Veröffentlichung dieses Artikels ist die neueste Version 4.0.0. Sie können sie zu jederzeit mit einer anderen Version ersetzen.  

Lassen Sie uns eine Klasse erstellen, welche die Logik der Interaktion mit Google Play abdeckt und den BillingClient aus der Billing Library startet. Wir nennen diese Classe BillingClientWrapper.

Diese Klasse wird das PurchasesUpdatedListener Interface implementieren. Wir überschreiben diese Methode für den Moment – onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) – was direkt nach einem erfolgten Kauf nötig ist. Das genaue Implementierungsverfahren beschreiben wir im nächsten Artikel. 

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 empfiehlt, mehr als eine aktive Verbindung zwischen BillingClient und Google Play zu vermeiden, damit Callbacks zu einem erfolgten Kauf nicht mehrmals erfolgen können. Sie sollten einen einzigen BillingClient in einer einzigen Klasse haben. Die Klasse in dem Beispiel ist nicht einzeln, aber wir können Dependency Injection nutzen (zum Beispiel mithilfe von Dagger oder Koin), damit nur eine Instanz gleichzeitig existieren kann. 

Der BillingClient muss jederzeit eine aktive Verbindung zu Google Play haben, wenn die Anfrage an die Billing Library erfolgt. Die Verbindung könnte jedoch zu beliebigen Zeitpunkten verloren gehen. Lass Sie uns also einen Wrapper schreiben, mit dem wir nur Anfragen machen können, sofern die Verbindung steht.  

Wir benötigen die IDs der Produkte aus dem Markt, um sie zu erhalten. Eine einfache Anfrage reicht jedoch nicht aus, sondern wir benötigen auch den Produkttyp (Abonnements oder einmalige Käufe). Daher erhalten wir die allgemeine Produktliste durch das “Kombinieren” der Ergebnisse zweier Anfragen.   

Die Anfrage für Produkt erfolgt nicht synchron. Wir brauchen einen Callback, der uns entweder eine Produktliste oder ein Fehlermodell bereitstellt. Tritt ein Fehler auf, sendet die  Billing Library einen ihrer BillingResponseCodes sowie eine debugMessage zurück. Lassen Sie uns das Callback-Interface und ein Modell für einen Fehler erstellen:

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

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

Hier ist der Code für eine private Methode, um Daten zu einem bestimmten Produkttyp zu erhalten. Der Code umfasst auch eine öffentliche Methode, welche die Ergebnisse der zwei Anfragen “kombiniert” und dem Nutzer eine finale Produktliste bereitstellt oder eine Fehlernachricht anzeigt.  

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!

Wir haben nun die wertvollen Informationen zu den Produkten (SkuDetails) und sehen die lokalisierten Namen, Preise, Produkttypen sowie den Rechnungszeitraum und die Informationen zum Einführungspreis und der Testversion für Abonnements (falls vorgesehen). So sieht die letztendliche Klasse aus:

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

Das ist alles für heute. In den nächsten Artikel besprechen wir die Kaufimplementierung, das Testen und den Umgang mit Fehlern.