I'm having problems to handle in-app billing inventory on devices with multiple accounts.
DEVICE 1:
only one account on the device (user = X)
logged with account X on google play
logged with account X on google play games
purchase done (sku = remove_ads)
DEVICE 2:
three accounts on the device (users = X, Y, Z)
logged with account X on google play
logged with account X on google play games
querying inventory
The code to handle the query inventory response is this:
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (result.isFailure()) {
Log.d("billing", "inventory: failed (" + result.mMessage + ")");
return;
}
Log.d("billing", "inventory: remove_ads purchased=" + inventory.hasPurchase("remove_ads");
}
The logcat output on DEVICE 1 is:
inventory: remove_ads purchased=true
And the logcat output on DEVICE 2 is:
inventory: remove_ads purchased=false
What am I missing? Thanks!
It was my mistake, this is what happened:
Test purchase done using DEVICE 1 on the published version (release signature), purchase done successfully.
I started working on the next version (debug signature) using DEVICE 1, but as the purchase was done on this device, google play billing cached the response and it returned true when I queried the inventory.
After a while I started working using DEVICE 2, then the google play billing was not returning anything when I queried the inventory, because the purchase was not cached and the APK signature didn't match.
It all started working when I signed the APK with the release signature and tested it on DEVICE 2.
Conclusion: pay more attention with the signature and beware of cached responses
Related
I know as of May 2015 google changed how they handle your app in draft mode -
In a few cases, you can test Google Play functionality with an unpublished app. For example, you can test an unpublished app's in-app billing support by using static responses, special reserved product IDs that always return a specific result (like "purchased" or "refunded").
My question is can you use IabHelper.queryInventoryAsync() on your prouducts in draft mode. I know to do a purchase you have to use reserved product id but what about query? This question stems from the fact that I am returning null when I get Inventory back from my Listener.
final String SKU_VERBAL_HINT = "verbal_00.thisthat";
// compute your public key and store it in base64EncodedPublicKey
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d("Issue", "Problem setting up In-app Billing: " + result);
}else{
//List<String> additionalSkuList = new ArrayList<String>();
//additionalSkuList.add(SKU_VERBAL_HINT);
String[] moreSkus = {SKU_VERBAL_HINT};
mHelper.queryInventoryAsync(true, Arrays.asList(moreSkus),
mQueryFinishedListener);
Log.d("Issue", "No Problem setting up in-app billing " + result);
}
}
});
mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
if (result.isFailure()) {
// handle error
return;
}
**-------->FAILS HERE, null inventory** String verbalPrice =
inventory.getSkuDetails(SKU_VERBAL_HINT).getPrice();
String verbal2 = verbalPrice;
// update the UI
}
};
Check out this answer. Draft apps are not supported.
BlockquoteDraft Apps are No Longer Supported
Previously, you could publish a "draft" version of your app for testing. This functionality is no longer supported. Instead, there are two ways you can test how a pre-release app functions on the Google Play store:
You can publish an app to the alpha or beta distribution channels. This makes the app available on the Google Play store, but only to the testers you put on a "whitelist".
In a few cases, you can test Google Play functionality with an unpublished app. For example, you can test an unpublished app's in-app billing support by using static responses, special reserved product IDs that always return a specific result (like "purchased" or "refunded").
Blockquote
https://stackoverflow.com/a/24866513/2192015
From this is sounds like the app has to be published to the alpha or beta channel to do testing.
http://developer.android.com/google/play/billing/billing_testing.html#draft_apps
I am new for in-app purchase in android, I have implemented using this tutorial
I have uploaded signed APK with License Key for the app to Alpha Testing, have created In-app products, Included product ID of the in-app product I have created in code.
Google play returns "The item you were attempting to purchase could not be found"
Following is the code I have used:
public static final String TAG = "com.some.name";
IabHelper mHelper;
static final String ITEM_SKU = "com.inapp.ID";
#Override
protected void onStart() {
super.onStart();
String base64EncodedPublicKey = "License";
mHelper = new IabHelper(MainScreenActivity.this, base64EncodedPublicKey);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
Log.d(TAG, "In-app Billing setup failed: " + result);
} else {
Log.d(TAG, "In-app Billing is set up OK");
}
}
});
on buy click the following method is called:
public void buyClick(View view) {
Log.e("Buy Clicked", "----------------");
mHelper.launchPurchaseFlow(MainScreenActivity.this, ITEM_SKU, 10001,
mPurchaseFinishedListener, "mypurchasetoken");
}
}
Can someone help me with the steps to be followed for successful in-app purchase and what are all the reason for the ERROR message "The item you were attempting to purchase could not be found"
Thanks in advance!
In-app Google play process(Rough-outline):
1. Create APK with License key provided(must) & with your in-app product ID correctly.
2. Upload it to Alpha Testing (For testing).
3. Create in-app Purchase products.
4. Fill all required fields, Upload required screenshots, App graphics wallpaper, etc.,
5. Publish the app, It will take about 24 hours max to get published.
6. Test/Check purchase using the test accounts added.(In the testing APK, Purchase button Trigger with exact product ID you created in Google Play In-app product)
Problem I faced or mistake/shortcoming I did are,
1. I tested purchase before publishing the app i.e when the app was in Draft. So it resulted the item you are trying to purchase is not available.
2. I have added non gmail accounts for testing, which resulted some odd error(Unable to connect to server).
When the user buys a product, I use a webservice to verify the purchase,to protect from replay attacks, I add a (nonce) developer payload to the purchase.
It works as expected.
But what about restoring transactions?
I can get the signed data and signature and every other info from the local inventory(by calling queryPurchases() in IabHelper), but I can't set a new developer payload anywhere, so I can't verify it on my webservice.
How do I do a restore transactions safely?
Help would be greatly appreciated...
edit: should I just stick to iab v2 for restoring transactions?
So, as far as I know, this is an unresolved security issue which compromises the security of in app billing api v3.
There is no way of securely (verifying with a webservice) restoring a purchase in in app billing api v3.
As of this writing, the Google Play Billing Library sample App retrieves the developer payload for verification when querying for purchased items. The code looks like this:
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
.
.
.
// 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"));
.
.
.
}
I'm working on a In App Store on my app, I used AndroidBillingLibrary by robotmedia,
when I purchase android.test.purchased using the library the response is OK, all the data I need is in there.
The problem is, when I switched to Android In-App Billing v3 this is all received from the response, no signatures.
{"packageName":"com.my.sampleapp","orderId":"transactionId.android.test.purchased","productId":"android.test.purchased","developerPayload":"","purchaseTime":0,"purchaseState":0,"purchaseToken":"inapp:com.my.sampleapp:android.test.purchased"}
I followed exactly this sample https://developer.android.com/training/in-app-billing/preparing-iab-app.html#GetSample but there's no signatures. I even run the given sample app by Google but no luck.
I put my Base64-encoded RSA public key correctly in
mHelper = new IabHelper(this, myPublicKey);
and this is my purchase code mHelper.launchPurchaseFlow(this, itempackage, 10001, mPurchaseFinishedListener);
OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.i("Billing", "purchasing: " + result.getMessage());
if (result.isFailure()) {
Log.i("Billing", "Error purchasing: " + result);
return;
} else if (purchase.getSku().equals("android.test.purchased")) {
Log.i("Billing - signature", purchase.getSignature());
consumeItems();
} else {
Log.i("Billing", "Error purchasing: " + result);
}
}
};
Somehow my mPurchaseFinishedListener is not receiving anything after the purchase but the protected void onActivityResult(int requestCode, int resultCode, Intent data) is receiving something but there's no signature.
Any solutions to this?
It's weird that v2 is receiving signatures and v3 is not.
You don't get signatures for the test IDs, android.test.purchased, etc.
You'll receive signatures with real purchases.
As was said by Rawkode, you no longer get signatures for test purchases (android.test.*). I took the dive and uploaded my app to the market place (just didn't publish it) using my real products.
Low and behold signatures started to be returned! I recommend amending any server side validation you are using to skip the signature check when the data contains an android.test.* item id.
What you mean by Signatures? Is that Subscriptions? Or an in-app item (consumable or not)?
Because in-App Billing v3 does not support subscriptions at the moment, if you want that feature you have to use v2 (https://developer.android.com/google/play/billing/billing_overview.html). Also, make sure your item is marked as "a managed item" on your developer console (for Billing v3).
Sorry if I misunderstood. Also, try enabling the debug mode on your IabHelper instance, this will provide you a better overview of your problem.
mHelper.enableDebugLogging(true, "YOURTAG");
I suggest taking a look at the implementation guide on the Developers website:
https://developer.android.com/google/play/billing/billing_integrate.html
There's also sample code there.
I'm currently testing my InApp billing mechanism (using the InApp Billing version 3 API, therefore taking the TrivialDrive example as reference).
I have one managed item, which is upgrade to premium version.
Now, purchasing the item with my test account works, but when I do a cancellation of the entire order in Google checkout afterwards, my code still tells me that the item is purchased an therefore grants the premium features.
Here is how I check for the purchase in my MainActivity. I do not save the purchase state locally somewhere, as I understood that the with the billing API v3, you can query for purchases ad hoc as needed.
#Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
iabHelper = new IabHelper(this, Helper.getPKey());
iabHelper.enableDebugLogging(true);
iabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
#Override
public void onIabSetupFinished(IabResult result) {
Log.d("IAB", "SETUP FINISHED");
if(!result.isSuccess())
{
Log.d("IAB", "SETUP NOT OK");
return;
}
else
Log.d("IAB", "SETUP OK");
iabHelper.queryInventoryAsync(
new QueryInventoryFinishedListener() {
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
Log.d("IAB", "Query inventory finished.");
if (result.isFailure()) {
Log.d("IAB","Failed to query inventory: " + result);
return;
}
Log.d("IAB", "Query inventory was successful.");
// Do we have the premium upgrade?
boolean mIsPremium = inv.hasPurchase(Helper.premiumSku);
Purchase p = inv.getPurchase(Helper.premiumSku);
if(p != null)
Log.d("IAB PURCHASE STATE", IabHelper.getResponseDesc(p.getPurchaseState()));
else
Log.d("IAB PURCHASE STATE", "Purchase is null");
Log.d("IAB", "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
}
}
);
}
});
}
I keep getting getPurchaseState = 0, which means is Purchased, even one hour after I cancelled the order. Why?
After having waited for about 12 hours and having tried everything suggested here, 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
I know this is a year old, but none of the answers/tips presented helped me so I thought I would add my solution.
First, I was experiencing the same issue. Namely, made a test purchase, cancelled it, still received a purchase state indicating valid purchase.
What I forgot was that I recently switched the 'License Test Response' field on the settings pane of the Google Play Developer Console from 'RESPOND_NORMALLY' to 'LICENSED'
After switching it back to 'RESPOND_NORMALLY', the purchase state of the cancelled purchase was correctly returned as such.
So, you might want to check that before you try waiting for days
Step 1. Wait approximately 10 minutes; Until you see the "cancelled order" was delivered. in your google wallet.
Sep 15 11:28 AM Cancelled The order was delivered.
Sep 15 11:18 AM Cancelled You cancelled this order. Reason: Customer request to cancel.
Step 2. Logout your test google account on the device and then re-login.
At least that solved my problem.
This problem also occures when using the app on another device with the same account. The item is not received as purchased until the device is restarted, even after hours. If trying to purchase again, the google wallet dialog says "item already owned". The return code from the iabHelper still is "user cancelled" cause the real response from the purchase activity is not given back, just written in the debug log.
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
So its not possible to react to this google billing dialog cause we are always getting the same result IABHELPER_USER_CANCELED, even if the dialog said "item already owned".
Edit:
I fix it with this:
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
if(responseCode == 7)
result = new IabResult(BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED, "Item already owned.");
else
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
So now if the response from the billing dialog is 7 as "Item already owned" i report it back to my listener.
What you can use is the autoRenewing field of the purchase data. According to the documentation:
autoRenewing: Indicates whether the subscription renews automatically. If true, the subscription is active, and will automatically renew on the next billing date. If false, indicates that the user has canceled the subscription.
And this field get updated immediately after the cancellation.
if I see correctly the reference code in the trivialdrivesample is wrong, which would be a shame for the official reference project for in app billing.
if purchase == null it just means it has never been purchased. To get the real information you have to call
purchase.getPurchaseState()
according to here
purchaseState The purchase state of the order. Possible values are 0 (purchased), 1 (canceled), 2 (refunded), or 3 (expired, for subscription purchases only).
It’s already well answered in the Google official docs. Copying the words here.
When the user cancels a subscription, Google Play does not offer a refund for the current billing cycle. Instead, it allows the user to have access to the cancelled subscription until the end of the current billing cycle, at which time it terminates the subscription. For example, if a user purchases a monthly subscription and cancels it on the 15th day of the cycle, Google Play will consider the subscription valid until the end of the 30th day (or other day, depending on the month).
That should explain it all. getPurchase() will still return the purchase data till the end of the current subscription cycle.
I found the following section in the documentation (IAB API v2), but I am not sure if this can be used for IAB API v3. The broadcast might still be sent though.
"... your application can receive an IN_APP_NOTIFY broadcast intent when Google Play receives a refund notification from Google Wallet. In this case, Google Play sends an IN_APP_NOTIFY message to your application. Your application can handle this message the same way it handles responses from an application-initiated REQUEST_PURCHASE message so that ultimately your application receives a PURCHASE_STATE_CHANGED message that includes information about the item that has been refunded. The refund information is included in the JSON string that accompanies the PURCHASE_STATE_CHANGED broadcast intent. Also, the purchaseState field in the JSON string is set to 2."
from: http://developer.android.com/google/play/billing/v2/api.html#billing-action-notify
I noticed the exact same thing:
Making an in-app purchase with a test account -> refunding the purchase with removing access -> getPurchaseState still returns Purchased (even after relogin and restart) and thus the access to the premium features is not removed in my app.
But when I tested the same thing with a real purchase:
Customer made a real purchase -> a couple of weeks later I refunded it -> Customer did not have access to the premium features of my app anymore.
So could it be, that this is only a problem for test purchases?
As of 2022 March 06, Billing Client version 4 API, you might still need to wait a few hours after cancellation until a purchased item is cancelled in Google Play.
Sometimes I saw cleaning the project also helps (In Android Studio: Build menu > Clear project)
However, in case of subscriptions you can also check and adjust the "Grace period" for your product in Google Play Console: