التعامل مع رموز استجابة الفوترة

عندما تؤدي استدعاء "مكتبة الفوترة في Play" إلى تنفيذ إجراء، تعرض المكتبة BillingResult والاستجابة لإبلاغ المطورين بالنتيجة. على سبيل المثال، إذا كنت تستخدم queryProductDetailsAsync للحصول على العروض المتاحة للمستخدم، يجب أن يحتوي رمز الاستجابة على الرمز صالح وتقديم ProductDetails الصحيح أو يحتوي على استجابة مختلفة تشير إلى سبب ProductDetails تعذّر توفير العنصر.

ليست كل رموز الاستجابة أخطاء. BillingResponseCode وصف تفصيلي لكل ردّ كما هو موضح في هذا الدليل. في ما يلي بعض الأمثلة على رموز الاستجابة التي لا تشير إلى أخطاء:

  • BillingClient.BillingResponseCode.OK : اكتمل الإجراء الذي تم تشغيله من خلال المكالمة بنجاح.
  • BillingClient.BillingResponseCode.USER_CANCELED : بالنسبة إلى الإجراءات التي تعرض تدفق واجهة مستخدم متجر Play إلى المستخدم، فإن هذه الاستجابة يشير إلى انتقال المستخدم بعيدًا عن تدفقات واجهة المستخدم هذه دون إكمال الدفع.

عندما يشير رمز الاستجابة إلى وجود خطأ، فإن السبب يرجع أحيانًا إلى حالات عابرة، وبالتالي يمكن التعافي منها. عند إجراء مكالمة إلى أحد تطبيقات Play تُرجع طريقة Billing Library رمز 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
}

ردود BillingResult القابلة لإعادة المحاولة

NETWORK_ERROR (رمز الخطأ 12)

المشكلة

يشير هذا الخطأ إلى حدوث مشكلة في الاتصال بالشبكة. بين الجهاز وأنظمة Play.

الحلّ المحتمَل

ولاسترداده، استخدم إعادات المحاولة البسيطة أو التراجع الأسي، اعتمادًا على تسبب في حدوث الخطأ.

SERVICE_تذكير (رمز الخطأ -3)

المشكلة

يشير هذا الخطأ إلى أن الطلب قد بلغ الحد الأقصى للمهلة قبل بإمكان Google Play الاستجابة لطلبك. قد يرجع ذلك مثلاً إلى تأخير أثناء تنفيذ الإجراء الذي طلبته مكالمة Play Billing Library.

الحلّ المحتمل

عادة ما تكون هذه مشكلة عابرة. أعد محاولة الطلب باستخدام إما استراتيجية تراجع بسيطة أو أسّية، اعتمادًا على الإجراء الذي نتج عنه خطأ.

إلغاء الإعجاب بـ SERVICE_DISCONNECTED أدناه، لن يتم قطع الاتصال بخدمة "الفوترة في Google Play"، إلى إعادة محاولة تنفيذ أي عملية تمت محاولة تنفيذها في Play Billing Library.

SERVICE_DISCONNECTED (رمز الخطأ -1)

المشكلة

يشير هذا الخطأ الفادح إلى اتصال تطبيق العميل بمنصّة Google Play. تخزين الخدمة عبر BillingClient تم فصله.

الحلّ المحتمل

لتجنّب حدوث هذا الخطأ قدر الإمكان، يُرجى التحقّق دائمًا من الاتصال بمحرّك بحث Google. خدمات Play قبل إجراء مكالمات باستخدام Play Billing Library عن طريق الاتصال BillingClient.isReady()

لمحاولة الاسترداد من SERVICE_DISCONNECTED ، فيجب أن يحاول تطبيق العميل إعادة إنشاء الاتصال باستخدام BillingClient.startConnection

تمامًا كما هي الحال مع SERVICE_TIMEOUT ، استخدم إعادات المحاولة البسيطة أو خوارزمية الرقود الأسي بناءً على الإجراء الذي تم تشغيله الخطأ.

SERVICE_UNAREA (رمز الخطأ 2)

ملاحظة مهمة:

بدءًا من الإصدار 6.0.0 من Google Play Billing Library، سيسري SERVICE_UNAVAILABLE فترة أطول بسبب مشكلات الشبكة. ويتم إرجاعه عندما تكون خدمة الفوترة غير متاح وسيناريوهات SERVICE_TIMEOUT المتوقّفة نهائيًا.

المشكلة

يشير هذا الخطأ العابر إلى أنّ خدمة "الفوترة في Google Play" متوقفة حاليًا غير متاح. ويعني هذا أن هناك مشكلة في الاتصال بالشبكة في معظم الحالات أي مكان بين جهاز العميل وخدمات "الفوترة في Google Play".

الحلّ المحتمَل

عادة ما تكون هذه مشكلة عابرة. أعد محاولة الطلب باستخدام إما استراتيجية تراجع بسيطة أو أسّية، اعتمادًا على الإجراء الذي نتج عنه خطأ.

إلغاء الإعجاب بـ SERVICE_DISCONNECTED ، لن يتم قطع الاتصال بخدمة "الفوترة في Google Play"، وتحتاج إلى لإعادة محاولة تنفيذ أي عملية تتم تجربتها.

BILLING_UNavailable (رمز الخطأ 3)

المشكلة

يشير هذا الخطأ إلى حدوث خطأ في فوترة المستخدم أثناء عملية الشراء. وفي ما يلي أمثلة على الحالات التي يمكن أن يحدث فيها ذلك:

  • إصدار تطبيق "متجر Play" المثبَّت على جهاز المستخدم قديم.
  • المستخدم في بلد غير متوافق.
  • إذا كان المستخدم من مستخدمي المؤسسة، وأوقف مشرف المؤسسة المستخدمين من إجراء عمليات شراء.
  • يتعذّر على Google Play تحصيل الرسوم من طريقة الدفع التي يستخدمها المستخدم. على سبيل المثال، ربما انتهت صلاحية بطاقة ائتمان المستخدم.

الحلّ المحتمَل

من غير المحتمل أن تساعد عمليات إعادة المحاولة التلقائية في هذه الحالة. ومع ذلك، يمكن أن تؤدي إعادة المحاولة اليدوية إذا عالج المستخدم الحالة التي تسببت في المشكلة. على سبيل المثال، إذا يحدِّث المستخدم إصدار "متجر Play" إلى إصدار متوافق، ثم يظهر الدليل يمكن أن تنجح إعادة محاولة العملية الأولية.

في حال حدوث هذا الخطأ عندما لا يكون المستخدم في الجلسة، قد لا تؤدي إعادة المحاولة إلى المعنى. عند تلقّي BILLING_UNAVAILABLE خطأ بسبب مسار الشراء، فمن المحتمل جدًا أن يكون المستخدم قد الملاحظات من Google Play أثناء عملية الشراء، وقد يكونون على دراية بما حدث خطأ. في هذه الحالة، يمكنك عرض رسالة خطأ تحدد شيئًا ما حدث خطأ وعرضت زر "إعادة المحاولة" لمنح المستخدم خيار إعادة المحاولة يدويًا بعد معالجة المشكلة.

ERROR (رمز الخطأ 6)

المشكلة

هذا خطأ فادح يشير إلى حدوث مشكلة داخلية في Google Play. نفسها.

الحلّ المحتمَل

قد تحدث أحيانًا مشاكل داخلية في Google Play تؤدي إلى ERROR تكون عابرة، ويمكن تنفيذ إعادة المحاولة باستخدام خوارزمية الرقود الأسي والتخفيف من حدتها. عندما يكون المستخدمون في جلسة، يُفضل إعادة محاولة بسيطة.

ITEM_ALREADY_OWNED

المشكلة

تشير هذه الاستجابة إلى أن مستخدم Google Play يملك بالفعل اشتراك أو منتج يتم شراؤه لمرة واحدة يحاولون شراءه وفي معظم الحالات، لا يحدث هذا الخطأ مؤقتًا إلا إذا كان ناتجًا عن ذاكرة التخزين المؤقت القديمة في Google Play

الحلّ المحتمل

لتجنب حدوث هذا الخطأ عندما لا يكون السبب هو مشكلة في ذاكرة التخزين المؤقت، لا تعرض منتج للشراء عندما يمتلكه المستخدم بالفعل. تأكد من التحقق من استحقاقات المستخدم عند عرض المنتجات المتاحة للشراء وتصفية ما يمكن للمستخدم شراؤه وفقًا لذلك. عندما يتلقّى تطبيق العميل هذا الخطأ بسبب مشكلة في ذاكرة التخزين المؤقت، يظهر الخطأ ذاكرة التخزين المؤقت في Google Play للحصول على أحدث البيانات من خلفية Play من المفترض أن تؤدي إعادة المحاولة بعد الخطأ إلى حل هذا المثيل المؤقت المحدّد في هذه الحالة. الاتصال بالرقم BillingClient.queryPurchasesAsync() بعد تلقّي ITEM_ALREADY_OWNED للتحقق مما إذا كان المستخدم قد حصل على المنتج، وإذا لم يكن الأمر كذلك تنفيذ منطق إعادة محاولة بسيط لإعادة محاولة الشراء.

ITEM_NOT_OWNED

المشكلة

تشير استجابة الشراء هذه إلى أن مستخدم Google Play لا يمتلك اشتراك أو منتج يتم شراؤه لمرة واحدة ويحاول المستخدم استبداله الإقرار أو الاستهلاك. وهذا ليس خطأ عابرًا في معظم الحالات إلا إذا تسبّب فيه Google. ستتغيّر حالة ذاكرة التخزين المؤقت في Play.

الحلّ المحتمل

عندما يظهر الخطأ بسبب مشكلة في ذاكرة التخزين المؤقت، يؤدي الخطأ إلى تشغيل Google ذاكرة التخزين المؤقت في Play للاطّلاع على أحدث البيانات من خلفية Play. جارٍ إعادة المحاولة باستخدام استراتيجية بسيطة لإعادة المحاولة بعد أن يؤدي الخطأ إلى حل هذه المشكلة مثيل عابر. يمكنك الاتصال بالرقم BillingClient.queryPurchasesAsync() بعد الحصول على ITEM_NOT_OWNED للتحقق مما إذا كان المستخدم لديه اكتسبت المنتج. وإذا لم يكن كذلك، فاستخدم منطق إعادة المحاولة البسيط لإعادة محاولة عملية الشراء.

استجابات نتائج الفوترة غير القابلة لإعادة المحاولة

لا يمكنك التعافي من هذه الأخطاء باستخدام منطق إعادة المحاولة.

FEATURE_NOT_SUPPORTED

المشكلة

يشير هذا الخطأ الذي لا يمكن تكراره إلى أنّ ميزة "الفوترة في Google Play" على جهاز المستخدم، وذلك على الأرجح بسبب إصدار قديم من "متجر Play".

على سبيل المثال، ربما يكون لدى بعض مستخدمي لا تتيح الأجهزة المراسلة داخل التطبيق.

التخفيف المحتمل

يُرجى استخدام BillingClient.isFeatureSupported() للتحقّق من دعم الميزات قبل إجراء الاتصال بخدمة "الفوترة في Play". المكتبة.

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

USER_CANCELED

المشكلة

نقر المستخدم على واجهة مستخدم مسار الفوترة.

الحلّ المحتمل

والهدف من ذلك هو توفير المعلومات فقط وقد لا يتم إكمال هذا الإجراء بنجاح.

ITEM_UN available

المشكلة

ألّا يكون منتج الاشتراك في خدمة "الفوترة في Google Play" أو المنتج الذي يتم شراؤه لمرة واحدة متاح للشراء لهذا المستخدم.

التخفيف المحتمل

تأكَّد من أنّ تطبيقك يُعيد تحميل تفاصيل المنتج من خلال queryProductDetailsAsync على النحو المقترَح. ضع في اعتبارك عدد المرات تغييرات كتالوج المنتجات على إعدادات Play Console لتنفيذ أو تحديثات إضافية إذا لزم الأمر. محاولة بيع المنتجات التي تُرجع القيمة الصحيحة فقط على خدمة "الفوترة في Google Play" المعلومات عبر queryProductDetailsAsync. تحقَّق من إعدادات أهلية المنتج بحثًا عن أي حالات عدم الاتساق. على سبيل المثال، قد تقوم بالاستعلام عن منتج متاح فقط لمجموعة منطقة غير المنطقة التي يحاول المستخدم شراءها لتصبح المنتجات متاحة للشراء، يجب أن تكون نشطة وأن يكون تطبيقها المنشور، ويجب أن يكون تطبيقه متاحًا في بلد المستخدم.

في بعض الأحيان، خاصة أثناء الاختبار، تكون كل الأشياء صحيحة في المنتج من التهيئة، ولكن لا يزال يظهر هذا الخطأ للمستخدمين. قد يرجع ذلك إلى تأخير في نشر تفاصيل المنتج على خوادم Google إعادة المحاولة لاحقًا.

خطأ مطور البرامج

المشكلة

وهذا خطأ فادح يشير إلى استخدامك لواجهة برمجة تطبيقات بشكل غير صحيح. على سبيل المثال، يمكن أن يؤدي تقديم معلَمات غير صحيحة إلى BillingClient.launchBillingFlow إلى تسبب هذا الخطأ.

الحلّ المحتمَل

تأكَّد من أنّك تستخدم بشكل صحيح مكتبة الفوترة المختلفة في Play. الاتصالات. راجِع رسالة تصحيح الأخطاء أيضًا للحصول على مزيد من المعلومات عن الخطأ.