Mit BillingResult-Antwortcodes umgehen

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

Nicht alle Antwortcodes sind Fehler. Auf der Referenzseite BillingResponseCode finden Sie eine detaillierte Beschreibung der einzelnen Antworten, die in diesem Leitfaden behandelt werden. Beispiele für Antwortcodes, die keine Fehler anzeigen:

Wenn der Antwortcode einen Fehler anzeigt, wird dies manchmal durch vorübergehende Zustände verursacht, sodass eine Wiederherstellung möglich ist. Wenn ein Aufruf einer Methode der Play Billing Library einen BillingResponseCode-Wert zurückgibt, der auf eine wiederherstellbare Bedingung hinweist, sollten Sie den Aufruf noch einmal versuchen. In anderen Fällen werden Bedingungen nicht als vorübergehend eingestuft und ein erneuter Versuch wird daher nicht empfohlen.

Für vorübergehende Fehler sind unterschiedliche Wiederholungsstrategien erforderlich, je nachdem, ob der Fehler auftritt, während Nutzer eine Sitzung haben (z. B. wenn ein Nutzer einen Kaufvorgang durchläuft) oder im Hintergrund (z. B. wenn Sie die vorhandenen Käufe des Nutzers während onResume abfragen). Im Abschnitt Wiederholstrategien unten finden Sie Beispiele für diese verschiedenen Strategien. Im Abschnitt Wiederholbare AntwortenBillingResult wird empfohlen, welche Strategie für jeden Antwortcode am besten geeignet ist.

Neben dem Antwortcode enthalten einige Fehlerantworten Meldungen zum Debuggen und Logging.

Strategien für Wiederholungen

Einfache Wiederholung

Wenn der Nutzer eine Sitzung hat, ist es besser, eine einfache Wiederholungsstrategie zu implementieren, damit der Fehler die Nutzerfreundlichkeit so wenig wie möglich beeinträchtigt. In diesem Fall empfehlen wir eine einfache Wiederholungsstrategie mit einer maximalen Anzahl von Versuchen als Endbedingung.

Das folgende Beispiel zeigt eine einfache Wiederholungsstrategie zum Umgang mit einem Fehler 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)
  }
  ...
}

Wiederholung mit exponentiellem Backoff

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

Dies ist beispielsweise sinnvoll, wenn neue Käufe bestätigt werden, da dieser Vorgang im Hintergrund erfolgen 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 gibt an, dass ein Problem mit der Netzwerkverbindung zwischen dem Gerät und den Play-Systemen vorliegt.

Mögliche Lösung

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

SERVICE_TIMEOUT (Fehlercode -3)

Problem

Dieser Fehler gibt an, dass die maximale Zeitüberschreitung für die Anfrage erreicht wurde, bevor Google Play antworten konnte. Dies kann beispielsweise durch eine Verzögerung bei der Ausführung der vom Play Billing Library-Aufruf angeforderten Aktion verursacht werden.

Mögliche Lösung

Das ist in der Regel nur vorübergehend. Wiederholen Sie die Anfrage 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 fatale Fehler gibt an, dass die Verbindung der Clientanwendung zum Google Play Store-Dienst über BillingClient unterbrochen wurde.

Mögliche Lösung

Um diesen Fehler so weit wie möglich zu vermeiden, sollten Sie vor jedem Aufruf der Play Billing Library die Verbindung zu den Google Play-Diensten prüfen. Rufen Sie dazu BillingClient.isReady() auf.

Wenn Sie versuchen möchten, eine Wiederherstellung von SERVICE_DISCONNECTED auszuführen, sollte Ihre Clientanwendung versuchen, die Verbindung mit BillingClient.startConnection wiederherzustellen.

Verwenden Sie wie bei SERVICE_TIMEOUT einfache Wiederholungen oder exponentielles 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. Er wird zurückgegeben, wenn der Abrechnungsdienst nicht verfügbar ist, und in den eingestellten SERVICE_TIMEOUT-Fallszenarien.

Problem

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

Mögliche Lösung

Das ist in der Regel nur vorübergehend. Wiederholen Sie die Anfrage 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 gerade ausgeführten Vorgang wiederholen.

BILLING_UNAVAILABLE (Fehlercode 3)

Problem

Dieser Fehler gibt an, dass beim Kaufvorgang ein Abrechnungsfehler aufgetreten ist. Beispiele:

  • 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 Administrator hat den Kauf für Nutzer deaktiviert.
  • Google Play kann die Zahlungsmethode des Nutzers nicht belasten. Beispielsweise kann die Kreditkarte des Nutzers abgelaufen sein.

Mögliche Lösung

Automatische Wiederholungen werden in diesem Fall wahrscheinlich nicht helfen. Ein manueller Wiederholungsversuch 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 Neuversuch des ursprünglichen 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 Sie aufgrund des Kaufvorgangs einen BILLING_UNAVAILABLE-Fehler erhalten, hat der Nutzer höchstwahrscheinlich während des Kaufvorgangs Feedback von Google Play erhalten und weiß möglicherweise, was schiefgelaufen ist. In diesem Fall können Sie eine Fehlermeldung anzeigen, in der angegeben wird, dass ein Fehler aufgetreten ist, und eine Schaltfläche „Noch einmal versuchen“ anbieten, damit der Nutzer nach der Behebung des Problems manuell noch einmal versuchen kann.

FEHLER (Fehlercode 6)

Problem

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

Mögliche Lösung

Manchmal sind interne Google Play-Probleme, die zu ERROR führen, vorübergehend. Zur Abmilderung kann ein erneuter Versuch mit einem exponentiellen Backoff implementiert werden. Während einer Sitzung empfiehlt sich eine einfache Wiederholung.

ITEM_ALREADY_OWNED

Problem

Diese Antwort gibt an, dass der Google Play-Nutzer bereits das Abo oder das Produkt zum Einmalkauf 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

Um zu vermeiden, dass dieser Fehler 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 präsentierst, und filtere die Produkte entsprechend. Wenn die Client-App aufgrund eines Cache-Problems diesen Fehler erhält, wird der Google Play-Cache mit den neuesten Daten aus dem Play-Backend aktualisiert. Wenn Sie nach dem Fehler noch einmal versuchen, sollte dieses Problem in diesem Fall behoben werden. Rufe BillingClient.queryPurchasesAsync() auf, nachdem du eine ITEM_ALREADY_OWNED erhalten hast, um zu prüfen, ob der Nutzer das Produkt gekauft hat. Ist das nicht der Fall, implementiere eine einfache Wiederholungslogik, um den Kauf noch einmal zu versuchen.

ITEM_NOT_OWNED

Problem

Diese Kaufantwort gibt an, dass der Google Play-Nutzer nicht der Inhaber des Abos oder des einmalig gekauften Produkts ist, das er ersetzen, bestätigen oder nutzen möchte. In den meisten Fällen ist dies kein vorübergehender Fehler, es sei denn, der Cache von Google Play ist veraltet.

Mögliche Lösung

Wenn der Fehler aufgrund eines Cache-Problems auftritt, löst er aus, dass der Cache von Google Play mit den neuesten Daten aus dem Back-End von Google Play aktualisiert wird. Wenn Sie nach dem Fehler noch einmal mit einer einfachen Wiederholungsstrategie versuchen, sollte dieses bestimmte vorübergehende Problem behoben werden. Rufe BillingClient.queryPurchasesAsync() auf, nachdem du eine ITEM_NOT_OWNED erhalten hast, um zu prüfen, ob der Nutzer das Produkt gekauft hat. Falls nicht, kannst du mit einer einfachen Wiederholungslogik einen weiteren Kaufversuch starten.

Nicht wiederholbare BillingResult-Antworten

Diese Fehler können nicht mithilfe einer Wiederholungslogik behoben werden.

FEATURE_NOT_SUPPORTED

Problem

Dieser nicht wiederholbare Fehler gibt an, dass die Google Play Billing-Funktion auf dem Gerät des Nutzers nicht unterstützt wird, wahrscheinlich aufgrund einer alten Play Store-Version.

Möglicherweise unterstützen einige Nutzergeräte In-App-Messaging nicht.

Mögliche Abhilfe

Verwenden Sie BillingClient.isFeatureSupported(), um die Funktionsunterstützung zu prüfen, bevor Sie die Play Billing Library aufrufen.

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

USER_CANCELED

Problem

Der Nutzer hat die Benutzeroberfläche des Abrechnungsvorgangs verlassen.

Mögliche Lösung

Dieser Vorgang dient nur zu Informationszwecken und kann fehlschlagen.

ITEM_UNAVAILABLE

Problem

Das Google Play Billing-Abo oder das Einmalkaufprodukt ist für diesen Nutzer nicht zum Kauf verfügbar.

Mögliche Abhilfe

Achten Sie darauf, dass die Produktdetails in Ihrer App wie empfohlen über queryProductDetailsAsync aktualisiert werden. Berücksichtigen Sie, wie oft sich Ihr Produktkatalog in der Play Console-Konfiguration ändert, um bei Bedarf zusätzliche Aktualisierungen vorzunehmen. Bieten Sie nur Produkte über Google Play Billing an, die über queryProductDetailsAsync die richtigen Informationen zurückgeben. Prüfen Sie die Konfiguration der Produktberechtigung auf Inkonsistenzen. Beispielsweise wird möglicherweise nach einem Produkt gesucht, das nur für eine andere Region als diejenige verfügbar ist, in der der Nutzer einen Kauf abschließen 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 während des Tests, ist in der Produktkonfiguration alles korrekt, aber Nutzern wird dieser Fehler trotzdem angezeigt. Das kann an einer Verzögerung bei der Übertragung der Produktdetails auf die Google-Server liegen. Versuchen Sie es später noch einmal.

DEVELOPER_ERROR

Problem

Dies ist ein schwerwiegender Fehler, der darauf hinweist, dass Sie eine API nicht ordnungsgemäß verwenden. Ein Beispiel für einen solchen Fehler ist die Angabe falscher Parameter für BillingClient.launchBillingFlow.

Mögliche Lösung

Achten Sie darauf, dass Sie die verschiedenen Aufrufe der Play Billing Library richtig verwenden. Weitere Informationen zum Fehler finden Sie in der Debug-Nachricht.