BillingResult প্রতিক্রিয়া কোডগুলি পরিচালনা করুন

যখন প্লে বিলিং লাইব্রেরির কোনো কল কোনো অ্যাকশন ট্রিগার করে, তখন লাইব্রেরিটি ডেভেলপারদের ফলাফল জানানোর জন্য একটি BillingResult রেসপন্স রিটার্ন করে। উদাহরণস্বরূপ, যদি আপনি ব্যবহারকারীর জন্য উপলব্ধ অফারগুলো পেতে queryProductDetailsAsync ব্যবহার করেন, তাহলে রেসপন্স কোডটিতে হয় একটি OK কোড থাকে এবং সঠিক ProductDetails অবজেক্টটি প্রদান করে, অথবা এটি একটি ভিন্ন রেসপন্স প্রদান করে যা নির্দেশ করে কেন ProductDetails অবজেক্টটি প্রদান করা যায়নি।

সব রেসপন্স কোডই ত্রুটি নয়। BillingResponseCode রেফারেন্স পেজটিতে এই গাইডে আলোচিত প্রতিটি রেসপন্সের বিস্তারিত বিবরণ দেওয়া আছে। যেসব রেসপন্স কোড ত্রুটি নির্দেশ করে না, তার কয়েকটি উদাহরণ হলো:

  • BillingClient.BillingResponseCode.OK : কলটির দ্বারা সৃষ্ট কাজটি সফলভাবে সম্পন্ন হয়েছে।
  • BillingClient.BillingResponseCode.USER_CANCELED : যেসব অ্যাকশনের মাধ্যমে ব্যবহারকারীকে প্লে স্টোর UI ফ্লো দেখানো হয়, সেগুলোর ক্ষেত্রে এই রেসপন্সটি নির্দেশ করে যে ব্যবহারকারী প্রক্রিয়াটি সম্পন্ন না করেই সেই UI ফ্লো থেকে বেরিয়ে গেছেন।

যখন রেসপন্স কোডটি কোনো ত্রুটি নির্দেশ করে, তখন এর কারণ কখনও কখনও ক্ষণস্থায়ী পরিস্থিতি হয়ে থাকে এবং তাই তা থেকে পুনরুদ্ধার সম্ভব। যখন প্লে বিলিং লাইব্রেরির কোনো মেথড কল করার ফলে প্রাপ্ত 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) }
      } finally {
        tries++
      }
    } while (tries <= maxTries && !isConnectionEstablished)
  }
  ...
}

এক্সপোনেনশিয়াল ব্যাকঅফ রিট্রাই

আমরা প্লে বিলিং লাইব্রেরির সেইসব অপারেশনের জন্য এক্সপোনেনশিয়াল ব্যাকঅফ ব্যবহার করার পরামর্শ দিই, যেগুলো ব্যাকগ্রাউন্ডে ঘটে এবং ব্যবহারকারী সেশনে থাকাকালীন তার অভিজ্ঞতাকে প্রভাবিত করে না।

উদাহরণস্বরূপ, নতুন কেনাকাটা স্বীকার করার সময় এটি প্রয়োগ করা উপযুক্ত হবে, কারণ এই কাজটি ব্যাকগ্রাউন্ডে হতে পারে এবং কোনো ত্রুটি ঘটলে তাৎক্ষণিকভাবে স্বীকৃতির প্রয়োজন হয় না।

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
}

পুনরায় বিলিং ফলাফল প্রতিক্রিয়া

নেটওয়ার্ক ত্রুটি (ত্রুটি কোড ১২)

সমস্যা

এই ত্রুটিটি নির্দেশ করে যে ডিভাইস এবং প্লে সিস্টেমগুলোর মধ্যে নেটওয়ার্ক সংযোগে কোনো সমস্যা ছিল।

সম্ভাব্য সমাধান

ত্রুটিটি পুনরুদ্ধার করতে, কোন ক্রিয়াকলাপের কারণে ত্রুটিটি ঘটেছে তার উপর নির্ভর করে সাধারণ পুনঃপ্রচেষ্টা বা এক্সপোনেনশিয়াল ব্যাকঅফ ব্যবহার করুন।

সার্ভিস টাইমআউট (ত্রুটি কোড -৩)

সমস্যা

এই ত্রুটিটি নির্দেশ করে যে, গুগল প্লে সাড়া দেওয়ার আগেই অনুরোধটি সর্বোচ্চ সময়সীমা অতিক্রম করেছে। উদাহরণস্বরূপ, প্লে বিলিং লাইব্রেরি কলের মাধ্যমে অনুরোধ করা কাজটি সম্পাদনে বিলম্বের কারণে এটি হতে পারে।

সম্ভাব্য সমাধান

এটি সাধারণত একটি সাময়িক সমস্যা। কোন কাজটি করার ফলে ত্রুটিটি ঘটেছে, তার উপর নির্ভর করে একটি সাধারণ অথবা এক্সপোনেনশিয়াল ব্যাকঅফ কৌশল ব্যবহার করে অনুরোধটি পুনরায় চেষ্টা করুন।

নিচে SERVICE_DISCONNECTED মতো নয়, এক্ষেত্রে Google Play Billing পরিষেবার সংযোগ বিচ্ছিন্ন হয় না এবং আপনাকে কেবল Play Billing Library-এর যে অপারেশনটি চেষ্টা করা হয়েছিল, সেটিই পুনরায় করতে হবে।

পরিষেবা বিচ্ছিন্ন (ত্রুটি কোড -১)

সমস্যা

এই মারাত্মক ত্রুটিটি নির্দেশ করে যে BillingClient এর মাধ্যমে Google Play Store পরিষেবার সাথে ক্লায়েন্ট অ্যাপের সংযোগ বিচ্ছিন্ন হয়ে গেছে।

সম্ভাব্য সমাধান

প্লে বিলিং লাইব্রেরি সংস্করণ 8.0.0-এ enableAutoServiceReconnection() ফিচারটি চালু করা হয়েছে। আপনার BillingClient বিল্ড করার সময় এই ফিচারটি সক্রিয় করার জন্য দৃঢ়ভাবে সুপারিশ করা হচ্ছে । এর ফলে, সার্ভিসটি সংযোগ বিচ্ছিন্ন থাকা অবস্থায় কোনো বিলিং এপিআই কল করা হলে লাইব্রেরিটি স্বয়ংক্রিয়ভাবে সংযোগ পুনঃস্থাপন করার চেষ্টা করে, যা এই ত্রুটির ঘটনা উল্লেখযোগ্যভাবে হ্রাস করে।

কোটলিন

val billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Enable automatic service reconnection
    .build()

জাভা

BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Enable automatic service reconnection
    .build();
আপনি যদি স্বয়ংক্রিয় পরিষেবা পুনঃসংযোগ সক্ষম করে থাকেন

প্লে বিলিং লাইব্রেরি স্বয়ংক্রিয়ভাবে পুনরায় সংযোগ করার চেষ্টা করবে। এপিআই কল করার সময় আপনি যদি এখনও SERVICE_DISCONNECTED রেসপন্স কোড পান, তাহলে বুঝতে হবে যে লাইব্রেরিটি তার স্বয়ংক্রিয় চেষ্টার পরেও পুনরায় সংযোগ করতে পারেনি। এই পরিস্থিতিতে, আপনার অ্যাপে রিট্রাই লজিক প্রয়োগ করা উচিত:

  • ব্যবহারকারী-প্রবর্তিত কার্যকলাপের (সেশনের মধ্যে) ক্ষেত্রে: এপিআই কলটি পুনরায় চেষ্টা করুন। অন্তর্নিহিত সমস্যাটি সাময়িক হতে পারে।
  • ব্যাকগ্রাউন্ড অনুরোধের ক্ষেত্রে: সংযোগ বিচ্ছিন্ন থাকার সময় দীর্ঘায়িত হলে সিস্টেমের উপর অতিরিক্ত চাপ এড়াতে এক্সপোনেনশিয়াল ব্যাকঅফ সহ পুনরায় চেষ্টার ব্যবস্থা করুন।
যদি আপনি স্বয়ংক্রিয় পরিষেবা পুনঃসংযোগ সক্ষম না করে থাকেন

এই ত্রুটিটি যথাসম্ভব এড়াতে, প্লে বিলিং লাইব্রেরি ব্যবহার করে কল করার আগে BillingClient.isReady() কল করে গুগল প্লে পরিষেবার সাথে সংযোগ সর্বদা পরীক্ষা করুন।

SERVICE_DISCONNECTED থেকে পুনরুদ্ধারের চেষ্টা করতে, আপনার ক্লায়েন্ট অ্যাপের BillingClient.startConnection ব্যবহার করে সংযোগটি পুনরায় স্থাপন করার চেষ্টা করা উচিত।

SERVICE_TIMEOUT মতোই, কোন কাজের ফলে ত্রুটিটি ঘটেছে তার উপর নির্ভর করে সাধারণ পুনঃপ্রচেষ্টা (simple retries) বা এক্সপোনেনশিয়াল ব্যাকঅফ (exponential backoff) ব্যবহার করুন।

পরিষেবা অনুপলব্ধ (ত্রুটি কোড ২)

গুরুত্বপূর্ণ দ্রষ্টব্য:

Google Play Billing Library 6.0.0 সংস্করণ থেকে, নেটওয়ার্ক সমস্যার জন্য SERVICE_UNAVAILABLE আর ফেরত দেওয়া হয় না। এটি তখন ফেরত দেওয়া হয় যখন বিলিং পরিষেবা অনুপলব্ধ থাকে এবং বাতিলকৃত SERVICE_TIMEOUT পরিস্থিতিগুলোতে।

সমস্যা

এই সাময়িক ত্রুটিটি নির্দেশ করে যে গুগল প্লে বিলিং পরিষেবাটি বর্তমানে অনুপলব্ধ। বেশিরভাগ ক্ষেত্রে, এর মানে হলো ক্লায়েন্ট ডিভাইস এবং গুগল প্লে বিলিং পরিষেবার মধ্যে যেকোনো জায়গায় নেটওয়ার্ক সংযোগে সমস্যা রয়েছে।

সম্ভাব্য সমাধান

এটি সাধারণত একটি সাময়িক সমস্যা। কোন কাজটি করার ফলে ত্রুটিটি ঘটেছে, তার উপর নির্ভর করে একটি সাধারণ অথবা এক্সপোনেনশিয়াল ব্যাকঅফ কৌশল ব্যবহার করে অনুরোধটি পুনরায় চেষ্টা করুন।

SERVICE_DISCONNECTED বিপরীতে, Google Play বিলিং পরিষেবার সংযোগ বিচ্ছিন্ন হয় না এবং যে কাজটি করার চেষ্টা করা হচ্ছে, তা আপনাকে পুনরায় করতে হবে।

বিলিং অনুপলব্ধ (ত্রুটি কোড ৩)

সমস্যা

এই ত্রুটিটি নির্দেশ করে যে ক্রয় প্রক্রিয়ার সময় ব্যবহারকারীর বিলিং-এ কোনো ভুল হয়েছে। কখন এটি ঘটতে পারে তার কিছু উদাহরণ হলো:

  • ব্যবহারকারীর ডিভাইসে থাকা প্লে স্টোর অ্যাপটি পুরোনো হয়ে গেছে।
  • ব্যবহারকারী একটি অসমর্থিত দেশে আছেন।
  • ব্যবহারকারী একজন এন্টারপ্রাইজ ব্যবহারকারী, এবং তার এন্টারপ্রাইজ অ্যাডমিন অন্যান্য ব্যবহারকারীদের কেনাকাটা করা থেকে বিরত রেখেছেন।
  • গুগল প্লে ব্যবহারকারীর পেমেন্ট পদ্ধতি থেকে টাকা কাটতে পারছে না। উদাহরণস্বরূপ, ব্যবহারকারীর ক্রেডিট কার্ডের মেয়াদ শেষ হয়ে যেতে পারে।

সম্ভাব্য সমাধান

এক্ষেত্রে স্বয়ংক্রিয়ভাবে পুনরায় চেষ্টা করলে সম্ভবত কোনো লাভ হবে না। তবে, ব্যবহারকারী যদি সমস্যা সৃষ্টিকারী পরিস্থিতিটির সমাধান করেন, তাহলে ম্যানুয়ালি পুনরায় চেষ্টা করলে তা সহায়ক হতে পারে। উদাহরণস্বরূপ, যদি ব্যবহারকারী তার প্লে স্টোর সংস্করণটি একটি সমর্থিত সংস্করণে আপডেট করেন, তাহলে প্রাথমিক প্রক্রিয়াটি ম্যানুয়ালি পুনরায় চেষ্টা করলে কাজ হতে পারে।

ব্যবহারকারী যখন সেশনে না থাকেন, তখন এই ত্রুটি দেখা দিলে পুনরায় চেষ্টা করার কোনো মানে হয় না। যখন আপনি ক্রয় প্রক্রিয়ার ফলে একটি BILLING_UNAVAILABLE ত্রুটি পান, তখন খুব সম্ভবত ব্যবহারকারী ক্রয় প্রক্রিয়া চলাকালীন Google Play থেকে প্রতিক্রিয়া পেয়েছেন এবং কী ভুল হয়েছে সে সম্পর্কে অবগত থাকতে পারেন। এই ক্ষেত্রে, আপনি একটি ত্রুটি বার্তা দেখাতে পারেন যেখানে উল্লেখ থাকবে যে কিছু ভুল হয়েছে এবং একটি “আবার চেষ্টা করুন” বোতাম রাখতে পারেন, যাতে ব্যবহারকারী সমস্যাটি সমাধান করার পর নিজে থেকে পুনরায় চেষ্টা করার সুযোগ পান।

ত্রুটি (ত্রুটি কোড ৬)

সমস্যা

এটি একটি মারাত্মক ত্রুটি যা গুগল প্লে-এর নিজস্ব অভ্যন্তরীণ সমস্যার ইঙ্গিত দেয়।

সম্ভাব্য সমাধান

কখনও কখনও গুগল প্লে-র অভ্যন্তরীণ সমস্যার কারণে ERROR দেখা দেয়, যা ক্ষণস্থায়ী হয় এবং এর প্রতিকারের জন্য এক্সপোনেনশিয়াল ব্যাকঅফ সহ পুনরায় চেষ্টা করা যেতে পারে। ব্যবহারকারীরা যখন সেশনে থাকেন, তখন শুধু পুনরায় চেষ্টা করাই শ্রেয়।

আইটেমটি ইতিমধ্যেই মালিকানাধীন

সমস্যা

এই প্রতিক্রিয়াটি নির্দেশ করে যে গুগল প্লে ব্যবহারকারী যে সাবস্ক্রিপশন বা এককালীন ক্রয়ের পণ্যটি কেনার চেষ্টা করছেন, সেটি তার কাছে ইতিমধ্যেই রয়েছে। বেশিরভাগ ক্ষেত্রে, এটি একটি সাময়িক ত্রুটি নয়, তবে গুগল প্লে-এর পুরনো ক্যাশের কারণে এটি ঘটলে তা ব্যতিক্রম।

সম্ভাব্য সমাধান

ক্যাশে সংক্রান্ত সমস্যা না হলে এই ত্রুটিটি ঘটা এড়ানোর জন্য, ব্যবহারকারীর কাছে পণ্যটি আগে থেকেই থাকলে সেটি কেনার জন্য অফার করবেন না। কেনার জন্য উপলব্ধ পণ্যগুলি দেখানোর সময় ব্যবহারকারীর এনটাইটেলমেন্টগুলি পরীক্ষা করে দেখুন এবং সেই অনুযায়ী ব্যবহারকারী কী কিনতে পারবেন তা ফিল্টার করুন। যখন ক্লায়েন্ট অ্যাপ ক্যাশে সংক্রান্ত সমস্যার কারণে এই ত্রুটিটি পায়, তখন ত্রুটিটি Google Play-এর ব্যাকএন্ড থেকে সর্বশেষ ডেটা দিয়ে ক্যাশে আপডেট করার জন্য ট্রিগার করে। এই ক্ষেত্রে, ত্রুটির পরে পুনরায় চেষ্টা করলে এই নির্দিষ্ট ক্ষণস্থায়ী সমস্যাটির সমাধান হওয়া উচিত। ITEM_ALREADY_OWNED পাওয়ার পরে ব্যবহারকারী পণ্যটি অর্জন করেছেন কিনা তা পরীক্ষা করতে BillingClient.queryPurchasesAsync() কল করুন, এবং যদি তা না হয়ে থাকে তবে কেনার পুনরায় চেষ্টা করার জন্য একটি সহজ রিট্রাই লজিক প্রয়োগ করুন।

আইটেমটি নিজের নয়

সমস্যা

এই ক্রয় প্রতিক্রিয়াটি নির্দেশ করে যে, গুগল প্লে ব্যবহারকারী যে সাবস্ক্রিপশন বা এককালীন ক্রয় পণ্যটি প্রতিস্থাপন, স্বীকার বা ব্যবহার করার চেষ্টা করছেন, তিনি সেটির মালিক নন। বেশিরভাগ ক্ষেত্রে এটি একটি ক্ষণস্থায়ী ত্রুটি নয়, তবে গুগল প্লে-এর ক্যাশে পুরোনো বা অচল হয়ে যাওয়ার কারণে এটি ঘটলে তা ব্যতিক্রম।

সম্ভাব্য সমাধান

যখন ক্যাশ সমস্যার কারণে ত্রুটি আসে, তখন এই ত্রুটিটি গুগল প্লে-এর ব্যাকএন্ড থেকে সর্বশেষ ডেটা দিয়ে ক্যাশ আপডেট করার জন্য সক্রিয় হয়। ত্রুটির পরে একটি সাধারণ রিট্রাই স্ট্র্যাটেজি ব্যবহার করে পুনরায় চেষ্টা করলে এই নির্দিষ্ট ক্ষণস্থায়ী সমস্যাটির সমাধান হওয়া উচিত। ITEM_NOT_OWNED ত্রুটি পাওয়ার পর ব্যবহারকারী পণ্যটি কিনেছেন কিনা তা পরীক্ষা করতে BillingClient.queryPurchasesAsync() কল করুন। যদি তারা না কিনে থাকেন, তাহলে ক্রয়টি পুনরায় চেষ্টা করার জন্য সাধারণ রিট্রাই লজিক ব্যবহার করুন।

অ-পুনরুদ্ধারযোগ্য বিলিং ফলাফল প্রতিক্রিয়া

রিট্রাই লজিক ব্যবহার করে এই ত্রুটিগুলো থেকে পুনরুদ্ধার করা সম্ভব নয়।

বৈশিষ্ট্য সমর্থিত নয়

সমস্যা

এই পুনরায় সমাধান-অযোগ্য ত্রুটিটি নির্দেশ করে যে ব্যবহারকারীর ডিভাইসে গুগল প্লে বিলিং ফিচারটি সমর্থিত নয়, সম্ভবত প্লে স্টোরের পুরোনো সংস্করণের কারণে।

উদাহরণস্বরূপ, হতে পারে আপনার কিছু ব্যবহারকারীর ডিভাইসে ইন-অ্যাপ মেসেজিং সাপোর্ট করে না।

সম্ভাব্য প্রশমন

প্লে বিলিং লাইব্রেরিতে কল করার আগে ফিচার সাপোর্ট যাচাই করতে BillingClient.isFeatureSupported() ব্যবহার করুন।

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

ব্যবহারকারী বাতিল করেছেন

সমস্যা

ব্যবহারকারী বিলিং ফ্লো UI থেকে বেরিয়ে গেছেন।

সম্ভাব্য সমাধান

এটি শুধুমাত্র তথ্যমূলক এবং এটি সুন্দরভাবে ব্যর্থ হতে পারে।

আইটেমটি অনুপলব্ধ

সমস্যা

এই ব্যবহারকারীর জন্য গুগল প্লে বিলিং সাবস্ক্রিপশন বা এককালীন ক্রয়ের পণ্যটি কেনার জন্য উপলব্ধ নয়।

সম্ভাব্য প্রশমন

আপনার অ্যাপটি যেন সুপারিশ অনুযায়ী queryProductDetailsAsync এর মাধ্যমে পণ্যের বিবরণ রিফ্রেশ করে, তা নিশ্চিত করুন। প্রয়োজনে অতিরিক্ত রিফ্রেশ ব্যবস্থা চালু করার জন্য, প্লে কনসোল কনফিগারেশনে আপনার পণ্যের ক্যাটালগ কত ঘন ঘন পরিবর্তিত হয় তা বিবেচনা করুন। শুধুমাত্র সেইসব পণ্যই গুগল প্লে বিলিং-এ বিক্রি করার চেষ্টা করুন, যেগুলো queryProductDetailsAsync এর মাধ্যমে সঠিক তথ্য প্রদান করে। পণ্যের যোগ্যতা কনফিগারেশনে কোনো অসঙ্গতি আছে কিনা তা পরীক্ষা করুন। উদাহরণস্বরূপ, আপনি এমন একটি পণ্যের জন্য কোয়েরি করতে পারেন যা ব্যবহারকারী যে অঞ্চলটি কেনার চেষ্টা করছেন, তার বাইরে অন্য কোনো অঞ্চলের জন্য উপলব্ধ। কেনার জন্য উপলব্ধ হতে হলে, একটি পণ্যকে সক্রিয় থাকতে হবে, তার অ্যাপটি প্রকাশিত হতে হবে এবং অ্যাপটি ব্যবহারকারীর দেশে উপলব্ধ থাকতে হবে।

কখনও কখনও, বিশেষ করে পরীক্ষার সময়, পণ্যের কনফিগারেশনে সবকিছু সঠিক থাকা সত্ত্বেও ব্যবহারকারীরা এই ত্রুটিটি দেখতে পান। গুগলের সার্ভার জুড়ে পণ্যের বিবরণ প্রচারে বিলম্বের কারণে এটি হতে পারে। পরে আবার চেষ্টা করুন।

ডেভেলপার_ত্রুটি

সমস্যা

এটি একটি মারাত্মক ত্রুটি যা নির্দেশ করে যে আপনি কোনো এপিআই (API) ভুলভাবে ব্যবহার করছেন। উদাহরণস্বরূপ, BillingClient.launchBillingFlow এ ভুল প্যারামিটার সরবরাহ করলে এই ত্রুটিটি হতে পারে।

সম্ভাব্য সমাধান

নিশ্চিত করুন যে আপনি বিভিন্ন প্লে বিলিং লাইব্রেরি কলগুলো সঠিকভাবে ব্যবহার করছেন। এছাড়াও, ত্রুটি সম্পর্কে আরও তথ্যের জন্য ডিবাগ বার্তাটি দেখুন।