I'm working on an app which support subscription method my project is working completely fine and now I'm testing my app but the problem which struggling alot how to handle process if user unsubscribed and his subscription time expired and also what to know litle bit about aknowledge with no consumable and if user user unsubcribed in trial days or user unsubscribed in his validity period.
here is the code where purchase will get process
it will be called after query of skus where
i process method like purchased or pending purchased.....
.......
private fun processPurchases(purchasesResult: Set<Purchase>) =
CoroutineScope(Job() + Dispatchers.IO).launch {
// Log.d(LOG_TAG, "processPurchases called")
val validPurchases = HashSet<Purchase>(purchasesResult.size)
// Log.d(LOG_TAG, "processPurchases newBatch content $purchasesResult")
purchasesResult.forEach { purchase ->
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// subscriptionPrefs.setSubscriptionValue(true)
if(purchase.sku==PlayBillingUtils.MONTHLY_PACKAGE){
subscriptionPurchased.postValue("you have successfully subscribed to monthly package")
subscriptionPrefs.setSubscriptionValue(false)
}
else if (purchase.sku == PlayBillingUtils.THREE_MONTH_PACKAGE){
purchasingMutableLiveData.postValue("you have sucessfully subscribed to 3 month package")
subscriptionPrefs.setSubscriptionValue(false)
}
if (isSignatureValid(purchase)) {
validPurchases.add(purchase) }
} else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
purchasingMutableLiveData.postValue("Purchase Pending")
}
}
// here the token get acknowledge
acknowledgeNonConsumablePurchasesAsync(purchasesResult.toList())
}
Purchase all purchase Update will be invoked here but there is no method which will handle future call like when user unsubscribed or time expired want to know how to solve this problem
override fun onPurchasesUpdated(billingResult: BillingResult?, purchasemutableList: MutableList<Purchase>?) {
billingResult?.let {
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
// will handle server verification, consumables, and updating the local cache
purchasemutableList?.apply { processPurchases(this.toSet()) }
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
subscriptionPrefs.setSubscriptionValue(false)
}
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
// item already owned? call queryPurchasesAsync to verify and process all such items
println( billingResult.debugMessage)
subscriptionPrefs.setSubscriptionValue(true)
purchasingMutableLiveData.postValue("Item Already owned , enjoy ")
queryPurchasesAsync()
}
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
purchasingMutableLiveData.postValue("Service Disconnected")
connectToPlayBillingService()
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED->{
purchasingMutableLiveData.postValue("Service Disconnected")
}
BillingClient.BillingResponseCode.SERVICE_TIMEOUT->{
purchasingMutableLiveData.postValue("Service Time Out try again")
}
BillingClient.BillingResponseCode.USER_CANCELED->{
subscriptionPrefs.setSubscriptionValue(false)
purchasingMutableLiveData.postValue("service is cancelled")
}
else -> {
println(billingResult.debugMessage)
}
}}
}
here i am passing subscription is aknowledge
private fun acknowledgeNonConsumablePurchasesAsync(nonConsumables: List<Purchase>) {
nonConsumables.forEach { purchase ->
val params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase
.purchaseToken).build()
playstoreBillingCLient.acknowledgePurchase(params) { billingResult ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
println("acknowledged")
}
else ->
println( "acknowledgeNonConsumablePurchasesAsync response is ${billingResult.debugMessage}")
}
}
}
}
Related
With the Qonversion API, I am adding a feature for users to purchase subscriptions. When the user comes back after the purchased subscription, I want to check the subscription status, but I get a "null" value from checkEntitlements as a return. At the same time, although the purchase is positive in the purchase function, it does not enter onSuccess at all. How can I check the user's subscription status? (When I try to subscribe again after the purchase is made, google says I am already subscribed, so the subscription process is successful.)
Fragment:
class PurchaseFragment : Fragment() {
private var _binding: FragmentPurchaseBinding? = null
private val binding get() = _binding!!
private var product: QProduct? = null
Displaying product:
private fun displayProducts() {
Qonversion.shared.offerings(object : QonversionOfferingsCallback {
override fun onSuccess(offerings: QOfferings) {
val mainOffering = offerings.main
if (mainOffering != null && mainOffering.products.isNotEmpty()) {
binding.monthlyPrice.text = mainOffering.products[0].prettyPrice
product = mainOffering.products[0]
}
}
override fun onError(error: QonversionError) {
Log.d(TAG, "Error: $error")
}
})
}
Making purchase:
private fun makePurchase(product: QProduct) {
Qonversion.shared.purchase(
requireActivity(),
product,
callback = object : QonversionEntitlementsCallback {
override fun onSuccess(entitlements: Map<String, QEntitlement>) {
val premiumEntitlement = entitlements["premium_permisson"]
if (premiumEntitlement != null && premiumEntitlement.isActive) {
Log.d(TAG, "Entitlements: $premiumEntitlement")
} else {
Log.d(TAG, "Entitlements else: $premiumEntitlement")
}
}
override fun onError(error: QonversionError) {
// Handle error here
if (error.code === QonversionErrorCode.CanceledPurchase) {
Log.d(TAG, "Canceled")
}
}
})
}
Check status of user:
private fun isItPremiumUser() {
Qonversion.shared.checkEntitlements(object : QonversionEntitlementsCallback {
override fun onSuccess(entitlements: Map<String, QEntitlement>) {
val premiumEntitlement = entitlements["premium_permisson"]
Log.d(TAG, "isItPremiumUser $premiumEntitlement") // it returns null
if (premiumEntitlement != null && premiumEntitlement.isActive) {
Log.d(TAG, "isItPremiumUser Entitlements: $premiumEntitlement")
// handle active entitlement here
// also you can check renew state if needed
// for example to check if user has canceled subscription and offer him a discount
when (premiumEntitlement.renewState) {
QEntitlementRenewState.NonRenewable -> {
Log.d(TAG, "NonRenewable Entitlements: $premiumEntitlement")
// NonRenewable is the state of a consumable or non-consumable in-app purchase
}
QEntitlementRenewState.WillRenew -> {
Log.d(TAG, "WillRenew Entitlements: $premiumEntitlement")
// WillRenew is the state of an auto-renewable subscription
}
QEntitlementRenewState.BillingIssue -> {
Log.d(TAG, "BillingIssue Entitlements: $premiumEntitlement")
// Prompt the user to update the payment method.
}
QEntitlementRenewState.Canceled -> {
Log.d(TAG, "Canceled Entitlements: $premiumEntitlement")
// The user has turned off auto-renewal for the subscription, but the subscription has not expired yet.
// Prompt the user to resubscribe with a special offer.
}
else -> {
Log.d(TAG, "else Entitlements: $premiumEntitlement")
}
}
}
}
override fun onError(error: QonversionError) {
// handle error here
Log.d(TAG, "onError: $error")
}
})
}
I have two subscriptions in the application, each of them individually works correctly. But when I try to do an Update/Downgrade, problems arise:
I have two subscriptions in the application, each of them individually works correctly. But when I try to do an Update/Downgrade, problems arise:
When I try to change one subscription to another, I get the correct window with changing the subscription and shows that the new subscription was purchased successfully, but when I check on Google Play, it shows only the one I bought first, after a while I try again, this time it does not appear replacement window, only the standard purchase window, the second one is bought without problems and both are visible in the Store. Maybe I have some wrong approach to updating, everything seems to be in its place otherwise, the window with the replacement would not be displayed correctly.
My setSuccessfulScreen() method, in onPurchasesUpdated(), works correctly on the first purchase, but it crashes with an error when I try to update the subscription:
Fatal Exception: java.lang.RuntimeException: Error receiving broadcast Intent { act=com.android.vending.billing.PURCHASES_UPDATED
I thought maybe the case in Binding, I replaced with findViewById(), it does not help, without this method there is no crash.
My subscription code:
private fun buySubscription() {
val skuList = ArrayList<String>()
skuList.add("")
skuList.add("")
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS)
billingClient!!.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
when (subsFlag) {
MONTH -> {
if (!ProviderPreferences.isSubscribed()) {
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[1]!!).build())
} else {
if (ProviderPreferences.getSubscribedType() == MONTH) {
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[1]!!).build())
} else {
val updateParams = BillingFlowParams.SubscriptionUpdateParams.newBuilder().setOldSkuPurchaseToken(ProviderPreferences.getPurchaseToken()!!).setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED).build()
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[1]!!).setSubscriptionUpdateParams(updateParams).build())
}
}
}
YEAR -> {
if (!ProviderPreferences.isSubscribed()) {
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[0]!!).build())
} else {
if (ProviderPreferences.getSubscribedType() == YEAR) {
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[0]!!).build())
} else {
val updateParams = BillingFlowParams.SubscriptionUpdateParams.newBuilder().setOldSkuPurchaseToken(ProviderPreferences.getPurchaseToken()!!).setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.DEFERRED).build()
billingClient!!.launchBillingFlow(requireActivity(), BillingFlowParams.newBuilder().setSkuDetails(skuDetailsList[0]!!).setSubscriptionUpdateParams(updateParams).build())
}
}
}
}
}
}
}
private fun purchases() {
val acknowledgePurchaseResponseListener = AcknowledgePurchaseResponseListener { billingResult -> }
billingClient = BillingClient.newBuilder(App.getInstance()).enablePendingPurchases().setListener { billingResult: BillingResult, list: List<Purchase>? ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val purchase = list!![0]
handlePurchases(acknowledgePurchaseResponseListener, purchase)
ProviderPreferences.saveSubscriptionType(subsFlag)
ProviderPreferences.savePurchaseToken(list[0].purchaseToken)
setSuccessfulScreen()
}
}.build()
connectToGooglePlayBilling()
}
private fun handlePurchases(acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener?, purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
ProviderPreferences.saveSubscription(true)
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()
billingClient!!.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener!!)
}
}
}
private fun connectToGooglePlayBilling() {
billingClient!!.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
billingClient!!.queryPurchasesAsync(BillingClient.SkuType.SUBS) { billingResult, list ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && list != null) {
}
}
}
override fun onBillingServiceDisconnected() {
connectToGooglePlayBilling()
}
})
}
private fun setSuccessfulScreen() {
group!!.visibility = View.GONE
group2!!.visibility = View.VISIBLE
subscribeBtn!!.visibility = View.GONE
lottieAnimation!!.playAnimation()
}
I am using the billing v4 library.
Please tell me what could be the reason, maybe something is wrong with the subscription change code, I have not worked with subscription renewal before. And what could be the reason for the crash.
Thank you in advance.
I have integrated the google play billing library inside my application to make a subscription purchase. It works well. The user receives purchase confirmation mail from google.
confirmation mail from google
The active subscription is also visible in the play store subscriptions screen.
Active subscription showing in play store
In the play console, the order detail said the user was successfully charged for a subscription. However, It gets canceled instantly.
play console order history
I also check purchase acknowledgment, it returns true on every purchase. Initially, I thought there would be a mistake in my code. So, I tried various billing libraries from Github. The problem persists. In the end, I replaced the entire google billing library with Revenue cat. Followed every step described on Revenue cat documents. Still, getting the same issue.
Is there anything that I am missing to implement or done incorrectly? please help me out. Thank you
code for fetching available products:
private fun fetchOffering(){
Purchases.sharedInstance.getOfferingsWith({ error ->
// An error occurred
handleBillingError(requireActivity(), error)
}) { offerings ->
offerings.current?.availablePackages?.takeUnless { it.isNullOrEmpty() }?.let {
// All packages from current offering
if (it.isNotEmpty()){
it.forEach { p: Package ->
offeringPackages.add(p)
}
isProductsAvailable = true
}
Log.d("RevenueCat", "fetchOffering: success: ${it.size}")
}
}
}
code for making purchase:
private fun makePurchase(pack:Package){
Purchases.sharedInstance.purchasePackageWith(
requireActivity(),
packageToPurchase = pack /* package from the fetched Offering*/,
onError = {error, userCancelled ->
Log.d("RevenueCat", "makePurchase: userCancelled: $userCancelled")
handleBillingError(requireActivity(), error)
},
onSuccess = { product, purchaserInfo ->
if (purchaserInfo.entitlements[REVENUE_ENTITLEMENT_ID_PRO]?.isActive == true) {
Log.d("RevenueCat", "makePurchase: success: ${product.originalJson} ")
afterPurchaseSuccessSetup()
}
})
}
I don't know where you are lagging, you should follow these steps to make subscribe purchase.
private lateinit var billingClient: BillingClient
private val skuListsubscribe = listOf("subscription_1", "subscription_2")
initialize billing client.
private fun setupBillingClient() {
billingClient = BillingClient.newBuilder(BaseActivity.mContext!!)
.enablePendingPurchases()
.setListener(this)
.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.
loadAllSKUsSubscription()
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
Log.e("TAG", "onBillingServiceDisconnected: ")
}
})
}
private fun loadAllSKUsSubscription() = if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(skuListsubscribe)
.setType(BillingClient.SkuType.SUBS)
.build()
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
// Process the result.
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList!!.isNotEmpty()) {
for (skuDetails in skuDetailsList) {
if (skuDetails.sku == subscribtionId) {
tv_full_category_price.text = skuDetails.price
realAmountSubscription = skuDetails.price
rl_bestvalue_plan.setOnClickListener {
isInAppPurchase = false
isSubscribtion = true
val billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(requireActivity(), billingFlowParams)
}
}
}
}
}
} else {
println("Billing Client not ready")
}
override purchase update method
override fun onPurchasesUpdated(
billingResult: BillingResult,
purchases: MutableList<Purchase>?,
) {
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
acknowledgePurchase(purchase)
}
} else if (billingResult?.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
//logger("User Cancelled")
//logger(billingResult?.debugMessage.toString())
} else {
//logger(billingResult?.debugMessage.toString())
}
}
private fun acknowledgePurchase(purchase: Purchase) {
if (isSubscribtion) {
handleNonConsumableProduct(purchase = purchase)
}
}
fun handleConsumableProduct(purchase: Purchase) {
val consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (isVisible && isAdded)
Snackbar.MakeInternetSnackbar(BaseActivity.mContext!!,
subscription_fragment_indicator_layout,
"Purchased Successfully")
InAppPaymentApi(realAmount.toString(), audio_id!!, purchase.purchaseToken)
}
}
}
I have always worked with Java but this time I am trying to develop in Kotlin, I have an app that will have a button to pay to remove the ads (with Google Play's billing system) but I can't get it to work.
I have already uploaded my signed apk to Play Console and create the product that has the ID remove_ads
This is what I have in the AndroidManifest.xml file "android.permission.INTERNET" and "com.android.vending.BILLING"
In build.gradle:
implementation("com.android.billingclient:billing:4.0.0") implementation("com.android.billingclient:billing-ktx:4.0.0")
In my Kotlin Class "Settings":
OnCreate:
btnRemoveAds.setOnClickListener {
Toast.makeText(this#Settings, "Click Button", Toast.LENGTH_SHORT).show()
billingClient = BillingClient.newBuilder(this)
.setListener(purchasesUpdatedListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
Toast.makeText(this#Settings, "SetupFinished", Toast.LENGTH_SHORT).show()
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Toast.makeText(this#Settings, "OK", Toast.LENGTH_SHORT).show()
// The BillingClient is ready. You can query purchases here.
billingClient.queryPurchasesAsync(
SkuType.INAPP
) { billingResult, list ->
if (list.size != 0){
if (list.get(0).purchaseState == Purchase.PurchaseState.PURCHASED){
setPlus(true)
setResult(RESULT_OK)
} else{
queryAvaliableProducts()
}
} else{
queryAvaliableProducts()
}
}
} else{
Toast.makeText(this#Settings, "NO", Toast.LENGTH_SHORT).show()
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(this#Settings, "Disconnected", Toast.LENGTH_SHORT).show()
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})
}
The only Toast I can see when pressing the button is "Click Button"
These are my methods:
private fun queryAvaliableProducts() {
val skuList: MutableList<String> = ArrayList()
skuList.add("remove_ads")
val params = SkuDetailsParams.newBuilder()
params.setSkusList(skuList).setType(INAPP)
billingClient.querySkuDetailsAsync(
params.build()
) { billingResult, skuDetailsList -> // Process the result.
if (skuDetailsList!!.size != 0) {
val billingFlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetailsList[0])
.build()
val responseCode = billingClient.launchBillingFlow(
this#Settings,
billingFlowParams
).responseCode
}
}
}
private val purchasesUpdatedListener =
PurchasesUpdatedListener { billingResult, purchases ->
// To be implemented in a later section.
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.
}
}
fun handlePurchase(purchase: Purchase) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(
acknowledgePurchaseParams,
acknowledgePurchaseResponseListener
)
}
}
}
var acknowledgePurchaseResponseListener =
AcknowledgePurchaseResponseListener { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
setPlus(true)
setResult(RESULT_OK)
finish()
}
}
I also implemented a button to check connection status after billingClient.startConnection
btnConnectionState.setOnClickListener {
Toast.makeText(this#Settings, billingClient.connectionState.toString(), Toast.LENGTH_SHORT).show()
}
I am getting 2 (Connected) as a result so it seems weird to me that onBillingSetupFinished and onBillingServiceDisconnected never get executed, are there any errors in my code?
There are no errors in your code afaik except the Toast itself. onBillingServiceDisconnected isn't called on the main thread. If you put a try catch around it like so:
try {
Toast.makeText(this#Settings, "NO", Toast.LENGTH_SHORT).show()
} catch (ex: Exception) {
Log.e("MyApp", "Error", ex)
}
It will print an error like this
06-05 00:00:38.713 8595 9215 E MyApp :
java.lang.NullPointerException: Can't toast on a thread that has not
called Looper.prepare()
You can also print the thread the callback is called from:
Log.e("MyApp", "Running on ${Thread.currentThread().name}")
and it will tell you
Running on PlayBillingLibrary-1
So switch back to the main thread for the Toast (there are ways) or use a simple Log statement to verify that the method is called.
This is not a question actually. I spent some time on this, so I am putting here the steps involved to make an in-app purchase.
added dependencies
//billing-client
implementation 'com.android.billingclient:billing:2.2.1'
implementation 'com.android.billingclient:billing-ktx:2.2.1'
added permission in manifest
Created application in Play Console.
Play Console -> settings -> Pricing Template :: added pricing template.
Select Application Form Console -> Store Presence -> In-App Products -> Managed
Products -> Create a Managed Product.
For Testing, first of all, you need to release a build as Alpha (with some testing mail id registered in closed track.)
After Successful publication you can use the same build (maybe needed the same version code and version number) (Both debug and Release works) for test the purchase.
Ensure the testing mail IDs are google ids (#gmail.com)
Here are the code snippets that I have used for billing.
implement PurchasesUpdatedListener, on fragment or activity
Setup BIlling Client
private fun setupBillingClient() {
billingClient = BillingClient.newBuilder(mainActivity)
.enablePendingPurchases()
.setListener(this)
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// The BillingClient is setup
log("Setup Billing Done")
loadAllSKUs()
}
}
override fun onBillingServiceDisconnected() {
log("Failed")
}
})
}
Load All Skus (Ids of your purchasing templates (use arraylist))
private fun loadAllSKUs() = if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build()
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
// Process the result.
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList.isNotEmpty()) {
for (skuDetails in skuDetailsList) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList.isNotEmpty()) {
this.skuDetailsList = skuDetailsList
}
}
}
}
} else {
log("Billing Client not ready")
}
Make Google Payment
private fun invokeGooglePayPayment(skuDetails: SkuDetails) {
val billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(mainActivity, billingFlowParams)
}
If you need to purchase again on the same in-app purchase item you can use this code,
override fun onPurchasesUpdated(
billingResult: BillingResult?,
purchases: MutableList<Purchase>?
) {
//todo remove this function only for token log
if (!purchases.isNullOrEmpty()) {
for (purchase in purchases) {
Log.e("Purchase Token" + purchase.purchaseToken, "OK")
}
}
if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
acknowledgePurchase(purchase) // do after purchase
}
} else if (billingResult?.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
//todo
} else if (billingResult?.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken("inapp:${BuildConfig.APPLICATION_ID}:${selectedSkuDetails?.sku}")
.build()
runBlocking {
CoroutineScope(Dispatchers.IO).launch {
billingClient.consumePurchase(consumeParams)
}
MainScope().launch {
invokeGooglePayPayment(selectedSkuDetails!!)
}
}
}
}