Mit BillingResult-Antwortcodes umgehen

Wenn ein Aufruf der Play Billing Library eine Aktion auslöst, gibt die Bibliothek eine BillingResult Antwort zurück, um 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 den Grund dafür angibt, warum das ProductDetails -Objekt nicht bereitgestellt werden konnte.

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

Wenn der Antwortcode einen Fehler anzeigt, ist die Ursache manchmal auf vorübergehende Bedingungen zurückzuführen, sodass eine Wiederherstellung möglich ist. Wenn ein Aufruf einer Play Billing Library-Methode einen BillingResponseCode Wert zurückgibt, der auf eine wiederherstellbare Bedingung hinweist, sollten Sie den Aufruf wiederholen. In anderen Fällen werden Bedingungen nicht als vorübergehend betrachtet und daher wird eine Wiederholung nicht empfohlen.

Bei vorübergehenden Fehlern sind je nach den Umständen unterschiedliche Wiederholungsstrategien erforderlich. Es ist beispielsweise wichtig, ob der Fehler auftritt, wenn Nutzer in einer Sitzung sind (z. B. während eines Kaufvorgangs) oder im Hintergrund (z. B. wenn Sie die bestehenden Käufe des Nutzers während onResume abfragen). Im Abschnitt Wiederholungsstrategien unten finden Sie Beispiele für diese verschiedenen Strategien. Im Abschnitt Wiederholbare BillingResult Antworten wird empfohlen, welche Strategie für die einzelnen Antwortcodes am besten geeignet ist.

Neben dem Antwortcode enthalten einige Fehlerantworten auch Nachrichten für Debugging- und Protokollierungszwecke.

Wiederholungsstrategien

Einfache Wiederholung

In Situationen, in denen der Nutzer in einer Sitzung ist, ist es besser, eine einfache Wiederholungsstrategie 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 Abbruchbedingung.

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

// Initialize the BillingClient.
private val billingClient = BillingClient.newBuilder(context)
    .setListener(this)
    .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
    .build()

private val coroutineScope = kotlinx.coroutines.CoroutineScope(
    kotlinx.coroutines.SupervisorJob() + kotlinx.coroutines.Dispatchers.Main.immediate
)

private var connectionJob: kotlinx.coroutines.Job? = null

// Establish a connection to Google Play.
fun startBillingConnection() {
    connectionJob?.cancel()
    connectionJob = coroutineScope.launch {
        connectWithRetry()
    }
}

// Suspended helper to perform a single connection attempt
private suspend fun connectBilling(): BillingResult =
    kotlinx.coroutines.suspendCancellableCoroutine { continuation ->
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (continuation.isActive) {
                    continuation.resume(billingResult)
                }
            }

            override fun onBillingServiceDisconnected() {
                Log.e(TAG, "Google Play Billing Service disconnected")
                if (continuation.isActive) {
                    continuation.resume(
                        BillingResult.newBuilder()
                            .setResponseCode(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
                            .setDebugMessage("Service disconnected during connection setup")
                            .build()
                    )
                } else {
                    startBillingConnection()
                }
            }
        })
    }

// Billing connection retry logic. This is a simple max retry pattern
private suspend fun connectWithRetry() {
    val maxTries = 3
    var tries = 1
    var isConnectionEstablished = false
    while (tries <= maxTries && !isConnectionEstablished) {
        val billingResult = connectBilling()
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            isConnectionEstablished = true
            Log.d(TAG, "Billing response OK")
        } else {
            Log.e(TAG, "Billing connection retry failed: ${billingResult.debugMessage}")
            tries++
            if (tries <= maxTries) {
                delay(2000L) // Wait 2 seconds before retrying
            }
        }
    }
}

fun cleanUp() {
    coroutineScope.cancel()
}
// ...

Wiederholung mit exponentiellem Backoff

Wir empfehlen, für Play Billing Library-Vorgänge, die im Hintergrund ausgeführt werden und die Nutzererfahrung während einer Sitzung nicht beeinträchtigen, exponentiellen Backoff zu verwenden.

Dies ist beispielsweise beim Bestätigen neuer Käufe sinnvoll, da dieser Vorgang im Hintergrund ausgeführt werden kann und die Bestätigung bei einem Fehler nicht in Echtzeit erfolgen muss.

private suspend fun acknowledge(purchaseToken: String): BillingResult =
    kotlinx.coroutines.suspendCancellableCoroutine { continuation ->
        val params = AcknowledgePurchaseParams.newBuilder()
            .setPurchaseToken(purchaseToken)
            .build()
        billingClient.acknowledgePurchase(params) { billingResult ->
            continuation.resumeWith(Result.success(billingResult))
        }
    }

private suspend fun queryPurchases(productType: String): Pair<BillingResult, List<Purchase>> =
    kotlinx.coroutines.suspendCancellableCoroutine { continuation ->
        val params = QueryPurchasesParams.newBuilder()
            .setProductType(productType)
            .build()
        billingClient.queryPurchasesAsync(params) { billingResult, purchaseList ->
            continuation.resumeWith(Result.success(Pair(billingResult, purchaseList)))
        }
    }

suspend fun acknowledgePurchase(purchaseToken: String) {
    val retryDelayMs = 2000L
    val retryFactor = 2
    val maxTries = 3

    var tries = 1
    var currentDelay = retryDelayMs
    var acknowledgePurchaseResult: BillingResult

    do {
        acknowledgePurchaseResult = acknowledge(purchaseToken)
        val playBillingResponseCode = acknowledgePurchaseResult.responseCode

        when (playBillingResponseCode) {
            BillingClient.BillingResponseCode.OK -> {
                Log.i(TAG, "Acknowledgement was successful")
                return
            }

            BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
                Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED")
                val (billingResult, purchaseList) = queryPurchases(BillingClient.ProductType.SUBS)
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    purchaseList.forEach { purchase ->
                        acknowledge(purchase.purchaseToken)
                    }
                }
                return
            }

            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}"
                )
                if (tries < maxTries) {
                    delay(currentDelay)
                    currentDelay *= retryFactor
                    tries++
                } else {
                    break
                }
            }

            else -> {
                Log.e(
                    TAG,
                    "Acknowledgement failed and cannot be retried -- " +
                        "Response Code: ${acknowledgePurchaseResult.responseCode} -- " +
                        "Debug Message: ${acknowledgePurchaseResult.debugMessage}"
                )
                throw Exception("Failed to acknowledge the purchase!")
            }
        }
    } while (tries <= maxTries)

    throw Exception("Failed to acknowledge the purchase after $maxTries attempts!")
}

Wiederholbare BillingResult-Antworten

NETWORK_ERROR (Fehlercode 12)

Problem

Dieser Fehler gibt an, dass ein Problem mit der Verbindung zwischen dem Gerät und den Play-Systemen vorliegt.

Mögliche Lösung

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

SERVICE_TIMEOUT (Fehlercode -3)

Problem

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

Mögliche Lösung

Dies ist in der Regel ein vorübergehendes Problem. 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 unterbrochen. Sie müssen nur den Vorgang der Play Billing Library wiederholen.

SERVICE_DISCONNECTED (Fehlercode -1)

Problem

Dieser schwerwiegende Fehler gibt an, dass die Verbindung der Client-App zum Google Play Store-Dienst über BillingClient unterbrochen wurde.

Mögliche Lösung

In der Play Billing Library Version 8.0.0 wurde die Funktion enableAutoServiceReconnection() eingeführt. Wir empfehlen dringend, diese Funktion zu aktivieren , wenn Sie Ihren BillingClient erstellen. So kann die Bibliothek automatisch versuchen, die Verbindung wiederherzustellen, wenn ein API-Aufruf für die Abrechnung erfolgt, während die Verbindung zum Dienst unterbrochen ist. Dadurch wird die Häufigkeit dieses Fehlers erheblich reduziert.

Kotlin

val billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases(
        PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()
    )
    .enableAutoServiceReconnection() // Enable automatic service reconnection
    .build()

Java

BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Enable automatic service reconnection
    .build();
Wenn Sie die automatische Wiederverbindung des Dienstes aktiviert haben

Die Play Billing Library versucht automatisch, die Verbindung wiederherzustellen. Wenn Sie beim Aufruf der API weiterhin den Antwortcode SERVICE_DISCONNECTED erhalten, bedeutet das, dass die Bibliothek nach ihren automatischen Versuchen keine Verbindung herstellen konnte. In diesem Fall sollten Sie in Ihrer App eine Wiederholungslogik implementieren:

  • Bei vom Nutzer initiierten Aktionen (während einer Sitzung) : Verwenden Sie einfache Wiederholungen des API-Aufrufs. Das zugrunde liegende Problem ist möglicherweise vorübergehend.
  • Bei Hintergrundanfragen:Implementieren Sie Wiederholungen mit exponentiellem Backoff, um das System nicht zu überlasten, wenn die Verbindung länger unterbrochen ist.
Wenn Sie die automatische Wiederverbindung des Dienstes NICHT aktiviert haben

Um diesen Fehler so weit wie möglich zu vermeiden, prüfen Sie immer die Verbindung zu den Google Play-Diensten, bevor Sie Aufrufe mit der Play Billing Library ausführen. Rufen Sie dazu BillingClient.isReady() auf.

Um zu versuchen, SERVICE_DISCONNECTED zu beheben, sollte Ihre Client-App versuchen, die Verbindung mit BillingClient.startConnection wiederherzustellen.

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

SERVICE_UNAVAILABLE (Fehlercode 2)

Wichtiger Hinweis:

Ab der Google Play Billing Library 6.0.0 wird SERVICE_UNAVAILABLE nicht mehr für Netzwerkprobleme zurückgegeben. Er wird zurückgegeben, wenn der Abrechnungsdienst nicht verfügbar ist und in den veralteten SERVICE_TIMEOUT-Szenarien.

Problem

Dieser vorübergehende Fehler gibt an, dass der Google Play Billing-Dienst derzeit nicht verfügbar ist. In den meisten Fällen liegt ein Problem mit der Netzwerkverbindung zwischen dem Clientgerät und den Google Play Billing-Diensten vor.

Mögliche Lösung

Dies ist in der Regel ein vorübergehendes Problem. 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 unterbrochen. Sie müssen nur den Vorgang wiederholen.

BILLING_UNAVAILABLE (Fehlercode 3)

Problem

Dieser Fehler gibt an, dass während des Kaufvorgangs ein Abrechnungsfehler für den Nutzer aufgetreten ist. Beispiele für Situationen, in denen dies auftreten kann:

  • Die Google 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 Käufe für Nutzer deaktiviert.
  • Google Play kann die Zahlungsmethode des Nutzers nicht belasten. Beispielsweise ist die Kreditkarte des Nutzers möglicherweise abgelaufen.
  • Die Google Play Store App wird vom System blockiert (z. B. in einem vom OEM angepassten Kindermodus). In diesem Fall enthält BillingResult die Debug-Nachricht Google Play Store is blocked (Google Play Store ist blockiert).

Mögliche Lösung

  • Automatische Wiederholungen sind in diesem Fall unwahrscheinlich hilfreich. Eine manuelle Wiederholung kann jedoch helfen, wenn der Nutzer die Bedingung behebt, die das Problem verursacht hat. Wenn der Nutzer beispielsweise seine Google Play Store-Version auf eine unterstützte Version aktualisiert, kann eine manuelle Wiederholung des ursprünglichen Vorgangs funktionieren.

  • Wenn dieser Fehler auftritt, wenn der Nutzer nicht in einer Sitzung ist, ist eine Wiederholung möglicherweise nicht sinnvoll. Wenn Sie aufgrund des Kaufvorgangs einen BILLING_UNAVAILABLE Fehler erhalten, hat der Nutzer während des Kaufvorgangs sehr wahrscheinlich Feedback von Google Play erhalten und weiß möglicherweise, was schiefgelaufen ist. In diesem Fall können Sie eine Fehlermeldung anzeigen, die angibt, dass etwas schiefgelaufen ist, und eine Schaltfläche Nochmal versuchen anbieten, damit der Nutzer nach Behebung des Problems eine manuelle Wiederholung durchführen kann.

ERROR (Fehlercode 6)

Problem

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

Mögliche Lösung

Manchmal sind interne Google Play-Probleme, die zu ERROR vorübergehend sind. Eine Wiederholung mit exponentiellem Backoff kann zur Minimierung des Problems implementiert werden. Wenn Nutzer in einer Sitzung sind, ist eine einfache Wiederholung vorzuziehen.

ITEM_ALREADY_OWNED

Problem

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

Mögliche Lösung

Um zu vermeiden, dass dieser Fehler auftritt, wenn die Ursache kein Cacheproblem ist, bieten Sie ein Produkt nicht zum Kauf an, wenn der Nutzer es bereits besitzt. Prüfen Sie die Berechtigungen des Nutzers, wenn Sie die zum Kauf verfügbaren Produkte anzeigen, und filtern Sie die Produkte entsprechend. Wenn die Client-App diesen Fehler aufgrund eines Cacheproblems erhält, wird der Cache von Google Play mit den neuesten Daten aus dem Backend von Play aktualisiert. Eine Wiederholung nach dem Fehler sollte dieses vorübergehende Problem in diesem Fall beheben. Rufen Sie BillingClient.queryPurchasesAsync() auf, nachdem Sie ITEM_ALREADY_OWNED erhalten haben, um zu prüfen, ob der Nutzer das Produkt erworben hat. Wenn dies nicht der Fall ist, implementieren Sie eine einfache Wiederholungslogik, um den Kauf noch einmal zu versuchen.

ITEM_NOT_OWNED

Problem

Diese Kaufantwort gibt an, dass der Google Play-Nutzer das Abo oder das Produkt mit einmaligem Kauf, das er ersetzen, bestätigen oder verwenden möchte, nicht besitzt. In den meisten Fällen ist dies 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 Cacheproblems auftritt, wird der Cache von Google Play mit den neuesten Daten aus dem Backend von Play aktualisiert. Eine Wiederholung mit einer einfachen Wiederholungsstrategie nach dem Fehler sollte dieses vorübergehende Problem beheben. Rufen Sie BillingClient.queryPurchasesAsync() auf, nachdem Sie ITEM_NOT_OWNED erhalten haben, um zu prüfen, ob der Nutzer das Produkt erworben hat. Wenn nicht, verwenden Sie eine einfache Wiederholungslogik, um den Kauf noch einmal zu versuchen.

Nicht wiederholbare BillingResult-Antworten

Diese Fehler können nicht mit 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 Google Play Store-Version.

Beispielsweise unterstützen einige Geräte Ihrer Nutzer möglicherweise keine In-App-Nachrichten.

Mögliche Abhilfe

Verwenden Sie BillingClient.isFeatureSupported(), um die Unterstützung von Funktionen zu prüfen, bevor Sie den Aufruf an die Play Billing Library ausführen.

when {
    billingClient.isReady -> {
        val billingResult =
            billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            // use Feature
        }
    }
}

USER_CANCELED

Problem

Der Nutzer hat die Benutzeroberfläche des Abrechnungsablaufs verlassen.

Mögliche Lösung

Dies ist nur eine Information und kann problemlos fehlschlagen.

ITEM_UNAVAILABLE

Problem

Das Abo oder das Produkt mit einmaligem Kauf von Google Play Billing ist für diesen Nutzer nicht verfügbar.

Mögliche Abhilfe

Achten Sie darauf, dass Ihre App die Produktdetails wie empfohlen über queryProductDetailsAsync aktualisiert. Berücksichtigen Sie, wie oft sich Ihr Produktkatalog in der Play Console-Konfiguration ändert, um bei Bedarf zusätzliche Aktualisierungen zu implementieren. Versuchen Sie nur, Produkte in Google Play Billing zu verkaufen, die über queryProductDetailsAsync die richtigen Informationen zurückgeben. Prüfen Sie die Konfiguration der Produktberechtigung auf Inkonsistenzen. Möglicherweise fragen Sie beispielsweise ein Produkt ab, das nur in einer anderen Region als der verfügbar ist, in der der Nutzer kaufen möchte. Damit ein Produkt gekauft werden kann, muss es aktiv sein, die zugehörige App muss veröffentlicht sein und die App muss im Land des Nutzers verfügbar sein.

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

DEVELOPER_ERROR

Problem

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

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.