Gdy wywołanie Biblioteki płatności w Play spowoduje działanie, biblioteka zwraca odpowiedź BillingResult, aby poinformować deweloperów o wyniku. Jeśli na przykład używasz
queryProductDetailsAsync
do pobierania dostępnych ofert dla użytkownika, kod odpowiedzi zawiera kod OK i odpowiedni obiekt ProductDetails
lub inną odpowiedź, która wskazuje przyczynę, dla której nie można było podać obiektu ProductDetails.
Nie wszystkie kody odpowiedzi oznaczają błędy. BillingResponseCode
Strona referencyjna zawiera 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:
BillingClient.BillingResponseCode.OK: działanie wywołane przez połączenie zostało wykonane.BillingClient.BillingResponseCode.USER_CANCELED: w przypadku działań, które wyświetlają użytkownikowi procesy interfejsu Sklepu Play, ta odpowiedź oznacza, że użytkownik opuścił te procesy interfejsu bez ukończenia procesu.
Jeśli kod odpowiedzi wskazuje błąd, przyczyną mogą być przejściowe warunki, a tym samym możliwe jest przywrócenie działania. Gdy wywołanie metody Biblioteki płatności w Play zwraca wartość BillingResponseCode, która wskazuje stan, z którego można się wycofać, należy ponowić wywołanie. W innych przypadkach warunki nie są uznawane za przejściowe, dlatego ponawianie nie jest zalecane.
W przypadku błędów przejściowych należy stosować różne strategie ponawiania prób 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 w tle (np. gdy podczas onResume wysyłasz zapytanie o dotychczasowe zakupy użytkownika).
W sekcji strategii ponawiania poniżej znajdziesz przykłady różnych strategii, a w sekcji
odpowiedzi z możliwością ponowienia BillingResult
znajdziesz rekomendacje, która strategia najlepiej sprawdza się w przypadku poszczególnych kodów odpowiedzi.
Oprócz kodu odpowiedzi niektóre odpowiedzi na błędy zawierają komunikaty do celów debugowania i rejestrowania.
Strategie ponawiania
Proste ponawianie próby
W sytuacjach, gdy użytkownik jest w sesji, lepiej wdrożyć prostą strategię ponawiania, aby błąd jak najmniej zakłócał jego wrażenia. W takim przypadku zalecamy prostą strategię ponawiania z maksymalną liczbą prób jako warunkiem zakończenia.
Poniższy przykład przedstawia prostą strategię ponawiania prób, która umożliwia obsługę błędu
podczas nawiązywania połączenia BillingClient:
// 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 próby ze wzrastającym czasem do ponowienia
W przypadku operacji Biblioteki płatności w Play, które są wykonywane w tle i nie wpływają na wrażenia użytkownika podczas sesji, zalecamy stosowanie wzrastającego czasu do ponowienia.
Na przykład warto to zrobić w przypadku potwierdzania nowych zakupów, ponieważ ta operacja może odbywać się w tle, a potwierdzenie nie musi następować w czasie rzeczywistym, jeśli wystąpi błąd.
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 przywrócić działanie, użyj prostych ponownych prób lub wzrastającego czasu do ponowienia, w zależności od tego, która czynność spowodowała 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ł odpowiedzieć. Może to być spowodowane np. opóźnieniem w wykonaniu działania, o które prosi wywołanie Biblioteki płatności w Play.
Możliwe rozwiązanie
Zwykle jest to problem przejściowy. Ponów próbę wysłania żądania, stosując prostą lub wzrastającą strategię czasu do ponowienia, w zależności od tego, która czynność zwróciła błąd.
W przeciwieństwie do SERVICE_DISCONNECTED poniżej połączenie z usługą Płatności w Google Play nie jest przerywane i wystarczy ponowić próbę wykonania operacji Biblioteki płatności w Google Play.
SERVICE_DISCONNECTED (kod błędu -1)
Problem
Ten błąd krytyczny oznacza, że połączenie aplikacji klienta z usługą Google Play Store za pomocą interfejsu BillingClient zostało przerwane.
Możliwe rozwiązanie
Zdecydowanie zalecane: włącz automatyczne ponowne łączenie z usługą
W Bibliotece płatności w Play w wersji 8.0.0 wprowadziliśmy funkcję
enableAutoServiceReconnection().
Zdecydowanie zalecamy włączenie tej funkcji podczas tworzenia BillingClient. Dzięki temu biblioteka może automatycznie próbować ponownie nawiązać połączenie, gdy podczas rozłączenia usługi zostanie wykonane wywołanie interfejsu Billing API, co znacznie zmniejsza 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 spróbuje ponownie nawiązać połączenie. Jeśli podczas wywoływania 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ń zainicjowanych przez użytkownika (w sesji): używaj prostych ponownych prób wywołania interfejsu API. Przyczyna problemu może być tymczasowa.
- W przypadku żądań w tle: zaimplementuj ponawianie prób ze wzrastającym czasem do ponowienia, aby uniknąć przeciążenia systemu w przypadku przedłużającego się rozłączenia.
Jeśli NIE masz włączonego automatycznego ponownego łączenia z usługą
Aby w jak największym stopniu uniknąć tego błędu, przed wykonywaniem wywołań za pomocą Biblioteki płatności w Play zawsze sprawdzaj połączenie z usługami Google Play, wywołując metodę 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ą metody BillingClient.startConnection.
Podobnie jak w przypadku SERVICE_TIMEOUT
używaj prostych ponownych prób 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 w przypadku problemów z siecią nie jest już zwracana wartość SERVICE_UNAVAILABLE. Jest zwracany, gdy usługa płatności jest niedostępna i w przypadku wycofanych scenariuszy SERVICE_TIMEOUT.
Problem
Ten przejściowy błąd oznacza, że usługa Płatności w Google Play jest obecnie niedostępna. W większości przypadków oznacza to 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 próbę wysłania żądania, stosując prostą lub wzrastającą strategię czasu do ponowienia, w zależności od tego, która czynność zwróciła błąd.
W przeciwieństwie do SERVICE_DISCONNECTED połączenie z usługą Płatności w Google Play nie jest przerywane 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 rozliczeń użytkownika. Przykłady sytuacji, w których może to nastąpić:
- 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 zablokowana przez system (np. w trybie dla dzieci dostosowanym przez producenta OEM). W tym przypadku
BillingResultzawiera komunikat debugowania Sklep Play jest zablokowany.
Możliwe rozwiązanie
Automatyczne ponawianie prób w tym przypadku prawdopodobnie nie pomoże. Ręczne ponowienie próby może jednak pomóc, jeśli użytkownik rozwiąże problem, który spowodował błąd. Jeśli na przykład użytkownik zaktualizuje wersję Sklepu Play do obsługiwanej wersji, ręczna ponowna próba wykonania pierwotnej operacji może się powieść.
Jeśli ten błąd wystąpi, gdy użytkownik nie jest w sesji, ponawianie może nie mieć sensu. Jeśli w wyniku procesu zakupu otrzymasz błąd
BILLING_UNAVAILABLE, najprawdopodobniej użytkownik otrzymał informację zwrotną z 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 z informacją, że coś poszło nie tak, i udostępnić przycisk Spróbuj ponownie, aby umożliwić użytkownikowi ręczne ponowienie próby po rozwiązaniu problemu.
BŁĄD (kod błędu 6)
Problem
Jest to błąd krytyczny, który wskazuje na wewnętrzny problem z Google Play.
Możliwe rozwiązanie
Czasami wewnętrzne problemy Google Play, które prowadzą do ERROR, są przejściowe i można je rozwiązać, wdrażając ponawianie z wykładniczym wycofywaniem. Gdy użytkownicy są w trakcie sesji, preferowane jest proste ponowienie próby.
ITEM_ALREADY_OWNED
Problem
Ta odpowiedź oznacza, że użytkownik Google Play ma już subskrypcję lub jednorazowy zakup produktu, który próbuje kupić. W większości przypadków nie jest to błąd przejściowy, chyba że jest 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 już go ma. Sprawdź uprawnienia użytkownika, gdy wyświetlasz produkty dostępne do kupienia, i odpowiednio filtruj to, co może kupić.
Gdy aplikacja kliencka otrzyma ten błąd z powodu problemu z pamięcią podręczną, spowoduje to zaktualizowanie pamięci podręcznej Google Play najnowszymi danymi z backendu Play.
Ponowna próba po wystąpieniu błędu powinna w tym przypadku rozwiązać ten konkretny przejściowy problem. Po otrzymaniu ITEM_ALREADY_OWNED wywołaj BillingClient.queryPurchasesAsync(), aby sprawdzić, czy użytkownik kupił produkt. Jeśli nie, zaimplementuj prostą logikę ponawiania, aby ponownie spróbować dokonać zakupu.
ITEM_NOT_OWNED
Problem
Ta odpowiedź dotycząca zakupu wskazuje, że użytkownik Google Play nie jest właścicielem subskrypcji ani produktu jednorazowego zakupu, który próbuje zastąpić, potwierdzić lub skonsumować. W większości przypadków nie jest to błąd przejściowy, chyba że jest spowodowany nieaktualnym stanem pamięci podręcznej Google Play.
Możliwe rozwiązanie
Gdy błąd jest spowodowany problemem z pamięcią podręczną, powoduje on zaktualizowanie pamięci podręcznej Google Play najnowszymi danymi z backendu Play. Ponowienie próby z użyciem prostej strategii ponawiania po wystąpieniu błędu powinno rozwiązać ten konkretny problem z przejściową instancją. Wywołaj funkcję BillingClient.queryPurchasesAsync() po uzyskaniu wartości ITEM_NOT_OWNED, aby sprawdzić, czy użytkownik ma 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żna ich naprawić za pomocą logiki ponawiania.
FEATURE_NOT_SUPPORTED
Problem
Ten błąd niepodlegający ponowieniu 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 środki zaradcze
Przed wywołaniem biblioteki płatności w Google Play użyj BillingClient.isFeatureSupported(), aby sprawdzić, czy funkcja jest obsługiwana.
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 zamknął interfejs przepływu płatności.
Możliwe rozwiązanie
Ma charakter wyłącznie informacyjny i może zakończyć się niepowodzeniem.
ITEM_UNAVAILABLE
Problem
Subskrypcja lub produkt kupowany raz w ramach Płatności w Google Play nie jest dostępny dla tego użytkownika.
Możliwe środki zaradcze
Upewnij się, że aplikacja odświeża szczegóły produktu za pomocą funkcji queryProductDetailsAsync zgodnie z zaleceniami. W konfiguracji w Konsoli Play uwzględnij, jak często zmienia się katalog produktów, aby w razie potrzeby 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ę kwalifikowalności produktów pod kątem wszelkich 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 można było kupić produkt, musi on być aktywny, zawierająca go aplikacja musi być opublikowana i dostępna w kraju użytkownika.
Czasami, zwłaszcza podczas testowania, wszystko jest prawidłowo skonfigurowane w usłudze, ale użytkownicy nadal widzą ten błąd. Może to być spowodowane opóźnieniem 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 w funkcji 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. Sprawdź też komunikat debugowania, aby uzyskać więcej informacji o błędzie.