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.
Related
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
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
}
}
}
}
}
}
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")
}
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?
I've moved from an old gradle of billing api, to the most recent to date, and now I've tried adding
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
but I can not get it to work, here's the error
Caused by: java.lang.IllegalArgumentException: Support for pending purchases must be enabled. Enable this by calling 'enablePendingPurchases()' on BillingClientBuilder.
at com.android.billingclient.api.BillingClient$Builder.build(BillingClient.java:309)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.setupBillingClient(ponerDineroActivity.java:144)
at com.aplicacion.vivaluganoapp.ar.ponerDineroActivity.onCreate(ponerDineroActivity.java:125)
complete code:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_poner_dinero);
recyclerProduct.setHasFixedSize(true);
recyclerProduct.setLayoutManager(new LinearLayoutManager(this));
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
enablePendingPurchases.build();
setupBillingClient();
}
private void setupBillingClient() {
billingClient = BillingClient.newBuilder (this).setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(BillingResult responseCode) {
int maca = BillingClient.BillingResponseCode.OK;
String maca2 = String.valueOf(maca);
String maca3 = String.valueOf(responseCode);
if (maca3 == maca2)
{
Toast.makeText(ponerDineroActivity.this, "WORKS", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(ponerDineroActivity.this, "ERROR", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onBillingServiceDisconnected() {
Toast.makeText(ponerDineroActivity.this, "Disconnected from Billing", Toast.LENGTH_SHORT).show();
}
});
}
if I place only:
BillingClient.Builder enablePendingPurchases = BillingClient.newBuilder(this);
the error is:
Caused by: java.lang.IllegalArgumentException: Please provide a valid listener for purchases updates.
any help? i'm tired of trying
From the first stacktrace in your question
Enable this by calling 'enablePendingPurchases()'
we can find documentation for method enablePendingPurchases()
This method is required to be called to acknowledge your application
has been updated to support purchases that are pending. Pending
purchases are not automatically enabled since your application will
require updates to ensure entitlement is not granted before payment
has been secured. For more information on how to handle pending
transactions see
https://developer.android.com/google/play/billing/billing_library_overview
If this method is not called, BillingClient instance creation fails.
Your line of code should be:-
enablePendingPurchases = BillingClient.newBuilder(this)
.enablePendingPurchases()
.setListener(this);
Instead of :-
enablePendingPurchases = BillingClient.newBuilder(this).setListener(this);
This worked for me.
Just add enablePendingPurchases() like below:
billingClient = BillingClient.newBuilder(this)
.setListener(this)
.enablePendingPurchases()
.build();
BillingClient billingClient =
BillingClient.newBuilder(context!!)
.enablePendingPurchases()
.setListener(this)
build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode==BillingClient.BillingResponseCode.OK) {
skuList = HashMap()
skuList.put(BillingClient.SkuType.SUBS, listOf(getString(R.string.subscription_monthly),getString(R.string.subscription_yearly)))
querySkuDetailsAsync(BillingClient.SkuType.SUBS,skuList.get(BillingClient.SkuType.SUBS),object :SkuDetailsResponseListener{
override fun onSkuDetailsResponse(billingResult: BillingResult?, skuDetailsList: MutableList<SkuDetails>?) {
DebugLog.e("DATAAA "+skuDetailsList?.size+"")
}
})
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
})