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żyjesz elementu queryProductDetailsAsync, aby uzyskać dostępne oferty dla użytkownika, kod odpowiedzi będzie zawierać kod OK i odpowiednią wartość elementu ProductDetails lub inną odpowiedź wskazującą przyczynę, dla której nie można podać elementu ProductDetails.

Nie wszystkie kody odpowiedzi są błędami. Na stronie referencyjnej BillingResponseCodeznajdują się szczegółowe opisy wszystkich odpowiedzi omawianych w tym przewodniku. Oto kilka przykładów kodów odpowiedzi, które nie wskazują na błędy:

Gdy kod odpowiedzi wskazuje na błąd, przyczyną może być chwilowy stan, a w takim przypadku możliwe jest odzyskanie. Gdy wywołanie metody Biblioteki płatności Google Play zwraca wartość BillingResponseCode, która wskazuje na stan możliwy do odzyskania, należy ponownie wykonać wywołanie. W innych przypadkach warunki nie są uznawane za przejściowe, dlatego nie zalecamy ponownego próbowania.

Przejściowe błędy wymagają różnych strategii ponownego próby w zależności od czynników takich jak to, czy błąd wystąpił podczas sesji (np. gdy użytkownik przechodzi przez proces zakupu) czy w tle (np. gdy wysyłasz zapytanie o dotychczasowe zakupy użytkownika podczas onResume). W sekcji o strategiach ponownego próby znajdziesz przykłady tych strategii, a w sekcji odpowiedzi dostępnych do pobrania BillingResult, w której znajdziesz informacje o tym, która strategia sprawdza się najlepiej w przypadku danego kodu odpowiedzi.

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

.

Strategie ponawiania

Prosta próba

W sytuacjach, gdy użytkownik jest w sesji, lepiej jest zastosować prostą strategię ponownego próbowania, aby błąd jak najmniej zakłócał wrażenia użytkownika. W takim przypadku zalecamy zastosowanie prostej strategii ponownego próbowania z maksymalną liczbą prób jako warunkiem zakończenia.

Ten przykład pokazuje prostą strategię ponownych prób, która pozwala obsłużyć 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) }
      } finally {
        tries++
      }
    } while (tries <= maxTries && !isConnectionEstablished)
  }
  ...
}

Wzrastający czas do ponownej próby

Zalecamy stosowanie wykładniczego zmniejszania częstotliwości w przypadku operacji Biblioteki płatności Google Play, które odbywają się w tle i nie wpływają na wrażenia użytkownika podczas sesji.

Można to na przykład zrobić podczas potwierdzania nowych zakupów, ponieważ ta operacja może być wykonywana w tle, a potwierdzenie nie musi być wysyłane 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 pobrać ponownie

NETWORK_ERROR (kod błędu 12)

Problem

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

Możliwe rozwiązanie

Aby odzyskać kontrolę, użyj prostych prób 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 wskazuje, ż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 wywołanego przez wywołanie Biblioteki płatności w Google Play.

Możliwe rozwiązanie

Jest to zwykle problem przejściowy. Ponownie wysyła żądanie, stosując strategię prostego lub wykładniczego odsunięcia w czasie, w zależności od działania, które zwróciło błąd.

W przeciwieństwie do SERVICE_DISCONNECTEDponiżej połączenie z usługą Płatności w Google Play nie zostało przerwane, więc wystarczy ponownie wykonać 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 klienckiej z usługą Google Play Store za pomocą interfejsu BillingClient zostało zerwane.

Możliwe rozwiązanie

Aby w jak największym stopniu uniknąć tego błędu, przed wywołaniem metody w bibliotece płatności Play zadbaj o połączenie z usługami Google Play. Aby to zrobić, wywołaj metodę BillingClient.isReady().

Aby spróbować odzyskać połączenie z SERVICE_DISCONNECTED, aplikacja klienta powinna spróbować ponownie nawiązać połączenie za pomocą BillingClient.startConnection.

Podobnie jak w przypadku funkcji SERVICE_TIMEOUT, użyj prostych 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 wartość SERVICE_UNAVAILABLE nie jest już zwracana w przypadku problemów z siecią. Zwracany, gdy usługa płatności jest niedostępna, oraz w przypadkach SERVICE_TIMEOUT, które zostały wycofane.

Problem

Ten przejściowy błąd wskazuje, ż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 w dowolnym miejscu między urządzeniem klienta a usługami Google Play Billing.

Możliwe rozwiązanie

Jest to zwykle problem przejściowy. Ponownie wysyła żądanie, stosując strategię prostego lub wykładniczego odsunięcia w czasie, w zależności od działania, które zwróciło błąd.

W przeciwieństwie do SERVICE_DISCONNECTED połączenie z usługą Płatności w Google Play nie zostało zerwane, więc musisz ponownie wykonać próbę wykonania danej operacji.

BILLING_UNAVAILABLE (kod błędu 3)

Problem

Ten błąd wskazuje, że podczas procesu zakupu wystąpił błąd płatności 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 jego administrator firmy wyłączył możliwość dokonywania zakupów.
  • Google Play nie może obciążyć formy płatności użytkownika. Na przykład karta kredytowa użytkownika może mieć wygasłą ważność.

Możliwe rozwiązanie

W tym przypadku automatyczne próby nie pomogą. Jednak ręczne ponowne uruchomienie może pomóc, jeśli użytkownik rozwiąże problem. Jeśli na przykład użytkownik zaktualizuje wersję Sklepu Play na obsługiwaną, można ręcznie powtórzyć początkową operację.

Jeśli ten błąd wystąpi, gdy użytkownik nie jest w sesji, ponowne próbowanie może nie mieć sensu. Jeśli w ramach procesu zakupu otrzymasz błąd BILLING_UNAVAILABLE, najprawdopodobniej użytkownik otrzymał opinię 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, który informuje o wystąpieniu problemu, oraz przycisk „Spróbuj ponownie”, aby umożliwić użytkownikowi ręczne ponowne wykonanie czynności po rozwiązaniu problemu.

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

Problem

Jest to błąd krytyczny wskazujący na wewnętrzny problem z Google Play.

Możliwe rozwiązanie

Czasami wewnętrzne problemy Google Play, które powodują błąd ERROR, są przejściowe. Aby je rozwiązać, można zastosować ponowne próby z wykładniczym zmniejszaniem częstotliwości. Gdy użytkownicy są w sesji, lepiej jest po prostu ponownie wykonać próbę.

ITEM_ALREADY_OWNED

Problem

Ta odpowiedź wskazuje, że użytkownik Google Play jest już subskrybentem lub dokonał jednorazowego zakupu produktu, który próbuje kupić. W większości przypadków nie jest to błąd przejściowy, chyba że jest spowodowany przez zapełnioną pamięć podręczną Google Play.

Możliwe rozwiązanie

Aby uniknąć tego błędu, gdy przyczyną nie jest problem z pamięcią podręczną, nie oferuj produktu do zakupu, jeśli użytkownik już go ma. Pamiętaj, aby sprawdzać uprawnienia użytkownika, gdy wyświetlasz produkty dostępne do kupienia, i odpowiednio filtrować produkty, które użytkownik może kupić. Gdy aplikacja klienta otrzyma ten błąd z powodu problemu z pamięcią podręczną, spowoduje ona zaktualizowanie pamięci podręcznej Google Play najnowszymi danymi z back-endu Google Play. W tym przypadku ponowne wykonanie operacji po wystąpieniu błędu powinno rozwiązać problem. Po otrzymaniu wywołania ITEM_ALREADY_OWNEDwywołaj funkcję BillingClient.queryPurchasesAsync(), aby sprawdzić, czy użytkownik kupił produkt. Jeśli nie, zastosuj prostą logikę ponownego próbowania, aby ponownie spróbować dokonać zakupu.

ITEM_NOT_OWNED

Problem

Ta odpowiedź na zakup wskazuje, że użytkownik Google Play nie jest właścicielem subskrypcji ani produktu zakupionego jednorazowo, który próbuje zastąpić, potwierdzić lub wykorzystać. W większości przypadków nie jest to błąd przejściowy, chyba że jest spowodowany przez nieaktualny stan 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 back-endu Google Play. Ponowna próba z prostą strategią ponownego próbowania po błędzie powinna rozwiązać ten konkretny problem przejściowy. Wywołaj BillingClient.queryPurchasesAsync() po otrzymaniu ITEM_NOT_OWNED, aby sprawdzić, czy użytkownik nabył produkt. Jeśli nie, użyj prostej logiki ponownego próbowania, aby ponownie spróbować dokonać zakupu.

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

Nie można ich naprawić za pomocą logiki ponownego próbowania.

FEATURE_NOT_SUPPORTED

Problem

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

Może na przykład być tak, że niektóre urządzenia użytkowników nie obsługują wiadomości w aplikacji.

Możliwe działania zaradcze

Zanim skontaktujesz się z biblioteką płatności Google Play, użyj BillingClient.isFeatureSupported(), aby sprawdzić, czy dana funkcja jest obsługiwana.

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

USER_CANCELED

Problem

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

Możliwe rozwiązanie

Jest to tylko informacja i może się nie udać.

ITEM_UNAVAILABLE

Problem

Subskrypcja lub produkt kupowany jednorazowo w Płatnościach w Google Play nie jest dostępny do kupienia przez tego użytkownika.

Możliwe działania zaradcze

Upewnij się, że aplikacja odświeża szczegóły produktu za pomocą queryProductDetailsAsync zgodnie z zaleceniami. Pamiętaj, aby w razie potrzeby stosować dodatkowe odświeżenia, biorąc pod uwagę, jak często zmienia się twój katalog produktów w Konsoli Play. Staraj się sprzedawać produkty w Płatnościach w Google Play, które zwracają prawidłowe informacje za pomocą queryProductDetailsAsync. Sprawdź konfigurację wymagań dotyczących 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 w porządku w konfiguracji produktu, 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 wskazuje, że interfejs API jest używany nieprawidłowo. Na przykład podanie nieprawidłowych parametrów do funkcji BillingClient.launchBillingFlow może spowodować ten błąd.

Możliwe rozwiązanie

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