Obsługa kodów odpowiedzi BillingResult

Gdy wywołanie Biblioteki płatności w Play powoduje wykonanie działania, biblioteka zwraca odpowiedź BillingResult , aby poinformować deweloperów o wyniku. Jeśli na przykład używasz queryProductDetailsAsync, aby uzyskać dostępne dla użytkownika oferty, kod odpowiedzi zawiera kod OK i prawidłowy obiekt ProductDetails lub inną odpowiedź wskazującą przyczynę, dla której nie można było podać obiektu ProductDetails.

Nie wszystkie kody odpowiedzi oznaczają błędy. Na stronie referencyjnej BillingResponseCode znajdziesz szczegółowy opis każdej z odpowiedzi omówionych w tym przewodniku. Oto kilka przykładów kodów odpowiedzi, które nie wskazują błędów:

Gdy kod odpowiedzi wskazuje błąd, jego przyczyną mogą być przejściowe warunki, a tym samym możliwe jest odzyskanie. Gdy wywołanie metody Biblioteki płatności w Play zwraca BillingResponseCode wartość, która wskazuje na możliwy do odzyskania stan, należy ponowić wywołanie. W innych przypadkach warunki nie są uważane za przejściowe, dlatego nie zalecamy ponawiania.

Błędy przejściowe wymagają różnych strategii ponawiania w zależności od czynników takich jak to, czy błąd występuje, gdy użytkownicy są w sesji (np. gdy użytkownik przechodzi proces zakupu), czy też w tle (np. gdy podczas onResume wysyłasz zapytanie o dotychczasowe zakupy użytkownika). W sekcji Strategie ponawiania poniżej znajdziesz przykłady tych różnych strategii, a w sekcji Odpowiedzi BillingResult , które można ponowić, dowiesz się , która strategia najlepiej sprawdza się w przypadku każdego kodu odpowiedzi.

Oprócz kodu odpowiedzi niektóre odpowiedzi na błędy zawierają komunikaty do debugowania i rejestrowania.

Strategie ponawiania

Proste ponawianie

W sytuacjach, gdy użytkownik jest w sesji, lepiej jest wdrożyć prostą strategię ponawiania, aby błąd jak najmniej zakłócał korzystanie z aplikacji. W takim przypadku zalecamy prostą strategię ponawiania z maksymalną liczbą prób jako warunkiem zakończenia.

W przykładzie poniżej pokazujemy prostą strategię ponawiania, która umożliwia obsługę błędu podczas nawiązywania BillingClient połączenia:

// 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()
}
// ...

Ponawianie ze wzrastającym czasem do ponowienia

W przypadku operacji Biblioteki płatności w Play, które odbywają się w tle i nie wpływają na komfort użytkownika podczas sesji, zalecamy stosowanie wzrastającego czasu do ponowienia.

Na przykład warto wdrożyć tę strategię podczas potwierdzania nowych zakupów, ponieważ ta operacja może odbywać się w tle, a w przypadku wystąpienia błędu potwierdzenie nie musi nastąpić w czasie rzeczywistym.

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!")
}

Odpowiedzi BillingResult, które można ponowić

NETWORK_ERROR (kod błędu 12)

Problem

Ten błąd oznacza, że wystąpił problem z połączeniem sieciowym między urządzeniem a systemami Play.

Możliwe rozwiązanie

Aby rozwiązać ten problem, użyj prostych ponowień lub wzrastającego czasu do ponowienia, w zależności od tego, które działanie spowodowało błąd.

SERVICE_TIMEOUT (kod błędu –3)

Problem

Ten błąd oznacza, że żądanie osiągnęło maksymalny limit czasu, zanim Google Play zdołało odpowiedzieć. Może to być spowodowane na przykład opóźnieniem w wykonaniu działania żądanego przez wywołanie Biblioteki płatności w Play.

Możliwe rozwiązanie

Zwykle jest to problem przejściowy. Ponów żądanie, stosując prostą strategię ponawiania lub wzrastający czas do ponowienia, w zależności od tego, które działanie zwróciło błąd.

W przeciwieństwie do SERVICE_DISCONNECTED poniżej połączenie z usługą płatności w Google Play nie jest przerwane i wystarczy ponowić próbę wykonania operacji Biblioteki płatności w Play.

SERVICE_DISCONNECTED (kod błędu –1)

Problem

Ten błąd krytyczny oznacza, że połączenie aplikacji klienckiej z usługą Sklepu Google Play za pomocą BillingClient zostało przerwane.

Możliwe rozwiązanie

W wersji 8.0.0 Biblioteki płatności w Play wprowadziliśmy funkcję enableAutoServiceReconnection(). Zdecydowanie zalecamy włączenie tej funkcji podczas tworzenia BillingClient. Dzięki temu biblioteka automatycznie podejmie próbę ponownego nawiązania połączenia, gdy podczas rozłączenia usługi zostanie wykonane wywołanie interfejsu API płatności, co znacznie zmniejszy liczbę wystąpień tego błędu.

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();
Jeśli masz włączone automatyczne ponowne łączenie z usługą

Biblioteka płatności w Play automatycznie podejmie próbę ponownego połączenia. Jeśli podczas wywołania interfejsu API nadal otrzymujesz kod odpowiedzi SERVICE_DISCONNECTED, oznacza to, że bibliotece nie udało się ponownie połączyć po automatycznych próbach. W takim przypadku w aplikacji należy wdrożyć logikę ponawiania:

  • W przypadku działań inicjowanych przez użytkownika (w sesji): użyj prostych ponowień wywołania interfejsu API. Podstawowy problem może być tymczasowy.
  • W przypadku żądań w tle: wdróż ponawianie ze wzrastającym czasem do ponowienia, aby uniknąć przeciążenia systemu w przypadku długotrwałego rozłączenia.
Jeśli NIE masz włączonego automatycznego ponownego łączenia z usługą

Aby jak najbardziej uniknąć tego błędu, przed wykonaniem wywołań za pomocą Biblioteki płatności w Play zawsze sprawdzaj połączenie z Usługami Google Play, wywołując BillingClient.isReady().

Aby spróbować odzyskać połączenie po wystąpieniu błędu SERVICE_DISCONNECTED , aplikacja kliencka powinna spróbować ponownie nawiązać połączenie za pomocą BillingClient.startConnection.

Podobnie jak w przypadku błędu SERVICE_TIMEOUT , użyj prostych ponowień lub wzrastającego czasu do ponowienia, w zależności od tego, które działanie spowodowało błąd.

SERVICE_UNAVAILABLE (kod błędu 2)

Ważna uwaga:

Od wersji 6.0.0 Biblioteki płatności w Google Play błąd SERVICE_UNAVAILABLE nie jest już zwracany w przypadku problemów z siecią. Jest on zwracany, gdy usługa płatności jest niedostępna, oraz w wycofanych scenariuszach błędu SERVICE_TIMEOUT.

Problem

Ten błąd przejściowy oznacza, że usługa płatności w Google Play jest obecnie niedostępna. W większości przypadków oznacza to, że występuje problem z połączeniem sieciowym między urządzeniem klienta a usługami płatności w Google Play.

Możliwe rozwiązanie

Zwykle jest to problem przejściowy. Ponów żądanie, stosując prostą strategię ponawiania lub wzrastający czas do ponowienia, w zależności od tego, które działanie zwróciło błąd.

W przeciwieństwie do błędu SERVICE_DISCONNECTED połączenie z usługą płatności w Google Play nie jest przerwane i musisz ponowić próbę wykonania operacji.

BILLING_UNAVAILABLE (kod błędu 3)

Problem

Ten błąd oznacza, że podczas procesu zakupu wystąpił błąd rozliczeniowy użytkownika. Oto przykłady sytuacji, w których może wystąpić ten błąd:

  • Aplikacja Sklep Play na urządzeniu użytkownika jest nieaktualna.
  • Użytkownik znajduje się w nieobsługiwanym kraju.
  • Użytkownik jest użytkownikiem firmowym, a administrator firmowy wyłączył możliwość dokonywania zakupów przez użytkowników.
  • Google Play nie może obciążyć formy płatności użytkownika. Na przykład karta kredytowa użytkownika mogła stracić ważność.
  • Aplikacja Sklep Play jest blokowana przez system (np. w trybie dla dzieci dostosowanym przez producenta OEM). W takim przypadku BillingResult zawiera komunikat debugowania Sklep Play jest zablokowany.

Możliwe rozwiązanie

  • W tym przypadku automatyczne ponawianie raczej nie pomoże. Jeśli jednak użytkownik rozwiąże problem, który spowodował błąd, ręczne ponowienie może pomóc. Jeśli na przykład użytkownik zaktualizuje Sklep Play do obsługiwanej wersji, ręczne ponowienie pierwotnej operacji może zadziałać.

  • Jeśli ten błąd wystąpi, gdy użytkownik nie jest w sesji, ponawianie może nie mieć sensu. Gdy w wyniku procesu zakupu otrzymasz błąd BILLING_UNAVAILABLE , jest bardzo prawdopodobne, że użytkownik otrzymał informację zwrotną od Google Play podczas procesu zakupu i może wiedzieć, co poszło nie tak. W takim przypadku możesz wyświetlić komunikat o błędzie informujący, że coś poszło nie tak, i przycisk Spróbuj ponownie, aby dać użytkownikowi możliwość ręcznego ponowienia po rozwiązaniu problemu.

BŁĄD (kod błędu 6)

Problem

Jest to błąd krytyczny, który wskazuje na wewnętrzny problem z samym Google Play.

Możliwe rozwiązanie

Czasami wewnętrzne problemy z Google Play, które prowadzą do ERROR są przejściowe, a w celu ich złagodzenia można wdrożyć ponawianie ze wzrastającym czasem do ponowienia. Gdy użytkownicy są w sesji, preferowane jest proste ponawianie.

ITEM_ALREADY_OWNED

Problem

Ta odpowiedź oznacza, że użytkownik Google Play jest już właścicielem subskrypcji lub jednorazowego zakupu, który próbuje kupić. W większości przypadków nie jest to błąd przejściowy, z wyjątkiem sytuacji, gdy jest on spowodowany nieaktualną pamięcią podręczną Google Play.

Możliwe rozwiązanie

Aby uniknąć tego błędu, gdy jego przyczyną nie jest problem z pamięcią podręczną, nie oferuj produktu do zakupu, jeśli użytkownik jest już jego właścicielem. Gdy wyświetlasz produkty dostępne do zakupu, sprawdź uprawnienia użytkownika i odpowiednio odfiltruj to, co może kupić. Gdy aplikacja kliencka otrzyma ten błąd z powodu problemu z pamięcią podręczną, błąd spowoduje zaktualizowanie pamięci podręcznej Google Play najnowszymi danymi z backendu Play. Ponowienie po wystąpieniu błędu powinno w tym przypadku rozwiązać ten konkretny przejściowy problem. Wywołaj BillingClient.queryPurchasesAsync() po otrzymaniu błędu ITEM_ALREADY_OWNED aby sprawdzić, czy użytkownik kupił produkt, a jeśli nie, wdroż prostą logikę ponawiania, aby ponownie spróbować dokonać zakupu.

ITEM_NOT_OWNED

Problem

Ta odpowiedź na zakup oznacza, że użytkownik Google Play nie jest właścicielem subskrypcji ani produktu jednorazowego zakupu, który próbuje zastąpić, potwierdzić lub wykorzystać. W większości przypadków nie jest to błąd przejściowy, z wyjątkiem sytuacji, gdy jest on spowodowany nieaktualną pamięcią podręczną Google Play.

Możliwe rozwiązanie

Gdy błąd zostanie odebrany z powodu problemu z pamięcią podręczną, błąd spowoduje zaktualizowanie pamięci podręcznej Google Play najnowszymi danymi z backendu Play. Ponowienie po wystąpieniu błędu za pomocą prostej strategii ponawiania powinno rozwiązać ten konkretny przejściowy problem. Po otrzymaniu błędu ITEM_NOT_OWNED wywołaj BillingClient.queryPurchasesAsync(), aby sprawdzić, czy użytkownik kupił produkt. Jeśli nie, użyj prostej logiki ponawiania, aby ponownie spróbować dokonać zakupu.

Odpowiedzi BillingResult, których nie można ponowić

Nie możesz rozwiązać tych błędów za pomocą logiki ponawiania.

FEATURE_NOT_SUPPORTED

Problem

Ten błąd, którego nie można ponowić, oznacza, że funkcja płatności w Google Play nie jest obsługiwana na urządzeniu użytkownika, prawdopodobnie z powodu starej wersji Sklepu Play.

Na przykład niektóre urządzenia użytkowników mogą nie obsługiwać wiadomości w aplikacji.

Możliwe zastosowanie złagodzeń

Przed wywołaniem Biblioteki płatności w Play sprawdź obsługę funkcji za pomocą BillingClient.isFeatureSupported().

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

USER_CANCELED

Problem

Użytkownik kliknął poza interfejsem procesu płatności.

Możliwe rozwiązanie

Jest to tylko informacja, a błąd można obsłużyć w sposób niezakłócający działania aplikacji.

ITEM_UNAVAILABLE

Problem

Subskrypcja lub produkt w ramach jednorazowego zakupu w Płatnościach w Google Play nie jest dostępny do zakupu dla tego użytkownika.

Możliwe zastosowanie złagodzeń

Upewnij się, że aplikacja odświeża szczegóły produktu za pomocą queryProductDetailsAsync zgodnie z zaleceniami. W razie potrzeby uwzględnij, jak często katalog produktów zmienia się w konfiguracji Konsoli Play, aby wdrożyć dodatkowe odświeżanie. Sprzedawaj w płatnościach w Google Play tylko te produkty, które zwracają prawidłowe informacje za pomocą queryProductDetailsAsync. Sprawdź konfigurację uprawnień do produktu pod kątem niespójności. Możesz na przykład wysyłać zapytanie o produkt, który jest dostępny tylko w regionie innym niż ten, w którym użytkownik próbuje dokonać zakupu. Aby produkt był dostępny do zakupu, musi być aktywny, aplikacja musi być opublikowana i dostępna w kraju użytkownika.

Czasami, zwłaszcza podczas testowania, konfiguracja produktu jest prawidłowa, ale użytkownicy nadal widzą ten błąd. Może to być spowodowane opóźnieniem w propagacji szczegółów produktu na serwerach Google. Spróbuj ponownie później.

DEVELOPER_ERROR

Problem

Jest to błąd krytyczny, który oznacza, że nieprawidłowo używasz interfejsu API. Na przykład podanie nieprawidłowych parametrów do BillingClient.launchBillingFlow może spowodować ten błąd.

Możliwe rozwiązanie

Upewnij się, że prawidłowo używasz różnych wywołań Biblioteki płatności w Play. Aby uzyskać więcej informacji o błędzie, sprawdź też komunikat debugowania.