Obsługa kodów odpowiedzi BillingResult

Gdy wywołanie Biblioteki płatności w Google Play wywoła działanie, biblioteka zwraca odpowiedź BillingResult, aby poinformować deweloperów o wyniku. Jeśli na przykład do pobierania ofert dostępnych dla użytkownika używasz metody queryProductDetailsAsync, kod odpowiedzi zawiera kod OK i właściwy obiekt ProductDetails lub inną odpowiedź wskazującą przyczynę, dla której nie można dostarczyć obiektu ProductDetails.

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

Gdy kod odpowiedzi wskazuje na błąd, przyczyną może być czasami sytuacja przejściowa, więc możliwe jest przywrócenie danych. Gdy wywołanie metody Biblioteki płatności w Play zwraca wartość BillingResponseCode, która wskazuje, że warunek można odzyskać, należy ponowić próbę. W innych przypadkach warunki nie są uznawane za przejściowe i dlatego nie zalecamy ponawiania próby.

Przejściowe błędy wymagają stosowania różnych strategii ponawiania prób w zależności od czynników takich jak to, czy błąd występuje podczas sesji użytkownika (np. gdy trwa proces zakupu) lub gdy błąd występuje w tle – na przykład gdy wysyłasz zapytanie o dotychczasowe zakupy użytkownika podczas onResume. W sekcji o strategiach ponawiania poniżej znajdziesz przykłady tych różnych strategii, a w sekcji Odpowiedzi możliwe do ponownego użycia BillingResult znajdziesz zalecenia, które strategie najlepiej sprawdzają się w przypadku poszczególnych kodów odpowiedzi.

Oprócz kodu odpowiedzi niektóre odpowiedzi o błędach zawierają komunikaty do debugowania i logowania.

Strategie ponawiania

Proste ponowienie

W sytuacjach, gdy użytkownik jest w trakcie sesji, lepiej wdrożyć prostą strategię ponawiania próby, aby błąd w jak najmniejszym stopniu wpływał na wrażenia użytkownika. W takim przypadku zalecamy prostą strategię ponawiania próby z maksymalną liczbą prób jako warunkiem wyjścia.

Ten przykład przedstawia prostą strategię ponawiania prób, aby naprawić błąd 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) }
        tries++
      }
    } while (tries <= maxTries && !isConnectionEstablished)
  }
  ...
}

Wykładnicza próba ponowienia

Zalecamy stosowanie wykładniczego ponowienia w przypadku operacji w Bibliotece płatności w Play, które odbywają się w tle i nie wpływają na wrażenia użytkownika, gdy trwa sesja.

Warto wdrożyć tę funkcję na przykład przy potwierdzaniu nowych zakupów, ponieważ ta operacja może odbywać się w tle, a potwierdzanie 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 na temat wyników płatności dostępnych ponownie

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 Google Play.

Możliwe rozwiązanie

W tym celu użyj prostych ponownych prób lub wykładniczego ponawiania, w zależności od tego, które działanie spowodowało błąd.

SERVICE_TIMEOUT (kod błędu -3)

Problem

Ten komunikat o błędzie oznacza, że żądanie przekroczyło limit czasu, po którym Google Play będzie mógł odpowiedzieć. Może to być na przykład opóźnione w wykonaniu działania żądanego przez wywołanie Biblioteki płatności w Play.

Możliwe rozwiązanie

Jest to zwykle problem przejściowy. Ponów żądanie, korzystając z prostej lub wykładniczej strategii wycofywania, w zależności od tego, które działanie zwróciło błąd.

W przeciwieństwie do przykładu SERVICE_DISCONNECTED połączenie z usługą płatności w Google Play nie zostanie przerwane. Wystarczy, że spróbujesz ponownie wykonać operację 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ą Sklep Google Play przez interfejs BillingClient zostało przerwane.

Możliwe rozwiązanie

Aby jak najlepiej uniknąć tego błędu, zawsze sprawdzaj połączenie z Usługami Google Play, zanim zaczniesz wykonywać wywołania z Biblioteki płatności w Play, wywołując metodę BillingClient.isReady().

Aby spróbować przywrócić połączenie za pomocą instancji SERVICE_DISCONNECTED, aplikacja kliencka powinna spróbować ponownie nawiązać połączenie przy użyciu metody BillingClient.startConnection.

Podobnie jak w przypadku SERVICE_TIMEOUT używaj prostych ponownych prób lub wykładniczego ponowienia w zależności od tego, które działanie wywołał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ą SERVICE_UNAVAILABLE nie jest już zwracany. Zwracany jest, gdy usługa rozliczeniowa jest niedostępna i jeśli został wycofany 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, że problem z połączeniem sieciowym występuje w dowolnym miejscu między urządzeniem klienckim a usługami płatności w Google Play.

Możliwe rozwiązanie

Jest to zwykle problem przejściowy. Ponów żądanie, korzystając z prostej lub wykładniczej strategii wycofywania, w zależności od tego, które działanie zwróciło błąd.

W przeciwieństwie do SERVICE_DISCONNECTED połączenie z usługą Płatności w Google Play nie jest przerywane i należy ponowić próbę wykonania operacji.

BILLING_UNAVAILABLE (kod błędu 3)

Problem

Ten błąd oznacza, że podczas procesu zakupu wystąpił błąd płatności użytkownika. Oto kilka przykładów:

  • Aplikacja Sklep Play na urządzeniu użytkownika jest nieaktualna.
  • Użytkownik jest w nieobsługiwanym kraju.
  • Użytkownik jest użytkownikiem firmowym, a jego administrator firmowy zablokował użytkownikom możliwość dokonywania zakupów.
  • Google Play nie może obciążyć formy płatności użytkownika. Na przykład wygasła karta kredytowa użytkownika.

Możliwe rozwiązanie

Automatyczne ponawianie prób raczej nie pomoże w tym przypadku. Ręczne ponawianie próby może jednak pomóc, jeśli użytkownik rozwiąże problem, który spowodował problem. Jeśli na przykład użytkownik zaktualizuje wersję Sklepu Play do obsługiwanej wersji, spróbuj ręcznie wykonać wstępną operację ponownie.

Jeśli ten błąd występuje, gdy użytkownik nie jest w sesji, ponawianie próby może nie mieć sensu. Jeśli w trakcie procesu zakupu pojawi się błąd BILLING_UNAVAILABLE, prawdopodobnie użytkownik otrzymał w trakcie procesu zakupu opinię z Google Play i może być świadomy tego, 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 dać użytkownikowi możliwość ręcznej ponownej próby po rozwiązaniu problemu.

ERROR (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 w Google Play, które prowadzą do wystąpienia ERROR, są przejściowe. Aby je złagodzić, można zastosować ponowienie ze wzrastającym czasem do ponowienia. Jeśli użytkownicy są w trakcie sesji, preferujemy ponowienie próby.

ELEMENT_JUŻ_WŁAŚCIWE

Problem

Ta odpowiedź wskazuje, że użytkownik Google Play ma już subskrypcję lub usługę, którą chce kupić (jednorazowo). W większości przypadków nie jest to chwilowy błąd, chyba że jest spowodowany przestarzałą pamięcią podręczną Google Play.

Możliwe rozwiązanie

Aby uniknąć tego błędu, gdy przyczyna nie jest związana z pamięcią podręczną, nie oferuj produktu do kupienia, gdy użytkownik już go posiada. Gdy prezentujesz produkty dostępne do kupienia, sprawdzaj uprawnienia użytkowników i odpowiednio filtruj produkty, które może kupić. Gdy aplikacja kliencka otrzyma ten błąd z powodu problemu z pamięcią podręczną, spowoduje to pobranie jej z pamięci podręcznej Google Play za pomocą najnowszych danych z backendu Google Play. W tym przypadku ponowienie próby po wystąpieniu błędu powinno rozwiązać ten problem. Po otrzymaniu ITEM_ALREADY_OWNED wywołaj BillingClient.queryPurchasesAsync(), aby sprawdzić, czy użytkownik nabył produkt. Jeśli tak nie jest, zaimplementuj prostą logikę ponawiania próby w celu ponowienia próby zakupu.

PRODUKT_NIE_ZASTOSOWANY

Problem

Ta odpowiedź dotycząca zakupu oznacza, że użytkownik Google Play nie jest właścicielem subskrypcji lub usługi jednorazowego zakupu, którą chce zastąpić, potwierdzić lub wykorzystać. W większości przypadków nie jest to chwilowy błąd, chyba że jest on spowodowany nieaktualnym stanem pamięci podręcznej Google Play.

Możliwe rozwiązanie

W przypadku błędu z powodu problemu z pamięcią podręczną błąd powoduje, że pamięć podręczna Google Play jest uzupełniana o najnowsze dane z backendu Google Play. Ponawianie próby za pomocą prostej strategii ponawiania próby po wystąpieniu błędu powinno rozwiązać problem z konkretną, przejściową instancją. Aby sprawdzić, czy użytkownik nabył produkt, zadzwoń pod numer BillingClient.queryPurchasesAsync() po otrzymaniu ITEM_NOT_OWNED. Jeśli nie, użyj prostej logiki ponownych prób, aby spróbować ponownie dokonać zakupu.

Odpowiedzi dotyczące wyników rozliczeń niemożliwych do ponownego użycia

Po wykonaniu tych błędów nie można rozwiązać problemu za pomocą mechanizmu ponownych prób.

FEATURE_NOT_SUPPORTED (Funkcja nieobsługiwana)

Problem

Ten błąd niepowtarzający się 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 nie obsługują wiadomości w aplikacji.

Możliwe złagodzenie

Przed wywołaniem Biblioteki płatności w Google Play użyj narzędzia BillingClient.isFeatureSupported(), aby sprawdzić obsługę funkcji.

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

USER_CANCELED

Problem

Użytkownik zrezygnował z interfejsu procesu płatności.

Możliwe rozwiązanie

Ma to jedynie charakter informacyjny i może spowodować błąd.

PRODUKT_NIEDOSTĘPNY

Problem

Ten użytkownik nie może kupić subskrypcji Płatności w Google Play ani usługi do jednorazowego zakupu.

Możliwe złagodzenie

Upewnij się, że aplikacja odświeża szczegóły produktu za pomocą queryProductDetailsAsync zgodnie z zaleceniami. Weź pod uwagę, jak często katalog produktów zmienia się w konfiguracji Konsoli Play, aby w razie potrzeby zaimplementować dodatkowe odświeżenia. W ramach Płatności w Google Play staraj się sprzedawać tylko te produkty, które zwracają prawidłowe informacje przez queryProductDetailsAsync. Sprawdź, czy w konfiguracji kwalifikacji produktu nie ma niespójności. Możesz na przykład zapytać o produkt dostępny tylko w regionie innym niż ten, który użytkownik próbuje kupić. 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, szczególnie podczas testowania, wszystko w konfiguracji usługi jest prawidłowe, ale użytkownicy nadal widzą ten błąd. Może to być spowodowane opóźnieniem w rozpowszechnianiu szczegółów usługi na serwerach Google. Spróbuj ponownie później.

BŁĄD_DEWELOPERA

Problem

To błąd krytyczny, który wskazuje, że używasz interfejsu API w nieprawidłowy sposób. Ten błąd może np. spowodować podanie nieprawidłowych parametrów w funkcji BillingClient.launchBillingFlow.

Możliwe rozwiązanie

Upewnij się, że prawidłowo używasz różnych wywołań Biblioteki płatności w Play. Więcej informacji o błędzie znajdziesz w komunikacie debugowania.