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

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

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

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

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.

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

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

  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.

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

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.

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

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

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

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

Further reading

Organic growth and Y Combinator
Organic growth and Y Combinator
August 24, 2021
14 min read, 50 min listen
What’s new in SKAdNetwork 4.0
What’s new in SKAdNetwork 4.0
June 30, 2022
5 min read
iOS in-app purchases, part 3: testing purchases in Xcode
iOS in-app purchases, part 3: testing purchases in Xcode
August 19, 2021
8 min read