Pengingat: Mulai 2 Agustus 2022, semua aplikasi baru harus menggunakan Library Penagihan versi 4 atau yang lebih baru. Pada 1 November 2022, semua update untuk aplikasi yang ada harus menggunakan Library Penagihan versi 4 atau yang lebih baru. Pelajari lebih lanjut.

Menangani kode respons BillingResult

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

Saat panggilan Library Layanan Penagihan Play memicu tindakan, library akan menampilkan respons BillingResult untuk memberi tahu developer tentang hasilnya. Misalnya, jika Anda menggunakan queryProductDetailsAsync untuk mendapatkan penawaran yang tersedia bagi pengguna, kode respons akan berisi kode OK dan memberikan objek ProductDetails yang tepat, atau berisi respons berbeda yang menunjukkan alasan objek ProductDetails tidak dapat diberikan.

Tidak semua kode respons merupakan error. Halaman referensi BillingResponseCode memberikan deskripsi mendetail tentang setiap respons yang dibahas dalam panduan ini. Beberapa contoh kode respons yang tidak menunjukkan error adalah:

Ketika kode respons menunjukkan error, terkadang hal tersebut disebabkan oleh kondisi sementara sehingga dapat dipulihkan. Saat panggilan ke metode Library Layanan Penagihan Play menampilkan nilai BillingResponseCode yang menunjukkan kondisi yang dapat dipulihkan, Anda harus mencoba lagi panggilan tersebut. Dalam kasus lain, kondisi tidak dianggap sementara sehingga disarankan untuk tidak mencoba lagi.

Error sementara memerlukan strategi percobaan ulang yang berbeda bergantung pada faktor-faktor seperti apakah error terjadi saat pengguna sedang berada dalam sesi—misalnya, saat pengguna berada dalam alur pembelian—atau error terjadi di latar belakang—misalnya, saat Anda membuat kueri pembelian yang sudah ada milik pengguna selama onResume. Bagian strategi percobaan ulang di bawah memberikan contoh strategi yang berbeda ini dan bagian Respons BillingResult yang Dapat Dicoba Ulang merekomendasikan strategi yang paling sesuai untuk setiap kode respons.

Selain kode respons, beberapa respons error menyertakan pesan untuk tujuan proses debug dan logging.

Strategi percobaan ulang

Percobaan ulang sederhana

Dalam situasi ketika pengguna sedang berada dalam sesi, sebaiknya implementasikan strategi percobaan ulang yang sederhana sehingga error tersebut tidak akan mengganggu pengalaman pengguna. Dalam hal ini, kami merekomendasikan strategi percobaan ulang sederhana dengan jumlah maksimum upaya sebagai kondisi keluar.

Contoh berikut menunjukkan strategi percobaan ulang sederhana untuk menangani error saat membuat koneksi 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)
  }
  ...
}

Percobaan ulang backoff eksponensial

Sebaiknya gunakan backoff eksponensial untuk operasi Library Layanan Penagihan Play yang terjadi di latar belakang dan tidak memengaruhi pengalaman pengguna saat pengguna berada dalam sesi.

Misalnya, akan lebih tepat untuk menerapkan ini saat mengonfirmasi pembelian baru karena operasi ini dapat terjadi di latar belakang, dan konfirmasi tidak perlu terjadi secara real time jika terjadi error.

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
}

Respons BillingResult yang dapat dicoba ulang

SERVICE_TIMEOUT (Kode Error -3)

Masalah

Error ini menunjukkan bahwa permintaan telah mencapai waktu tunggu maksimum sebelum Google Play dapat merespons. Hal ini dapat disebabkan, misalnya, karena keterlambatan eksekusi tindakan yang diminta oleh panggilan Library Layanan Penagihan Play.

Kemungkinan resolusi

Masalah ini biasanya bersifat sementara. Coba lagi permintaan tersebut menggunakan strategi backoff eksponensial atau percobaan ulang sederhana, bergantung pada tindakan yang menampilkan error.

Tidak seperti SERVICE_DISCONNECTED di bawah, koneksi ke Layanan Penagihan Google Play tidak terputus, dan Anda hanya perlu mencoba kembali operasi Library Layanan Penagihan Play.

SERVICE_DISCONNECTED (Kode Error -1)

Masalah

Error fatal ini menunjukkan bahwa koneksi aplikasi klien ke layanan Google Play Store melalui BillingClient telah terputus.

Kemungkinan resolusi

Untuk menghindari error ini sebanyak mungkin, selalu periksa koneksi ke layanan Google Play sebelum melakukan panggilan dengan Library Layanan Penagihan Play dengan memanggil BillingClient.isReady().

Untuk mencoba pemulihan dari SERVICE_DISCONNECTED, aplikasi klien Anda harus mencoba membuat kembali koneksi menggunakan BillingClient.startConnection.

Sama seperti SERVICE_TIMEOUT, gunakan percobaan ulang sederhana atau backoff eksponensial, bergantung pada tindakan yang memicu error.

SERVICE_UNAVAILABLE (Kode Error 2)

Masalah

Error sementara ini menunjukkan bahwa Layanan Penagihan Google Play saat ini tidak tersedia. Dalam kebanyakan kasus, hal ini berarti ada masalah koneksi jaringan antara perangkat klien dan layanan Layanan Penagihan Google Play.

Kemungkinan resolusi

Masalah ini biasanya bersifat sementara. Coba lagi permintaan tersebut menggunakan strategi backoff eksponensial atau percobaan ulang sederhana, bergantung pada tindakan yang menampilkan error.

Tidak seperti SERVICE_DISCONNECTED, koneksi ke layanan Layanan Penagihan Google Play tidak terputus, dan Anda perlu mencoba kembali operasi apa pun yang sedang dicoba.

BILLING_UNAVAILABLE (Kode Error 3)

Masalah

Error ini menunjukkan bahwa error penagihan pengguna terjadi selama proses pembelian. Contoh kapan hal ini dapat terjadi adalah:

  • Aplikasi Play Store di perangkat pengguna belum diupdate.
  • Pengguna berada di negara yang tidak didukung.
  • Pengguna tersebut adalah pengguna versi bisnis, dan admin perusahaan mereka telah menonaktifkan pengguna agar tidak melakukan pembelian.
  • Google Play tidak dapat menagih metode pembayaran pengguna. Misalnya, masa berlaku kartu kredit pengguna mungkin telah berakhir.

Kemungkinan resolusi

Percobaan ulang otomatis kemungkinan tidak akan membantu dalam kasus ini. Namun, percobaan ulang manual dapat membantu jika pengguna mengatasi kondisi yang menyebabkan masalah tersebut. Misalnya, jika pengguna mengupdate versi Play Store ke versi yang didukung, percobaan ulang manual operasi awal dapat berfungsi.

Jika error ini terjadi saat pengguna tidak berada dalam sesi, percobaan ulang mungkin bukan hal yang umum dilakukan. Saat Anda menerima error BILLING_UNAVAILABLE sebagai hasil dari alur pembelian, kemungkinan besar pengguna menerima masukan dari Google Play selama proses pembelian dan mungkin mengetahui error yang terjadi. Dalam hal ini, Anda dapat menampilkan pesan error yang menjelaskan error yang terjadi dan menawarkan tombol “Coba lagi” untuk memberi pengguna opsi percobaan ulang manual setelah mereka mengatasi masalah.

ERROR (Kode Error 6)

Masalah

Ini adalah error fatal yang menunjukkan masalah internal pada Google Play itu sendiri.

Kemungkinan resolusi

Terkadang masalah Google Play internal yang menyebabkan ERROR bersifat sementara, dan percobaan ulang dengan backoff eksponensial dapat diterapkan untuk mitigasi. Saat pengguna berada dalam sesi, sebaiknya lakukan percobaan ulang sederhana.

ITEM_ALREADY_OWNED

Masalah

Respons ini menunjukkan bahwa pengguna Google Play sudah memiliki langganan atau produk pembelian satu kali yang mereka coba beli. Umumnya, ini bukan error sementara, kecuali jika disebabkan oleh cache Google Play yang sudah tidak berlaku.

Kemungkinan resolusi

Jika penyebabnya bukan masalah cache, hindari error ini dengan tidak menawarkan produk untuk dibeli saat pengguna sudah memilikinya. Pastikan Anda memeriksa hak pengguna saat menampilkan produk yang tersedia untuk dibeli, dan memfilter apa yang dapat dibeli pengguna sebagaimana mestinya. Saat aplikasi klien menerima error ini karena masalah cache, error tersebut memicu cache Google Play untuk diperbarui dengan data terbaru dari backend Play. Mencoba ulang setelah error akan menyelesaikan masalah sementara ini dalam kasus ini. Panggil BillingClient.queryPurchasesAsync() setelah mendapatkan ITEM_ALREADY_OWNED untuk memeriksa apakah pengguna telah memperoleh produk, dan jika bukan, implementasikan logika percobaan ulang sederhana untuk mencoba kembali pembelian.

ITEM_NOT_OWNED

Masalah

Respons pembelian ini menunjukkan bahwa pengguna Google Play tidak memiliki langganan atau produk pembelian satu kali yang coba diganti, dikonfirmasi, atau digunakan oleh pengguna. Ini bukanlah error sementara dalam sebagian besar kasus, kecuali jika disebabkan oleh cache Google Play yang mengalami status tidak berlaku.

Kemungkinan resolusi

Saat error diterima karena masalah cache, error tersebut memicu cache Google Play untuk diperbarui dengan data terbaru dari backend Play. Mencoba ulang dengan strategi percobaan ulang sederhana setelah error akan menyelesaikan masalah sementara ini. Panggil BillingClient.queryPurchasesAsync() setelah mendapatkan ITEM_NOT_OWNED untuk memeriksa apakah pengguna telah mendapatkan produk. Jika belum, gunakan logika percobaan ulang sederhana untuk mencoba kembali pembelian.

Respons BillingResult yang Tidak Dapat Dicoba Ulang

Anda tidak dapat memulihkan dari error ini menggunakan logika percobaan ulang.

FEATURE_NOT_SUPPORTED

Masalah

Error yang tidak dapat dicoba ulang ini menunjukkan bahwa fitur Layanan Penagihan Google Play tidak didukung di perangkat pengguna, kemungkinan karena versi Play Store yang lama.

Misalnya, mungkin beberapa perangkat pengguna Anda tidak mendukung in-app messaging.

Kemungkinan mitigasi

Gunakan BillingClient.isFeatureSupported() untuk memeriksa dukungan fitur sebelum melakukan panggilan ke Library Layanan Penagihan Play.

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

USER_CANCELED

Masalah

Pengguna telah mengklik untuk keluar dari UI alur penagihan.

Kemungkinan resolusi

Hal ini hanya bersifat informatif dan bisa gagal.

ITEM_UNAVAILABLE

Masalah

Langganan Layanan Penagihan Google Play atau produk pembelian satu kali tidak tersedia untuk dibeli pengguna ini.

Kemungkinan mitigasi

Pastikan aplikasi Anda memperbarui detail produk melalui queryProductDetailsAsync seperti yang direkomendasikan. Pertimbangkan seberapa sering katalog produk Anda berubah pada konfigurasi Konsol Play untuk mengimplementasikan pembaruan tambahan jika diperlukan. Hanya jual produk di Layanan Penagihan Google Play yang menampilkan informasi yang tepat melalui queryProductDetailsAsync. Periksa pengaturan kelayakan produk untuk menemukan inkonsistensi. Misalnya, Anda mungkin membuat kueri untuk produk yang hanya tersedia untuk wilayah selain produk yang ingin dibeli pengguna. Agar tersedia untuk dibeli, produk harus aktif, aplikasinya harus dipublikasikan, dan aplikasinya harus tersedia di negara pengguna.

Terkadang, khususnya selama pengujian, semua yang ada di konfigurasi produk sudah benar, tetapi pengguna masih melihat error ini. Ini mungkin karena penundaan propagasi detail produk di seluruh server Google. Coba lagi nanti.

DEVELOPER_ERROR

Masalah

Ini adalah error fatal yang menunjukkan bahwa Anda tidak menggunakan API dengan benar. Misalnya, memberikan parameter yang salah ke BillingClient.launchBillingFlow dapat menyebabkan error ini.

Kemungkinan resolusi

Pastikan Anda menggunakan panggilan Library Layanan Penagihan Play yang berbeda dengan benar.