Search code examples
androidin-app-purchasegoogle-play-console

In app purchases (IAP) doesn't show in app after adding it in Google Play Console


I had 3 iaps in my application. They are working perfectly fine. Yesterday I decided to add a new one, so I just did:

enter image description here

The newest iap was added on 14th of August, so like 24 hours ago but it still doesn't visible in my application.

The onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> list) function still only returns the previous three iaps visible in logs, just like this:

com...myapp.packagename.here I size of productDetails list: 3

com...myapp.packagename.here I product detail instance: iap_id_1

com...myapp.packagename.here I product detail instance: iap_id_2

com...myapp.packagename.here I product detail instance: iap_id_3

My new iap's status is "Active" and I already waited a day, the application has been released as an internal test.

What the hell is going?

Thanks in advance.

E D I T:

Several days passed. The in app purchase product still not visible. I also tried to release a new inner test version.


Solution

  • Test your in app billing integration with this code,

    class BillingHelper(
        private var activity: Activity,
        var billingStateListener: BillingStateListener?
    ) {
        private var billingClient: BillingClient? = null
        private var successDialog: Boolean? = false
        private var productDetailsList: MutableList<ProductDetails> = mutableListOf()
    
        private val jobPurchase = Job()
        private val ioScope = CoroutineScope(Dispatchers.IO + jobPurchase)
    
        fun queryProduct(productId: String, successDialog: Boolean? = false) {
            this.successDialog = successDialog
    
            if (!activity.isOnline()) {
                billingStateListener?.onConnectionFailed()
                return
            }
    
            ioScope.launch {
                val purchaseListener = PurchasesUpdatedListener { billingResult, purchaseList ->
                    if (billingResult.responseCode == BillingResponseCode.OK
                        && !purchaseList.isNullOrEmpty()
                    ) {
                        handleItemAlreadyPurchase(purchaseList.first())
                        ensureMainThread {
                            if (successDialog == true)
                                showPurchaseSuccessDialog()
                            else {
                                billingStateListener?.onProductPurchased()
                            }
                        }
                    } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
                        ensureMainThread {
                            billingStateListener?.onPurchaseFailed()
                        }
                    }
                }
    
                billingClient = BillingClient.newBuilder(activity)
                    .enablePendingPurchases()
                    .setListener(purchaseListener)
                    .build()
    
                billingClient?.let { bc ->
                    bc.startConnection(object : BillingClientStateListener {
                        override fun onBillingServiceDisconnected() {
                            billingStateListener?.onConnectionFailed()
                        }
    
                        override fun onBillingSetupFinished(billingResult: BillingResult) {
                            if (billingResult.responseCode == BillingResponseCode.OK) {
                                queryPurchaseAsync(bc)
                                queryDetailsAsync(bc, productId)
                            }
                        }
                    })
                }
            }
        }
    
        private fun queryDetailsAsync(
            bc: BillingClient,
            productId: String
        ) {
            val queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
                .setProductList(
                    mutableListOf(
                        Product.newBuilder()
                            .setProductId(productId)
                            .setProductType(
                                BillingClient.ProductType.INAPP
                            )
                            .build()
                    )
                ).build()
    
            bc.queryProductDetailsAsync(queryProductDetailsParams) { billingRes, productDetailsList ->
                val responseCode1 = billingRes.responseCode
                if (responseCode1 == BillingResponseCode.OK) {
                    if (productDetailsList.isNotEmpty()) {
                        val productDetails = productDetailsList.first()
                        [email protected] =
                            productDetailsList
                        val productInfo = ProductInfo(
                            productId = productDetails.productId,
                            productTitle = productDetails.title,
                            price = productDetails?.oneTimePurchaseOfferDetails?.formattedPrice
                                ?: ""
                        )
    
                        ensureMainThread {
                            billingStateListener?.onProductAvailable(productInfo)
                        }
                    } else {
                        ensureMainThread {
                            billingStateListener?.onPurchaseFailed()
                        }
                    }
                } else {
                    ensureMainThread {
                        billingStateListener?.onQueryProductFailed()
                    }
                }
            }
        }
    
        private fun queryPurchaseAsync(
            bc: BillingClient
        ) {
            val purchasesParams = QueryPurchasesParams.newBuilder()
                .setProductType(BillingClient.ProductType.INAPP).build()
    
            bc.queryPurchasesAsync(
                purchasesParams
            ) { billingResult12: BillingResult, purchasesList: List<Purchase?> ->
                val responseCode = billingResult12.responseCode
                if (bc.isReady) {
                    if (responseCode == BillingResponseCode.OK) {
                        val skuList = ArrayList<String>()
                        val productList: MutableList<Product> = ArrayList()
    
                        if (purchasesList.isNotEmpty()) {
                            for (i in purchasesList.indices) {
                                val purchaseItem = purchasesList[i]
                                purchaseItem?.let { purchase ->
                                    skuList.add(purchase.products.first())
                                    handleItemAlreadyPurchase(purchase)
                                    billingStateListener?.onProductsAlreadyPurchased(purchase)
                                }
                            }
                            for (i in skuList.indices) {
                                val product = Product.newBuilder()
                                    .setProductId(skuList[i])
                                    .setProductType(BillingClient.ProductType.INAPP)
                                    .build()
                                productList.add(product)
                            }
                        }
                    }
                } else {
                    billingStateListener?.onConnectionFailed()
                }
            }
        }
    
        fun launchBilling() {
            if (activity.isOnline()) {
                billingClient?.let {
                    if (it.isReady) {
                        launchBillingFlow(
                            activity,
                            productDetailsList,
                            it,
                            billingStateListener
                        )
                    } else billingStateListener?.onPurchaseFailed()
                }
            } else {
                billingStateListener?.onConnectionFailed()
            }
        }
    
        private fun launchBillingFlow(
            activity: Activity,
            list: List<ProductDetails>,
            billingClient: BillingClient,
            billingStateListener: BillingStateListener?
        ) {
            val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(list.first()).build()
    
            val paramsList: MutableList<BillingFlowParams.ProductDetailsParams> = ArrayList()
            paramsList.add(productDetailsParams)
    
            val billingFlowParams = BillingFlowParams
                .newBuilder()
                .setProductDetailsParamsList(paramsList)
                .build()
            when (billingClient.launchBillingFlow(activity, billingFlowParams).responseCode) {
                BillingResponseCode.USER_CANCELED, BillingResponseCode.BILLING_UNAVAILABLE, BillingResponseCode.DEVELOPER_ERROR, BillingResponseCode.FEATURE_NOT_SUPPORTED, BillingResponseCode.ITEM_ALREADY_OWNED, BillingResponseCode.SERVICE_DISCONNECTED, BillingResponseCode.SERVICE_TIMEOUT,
                BillingResponseCode.ITEM_UNAVAILABLE ->
                    billingStateListener?.onPurchaseFailed()
    
                else -> {}
            }
        }
    
        private fun handleItemAlreadyPurchase(purchase: Purchase) {
            if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient?.acknowledgePurchase(
                        acknowledgePurchaseParams
                    ) { billingResult: BillingResult? ->
                        billingResult?.let { result ->
                            if (result.responseCode == BillingResponseCode.ITEM_ALREADY_OWNED) {
                                ensureMainThread {
                                    billingStateListener?.onPurchaseAcknowledged()
                                }
                            }
                        }
                    }
                } else {
                    ensureMainThread {
                        billingStateListener?.onPurchaseAcknowledged()
                    }
                }
            }
        }
    
        private fun showPurchaseSuccessDialog() {
            val builder1 = AlertDialog.Builder(activity, R.style.RoundedCornersDialog)
                .setTitle(activity.getString(R.string.congratulations))
                .setMessage(activity.getString(R.string.purchase_successful))
                .setCancelable(false)
                .setPositiveButton(
                    activity.getString(R.string.ok)
                ) { dialog: DialogInterface, which: Int ->
                    dialog.dismiss()
                    ensureMainThread {
                        billingStateListener?.onPurchaseDialogClicked()
                    }
                }
            val alertDialog1 = builder1.create()
            alertDialog1.show()
            val buttonbackground = alertDialog1.getButton(DialogInterface.BUTTON_POSITIVE)
            buttonbackground.setTextColor(ContextCompat.getColor(activity, R.color.color_primary))
        }
    
        fun cancelPurchaseJob() {
            jobPurchase.cancel()
        }
    
        fun isChecking() = jobPurchase.isActive
    
        interface BillingStateListener {
            fun onProductsAlreadyPurchased(purchase: Purchase?)
            fun onProductAvailable(productInfo: ProductInfo)
            fun onProductPurchased()
            fun onPurchaseDialogClicked()
            fun onQueryProductFailed()
            fun onPurchaseAcknowledged()
            fun onPurchaseFailed()
            fun onConnectionFailed()
        }
    
        @Keep
        data class ProductInfo(var productTitle: String, var productId: String, var price: String)
    }
    
    fun ensureMainThread(callback: () -> Unit) {
        if (isOnMainThread()) {
            callback()
        } else {
            Handler(Looper.getMainLooper()).post {
                callback()
            }
        }
    }
    
    fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
    

    In your activity,

    var billingHelper : BillingHelper?= null
    
    
    
    override fun onResume() {
            super.onResume()
            //Other code....
    
            //At last,
            if (billingHelper?.isChecking() == true)
                return
            billingHelper = BillingHelper(this, this)
            billingHelper?.queryProduct("product_id", false)
        }
    

    Yo'll get all the required details in respected callbacks if you've done it correctly

    NOTE: You'll have to modify the code according to your requirements, as this one will work for single product only.

    Good Luck!