acknowledgePurchase In-app billing coroutine - android

I am new to android and trying to implement in-app billing the first time.
I am using the google play in-app library. https://developer.android.com/google/play/billing/billing_library_overview
I want to implement subscription in-app purchase in kotlin.
I'm not able to resolve the issue: Suspend function 'acknowledgePurchase' should be called only from a coroutine or another suspend function. How can I call this function?
Here is my handlePurchase method:
fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState === Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val params =
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
billingClient.acknowledgePurchase(params.build()) }
}
}

Assuming you are using kotlin coroutines in your app.
BillingClient#acknowledgePurchase(params.build()) is a suspended function that should only be called from another suspend function or coroutine scope.
Let's say you are using this fun handlePurchase(purchase: Purchase) in a fragment, you can use viewLifecycleOwner.lifecycleScope.launch{} to launch the same.
fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
viewLifecycleOwner.lifecycleScope.launch {
val result = billingClient.acknowledgePurchase(params.build())
when (result.responseCode) {
BillingClient.BillingResponseCode.OK -> {
//success flow}
}
else -> {
//error flow
}
}
}
}
}
}

Related

Retrofit - OkHttp Authenticator- How to make other api calls pause till the authenticator completes execution

I am having multiple api calls going in a screen launched with viewmodel scope like this in viewmodel
viewModelScope.launch {
apiCallOne()
}
viewModelScope.launch {
apiCallTwo()
}
viewModelScope.launch {
apiCallThree()
}
And my auhtenticator is
override fun authenticate(route: Route?, response: Response): Request? {
return if (response.retryCount < 1) {
val token = refreshToken()
if (token != null)
response.request.newBuilder().header("Authorization", "Bearer $token")
.build()
else
null
} else {
navigateToOnboardingActivity()
null
}
}
It is working fine but when 3 api calls are launched parallely , the session is getting refreshed 3 times, how can i make it work like if apicallOne() get 401, it will got to autnenticator and call refresh token api, in this time apicallTwo()
and apicallThree() should paused and resumed after the first authenticaor gets successfull response.
Please note i cant call all apis in single launch like this
viewModelScope.launch{
apiCallOne()
apiCallTwo()
apiCallThree()
}
you can use like this example:
viewModelScope.launch{
val result1 = async{apiCallOne()}.await()
if (result1.code() != 401) {
apiCallTwo()
apiCallThree()
}
}
or this:
viewModelScope.launch{
val result1 = withContext(Dispatchers.Default) {apiCallOne()}
if (result1.code() != 401) {
apiCallTwo()
apiCallThree()
}
}

Google billing library cancelled all subscriptions

I'm using Google's Billing library to make subscriptions inside my app.
After ~~ 3 days google play has refund every subscription of my users, why did it was?
Some code with activity, which make subscriptions:
private var billingClient: BillingClient? = null
private val purchasesUpdateListener = PurchasesUpdatedListener { billingResult, purchases ->
val isSuccessResult = billingResult.responseCode == BillingClient.BillingResponseCode.OK
val hasPurchases = !purchases.isNullOrEmpty()
if (isSuccessResult && hasPurchases) {
purchases?.forEach(::confirmPurchase)
viewModel.hasSubscription.value = true
}
}
private fun confirmPurchase(purchase: Purchase) {
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient?.consumeAsync(consumeParams) { billingResult, _ ->
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
//all done
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdateListener)
.enablePendingPurchases()
.build()
connectToBillingService()
}
private fun connectToBillingService() {
billingClient?.startConnection(this)
}
private fun getPurchases(): List<Purchase> {
val purchasesResult = billingClient?.queryPurchases(BillingClient.SkuType.SUBS)
return purchasesResult?.purchasesList.orEmpty()
}
override fun onBillingSetupFinished(result: BillingResult) {
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
updateSkuMap()
}
}
}
Because you're using Play Billing Library 2.0 or above. Starting from Play Billing Library 2.0, all purchases must be acknowledged within three days. Failure to properly acknowledge purchases will result in purchases being refunded.
Checking Play Billing Library v2.0 release notes and Processing Purchases for more details.
Inside your code, you are using consumeAsync() to confirm a purchase,
which is the correct way for one time consumable product , because consumeAsync() will acknowledge for you automatically.
But for subscriptions, you should use client.acknowledgePurchase() method rather than consumeAsync()
Here is the sample for Kotlin
val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...
suspend fun handlePurchase() {
if (purchase.purchaseState === PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
client.acknowledgePurchase(acknowledgePurchaseParams.build())
}
}
}
}
May be I am too late to answer, but may be reference for other members.
Yes, refund happens in 3 days, when purchase is not acknowledged. And in this case it seems to be a consumable in-app purchase, where purchase is acknowledged locally. I can see in code, that purchase is getting consumed(acknowledged), but consume might be getting fail in ConsumeResponseListener. I would suggest to consume purchase only when purchase object is in 'PURCHASED' state. Which means to apply a check before going to consume it.
purchaseItem.getPurchaseState() == com.android.billingclient.api.Purchase.PurchaseState.PURCHASED

Android subscription/purchase is not found in code despite being defined in Google Play Console

I am trying to add a subscription to my app. I have created a subscription called "pro_subscription" in the Google Play Console and made sure it's enabled. I then created a new Alpha build and released it to myself, with an account I have configured for testing.
We added com.android.vending.BILLING to the permissions and already had the internet permission.
No matter what we've tried, when we try to fetch the subscription in order to purchase it, no results are found.
We have tried to fetch using Android APIs directly, in which mutableList?.isEmpty() is true:
fun querySkuDetails() {
    val purchasesUpdatedListener =
        PurchasesUpdatedListener { billingResult, purchases ->
            // To be implemented in a later section.
        }
    var billingClient = BillingClient.newBuilder(activity as MainActivity)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases()
        .build()
    billingClient.startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode ==  BillingClient.BillingResponseCode.OK) {
                // The BillingClient is ready. You can query purchases here.
                val skuList = ArrayList<String>()
                skuList.add("subscription")
                skuList.add("pro_subscription")
                val params = SkuDetailsParams.newBuilder()
                params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
                billingClient.querySkuDetailsAsync(params.build(), SkuDetailsResponseListener() { billingResult: BillingResult, mutableList: MutableList<SkuDetails>? ->
                    if (mutableList?.isEmpty()!!) {
                        Log.d("OFFERS: ", "mutableList is empty")
                        return#SkuDetailsResponseListener
                    }
                })
            }
        }
        override fun onBillingServiceDisconnected() {
            // Try to restart the connection on the next request to
            // Google Play by calling the startConnection() method.
        }
    })
And we also tried using the third-party revenuecat by configuring an offering and then fetching it, in which offerings.all.isEmpty() is true:
Purchases.sharedInstance.getOfferingsWith(
    onError = { error ->
        /* Optional error handling */
        throw IllegalArgumentException()
    },
    onSuccess = { offerings ->
        if (offerings.current == null) {
            Log.d("OFFERS: ", "offerings.current is null")
        }
        if (offerings.all.isEmpty()) {
            Log.d("OFFERS: ", "offerings.all.size is 0")
        }
        // Display current offering with offerings.current
        Purchases.sharedInstance.purchasePackageWith(
            activity as MainActivity,
            offerings.current?.getPackage("pro")!!,
            onError = { error, userCancelled -> /* No purchase */
                if (userCancelled) {
                    val requestCancelledToast = Toast.makeText(
                        context,
                        R.string.purchase_request_cancelled_toast,
                        Toast.LENGTH_LONG
                    )
                    requestCancelledToast.show()
                } else {
                    throw IllegalArgumentException()
                }
            },
            onSuccess = { product, purchaserInfo ->
                if (purchaserInfo.entitlements["pro"]?.isActive == true) {
                    // Unlock that great "pro" content
                    try {
                        val adView =
                            container!!.findViewById<AdView>(R.id.adView)
                        (adView.parent as ViewGroup).removeView(
                            adView
                        )
                    } catch (e: Exception) {
                        val errorRemoveToast = Toast.makeText(
                            context,
                            R.string.error_remove_ad,
                            Toast.LENGTH_LONG
                        )
                        errorRemoveToast.show()
                    }
                }
            })
    })
In both cases, no error is printed, caught, or otherwise identified.
Anyone know why we are not able to fetch the purchases based on the above steps? Thanks!
Did you add Library of billing on the app's gradle?
It is;
dependencies {
val billing_version = "4.0.0"
implementation("com.android.billingclient:billing-ktx:$billing_version")
}

Android billing library V3.0.0 does not work

It does not work. As simple as that. I used default code given in google docs.
private fun initBilling() {
val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchase ->
Log.d("Billing", billingResult.debugMessage)
}
val billingClient = BillingClient.newBuilder(this#Home)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
Log.d("Billing", billingResult.debugMessage
?: "billing setup finished with response code ${billingResult.responseCode}")
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
CoroutineScope(Dispatchers.IO).launch {
querySkuDetails(billingClient)
}
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
Log.d("Billing", "disconnected")
}
})
}
None of the Logs that I used were printed and no errors were showing. Start connection just does not trigger any methods from BillingClientStateListener.
There is an unknown issue with android billing library v3.0.0. Just switching to 2.1.0 helped.

queryPurchases() returns an empty list

I have implemented the Billing library in order to have in-app products in my app. I can make a successful purchase and everything works fine. If I try to make a purchase again, I get the response "Product already owned" which shows that the purchase is fine. I am using as productID the test id: "android.test.purchased"
Moreover, in the Main's Activity onResume() method, I call the queryPurchases() method in order to restore the product in case the user deletes the app and re-installs it. However, this method returns an empty purchases list although the response code is OK.
This is the way I am using the method:
private fun startServiceConnectionIfNeeded(executeOnSuccess: Runnable?) {
if (mBillingClient.isReady) {
executeOnSuccess?.run()
} else {
mBillingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(#BillingClient.BillingResponse billingResponse: Int) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: $billingResponse")
executeOnSuccess?.run()
} else {
Log.w(TAG, "onBillingSetupFinished() error code: $billingResponse")
}
}
override fun onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()")
}
})
}
}
// I call this method in Main's onResume()
fun updateBillingSharedPreferencesKey() {
val executeOnConnectedService = Runnable {
val purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP)
val purchasesList = purchasesResult?.purchasesList
purchasesList?.let {
// this list is always empty, although I have a valid product
for (purchase in it) {
// I never enter this loop to handle the product functionality
Log.d(TAG, "purchase.sku = ${purchase?.sku}")
}
}
}
startServiceConnectionIfNeeded(executeOnConnectedService)
}
Am I doing something wrong? The service connection and the response code is ok.
How should I call the queryPurchases() method to get my valid in-app product?

Categories

Resources