کدهای پاسخ BillingResult را مدیریت کنید

وقتی تماس کتابخانه صورت‌حساب Play اقدامی را راه‌اندازی می‌کند، کتابخانه پاسخ BillingResult را برای اطلاع توسعه‌دهندگان از نتیجه برمی‌گرداند. به عنوان مثال، اگر از queryProductDetailsAsync برای دریافت پیشنهادات موجود برای کاربر استفاده می‌کنید، کد پاسخ یا حاوی یک کد OK است و شیء ProductDetails مناسب را ارائه می‌کند، یا حاوی پاسخ متفاوتی است که دلیل عدم ارائه شی ProductDetails را نشان می‌دهد. .

همه کدهای پاسخ خطا نیستند. صفحه مرجع BillingResponseCode شرح مفصلی از هر یک از پاسخ های مورد بحث در این راهنما ارائه می دهد. چند نمونه از کدهای پاسخ که خطا را نشان نمی دهند عبارتند از:

  • BillingClient.BillingResponseCode.OK : اقدامی که توسط تماس ایجاد شد با موفقیت انجام شد.
  • BillingClient.BillingResponseCode.USER_CANCELED : برای اقداماتی که جریان های رابط کاربری Play Store را به کاربر نمایش می دهند، این پاسخ نشان می دهد که کاربر بدون تکمیل فرآیند از آن جریان های رابط کاربری دور شده است.

وقتی کد پاسخ یک خطا را نشان می دهد، گاهی اوقات علت آن به دلیل شرایط گذرا است و بنابراین بازیابی امکان پذیر است. وقتی تماس با روش کتابخانه صورت‌حساب Play یک مقدار BillingResponseCode را برمی‌گرداند که نشان‌دهنده وضعیت قابل بازیابی است، باید تماس را دوباره امتحان کنید. در موارد دیگر، شرایط گذرا در نظر گرفته نمی شوند و بنابراین سعی مجدد توصیه نمی شود.

خطاهای گذرا بسته به عواملی مانند این که آیا خطا زمانی که کاربران در جلسه هستند رخ می دهد یا خیر - برای مثال زمانی که کاربر در حال گذراندن یک جریان خرید است - یا خطا در پس زمینه رخ می دهد - به عنوان مثال، زمانی که شما در حال پرس و جو هستید، به استراتژی های متفاوتی نیاز دارند. خریدهای موجود کاربر در طول onResume . بخش استراتژی‌های امتحان مجدد در زیر نمونه‌هایی از این استراتژی‌های مختلف را ارائه می‌دهد و بخش پاسخ‌های نتیجه BillingResult قابل بازگشت پیشنهاد می‌کند که کدام استراتژی برای هر کد پاسخ بهتر عمل می‌کند.

علاوه بر کد پاسخ، برخی از پاسخ های خطا شامل پیام هایی برای اشکال زدایی و ورود به سیستم هستند.

استراتژی ها را دوباره امتحان کنید

سعی مجدد ساده

در شرایطی که کاربر در جلسه است، بهتر است یک استراتژی ساده مجدد را اجرا کنید تا خطا تا حد امکان تجربه کاربر را مختل کند. در آن صورت، ما یک استراتژی ساده سعی مجدد با حداکثر تعداد تلاش را به عنوان شرط خروج توصیه می کنیم.

مثال زیر یک استراتژی ساده برای تلاش مجدد برای رسیدگی به یک خطا در هنگام ایجاد اتصال 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)
  }
  ...
}

تلاش مجدد نمایی عقبگرد

توصیه می‌کنیم برای عملیات کتابخانه صورت‌حساب Play که در پس‌زمینه انجام می‌شوند و در زمانی که کاربر در جلسه است، روی تجربه کاربر تأثیری نمی‌گذارند، از عقب‌نشینی نمایی استفاده کنید.

به عنوان مثال، اجرای این مورد هنگام تأیید خریدهای جدید مناسب است زیرا این عملیات می‌تواند در پس‌زمینه اتفاق بیفتد، و اگر خطایی رخ دهد، نیازی نیست که تأیید در زمان واقعی اتفاق بیفتد.

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
}

پاسخ های Retriable BillingResult

NETWORK_ERROR (کد خطا 12)

مشکل

این خطا نشان می دهد که مشکلی در اتصال شبکه بین دستگاه و سیستم های Play وجود دارد.

وضوح ممکن

برای بازیابی، بسته به اینکه کدام عمل باعث ایجاد خطا شده است، از تلاش های مجدد ساده یا عقب نشینی نمایی استفاده کنید.

SERVICE_TIMEOUT (کد خطا -3)

مشکل

این خطا نشان می‌دهد که درخواست قبل از اینکه Google Play بتواند پاسخ دهد به حداکثر زمان پایان رسیده است. به عنوان مثال، این ممکن است به دلیل تاخیر در اجرای عملکرد درخواست شده توسط تماس کتابخانه صورت‌حساب Play باشد.

وضوح ممکن

این معمولا یک مسئله گذرا است. بسته به اینکه کدام عمل خطا را برمی گرداند، درخواست را با استفاده از یک استراتژی عقب نشینی ساده یا نمایی دوباره امتحان کنید.

برخلاف SERVICE_DISCONNECTED در زیر، اتصال به سرویس صورت‌حساب Google Play قطع نمی‌شود، و فقط باید هر عملیاتی را که کتابخانه صورت‌حساب Play انجام شد، دوباره امتحان کنید.

SERVICE_DISCONNECTED (کد خطا -1)

مشکل

این خطای مهلک نشان می دهد که اتصال برنامه مشتری به سرویس فروشگاه Google Play از طریق BillingClient قطع شده است.

وضوح ممکن

برای جلوگیری از این خطا تا حد امکان، همیشه قبل از برقراری تماس با کتابخانه صورت‌حساب Play، با تماس با BillingClient.isReady() اتصال به خدمات Google Play را بررسی کنید.

برای تلاش برای بازیابی از SERVICE_DISCONNECTED ، برنامه مشتری شما باید سعی کند با استفاده از BillingClient.startConnection اتصال را دوباره برقرار کند.

دقیقاً مانند SERVICE_TIMEOUT ، بسته به اینکه کدام عمل باعث خطا شده است، از تلاش‌های مجدد ساده یا عقب‌نشینی نمایی استفاده کنید.

SERVICE_UNAVAILABLE (کد خطا 2)

نکته مهم:

از کتابخانه صورت‌حساب Google Play نسخه 6.0.0، SERVICE_UNAVAILABLE دیگر برای مشکلات شبکه بازگردانده نمی‌شود. زمانی که سرویس صورت‌حساب در دسترس نباشد و سناریوهای موردی SERVICE_TIMEOUT منسوخ شده باشد، بازگردانده می‌شود.

مشکل

این خطای گذرا نشان می‌دهد که سرویس صورت‌حساب Google Play در حال حاضر در دسترس نیست. در بیشتر موارد، این بدان معناست که مشکل اتصال شبکه در هر نقطه بین دستگاه مشتری و خدمات صورت‌حساب Google Play وجود دارد.

وضوح ممکن

این معمولا یک مسئله گذرا است. بسته به اینکه کدام عمل خطا را برمی گرداند، درخواست را با استفاده از یک استراتژی عقب نشینی ساده یا نمایی دوباره امتحان کنید.

برخلاف SERVICE_DISCONNECTED ، اتصال به سرویس صورت‌حساب Google Play قطع نمی‌شود، و باید هر عملیاتی را که در حال انجام است، دوباره امتحان کنید.

BILLING_UNAVAILABLE (کد خطا 3)

مشکل

این خطا نشان می دهد که یک خطای صورتحساب کاربر در طول فرآیند خرید رخ داده است. نمونه هایی از زمانی که ممکن است این اتفاق بیفتد عبارتند از:

  • برنامه Play Store در دستگاه کاربر قدیمی است.
  • کاربر در یک کشور پشتیبانی نشده است.
  • کاربر یک کاربر سازمانی است و سرپرست سازمانی آنها کاربران را از خرید غیرفعال کرده است.
  • Google Play قادر به کسر هزینه از روش پرداخت کاربر نیست. به عنوان مثال، کارت اعتباری کاربر ممکن است منقضی شده باشد.

وضوح ممکن

بعید است که تکرار خودکار در این مورد کمک کند. با این حال، اگر کاربر شرایطی را که باعث ایجاد مشکل شده است، برطرف کند، یک امتحان مجدد دستی می‌تواند کمک کند. به عنوان مثال، اگر کاربر نسخه Play Store خود را به نسخه پشتیبانی شده به‌روزرسانی کند، یک امتحان مجدد دستی از عملیات اولیه می‌تواند کارساز باشد.

اگر این خطا زمانی رخ دهد که کاربر در جلسه نیست، ممکن است تلاش مجدد منطقی نباشد. هنگامی که یک خطای BILLING_UNAVAILABLE را در نتیجه جریان خرید دریافت می‌کنید، به احتمال زیاد کاربر در طول فرآیند خرید بازخوردی از Google Play دریافت کرده و ممکن است از اشتباه خود آگاه باشد. در این مورد، می‌توانید یک پیام خطایی نشان دهید که مشخص می‌کند مشکلی پیش آمده است و یک دکمه «دوباره امتحان کنید» را ارائه دهید تا پس از رفع مشکل، گزینه امتحان مجدد دستی را در اختیار کاربر قرار دهید.

ERROR (کد خطا 6)

مشکل

این یک خطای مرگبار است که نشان دهنده یک مشکل داخلی در خود گوگل پلی است.

وضوح ممکن

گاهی اوقات مشکلات داخلی Google Play که منجر به ERROR می‌شوند، گذرا هستند و برای کاهش می‌توان یک تلاش مجدد با عقب‌نشینی نمایی انجام داد. هنگامی که کاربران در جلسه هستند، یک امتحان مجدد ساده ترجیح داده می شود.

ITEM_ALREADY_OWNED

مشکل

این پاسخ نشان می‌دهد که کاربر Google Play از قبل مالک اشتراک یا محصول یک‌بار خریدی است که می‌خواهد بخرد. در بیشتر موارد، این یک خطای گذرا نیست، مگر زمانی که ناشی از حافظه پنهان Google Play قدیمی باشد.

وضوح ممکن

برای جلوگیری از وقوع این خطا زمانی که علت آن مشکل حافظه پنهان نیست، زمانی که کاربر قبلاً محصولی را در اختیار دارد، آن را برای خرید پیشنهاد نکنید. اطمینان حاصل کنید که هنگام نمایش محصولات موجود برای خرید، حقوق کاربر را بررسی کرده و آنچه را که کاربر می‌تواند خریداری کند، فیلتر کنید. هنگامی که برنامه مشتری این خطا را به دلیل مشکل حافظه پنهان دریافت می کند، این خطا باعث می شود که حافظه پنهان Google Play با آخرین داده های باطن Play به روز شود. تلاش مجدد پس از خطا باید این نمونه گذرا خاص را در این مورد برطرف کند. پس از دریافت ITEM_ALREADY_OWNED با BillingClient.queryPurchasesAsync() تماس بگیرید تا بررسی کنید آیا کاربر محصول را خریداری کرده است یا خیر، و اگر اینطور نیست، یک منطق ساده امتحان مجدد را برای تلاش مجدد برای خرید اجرا کنید.

ITEM_NOT_OWNED

مشکل

این پاسخ خرید نشان می‌دهد که کاربر Google Play مالک اشتراک یا محصول خرید یک‌باره‌ای نیست که کاربر سعی در جایگزینی، تأیید یا مصرف آن دارد. این یک خطای گذرا در بیشتر موارد نیست، مگر زمانی که به دلیل قرار گرفتن حافظه پنهان Google Play در حالت بیات ایجاد شود.

وضوح ممکن

هنگامی که خطا به دلیل مشکل حافظه پنهان دریافت می‌شود، این خطا باعث می‌شود که حافظه پنهان Google Play با آخرین داده‌های باطن Play به‌روزرسانی شود. تلاش مجدد با یک استراتژی ساده مجدد پس از خطا باید این نمونه گذرا خاص را حل کند. پس از دریافت ITEM_NOT_OWNED با BillingClient.queryPurchasesAsync() تماس بگیرید تا بررسی کنید که آیا کاربر محصول را خریداری کرده است یا خیر. اگر این کار را نکرده‌اند، از منطق ساده تلاش مجدد برای خرید مجدد استفاده کنید.

پاسخ های غیر قابل برگشت BillingResult

با استفاده از منطق سعی مجدد نمی توانید از این خطاها بازیابی کنید.

FEATURE_NOT_SUPPORTED

مشکل

این خطای غیر قابل برگشت نشان می دهد که ویژگی صورتحساب Google Play در دستگاه کاربر پشتیبانی نمی شود، احتمالاً به دلیل نسخه قدیمی Play Store.

به عنوان مثال، شاید برخی از دستگاه های کاربران شما از پیام رسانی درون برنامه ای پشتیبانی نکنند.

کاهش احتمالی

از BillingClient.isFeatureSupported() برای بررسی پشتیبانی از ویژگی ها قبل از برقراری تماس با کتابخانه صورتحساب Play استفاده کنید.

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

USER_CANCELED

مشکل

کاربر از رابط کاربری جریان صورت‌حساب کلیک کرده است.

وضوح ممکن

این فقط اطلاعاتی است و ممکن است به طرز دلپذیری شکست بخورد.

ITEM_UNAVAILABLE

مشکل

اشتراک صورت‌حساب Google Play یا محصول یک‌بار خرید برای این کاربر در دسترس نیست.

کاهش احتمالی

اطمینان حاصل کنید که برنامه شما جزئیات محصول را از طریق queryProductDetailsAsync همانطور که توصیه می شود بازخوانی می کند. به تعداد دفعات تغییر کاتالوگ محصولتان در پیکربندی کنسول Play توجه کنید تا در صورت نیاز، به‌روزرسانی‌های اضافی را اعمال کنید. فقط سعی کنید محصولاتی را در صورت‌حساب Google Play بفروشید که اطلاعات درست را از طریق queryProductDetailsAsync برمی‌گردانند. پیکربندی واجد شرایط بودن محصول را برای هرگونه ناهماهنگی بررسی کنید. به عنوان مثال، ممکن است در حال جستجو برای محصولی باشید که فقط برای منطقه ای غیر از منطقه ای که کاربر سعی در خرید آن دارد در دسترس است. برای اینکه یک محصول برای خرید در دسترس باشد، باید فعال باشد، برنامه آن باید منتشر شود، و برنامه آن باید در کشور کاربر موجود باشد.

گاهی اوقات، به ویژه در هنگام آزمایش، همه چیز در پیکربندی محصول درست است، اما کاربران همچنان این خطا را مشاهده می کنند. این ممکن است به دلیل تأخیر انتشار جزئیات محصول در سرورهای Google باشد. بعداً دوباره امتحان کنید.

DEVELOPER_ERROR

مشکل

این یک خطای مهلک است که نشان می دهد شما به درستی از یک API استفاده نمی کنید. به عنوان مثال، ارائه پارامترهای نادرست به BillingClient.launchBillingFlow می تواند باعث این خطا شود.

وضوح ممکن

مطمئن شوید که به درستی از تماس‌های مختلف کتابخانه صورت‌حساب Play استفاده می‌کنید. همچنین، برای اطلاعات بیشتر در مورد خطا، پیام اشکال زدایی را بررسی کنید.