Obsługa kodów odpowiedzi BillingResult

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:

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

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 BillingResult zawiera 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.