Search code examples
androidkotlinin-app-billingbillingplay-billing-library

android billing querySkuDetailsAsync always returns 0 skuDetails


I'm trying to enable in-app purchases, and can't figure out why my skuDetails query always returns 0 items.

I have publicized the app to my internal testing and added my mail address to the license test(tried both my developer mail and a 2. mail-address), I then downloaded the app on my phone, waited a couple of hours, and tried to make a purchase. It accepts my connection but always returns 0 elements in my skuDetailsList when I query SKU details. help, please?

manifest:

<uses-permission android:name="com.android.vending.BILLING" />

build.gradle:

 def billing_version = "3.0.0"
     implementation "com.android.billingclient:billing-ktx:$billing_version"

querySkuDetails method:

suspend fun querySkuDetails() {
    val skuList = ArrayList<String>()
    skuList.add(PREMIUM_UPGRADE_ID)
    val params = SkuDetailsParams.newBuilder()
    params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
    withContext(Dispatchers.IO) {
        billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
            // Process the result.
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                Log.v(TAG, "queryInventoryAsync - skuDetailsList:")
                if (!skuDetailsList.isNullOrEmpty()) {
                    for (item in skuDetailsList) {
                        Log.v(TAG, item.toString())
                        skuDetails = item
                    }
                } else {
                    Log.v(TAG, "skuDetailsList - is empty")
                }
                return@querySkuDetailsAsync
            }

Google play console: enter image description here

Full class:

import android.util.Log
import android.widget.Toast
import com.alexco.habitrack.activities.main.MainActivity
import com.alexco.habitrack.utilities.Persistence
import com.android.billingclient.api.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext


class BillingCli(private val activity: MainActivity, private val database: Persistence) {

    companion object {
        private const val TAG = "InAppBilling"
        private const val PREMIUM_UPGRADE_ID = "noaddsplease"
    }


    private var purchases: Purchase.PurchasesResult? = null
    private var skuDetails: SkuDetails? = null
    private val purchasesUpdateListener =
        PurchasesUpdatedListener { billingResult, purchases ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
                for (purchase in purchases) {
                    handlePurchase(purchase)
                }
            } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
                // Handle an error caused by a user cancelling the purchase flow.
            } else {
                // Handle any other error codes.
            }
        }

    private var billingClient: BillingClient = BillingClient.newBuilder(activity)
        .setListener(purchasesUpdateListener)
        .enablePendingPurchases()
        .build()

    init {
        connect()
    }

    private fun handlePurchase(purchase: Purchase) {

        if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
            Log.v(TAG, "purchase approved")
            database.setPremium(activity, true)
            activity.newHabitHandler.openAddHabitActivity(activity.tutorial.enabled)
            Toast.makeText(activity, "welcome to premium", Toast.LENGTH_LONG).show()

            if (!purchase.isAcknowledged) {
                val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
                suspend {
                    val ackPurchaseResult = withContext(Dispatchers.IO) {
                        billingClient.acknowledgePurchase(
                            acknowledgePurchaseParams.build()
                        ) {
                            Log.v(TAG, "purchase acknowledged")
                        }
                    }
                }
            }
        }
        if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {//pending
            Log.v(TAG, "purchased pending")
            return
        }
        if (purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {//pending
            Log.v(TAG, "purchase unspecified state")
            return
        }
    }

    private fun connect() {
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    Log.v(TAG, "service connected")
                    // The BillingClient is ready. You can query purchases here.
                    suspend {
                        querySkuDetails()
                    }
                    suspend {
                        purchases = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                        Log.v(TAG, "onBillingSetUpFinished, purchases: ${purchases.toString()}")
                    }
                } else {
                    Log.v(TAG, "service did not connect")
                }
            }

            override fun onBillingServiceDisconnected() {
                Log.v(TAG, "service disconnected")
            }
        })
    }

    suspend fun querySkuDetails() {
        val skuList = ArrayList<String>()
        skuList.add(PREMIUM_UPGRADE_ID)
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        withContext(Dispatchers.IO) {
            billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
                // Process the result.
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    Log.v(TAG, "queryInventoryAsync - skuDetailsList:")
                    if (!skuDetailsList.isNullOrEmpty()) {
                        for (item in skuDetailsList) {
                            Log.v(TAG, item.toString())
                            skuDetails = item
                        }
                    } else {
                        Log.v(TAG, "skuDetailsList - is empty")
                    }
                    return@querySkuDetailsAsync
                }

                if (billingResult.responseCode == 1) {
                    //user cancel
                    return@querySkuDetailsAsync
                }
                if (billingResult.responseCode == 2) {
                    Toast.makeText(activity, "Internet required for purchase", Toast.LENGTH_LONG)
                        .show()
                    return@querySkuDetailsAsync
                }
                if (billingResult.responseCode == 3) {
                    Toast.makeText(
                        activity,
                        "Incompatible Google Play Billing Version",
                        Toast.LENGTH_LONG
                    ).show()
                    return@querySkuDetailsAsync
                }
                if (billingResult.responseCode == 7) {
                    Toast.makeText(activity, "you already own Premium", Toast.LENGTH_LONG)
                        .show()
                    return@querySkuDetailsAsync
                }
                Toast.makeText(
                    activity,
                    "no skuDetails sorry",
                    Toast.LENGTH_LONG
                )
                    .show()
                Log.v(TAG, "responseCode: ${billingResult.responseCode}")
                Log.v(TAG, "debug: ${billingResult.debugMessage}")
            }
        }
    }

    fun makePurchase() {

        if (purchases != null) {
            if (purchases!!.purchasesList != null) {
                if (purchases!!.purchasesList!!.size > 0) {
                    Toast.makeText(
                        activity,
                        "you already owns premium\n-premium activated-",
                        Toast.LENGTH_LONG
                    ).show()
                    database.setPremium(activity, true)
                    activity.newHabitHandler.openAddHabitActivity(activity.tutorial.enabled)
                }
            }
        }

        // Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
        if (skuDetails != null) {
            val flowParams = BillingFlowParams.newBuilder()
                .setSkuDetails(skuDetails!!)
                .build()
            val responseCode = billingClient.launchBillingFlow(activity, flowParams).responseCode

            Log.v(TAG, "makePurchase - responseCode:$responseCode")
        } else {
            Log.v(TAG, "makePurchase - no skuDetails")
            Toast.makeText(activity, "Google didn't accept the purchase attempt", Toast.LENGTH_LONG)
                .show()
        }
    }

Solution

  • I deleted the item in the google play console and got a new item with a new id, and it worked ¯_(ツ)_/¯