Mit BillingResult-Antwortcodes umgehen

Wenn ein Play Billing Library-Aufruf eine Aktion auslöst, gibt die Bibliothek die Antwort BillingResult zurück, um die Entwickler über das Ergebnis zu informieren. Wenn Sie beispielsweise queryProductDetailsAsync verwenden, um die verfügbaren Angebote für den Nutzer abzurufen, enthält der Antwortcode entweder einen OK-Code und das richtige ProductDetails-Objekt oder eine andere Antwort, die angibt, warum das ProductDetails-Objekt nicht bereitgestellt werden konnte.

Nicht alle Antwortcodes sind Fehler. Auf der Referenzseite zu BillingResponseCode finden Sie eine detaillierte Beschreibung der einzelnen Antworten, die in diesem Leitfaden erläutert werden. Hier einige Beispiele für Antwortcodes, die nicht auf Fehler hinweisen:

Wenn der Antwortcode auf einen Fehler hinweist, liegt die Ursache manchmal an vorübergehenden Bedingungen und eine Wiederherstellung ist somit möglich. Wenn beim Aufruf einer Play Billing Library-Methode ein BillingResponseCode-Wert zurückgegeben wird, der auf eine wiederherstellbare Bedingung hinweist, sollten Sie den Aufruf wiederholen. In anderen Fällen gelten Bedingungen nicht als vorübergehend. Daher wird kein Wiederholungsversuch empfohlen.

Bei vorübergehenden Fehlern sind unterschiedliche Wiederholungsstrategien erforderlich, je nachdem, ob der Fehler in einer Sitzung der Nutzer auftritt – etwa wenn ein Nutzer einen Kaufvorgang durchläuft oder im Hintergrund erfolgt –, z. B. wenn Sie vorhandene Käufe des Nutzers während onResume abfragen. Im Abschnitt zu Strategien für Wiederholungsversuche unten finden Sie Beispiele für diese verschiedenen Strategien. Im Abschnitt „Wiederholbare Antworten“ BillingResult wird empfohlen, welche Strategie für den jeweiligen Antwortcode am besten funktioniert.

Neben dem Antwortcode enthalten einige Fehlerantworten auch Meldungen für Fehlerbehebungs- und Logging-Zwecke.

Wiederholungsstrategien

Einfache Wiederholung

In Situationen, in denen sich der Nutzer in einer Sitzung befindet, ist es besser, eine einfache Strategie für Wiederholungsversuche zu implementieren, damit der Fehler die Nutzererfahrung so wenig wie möglich beeinträchtigt. In diesem Fall empfehlen wir eine einfache Wiederholungsstrategie mit einer maximalen Anzahl von Versuchen als Exit-Bedingung.

Das folgende Beispiel zeigt eine einfache Wiederholungsstrategie zur Behandlung eines Fehlers beim Herstellen einer BillingClient-Verbindung:

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
  // Initialize the BillingClient.
  private val billingClient = BillingClient.newBuilder(context)
    .setListener(this)
    .enablePendingPurchases()
    .build()

  // Establish a connection to Google Play.
  fun startBillingConnection() {
    billingClient.startConnection(object : BillingClientStateListener {
      override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
          Log.d(TAG, "Billing response OK")
          // The BillingClient is ready. You can now query Products Purchases.
        } else {
          Log.e(TAG, billingResult.debugMessage)
          retryBillingServiceConnection()
        }
      }

      override fun onBillingServiceDisconnected() {
        Log.e(TAG, "GBPL Service disconnected")
        retryBillingServiceConnection()
      }
    })
  }

  // Billing connection retry logic. This is a simple max retry pattern
  private fun retryBillingServiceConnection() {
    val maxTries = 3
    var tries = 1
    var isConnectionEstablished = false
    do {
      try {
        billingClient.startConnection(object : BillingClientStateListener {
          override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
              isConnectionEstablished = true
              Log.d(TAG, "Billing connection retry succeeded.")
            } else {
              Log.e(
                TAG,
                "Billing connection retry failed: ${billingResult.debugMessage}"
              )
            }
          }
        })
      } catch (e: Exception) {
        e.message?.let { Log.e(TAG, it) }
        tries++
      }
    } while (tries <= maxTries && !isConnectionEstablished)
  }
  ...
}

Wiederholungsversuch exponentieller Backoffs

Wir empfehlen die Verwendung des exponentiellen Backoffs für Play Billing Library-Vorgänge, die im Hintergrund ausgeführt werden und die Nutzererfahrung während der Sitzung nicht beeinträchtigen.

Eine Implementierung wäre beispielsweise sinnvoll, wenn neue Käufe bestätigt werden, weil dieser Vorgang im Hintergrund ausgeführt werden kann und die Bestätigung nicht in Echtzeit erfolgen muss, wenn ein Fehler auftritt.

private fun acknowledge(purchaseToken: String): BillingResult {
  val params = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchaseToken)
    .build()
  var ackResult = BillingResult()
  billingClient.acknowledgePurchase(params) { billingResult ->
    ackResult = billingResult
  }
  return ackResult
}

suspend fun acknowledgePurchase(purchaseToken: String) {

  val retryDelayMs = 2000L
  val retryFactor = 2
  val maxTries = 3

  withContext(Dispatchers.IO) {
    acknowledge(purchaseToken)
  }

  AcknowledgePurchaseResponseListener { acknowledgePurchaseResult ->
    val playBillingResponseCode =
    PlayBillingResponseCode(acknowledgePurchaseResult.responseCode)
    when (playBillingResponseCode) {
      BillingClient.BillingResponseCode.OK -> {
        Log.i(TAG, "Acknowledgement was successful")
      }
      BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
        // This is possibly related to a stale Play cache.
        // Querying purchases again.
        Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED")
        billingClient.queryPurchasesAsync(
          QueryPurchasesParams.newBuilder()
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
        )
        { billingResult, purchaseList ->
          when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
              purchaseList.forEach { purchase ->
                acknowledge(purchase.purchaseToken)
              }
            }
          }
        }
      }
      in setOf(
         BillingClient.BillingResponseCode.ERROR,
         BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
         BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
       ) -> {
        Log.d(
          TAG,
          "Acknowledgement failed, but can be retried --
          Response Code: ${acknowledgePurchaseResult.responseCode} --
          Debug Message: ${acknowledgePurchaseResult.debugMessage}"
        )
        runBlocking {
          exponentialRetry(
            maxTries = maxTries,
            initialDelay = retryDelayMs,
            retryFactor = retryFactor
          ) { acknowledge(purchaseToken) }
        }
      }
      in setOf(
         BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
         BillingClient.BillingResponseCode.DEVELOPER_ERROR,
         BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
       ) -> {
        Log.e(
          TAG,
          "Acknowledgement failed and cannot be retried --
          Response Code: ${acknowledgePurchaseResult.responseCode} --
          Debug Message: ${acknowledgePurchaseResult.debugMessage}"
        )
        throw Exception("Failed to acknowledge the purchase!")
      }
    }
  }
}

private suspend fun <T> exponentialRetry(
  maxTries: Int = Int.MAX_VALUE,
  initialDelay: Long = Long.MAX_VALUE,
  retryFactor: Int = Int.MAX_VALUE,
  block: suspend () -> T
): T? {
  var currentDelay = initialDelay
  var retryAttempt = 1
  do {
    runCatching {
      delay(currentDelay)
      block()
    }
      .onSuccess {
        Log.d(TAG, "Retry succeeded")
        return@onSuccess;
      }
      .onFailure { throwable ->
        Log.e(
          TAG,
          "Retry Failed -- Cause: ${throwable.cause} -- Message: ${throwable.message}"
        )
      }
    currentDelay *= retryFactor
    retryAttempt++
  } while (retryAttempt < maxTries)

  return block() // last attempt
}

Wiederholbare „BillingResult“-Antworten

NETWORK_ERROR (Fehlercode 12)

Problem

Dieser Fehler weist auf ein Problem mit der Netzwerkverbindung zwischen dem Gerät und den Play-Systemen hin.

Mögliche Lösung

Verwenden Sie zur Wiederherstellung einfache Wiederholungsversuche oder exponentiellen Backoff, je nachdem, welche Aktion den Fehler ausgelöst hat.

SERVICE_TIMEOUT (Fehlercode -3)

Problem

Dieser Fehler gibt an, dass die Anfrage das maximale Zeitlimit erreicht hat, bevor Google Play antworten kann. Dies kann beispielsweise durch eine Verzögerung bei der Ausführung der Aktion verursacht werden, die vom Play Billing Library-Aufruf angefordert wurde.

Mögliche Lösung

Dies ist normalerweise ein vorübergehendes Problem. Wiederholen Sie die Anfrage entweder mit einer einfachen oder exponentiellen Backoff-Strategie, je nachdem, welche Aktion den Fehler zurückgegeben hat.

Im Gegensatz zu SERVICE_DISCONNECTED unten wird die Verbindung zum Google Play Billing-Dienst nicht getrennt. Du musst nur den versuchten Play Billing Library-Vorgang wiederholen.

SERVICE_DISCONNECTED (Fehlercode -1)

Problem

Dieser schwerwiegende Fehler weist darauf hin, dass die Verbindung der Client-App zum Google Play Store-Dienst über den BillingClient unterbrochen wurde.

Mögliche Lösung

Um diesen Fehler so gut wie möglich zu vermeiden, solltest du immer die Verbindung zu den Google Play-Diensten prüfen, bevor du die Play Billing Library aufrufst. Rufe dazu BillingClient.isReady() auf.

Wenn Sie eine Wiederherstellung von SERVICE_DISCONNECTED ausführen möchten, sollte Ihre Client-App versuchen, die Verbindung mit BillingClient.startConnection wiederherzustellen.

Verwenden Sie wie bei SERVICE_TIMEOUT einfache Wiederholungsversuche oder exponentiellen Backoff, je nachdem, welche Aktion den Fehler ausgelöst hat.

SERVICE_UNAVAILABLE (Fehlercode 2)

Wichtiger Hinweis:

Ab Google Play Billing Library 6.0.0 wird SERVICE_UNAVAILABLE bei Netzwerkproblemen nicht mehr zurückgegeben. Sie wird zurückgegeben, wenn der Abrechnungsdienst nicht verfügbar ist, und die verworfenen SERVICE_TIMEOUT-Fallszenarien.

Problem

Dieser vorübergehende Fehler weist darauf hin, dass der Google Play Billing-Dienst derzeit nicht verfügbar ist. In den meisten Fällen bedeutet dies, dass ein Problem mit der Netzwerkverbindung zwischen dem Clientgerät und den Google Play Billing-Diensten besteht.

Mögliche Lösung

Dies ist normalerweise ein vorübergehendes Problem. Wiederholen Sie die Anfrage entweder mit einer einfachen oder exponentiellen Backoff-Strategie, je nachdem, welche Aktion den Fehler zurückgegeben hat.

Im Gegensatz zu SERVICE_DISCONNECTED wird die Verbindung zum Google Play Billing-Dienst nicht getrennt und Sie müssen den versuchten Vorgang wiederholen.

BILLING_UNAVAILABLE (Fehlercode 3)

Problem

Dieser Fehler weist darauf hin, dass während des Kaufvorgangs ein Fehler bei der Nutzerabrechnung aufgetreten ist. Das kann unter anderem in folgenden Fällen auftreten:

  • Die Play Store App auf dem Gerät des Nutzers ist veraltet.
  • Der Nutzer befindet sich in einem nicht unterstützten Land.
  • Der Nutzer ist ein Unternehmensnutzer und sein Unternehmensadministrator hat die Nutzer für Käufe deaktiviert.
  • Google Play kann die Zahlungsmethode des Nutzers nicht belasten. Zum Beispiel kann es sein, dass die Kreditkarte des Nutzers abgelaufen ist.

Mögliche Lösung

In diesem Fall sind automatische Wiederholungsversuche unwahrscheinlich. Eine manuelle Wiederholung kann jedoch hilfreich sein, wenn der Nutzer die Bedingung behebt, die das Problem verursacht hat. Wenn der Nutzer beispielsweise seine Play Store-Version auf eine unterstützte Version aktualisiert, kann ein manueller Wiederholungsversuch des ersten Vorgangs funktionieren.

Wenn dieser Fehler auftritt, während sich der Nutzer nicht in einer Sitzung befindet, ist ein neuer Versuch möglicherweise nicht sinnvoll. Wenn du im Rahmen des Kaufvorgangs den Fehler BILLING_UNAVAILABLE erhältst, ist es sehr wahrscheinlich, dass der Nutzer während des Kaufvorgangs Feedback von Google Play erhalten hat und weiß, was schiefgelaufen ist. In diesem Fall könnten Sie eine Fehlermeldung mit einem Fehler anzeigen und die Schaltfläche „Noch einmal versuchen“ anbieten, um dem Nutzer die Möglichkeit zu geben, es noch einmal manuell zu versuchen, nachdem er das Problem behoben hat.

ERROR (Fehlercode 6)

Problem

Dies ist ein schwerwiegender Fehler, der auf ein internes Problem bei Google Play hinweist.

Mögliche Lösung

Manchmal sind interne Google Play-Probleme, die zu ERROR führen, nur vorübergehend. Zur Risikominderung kann ein Wiederholungsversuch mit einem exponentiellen Backoff implementiert werden. Wenn sich Nutzer in einer Sitzung befinden, ist eine einfache Wiederholung zu empfehlen.

ITEM_ALREADY_OWNED

Problem

Diese Antwort zeigt an, dass der Google Play-Nutzer bereits das Abo oder den Einmalkaufprodukt besitzt, das er kaufen möchte. In den meisten Fällen ist dies kein vorübergehender Fehler, es sei denn, er wird durch einen veralteten Google Play-Cache verursacht.

Mögliche Lösung

Damit dieser Fehler nicht auftritt, wenn die Ursache kein Cache-Problem ist, biete ein Produkt nicht zum Kauf an, wenn der Nutzer es bereits besitzt. Prüfe die Berechtigungen des Nutzers, wenn du die zum Kauf verfügbaren Produkte zeigst, und filtere entsprechend, was der Nutzer kaufen kann. Wenn die Client-App diesen Fehler aufgrund eines Cache-Problems erhält, löst der Fehler aus, dass der Cache von Google Play mit den neuesten Daten aus dem Google Play-Back-End aktualisiert wird. Ein neuer Versuch nach dem Fehler sollte in diesem Fall diese vorübergehende Instanz beheben. Rufen Sie BillingClient.queryPurchasesAsync() auf, nachdem Sie ITEM_ALREADY_OWNED erhalten haben, um zu prüfen, ob der Nutzer das Produkt erworben hat. Ist dies nicht der Fall, implementieren Sie eine einfache Wiederholungslogik, um den Kauf noch einmal zu tätigen.

ARTIKEL_NICHT_GEWIESEN

Problem

Diese Kaufantwort zeigt an, dass der Google Play-Nutzer nicht der Inhaber des Abos oder des Einmalkaufprodukts ist, das der Nutzer zu ersetzen, zu bestätigen oder zu verbrauchen versucht. Dies ist in den meisten Fällen kein vorübergehender Fehler, es sei denn, er wird dadurch verursacht, dass der Cache von Google Play veraltet ist.

Mögliche Lösung

Wenn der Fehler aufgrund eines Cache-Problems empfangen wird, löst der Fehler aus, dass der Cache von Google Play mit den neuesten Daten aus dem Google Play-Backend aktualisiert wird. Ein neuer Versuch mit einer einfachen Wiederholungsstrategie nach dem Fehler sollte diese vorübergehende Instanz beheben. Rufen Sie BillingClient.queryPurchasesAsync() auf, nachdem Sie ein ITEM_NOT_OWNED erhalten haben, um zu prüfen, ob der Nutzer das Produkt erworben hat. Ist dies nicht der Fall, können Sie eine einfache Wiederholungslogik verwenden, um den Kauf zu wiederholen.

Nicht abrufbare BillingResult-Antworten

Sie können diese Fehler nicht mithilfe einer Wiederholungslogik beheben.

FUNKTION_NICHT_UNTERSTÜTZT

Problem

Dieser nicht wiederholbare Fehler weist darauf hin, dass die Google Play Billing-Funktion auf dem Gerät des Nutzers nicht unterstützt wird. Wahrscheinlich liegt eine alte Play Store-Version vor.

Vielleicht unterstützen einige Geräte Ihrer Nutzer das In-App-Messaging nicht.

Mögliche Abhilfe

Verwende BillingClient.isFeatureSupported(), um die unterstützten Funktionen zu prüfen, bevor du die Play Billing Library aufrufst.

when {
  billingClient.isReady -> {
    if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) {
       // use feature
    }
  }
}

NUTZER_ABGEBROCHEN

Problem

Der Nutzer hat die Benutzeroberfläche für den Abrechnungsablauf verlassen.

Mögliche Lösung

Dies dient nur Informationszwecken und kann ordnungsgemäß fehlschlagen.

ARTIKEL_UNVERFÜGBAR

Problem

Das Google Play Billing-Abo oder Einmalkaufprodukt kann für diesen Nutzer nicht erworben werden.

Mögliche Abhilfe

Sorgen Sie dafür, dass Ihre App die Produktdetails über queryProductDetailsAsync wie empfohlen aktualisiert. Berücksichtige, wie oft sich dein Produktkatalog an der Konfiguration der Play Console ändert, um bei Bedarf zusätzliche Aktualisierungen zu implementieren. Verkaufe nur Produkte über Google Play Billing, die die richtigen Informationen über queryProductDetailsAsync zurückgeben. Prüfen Sie die Konfiguration der Eignungsvoraussetzungen für das Produkt auf Uneinheitlichkeiten. Beispiel: Sie fragen ein Produkt ab, das nur in einer anderen Region als derjenigen verfügbar ist, die der Nutzer kaufen möchte. Damit ein Produkt gekauft werden kann, muss es aktiv, die zugehörige App veröffentlicht und die App im Land des Nutzers verfügbar sein.

Manchmal, insbesondere beim Testen, ist in der Produktkonfiguration alles korrekt, Nutzer sehen jedoch diesen Fehler. Das kann an einer Verzögerung bei der Weitergabe der Produktdetails auf den Google-Servern liegen. Versuchen Sie es später noch einmal.

ENTWICKLERFEHLER

Problem

Dies ist ein schwerwiegender Fehler, der darauf hinweist, dass Sie eine API nicht ordnungsgemäß verwenden. Dieser Fehler kann beispielsweise auftreten, wenn Sie für BillingClient.launchBillingFlow falsche Parameter angeben.

Mögliche Lösung

Prüfe, ob du die verschiedenen Play Billing Library-Aufrufe korrekt verwendest. In der Debug-Nachricht finden Sie weitere Informationen zum Fehler.