BillingResult yanıt kodlarını işleme

Bir Play Faturalandırma Kitaplığı çağrısı bir işlemi tetiklediğinde kitaplık, geliştiricileri sonuç hakkında bilgilendirmek için BillingResult yanıtı döndürür. Örneğin, kullanıcı için mevcut teklifleri almak üzere queryProductDetailsAsync kullanıyorsanız yanıt kodu, OK kodu içerir ve doğru ProductDetails nesnesini sağlar ya da ProductDetails nesnesinin neden sağlanamadığını belirten farklı bir yanıt içerir.

Tüm yanıt kodları hata değildir. BillingResponseCode referans sayfasında, bu kılavuzda ele alınan yanıtların her biri ayrıntılı olarak açıklanmaktadır. Hata belirtmeyen yanıt kodlarına bazı örnekler:

Yanıt kodu bir hatayı gösterdiğinde, bunun nedeni bazen geçici koşullar olur ve bu nedenle kurtarma mümkündür. Play Faturalandırma Kitaplığı yöntemine yapılan bir çağrı, kurtarılabilir bir durumu gösteren bir BillingResponseCode değeri döndürdüğünde çağrıyı yeniden denemeniz gerekir. Diğer durumlarda koşullar geçici olarak kabul edilmez ve bu nedenle yeniden deneme önerilmez.

Geçici hatalar, hatanın kullanıcılar oturumdayken (örneğin, bir kullanıcı satın alma sürecinden geçerken) mi yoksa arka planda mı (örneğin, onResume sırasında kullanıcının mevcut satın alma işlemlerini sorgularken) oluştuğu gibi faktörlere bağlı olarak farklı yeniden deneme stratejileri gerektirir. Aşağıdaki yeniden deneme stratejileri bölümünde bu farklı stratejilerle ilgili örnekler verilmektedir. Yeniden Denenebilir BillingResult Yanıtlar bölümünde ise her yanıt kodu için en iyi strateji önerilmektedir.

Bazı hata yanıtları, yanıt koduna ek olarak hata ayıklama ve günlük kaydı amacıyla kullanılan mesajlar içerir.

Yeniden deneme stratejileri

Basit yeniden deneme

Kullanıcının oturumda olduğu durumlarda, hatanın kullanıcı deneyimini mümkün olduğunca az etkilemesi için basit bir yeniden deneme stratejisi uygulamak daha iyidir. Bu durumda, çıkış koşulu olarak maksimum deneme sayısına sahip basit bir yeniden deneme stratejisi kullanmanızı öneririz.

Aşağıdaki örnekte, BillingClient bağlantısı oluşturulurken hatayı işlemek için basit bir yeniden deneme stratejisi gösterilmektedir:

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)
  }
  ...
}

Eksponansiyel geri yükleme ile yeniden deneme

Kullanıcı oturumdayken arka planda gerçekleşen ve kullanıcı deneyimini etkilemeyen Play Faturalandırma Kitaplığı işlemleri için eksponansiyel geri yükleme kullanmanızı öneririz.

Örneğin, bu işlemi yeni satın alma işlemlerini onaylarken uygulamak uygun olur. Çünkü bu işlem arka planda gerçekleşebilir ve bir hata oluşursa onaylama işleminin anında yapılması gerekmez.

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
}

Yeniden denenebilir BillingResult yanıtları

NETWORK_ERROR (Hata Kodu 12)

Sorun

Bu hata, cihaz ile Play sistemleri arasındaki ağ bağlantısında bir sorun olduğunu gösterir.

Olası çözüm

Kurtarmak için, hatayı hangi işlemin tetiklediğine bağlı olarak basit yeniden denemeler veya eksponansiyel geri yükleme kullanın.

SERVICE_TIMEOUT (Hata Kodu -3)

Sorun

Bu hata, Google Play yanıt vermeden önce isteğin maksimum zaman aşımına ulaştığını gösterir. Bu durum, örneğin Play Faturalandırma Kitaplığı çağrısı tarafından istenen işlemin yürütülmesindeki bir gecikmeden kaynaklanabilir.

Olası çözüm

Bu genellikle geçici bir sorundur. Hangi işlem hatayı döndürdüğüne bağlı olarak basit veya eksponansiyel geri yükleme stratejisi kullanarak isteği yeniden deneyin.

Aşağıdaki SERVICE_DISCONNECTED'dan farklı olarak, Google Play Faturalandırma hizmetiyle bağlantı kesilmez ve yalnızca denenmiş olan Play Faturalandırma Kitaplığı işlemini yeniden denemeniz gerekir.

SERVICE_DISCONNECTED (Hata Kodu -1)

Sorun

Bu önemli hata, istemci uygulamasının BillingClient üzerinden Google Play Store hizmetine olan bağlantısının kesildiğini gösterir.

Olası çözüm

Play Faturalandırma Kitaplığı'nın 8.0.0 sürümünde enableAutoServiceReconnection() özelliği kullanıma sunuldu. BillingClient oluştururken bu özelliği etkinleştirmeniz önemle tavsiye edilir. Bu, hizmet bağlantısı kesilmişken bir faturalandırma API'si çağrısı yapıldığında kitaplığın bağlantıyı otomatik olarak yeniden kurmayı denemesine olanak tanır ve bu hatanın oluşma sıklığını önemli ölçüde azaltır.

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();
Otomatik hizmet yeniden bağlantısını etkinleştirdiyseniz

Play Faturalandırma Kitaplığı otomatik olarak yeniden bağlanmayı dener. Bir API çağrısı yaparken hâlâ SERVICE_DISCONNECTED yanıt kodu alıyorsanız bu, kitaplığın otomatik denemelerinden sonra yeniden bağlanamadığını gösterir. Bu senaryoda, uygulamanızda yeniden deneme mantığını uygulamanız gerekir:

  • Kullanıcı tarafından başlatılan işlemler (oturum içi) için: API çağrısının basit yeniden denemelerini kullanın. Sorunun nedeni geçici olabilir.
  • Arka plan istekleri için: Bağlantı kesilmesi uzun sürerse sistemi aşırı yüklemeyi önlemek için eksponansiyel geri yüklemeyle yeniden denemeler uygulayın.
Otomatik hizmet yeniden bağlantısını etkinleştirmediyseniz

Bu hatayı mümkün olduğunca önlemek için BillingClient.isReady() numaralı telefonu arayarak Play Faturalandırma Kitaplığı ile arama yapmadan önce her zaman Google Play Hizmetleri bağlantısını kontrol edin.

SERVICE_DISCONNECTED hatasından kurtarmayı denemek için istemci uygulamanız BillingClient.startConnection kullanarak bağlantıyı yeniden kurmayı denemelidir.

SERVICE_TIMEOUT ile olduğu gibi, hatayı hangi işlemin tetiklediğine bağlı olarak basit yeniden denemeler veya eksponansiyel geri yükleme kullanın.

SERVICE_UNAVAILABLE (Hata Kodu 2)

Önemli Not:

Google Play Faturalandırma Kitaplığı 6.0.0'dan itibaren, SERVICE_UNAVAILABLE artık ağ sorunları için döndürülmüyor. Faturalandırma hizmeti kullanılamadığında ve desteği sonlandırılan SERVICE_TIMEOUT kullanım alanlarında döndürülür.

Sorun

Bu geçici hata, Google Play Faturalandırma hizmetinin şu anda kullanılamadığını gösterir. Çoğu durumda bu, istemci cihaz ile Google Play Faturalandırma Hizmetleri arasında bir ağ bağlantısı sorunu olduğu anlamına gelir.

Olası çözüm

Bu genellikle geçici bir sorundur. Hangi işlem hatayı döndürdüğüne bağlı olarak basit veya eksponansiyel geri yükleme stratejisi kullanarak isteği yeniden deneyin.

SERVICE_DISCONNECTED'ın aksine, Google Play Faturalandırma hizmetiyle bağlantı kesilmez ve denenmekte olan işlemi yeniden denemeniz gerekir.

BILLING_UNAVAILABLE (Hata Kodu 3)

Sorun

Bu hata, satın alma işlemi sırasında kullanıcı faturalandırma hatası oluştuğunu gösterir. Bu durumun yaşanabileceği örnekler:

  • Kullanıcının cihazındaki Play Store uygulaması güncel değildir.
  • Kullanıcı, desteklenmeyen bir ülkede bulunuyor.
  • Kullanıcı bir kurumsal kullanıcıdır ve kurumsal yöneticisi, kullanıcıların satın alma işlemi yapmasını devre dışı bırakmıştır.
  • Google Play, kullanıcının ödeme yönteminden ücret alamıyor. Örneğin, kullanıcının kredi kartının süresi dolmuş olabilir.

Olası çözüm

Bu durumda otomatik yeniden denemelerin yardımcı olması beklenmez. Ancak kullanıcı, soruna neden olan koşulu düzeltirse manuel olarak yeniden deneme yardımcı olabilir. Örneğin, kullanıcı Play Store sürümünü desteklenen bir sürüme güncellerse ilk işlemin manuel olarak yeniden denenmesi işe yarayabilir.

Bu hata, kullanıcı oturumda değilken oluşursa yeniden denemenin bir anlamı olmayabilir. Satın alma süreci sonucunda BILLING_UNAVAILABLE hatası aldığınızda kullanıcının satın alma işlemi sırasında Google Play'den geri bildirim alması ve neyin yanlış gittiğinin farkında olması çok olasıdır. Bu durumda, bir hata oluştuğunu belirten bir hata mesajı gösterebilir ve kullanıcıya sorunu çözdükten sonra manuel olarak tekrar deneme seçeneği sunmak için "Tekrar dene" düğmesi ekleyebilirsiniz.

HATA (Hata Kodu 6)

Sorun

Bu, Google Play'in kendisiyle ilgili dahili bir sorunu gösteren önemli bir hatadır.

Olası çözüm

Bazen ERROR ile sonuçlanan dahili Google Play sorunları geçicidir ve azaltmak için eksponansiyel geri yükleme ile yeniden deneme uygulanabilir. Kullanıcılar oturumdayken basit bir yeniden deneme tercih edilir.

ITEM_ALREADY_OWNED

Sorun

Bu yanıt, Google Play kullanıcısının satın almaya çalıştığı aboneliğe veya tek seferlik satın alma ürününe zaten sahip olduğunu gösterir. Bu hata, Google Play'in eski bir önbelleğinden kaynaklanmadığı sürece çoğu durumda geçici bir hata değildir.

Olası çözüm

Bu hatanın, nedeninin önbellek sorunu olmadığı durumlarda oluşmasını önlemek için kullanıcı zaten sahip olduğu bir ürünü satın alması için teklif etmeyin. Satın alınabilecek ürünleri gösterirken kullanıcının haklarını kontrol ettiğinizden ve kullanıcının satın alabileceği ürünleri buna göre filtrelediğinizden emin olun. İstemci uygulaması, önbellek sorunu nedeniyle bu hatayı aldığında hata, Google Play'in önbelleğinin Play'in arka ucundaki en son verilerle güncellenmesini tetikler. Hatadan sonra yeniden denemek, bu durumda söz konusu geçici örneği çözmelidir. Kullanıcının ürünü satın alıp almadığını kontrol etmek için BillingClient.queryPurchasesAsync()ITEM_ALREADY_OWNED aldıktan sonra çağırın. Kullanıcı ürünü satın almadıysa satın alma işlemini yeniden denemek için basit bir yeniden deneme mantığı uygulayın.

ITEM_NOT_OWNED

Sorun

Bu satın alma yanıtı, Google Play kullanıcısının, değiştirmeye, onaylamaya veya kullanmaya çalıştığı aboneliğin ya da tek seferlik satın alınan ürünün sahibi olmadığını gösterir. Bu hata, Google Play'in önbelleğinin eski bir duruma girmesinden kaynaklanmadığı sürece çoğu durumda geçici bir hata değildir.

Olası çözüm

Önbellek sorunu nedeniyle hata alındığında, hata Google Play'in önbelleğinin Play'in arka ucundaki en son verilerle güncellenmesini tetikler. Hatadan sonra basit bir yeniden deneme stratejisiyle yeniden denemek bu geçici örneği çözmelidir. Kullanıcının ürünü edinip edinmediğini kontrol etmek için ITEM_NOT_OWNED aldıktan sonra BillingClient.queryPurchasesAsync() işlevini çağırın. Bu durumda, satın alma işlemini yeniden denemek için basit bir yeniden deneme mantığı kullanın.

Yeniden denenemeyen BillingResult yanıtları

Yeniden deneme mantığını kullanarak bu hataları düzeltemezsiniz.

FEATURE_NOT_SUPPORTED

Sorun

Bu yeniden denenemeyen hata, Google Play Faturalandırma özelliğinin kullanıcının cihazında desteklenmediğini gösterir. Bunun nedeni muhtemelen Play Store'un eski bir sürümünün kullanılmasıdır.

Örneğin, kullanıcılarınızın cihazlarından bazıları uygulama içi mesajlaşmayı desteklemiyor olabilir.

Olası çözüm

Play Faturalandırma Kitaplığı'na çağrı yapmadan önce özellik desteğini kontrol etmek için BillingClient.isFeatureSupported() kullanın.

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

USER_CANCELED

Sorun

Kullanıcı, faturalandırma akışı kullanıcı arayüzünün dışını tıklamıştır.

Olası çözüm

Bu yalnızca bilgilendirme amaçlıdır ve sorunsuz bir şekilde başarısız olabilir.

ITEM_UNAVAILABLE

Sorun

Google Play Faturalandırma aboneliği veya tek seferlik satın alma ürünü, bu kullanıcı tarafından satın alınamıyor.

Olası çözüm

Uygulamanızın, önerildiği şekilde queryProductDetailsAsync aracılığıyla ürün ayrıntılarını yenilediğinden emin olun. Gerekirse ek yenilemeler uygulamak için Play Console yapılandırmasında ürün kataloğunuzun ne sıklıkta değiştiğini göz önünde bulundurun. Yalnızca queryProductDetailsAsync aracılığıyla doğru bilgileri döndüren ürünleri Google Play Faturalandırma'da satmaya çalışın. Ürün uygunluğu yapılandırmasında tutarsızlık olup olmadığını kontrol edin. Örneğin, kullanıcının satın almaya çalıştığı bölge dışında yalnızca başka bir bölgede kullanılabilen bir ürün için sorgu gönderiyor olabilirsiniz. Bir ürünün satın alınabilmesi için etkin olması, uygulamasının yayınlanmış ve kullanıcının ülkesinde kullanılabilir olması gerekir.

Bazen, özellikle test sırasında ürün yapılandırmasında her şey doğru olmasına rağmen kullanıcılar bu hatayı görmeye devam eder. Bu durum, ürün ayrıntılarının Google'ın sunucularına yayılmasındaki gecikmeden kaynaklanıyor olabilir. Daha sonra tekrar deneyin.

DEVELOPER_ERROR

Sorun

Bu, bir API'yi yanlış kullandığınızı gösteren önemli bir hatadır. Örneğin, BillingClient.launchBillingFlow işlevine yanlış parametreler sağlamak bu hataya neden olabilir.

Olası çözüm

Farklı Play Faturalandırma Kitaplığı çağrılarını doğru şekilde kullandığınızdan emin olun. Ayrıca, hatayla ilgili daha fazla bilgi için hata ayıklama mesajını kontrol edin.