In-App Billing v3 - Don't detect refund - android

I've followed the training about "In-App Billing v3" of Google. I get to do a buy of a item but I've a problem.
https://developer.android.com/training/in-app-billing/index.html
I've canceled and refunded the buy but the app detect the buy as true. I can't continue testing my app because always I detect as purchased the item.
The only error that I've found in logCat is the next:
[3687] InAppBillingService.logResponseBundle: Bundle does not contain a response code
Appears after launching mHelper.queryInventoryAsync(mGotInventoryListener).
Any idea?

p.s. - this suggests it just takes time for the refund to be updated
http://code.google.com/p/marketbilling/issues/detail?id=88#makechanges
But I'm not convinced - I realise they're caching purchase data on the device but 24h is a long time...
Updated to add that more than 24 hours after I cancelled 'test' transactions, those accounts are still licensed!!
Updated again - after 36 hours the app was STILL licensed. I uninstalled and reinstalled and it was STILL licensed!!
Updated AGAIN! - I factory-reset the device, logged-in, installed the app and it was unlicensed...
AND another update - a reply from Google suggests that refunds are processed 'automatically' but can take 'upto 72 hours' to be refreshed on the device - there is no other route to detect a refund, so players get upto 3 days of stuff 'for free' if they refund - erm, OK this is In-App and not App purchase but still, that seems a BIT excessive?

After having waited for about 12 hours and having tried everything suggested here and on similar threads, I was still facing the same issue. What did the trick for me was the following adb command:
adb shell pm clear com.android.vending

You can easily negate the purchase for test purposes by consuming the item.
Using Trivial Drive sample I added the following code in MainActivity.java which will "consume" the premium upgrade when the app starts:
// Do we have the premium upgrade?
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
// dss added for test: Consume the premium upgrade for test purposes.
boolean testConsume = true;
if (mIsPremium && testConsume) {
Log.d(TAG, "NOT FOR PRODUCTION: We have a premium upgrade. Consuming it.");
mHelper.consumeAsync(inventory.getPurchase(SKU_PREMIUM), mConsumeFinishedListener);
mIsPremium = false;
} //dss end add
As a bonus you get a free quarter tank of gas when you consume the upgrade just because the sample treats all consumption as gasoline elsewhere. Search MainActivity for "Provisioning" to find where.

The way I am working around it is with a block of code that ignores the specific purchases I have made. I have a log statement in the code that prints out the purchase info, then I hardcode a list in my app of purchaseTimes to ignore. It is a mess and I have to re-compile every time I want to test, but I haven't found a better way yet.

Related

How to check if the user has an active subscription in Android, Google Play?

I have an app with a subscription in Google Play.
When the user starts the app, I need to know if the user has an active subscription. This would seem an obvious thing to do, but from searching and trying to implement it, it seems impossible?
I am using Google's newer billing 2/3, following Google's tutorials,
class BillingManager implements PurchasesUpdatedListener
...
public void checkAsync() {
Log.e(TAG, "checkAsync");
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult, List<PurchaseHistoryRecord> list) {
Log.e(TAG, "checkCached result: " + list);
if (list == null) {
return;
}
for (PurchaseHistoryRecord ps : list) {
//System.out.println("PAYMENT: " + ps.getSku() + " : " + ps.getPurchaseTime());
}
}
});
}
public void checkCached() {
Log.e(TAG, "checkCached");
List<Purchase> result = billingClient.queryPurchases(BillingClient.SkuType.SUBS).getPurchasesList();
Log.e(TAG, "checkCached result: " + result);
if (result == null) {
return;
}
for (Purchase purchase : result) {
handlePurchase(purchase);
}
}
This is how I think you're supposed to get a user's purchases. But it does not work at all, both calls return null always. It only returns the correct purchases when you reinstall the app or clear the data.
So how exactly is an app supposed to do this?
Purchasing works for the app once I enter internal testing, and download it through the Google Play link. (before that subscriptions do not work at all).
*** updated
So to further clarify:
I am using a valid test user, and subscriptions are working correctly. My question is on the what the API queryPurchases() or queryPurchaseHistoryAsync() are suppose to do.
What I am seeing, is that these only return purchases that have not be processed by the app. They seem to store that the purchase was processed in the apps data.
After the purchase these return null, after the app restarts these return null.
If I clear the app datam or reinstall the app then they return the purchase (once), then again null after restart.
From what I see, these are only useful to detect when a user reinstalls your app, or installs on a different phone. They cannot be used to determine the status of a subscription.
So my question is,
1 - is this something that just does not work in internal testing and will magically work differently when the app is release?
2 - is there a different API that your suppose to use to check the status of a subscription?
3 - are you suppose to manage subscriptions yourself in your app by storing a user preference/cookie when you acknowledge the subscription the first time so you know when the subscription expires?
You need "licenced testers". They would allow you to "sideload" your app on devices, even for debug builds. My interpretation of sideload in this case would cover installing from Android Studio build tools as well as adb install .... and other methods that don't involve the play store.
https://developer.android.com/google/play/billing/test
Ordinarily, the Google Play Billing API is blocked for apps that aren't signed and uploaded to Google Play. License testers can bypass this check, meaning you can sideload apps for testing, even for apps using debug builds with debug signatures without the need to upload to the new version of your app. Note that the package name must match that of the app that is configured for Google Play, and the Google account must be a license tester for the Google Play Console account.
I also don't see how you're using startConnection. Until that's completed successfully I wouldn't be sure you have the latest data. I wouldn't be surprised if that makes you get stale values. I would check that carefully to make sure there's no silent errors happening, by both looking at onBillingSetupFinished and onBillingServiceDisconnected. And for the time being avoid trusting queryPurchases():
https://medium.com/#NandagopalR/integrating-google-play-billing-into-an-android-application-b6eb6af176a7
The queryPurchases() method uses a cache of the Google Play Store app without initiating a network request. If you need to check the most recent purchase made by the user for each product ID, you can use queryPurchaseHistoryAsync(), passing the purchase type and a PurchaseHistoryResponseListener to handle the query result.
By the way what's the value of isReady() right before queryPurchaseHistoryAsync, and what's the value of BillingResult::getDebugMessage and BillingResult::getResponseCode?
Also, use isFeatureSupported, though it seems it's not like your problem is coming from here. But I'd advise not testing with subscriptions until you get all the moving parts working: https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isFeatureSupported(java.lang.String)
Okay, figured it out, was my mistake.
I was calling queryPurchases() in my main activity onCreate(), but the BillingClient was not ready yet.
I moved it to onBillingSetupFinished() and it now returns the correct purchases.
Everything is now working as expected. You get the active subscriptions when you call queryPurchases() after an app restart.

Android In-App Purchase Fails Unless User Clears Play Services cache

Our app has an in-app purchase to upgrade to our "Pro" product, in Google-speak this is a one-time non-consumable product. We have launched this product and are actually generating revenue...so aside from our own testing we know that across the globe users are successfully seeing on our in-app sales page 1) the price of the product, and 2) an enabled "buy now" button (please note these two items both are derived from this BillingManager query:
launch {
val sku = billingManager.querySKU(BillingManager.SKUProPack1)
if (sku != null) {
iapPriceText.text = getString(R.string.settings_pro_upgrade_price, sku.price)
skuDetails = sku
upgradeButton.isEnabled = true
} else {
iapPriceText.text = ""
upgradeButton.isEnabled = false
}
}
I'll point out here: skuDetails collected in the query above is also the source of data needed in the actual Play Store purchase flow...so merely enabling the button would only delay the disappointment. The first indication a user sees that there is a problem is on the sales page: no price and a greyed-out button that is disabled.
We have three confirmed cases where the user has contacted us saying "I'd like to buy but the buy-it button doesn't do anything". We have found an ugly-ish workaround which is to have the user clear the cache of their Google Play Services app (and then reset the device). After they do that, they can get back to the sales page and all is well: the price is displayed and the button is enabled.
I'm thinking the step of clearing the cache is a clue to perhaps something that could/should be done within our app to better serve the customer (and, of course, we hope generates more revenue). It's early days for us in this Pro version but the three confirmed cases would be about 2% of the amount who have gotten through the purchase process...which doesn't sound like a lot, but who knows how many have fruitlessly pounded on the buy-it button and then just walked away.
On this, I'm going to file a ticket with Google and will keep all posted here if that yields anything. AND HERE IT IS:
As promised, here is Google's non-response. The link they provided was nice but it did not include the troubleshooting step that we found that works for us (clearing the Play Services cache). Here is Google's response: Hi,
Thanks for your patience.
I'm sorry to hear that some of your users are having a issue making a purchase within your app. Please note after looking into your issue, it looks like the issue is being caused by user end issues. As not all of your users are having the same issue, it would not be a issue within the coding. We are aware that cache and connectivity can cause issues within apps; and we have made a user help article to help guide users on how to fix these issues: https://support.google.com/googleplay/answer/1050566?hl=en
If you begin seeing more users with this issue, you can use log reporting to help determine the issue, you can use the Android logging system which provides a mechanism for collecting and viewing system debug output.
To extract the logs you will need to use "logcat" command as an adb command or directly in a shell prompt of your emulator or connected device:
adb logcat -v time > applog.txt
If you have any other questions about the Play Console, please let me know and I will be happy to assist you further.
Have a great day.
Regards,
Sabrina.
Google Play Developer Support.

how to cancel an in-app test purchase on android?

Until june 20th 2016 i was able to cancel test purchases done in my app.
Doing multiple in-app purchases (not consumable) from the same test account made it easy to develop and test the code without too much hazzle.
After 20th june 2016, the purchases did not show in my merchant account and
i was unable to do more than 1 purchase from my test account. All i got was the: "you already own this item" message.
I logged a request to the google developer support group and the answer was:
Beginning June 20, 2016, we changed test purchases for one-time in-app purchases (IAPs).
Previously, test purchases for one-time IAPs generated order IDs. Starting June 20, 2016, one-time IAPs do not generate official order IDs (if at all) and will not appear in the Merchant Center. This behavior already applies to subscription IAPs.
You can learn more about testing in-app billing in the Android Developers Help Center: https://developer.android.com/google/play/billing/billing_testing.html#testing-purchases
allright.. so i go to the mentioned link and theres a section there:
Canceling completed test purchases
which states:
Google Play accumulates completed test purchases for each user but does not pass them on to financial processing.
In some cases, you might want to manually cancel a test purchase to continue testing. To do so, open the app page in the Play Store. If the test purchase that you want to cancel is a subscription, you can also use the cancel() method of the Purchases.subscriptions API.
Important: The refund() and revoke() methods of the Purchases.subscriptions API don't support test purchases.
So I go to the app page in play store...and do what exactly? the webpage does not state what i am supposed to do there. anyone know?
it does say: you can also use the cancel() method of the Purchases.subscriptions API.
which indicates that using the cancel() method is not the only method.
How to solve this without adding additional code in my app?
I went into the main Google Play Console page and clicked on Order Management. Under that I was able to select all test purchases and Refund them. I'm the primary developer of the app so I have access. If you are a tester you'd probably have to contact the support team and request that they refund your order.
All managed in-app products are consumable.
as stated in the docs.
That means that you can consume an owned item instead of cancelling the purchase and buy it all over again.
I suggest querying the inventory at the app launch time:
mIabHelper.queryInventoryAsync(this);
You can then consume the owned item in the callback:
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Purchase purchase = inventory.getPurchase(MY_SKU);
boolean isBought = (purchase != null && verifyDeveloperPayload(purchase));
if (isBought) {
mIabHelper.consumeAsync(purchase, new OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
//Clear the purchase info from persistent storage
}
});
}
}
This is OK for testing the IAB flow but make sure to remove this code from the release version.
On Play Console, go to Developer Account -> Account Details, set the license testers (you are one by default)
Purchase items
Go to Order Management, choose those test orders, select: refund, but DON'T FORGET to check REVOKE when you are refunding. (I forgot to revoke, and now can't find a way to take them back)
Anyway, another test account will do.
I found a solution which isn't very convenient, but works. It seems like you can consume uncomsumable products and that way you can buy them again. I'm working with phonegap so I only have example code for the cordova-plugin-purchase plugin:
store.when("your.product.id").updated(product => {
if(product.owned) {
var transaction = product.transaction;
product.transaction = null;
store.inappbilling.consumePurchase(
function() { // success
alert("consume success");
},
function(err, code) { // error
alert("consume error " + err)
},
product.id,
transaction.id
);
}
});
The updated callback gets called when you call store.refresh() or buy the product. So depending on your use case you'd want to implement an additional method of checking when to consume the product.
I have no experience with the native Android in-app payments, but obviously you will be able to consume the products there as well.
Edit: Sorry, I just read that you didn't want to include additional code in your project. I don't think that's possible at the moment, but would like to keep my answer here because it might help other people trying to test in-app payments.
What worked for me was a combination of both:
Go to order management and refund
clear cache/data in Play Store app (as well as your app in you placed some shared prefs).
Also, in case you get an item already owned status, you can consume the purchase using the purchase token and calling billingClient.consumeAsync().
Didn't find a solution for this.
My workaround is simply remove the current test user from the test users list, make a real purchase, then cancel it using the merchant console.
The queryPurchaseHistoryAsync method still finds test orders I've made over the last year, despite having long ago consumed, refunded, and revoked them. One precaution I've found helpful is clearing the data in the Google Play Store app (settings/apps/google play store/storage/clear data). queryPurchaseHistoryAsync pulls obsolete purchase data from here, although only the non-networking (and completely unreliable I've found) queryPurchases is supposed to do this. You may have to add additional code to your app after all, but it doesn't have to be much.
With the dropping of support for Trivial Drive 2 (the link in the docs takes you to a '404 page does not exist' error, the github files are archived, and updating to billing:2.1.0 will give you a vending import compile error in the IabHelper class), answers to this popular question involving IabHelper might be considered obsolete. Billing is a lot simpler now if you follow the basic code pieces in the docs starting here https://developer.android.com/google/play/billing/billing_overview with no messy helper classes. One persistent issue is a 'Both methods have same erasure, yet neither overides the other' method clash error you may run into with this implementation, see my solution here 'Both methods have same erasure, yet neither overides the other' method clash error in SkuDetailsResponseListener().
Once you have the newest billing code implemented, you can create a hidden trigger in your production app to call queryPurchaseHistoryAsync to get a purchaseHistoryRecordList. Then call consumeAsync for each item in this list. Here is the barebones code to consume all test orders, allowing multiple tests of your nonconsumables:
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP,
new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult,
List<PurchaseHistoryRecord> purchaseHistoryRecordList){
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchaseHistoryRecordList != null) {
for (PurchaseHistoryRecord purchaserecord : purchaseHistoryRecordList) {
if(purchaserecord!=null){
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaserecord.getPurchaseToken())
.setDeveloperPayload(purchaserecord.getDeveloperPayload())
.build();
billingClient.consumeAsync(consumeParams, consumelistener);
}}}}});
For people using a way based on the new TrivialDriveKotlin, consumable products are consumed during the installation of the app in the method
handleConsumablePurchasesAsync
If your purchase is not consumable, you can make it consumable by adding the corresponding sku into CONSUMABLE_SKUS in the GameSku object. Exemple:
val CONSUMABLE_SKUS = listOf(GAS, PREMIUM_CAR)
Then uninstall your app from your device and install it again, and your non consumable purchase is available again. Fast and simply.
Of course, don't forget to remove your

Can't get items info in IabHelper - in app billing - Android

When I call IabHelper.QueryInventoryFinishedListener, It does not return any item info like
the following.
D/IabHelper(2233): Calling getPurchases with continuation token: null
D/IabHelper(2233): Owned items response: 0
D/IabHelper(2233): Continuation token: null
D/IabHelper(2233): Querying SKU details.
D/IabHelper(2233): queryPrices: nothing to do because there are no SKUs.
D/IabHelper(2233): Ending async operation: refresh inventory
I uploaded the singed apk onto GP and installed the same apk(same version and singed) in my device by using 'adb install release.apk' command.
Also I set up items on 'In-app Products' and Gmail accounts with testing access.
I wait several hours but it still says 'queryPrices: nothing to do because there are no SKUs.'.
I used 'in app billing' with another apk before,and It was fine.
I don't know why it doesn't work this time..
Sorry for my bad English.
Thanks for your time!
Just in case you stumble upon this try to:
Upload an apk file in alpha testing or beta testing tab (but do not upload an apk file in production tab if the app should not yet be available to the public)
Supply all the required information (screenshots, add test users, create and activate the in app products)
Set the app to released.
A few hours later Google Play should return the SKUs and there details.

How long after canceling test orders does it reflect in my app

I created a google app, I put it on the play store, and now I am testing inapp purchases with my test account. But then I cancel my purchase so to test how cancellations propagate. But cancellations are not reflected in the app. I can still use my test account with full access to the goods in question. I am using managed products.
It's a whole lot of code so it would be outrageous to post it all here. But I have reviewed my code over and over, and it's doing the right thing. In fact here is a snippet: when a user first launch the app, I check for outstanding purchase and change the state of the good in question as follows
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
boolean value = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase) && 0 == premiumPurchase.getPurchaseState());
setLockStateOfPremiumPurchase(this, value);
So I am now wondering if the cancellation has propagated to my app. In other words: how long does it take google to change their database so that when I check the inventory, it returns not purchased? Or perhaps I am doing this wrong: should I be checking for something else?
When I look at the original json purchase state is 0
After you cancelled the order in Google Wallet, you need to wait from 5 min to couple hours to get data synchronized back to your device. I'm not sure what it depends on. You just need to wait.
Sometimes it helps to restart the device after you received a confirmation e-mail. After restarting, the device registers itself in Google Play anew and purchases cache gets updated faster.

Categories

Resources