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 odpowiedzi
omówionej 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.
Gdy kod odpowiedzi wskazuje błąd, przyczyną są czasami przejściowe warunki, więc przywrócenie jest możliwe. Gdy wywołanie metody Biblioteki płatności w Play zwraca wartość BillingResponseCode, która wskazuje stan, który można naprawić, 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 BillingResultsekcji odpowiedzi, które można ponowić
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ą wiadomości 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 pokazuje prostą strategię ponawiania prób, która umożliwia obsługę błędu
podczas nawiązywania połączenia BillingClient:
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) }
} finally {
tries++
}
} while (tries <= maxTries && !isConnectionEstablished)
}
...
}
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 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
}
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ą strategię ponawiania lub strategię wzrastającego 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 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 wywołanie interfejsu Billing API jest wykonywane, gdy usługa jest odłączona, co znacznie zmniejsza występowanie tego błędu.
Kotlin
val billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.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 biblioteka nie była w stanie ponownie się połączyć po automatycznych próbach.
W takim przypadku w aplikacji należy zaimplementować 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 żądanie, stosując prostą lub wzrastającą strategię ponawiania, 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.
- Nie udało się obciążyć formy płatności użytkownika w Google Play. 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 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, a w celu ich ograniczenia można wdrożyć ponawianie z wzrastającym czasem do ponowienia. Gdy użytkownicy są w 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, 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 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 problem przejściowy. Po uzyskaniu 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 nieaktualną pamięcią podręczną 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 -> {
if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) {
// 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 jednorazowo 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 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 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 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.
Ten błąd może wystąpić na przykład wtedy, gdy do funkcji BillingClient.launchBillingFlow zostaną przekazane nieprawidłowe parametry.
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.