56

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?

Toni Kanoni
  • 2,265
  • 4
  • 23
  • 29
  • 4
    I don't have the answer but can confirm I am also seeing this problem.
    1. do purchase
    2. go into google checkout and cancel order
    3. observe email is sent confirming order cancelled
    4. subsequent requests give PurchasedState = 0 (ie purchased)
    Perhaps it takes a while for the changes to filter through? Also see the following discussion in the comments section of this page, someone else reports the same problem. https://plus.google.com/u/1/+AndroidDevelopers/posts/R8DKwZDsz5m
    – Steve Jan 13 '13 at 18:11
  • After waiting the whole day for something to happen, I made another observation: try to delete the data for the play store app (not the cache but the data section of the app) and after that.. the purchase is gone... Seems that there's maybe a caching bug in the IAP v3 API? Can you verify this? – Toni Kanoni Jan 13 '13 at 19:36
  • 1
    I see a similar issue. I use IAP v3 API and I buy an item and I cancel the order in google checkout. But when I try to buy it again, IAP v3 returns "7:Item Already Owned". I'm using the trivialdrive sample also. The inventory shows the purchasestate as "0" for the item and the purchasedate as 1358439773 is 01/17/2013 4:22pm GMT. which is exactly the date and time that google checkout shows as the cancellation date. – Ralph Yozzo Jan 18 '13 at 15:27
  • 3
    This is tangential to your question, but I just wanted to point out that using IabHelper.getResponseDesc(p.getPurchaseState()) to obtain a description of the purchase state is not appropriate, since getResponseDesc() is for retrieving a description of a response code, rather than a description of a purchase state indicator. For the latter, per http://developer.android.com/google/play/billing/billing_reference.html, "Possible values are 0 (purchased), 1 (canceled), or 2 (refunded)." These constants don't seem to have been defined or even used in the original TrivialDrive sample code. – Carl Jan 19 '13 at 02:01
  • @Carl: Thanks for the tipp! Anyway, it seems that I am not the only one to experience these problems with the v3 API, and we would have to wait for Google to fix this. Otherwise, no solution? (except maybe using the IAP v2 API, which is way more complex than the v3... so I won't downgrade and live with the current bug). – Toni Kanoni Jan 19 '13 at 22:11
  • 2
    @Toni: I have now tested canceling an order with a modified version of the TrivialDrive sample app and had the same problem. I went into Checkout and canceled the order that I had placed from my own test account (not the developer account) and fifteen hours later the app still reported a purchase type of 0 (Purchased). This is on Nexus 7 with Google Play Store version 3.10.10. After this, I cleared the Google Play app's cache, stopped the TrivialDrive app and started it again. Still no change. Then, installed TrivialDrive for the first time ever on my Nexus One phone; still purchased. – Carl Jan 20 '13 at 03:43
  • 1
    This same issue is spoken of here: https://plus.google.com/+AndroidDevelopers/posts/R8DKwZDsz5m although the reporter says that the problem went away for him after updating from 3.10.9 (so I would imagine he updated to 3.10.10, the same version that still gives me trouble). Maybe we have to wait LONGER than 15 hours? Whatever. Like you, it is not that big a deal as you can always refuse to refund or just accept that someone may get your app free by requesting one. Not many people will do that, we hope; if they do, you can refuse if it becomes a problem. – Carl Jan 20 '13 at 03:45
  • 5
    OK, the answer is that it can take up to 72 hours. See this other SO page's accepted answer: http://stackoverflow.com/questions/13861625/in-app-billing-v3-dont-detect-refund – Carl Jan 20 '13 at 03:59
  • @Carl: Ah, that's good to know... I did not wait for 3 days, maybe it really needs that much time. Thanks for pointing to that other thread! – Toni Kanoni Jan 20 '13 at 14:55
  • OTOH, it has now been five days since I canceled the order, and my Nexus 7 is still reading purchased. Some of my other devices are correctly reading refunded, so one might say that with TrivialDrive, YMMV. Personally, I consider this behavior flaky - even a straight 72 hour maximum would be "flaky" in my opinion. Fortunately, it is not critical to my app. – Carl Jan 25 '13 at 08:51
  • were you able to found the solution for this. if you can, please help me on this. – skygeek Feb 27 '13 at 11:52
  • Hello friends I have same problem,in my I got the purchase failed with Responsecode=7 U have Already own this item, so i can't do the repurchase this item again. i have only one option to restore this item again. so please help me – Zala Janaksinh Dec 16 '13 at 10:09

11 Answers11

19

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

lalit_sam
  • 251
  • 3
  • 6
  • Thanks! This in combination with Jannon's answer (https://stackoverflow.com/questions/14303850/android-in-app-billing-purchase-state-stays-purchased-after-order-cancelation#answer-21053670) lead me to resolve te issue. – Fernando Jascovich Jun 01 '17 at 14:06
  • 13
    I wouldn't call this "a solution" if the problem is reproducible by your app users, they are not going to use adb to clear their purchases that way even if they know how to do it. The ability of enjoying an inapp purchase after getting a refund is not an issue from the point of view of the user... – Fran Marzoa Aug 29 '17 at 14:57
  • 1
    The solves the cached entitlements issue during development. The solution to the user side of this problem is to use the Google Play Developer API & service account to look directly at the purchase status of the product and use that as the basis for enabling entitlements. This should point you in the right direction: https://stackoverflow.com/questions/11115381/unable-to-get-the-subscription-information-from-google-play-android-developer-ap – ibuprofane Jul 22 '20 at 22:05
8

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

Jannon
  • 113
  • 1
  • 7
7

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.

Community
  • 1
  • 1
pinux
  • 4,468
  • 2
  • 18
  • 7
  • 2
    Reboot did it for me, too. But it seems like the user keeps the purchase as long as he doesnt reboot his device, which is never for a lot of people. – Kedu Oct 31 '14 at 13:31
  • 1
    I tried re-login, reboot, Cleared playstore app and service, re-install app and also downloaded app with same account on new device. But still showing me purchase stats say 0(purchased). – Bipin Vayalu Apr 01 '15 at 17:49
  • For what it's worth, the only thing that worked for me was clearing data (not just the cache) on the Google Play Store app. I'm on a nexus 7 android version 6.0.1 – sauce Jan 15 '16 at 15:57
  • 2
    seriously? we are talking about real life problems when you distribute your app, you can ask users to follow those steps! – Emil May 16 '18 at 00:02
6

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.

Kedu
  • 1,350
  • 14
  • 26
6

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.

Elyess Abouda
  • 659
  • 12
  • 20
  • 2
    What if it is not a subscription? – Fran Marzoa Aug 29 '17 at 14:52
  • 1
    it is returned false, right after cancellation is committed but it doesnt mean that you should downgrade your user to unpaid version because till end of the expiry date, you should still provide full version for the cancelled subscription. problem is that, you dont know the expiry date as google api is not returning. – Emil May 15 '18 at 23:55
  • Oh thank you soo much :) I am using subscriptions and give it a try – Rebar Nov 18 '20 at 13:56
1

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).

tmanthey
  • 4,547
  • 6
  • 35
  • 42
  • 3
    according to v3 documentation, there is no 3 (expiry). Does that mean we can no longer check subscription status via this method? http://developer.android.com/google/play/billing/billing_reference.html – neobie Aug 01 '14 at 00:26
  • Currently, this information is located here: https://developers.google.com/android-publisher/api-ref/purchases/products – frankish May 22 '19 at 13:00
1

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.

dakab
  • 5,379
  • 9
  • 43
  • 67
user1938357
  • 1,466
  • 3
  • 20
  • 33
  • 14
    I believe the OP is talking about managed purchases, not subscriptions. – Mark Jan 15 '14 at 06:11
  • 1
    Nobody said he were talking about a subscription. I have the very same problem with a managed purchase. There is no "subscription cycle" with these at all. – Fran Marzoa Aug 29 '17 at 14:55
  • Not necessarily. My (test) subscription was cancelled long ago and is still marked as "owned" in the IabHelper, although the valid period is exceeded. – Bevor Mar 25 '18 at 13:15
  • yes it is a problem with subscriptions as well. Google doc has a false information. if you query getPurchase cancelled subscriptions are returned even after expiry date. – Emil May 15 '18 at 23:58
0

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

mvandillen
  • 904
  • 10
  • 17
0

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?

Nickrey
  • 11
  • 1
0

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: enter image description here

ZP007
  • 582
  • 7
  • 12
0

I also faced the same issue. So instead of

purchase.purchaseState == Purchase.PurchaseState.PURCHASED

I parsed the JSON:

JSONObject(purchase.originalJson).optInt("purchaseState") == Purchase.PurchaseState.PURCHASED

The results are satisfactory!

coderGtm
  • 91
  • 1
  • 10