How can I use "refunded" state with new Google In App v3?

Can I still access this state with the new Google InApp v3 version?

Hi Daniela,

Please see this guide:

http://docs.coronalabs.com/guide/monetization/IAP/index.html#handling-refunds

Brent

I have tested it like this:

I’ve bought the V2 non-consumable in my game. Then I deleted the app and installed the updated version with V3. I used RESTORE to restore the non-consumable content. It worked fine.

Then I deleted the app again and cancelled the Google Wallet transaction payment (this should trigger the REFUNDED state, right!?)

I waited some hours and installed the app (with V3) again. And I still could use RESTORE to unlock all content (the non-consumable). I waited some days and still could use RESTORE. But this should not be possible, right?

Do I have to use the consume function with the refunded state somehow? I thought Google is setting back the transaction when a transaction is getting cancelled!?

Can anyone confirm this or tell me how I have to use the RESTORE and REFUNDED stats with V3?

One more question: How are you using the REFUNDED state for non-consumable content which is already unlocked in the app? So a user has unlocked the content and then he cancels the payment in Google Wallet. That means the game already is unlocked, so how do I look for the refunded state then when the user is playing his unlocked version?

Hi Daniela,

Please see this guide:

http://docs.coronalabs.com/guide/monetization/IAP/index.html#handling-refunds

Brent

I have tested it like this:

I’ve bought the V2 non-consumable in my game. Then I deleted the app and installed the updated version with V3. I used RESTORE to restore the non-consumable content. It worked fine.

Then I deleted the app again and cancelled the Google Wallet transaction payment (this should trigger the REFUNDED state, right!?)

I waited some hours and installed the app (with V3) again. And I still could use RESTORE to unlock all content (the non-consumable). I waited some days and still could use RESTORE. But this should not be possible, right?

Do I have to use the consume function with the refunded state somehow? I thought Google is setting back the transaction when a transaction is getting cancelled!?

Can anyone confirm this or tell me how I have to use the RESTORE and REFUNDED stats with V3?

One more question: How are you using the REFUNDED state for non-consumable content which is already unlocked in the app? So a user has unlocked the content and then he cancels the payment in Google Wallet. That means the game already is unlocked, so how do I look for the refunded state then when the user is playing his unlocked version?

I have spent hours researching this on this forum and others, read through the documentation several times, to no avail. I am also using v3 with my actual (and only) product id through beta publishing. Everything works correctly, including test purchasing, and restoring the purchase after an uninstall. However, when I cancel the test transaction in the merchant account, restore() never gets state “refunded” in the listener, it always reports “purchased”.  What are we missing?

Another general question, instead of only calling restore() at app start-up when the local payment variable is false (as suggested by Rob in his tutorial), I call restore() every time the app starts regardless of the payment variable state so I can lock the program up if there was a refund. Is that the way one would want to do that? I feel like I read somewhere that Google will suspend an app if there are a large amount of restore() calls, but wherever I saw that, it was a forum post, so the person could be wrong.

I decided not to post code since this is a general question, nothing specific to my implementation.

Thanks in advance!

The only thing coming to mind is the following info which you also can find in the link above:

In the Google Play Marketplace, there is no “restored” state for items. All purchased items will be grouped under the “purchased” state. When you do a restore, you should clear all purchases saved to file/database — except for consumable purchases — and treat the returned restored purchases as normal purchases.

Hi d.mach. Yes you are correct, google has no specific “restored” state for the callback, just “purchased”, which is all well and good. But my second question, to which you are referring, is whether or not it is kosher to call the restore() FUNCTION every time the app starts in an attempt to catch the “refunded” state. And even if it is ok with google’s TOS, is that an advisable practice to try to catch the “refunded” state by specifically calling restore()? Or does the “refunded” state come in at any given time after the store is initialized with init(), making the restore() call redundant?

Were you ever able to solve refund handling for your app with IAP v3? I hope apple never decides to implement refunds for IAP’s!

You actually have to buy the item, then later go into Google Wallet and cancel the purchase if you want to get a refunded state.  Just canceling the dialog while starting a purchase wont trigger it.

Rob

I think you misunderstood my post. I am indeed cancelling (refunding) the transaction in the developer merchant account (wallet.google.com), which then refunds the test purchase, and the tester account gets an email stating it was refunded. I am not referring to simply canceling the payment dialog as you suggested I was.

So with that being cleared up, can you address the question?

EDIT: I’m also particularly curious to find out an answer to my other question above about restore() usage.

Rob or Brent, any insight?

@csavalas - This is a known widespread issue with the Google API, nothing to do with Corona.  The problem is that v3 locally stores purchase state in cache, and it can take some time for it to update on the device.  Some users reported it taking up to 30 days to finally change to Refunded state on device.  Unfortunately there isn’t much that can be done until Google corrects the issue.  

Thanks for answering Jon. Where did you come across that info? That sounds like a nightmare for testing…

My other question: is it correct procedure to call restore() every time the app starts to try to catch the “refunded” state in the callback, or is “refunded” pushed to the callback at any given time after calling init(), making the restore call at launch unnecessary?

I hope it’s the latter, because calling restore() at every app launch seems clunky.

If you search around stackoverflow you will see people talking about it.  

My other question: is it correct procedure to call restore() every time the app starts to try to catch the “refunded” state in the callback, or is “refunded” pushed to the callback at any given time after calling init(), making the restore call at launch unnecessary?

 

I hope it’s the latter, because calling restore() at every app launch seems clunky.

The state of each item should automatically update in the callback you use when you initialize the store.  To be safe you should look for the item(s) in the Purchased state, as this returns all items previously purchased.  Items cancelled/refunded/consumed (in theory) will not be tagged “purchased”.  

That makes logical sense to me, so no need to call restore() every time, but at least call init() every time. In Rob’s tutorial, (relevant excerpt below), he doesn’t even initialize the store unless your the local isPaid variable in your app is false. It would be nice to hear his thoughts on this too, as it seems to me that the app should call init() every time it starts for Android devices, to handle things like refunds that can be pushed to the device at any time. Thanks again Jon

 if store.availableStores.google and not mySettings.isPaid then timer.performWithDelay( 1000, function() store.init( "google", transactionCallback ); restoring = true; store.restore(); end ) end

Rob? :slight_smile:

The code I took that example from didn’t really consider refunds. You could simply remove that check and always init.

Rob

Thanks for the reply Rob, but now the more I think about it, I think calling restore every time may be necessary after all, since init() does not trigger the callback to get the ownership state of items. After Jon suggested to be safe by checking if items are indeed “purchased” at every startup rather than relying on the “refunded” state ever coming in, out of curiosity I tried to make the purchase of my non consumable “pro version” upgrade item on the device that still hasn’t gotten the “refunded” state for that item, so the app is still in the “paid” state. Lo and behold, it was a valid, purchaseable item, meaning that google is not falsely reporting that I still own it, it just never sent out the “refunded” state, so it never locked back up. So that being said, here are implications/questions I see following Jon’s advice:

  1. Do I call restore() every app start, and check if I get “purchased” back for the item, and if not, lock the app up?

  2. In the case that a PAID user starts the program at some point with no internet, will it still report back “purchased” on the restore() call, since that was the last known state of the item? Or will it falsely lock things up?

  3. Will calling restore at every startup pi*s off google?

  4. Is this seriously the best way to handle it? lol

Cheers

According to Google the purchase state is cached locally, so the device doesn’t need to depend on internet connection.  However I will tell you in my experience using another platform (not Corona), I had a number of users of varying devices report that the “pro” app locked back up when there was no internet connection, and successfully unlock when there was.  After hours of research I couldn’t find a reasonable solution, so instead I saved the purchase state locally myself (like in iOS).  Yes this does open you up to security issues, but 99% of users won’t bother (at least not with my apps).  I was not able to replicate the issue these users were having a several different devices I own.

After doing some further research it seems that Google Play Store does not send the refund state to the device until it receives the official refund confirmation from Google Merchant.  I am wondering if the significant delay (weeks) from Merchant is because they have to wait on the banks to confirm the transaction was returned?  Who knows…

Anyhow, if I were you I would just call restore() every time on Android.  

  1. Google, yes you should call store.restore() every time the app starts up or comes back from suspended. Apple, no, you must have a “restore” button and allow the user to choose when to restore.

  2. If you don’t have internet I don’t believe you will get any results from store.restore(). Your app should have saved the last known purchase state and should operate from those settings.

  3. No

  4. Yes, for Google.

Thanks again to both of you.

So it seems going down this route, I’d just eliminate handling of “refunded” altogether, but thinking it through some more, let’s say the user did indeed get a refund from me, and the item isn’t owned anymore. Calling restore() is not going to even trigger the callback if the user doesn’t own anything, correct? So I can’t simply add a blanket statement like ( if tstate ~= purchased then lock it up…) at the end of the listener. It’ would just be a silent call. And since the listener isn’t triggered for restore() with no internet connection either, I don’t see a way to differentiate between those two different conditions.

I’m beginning to think I may have to just let refunds “slide”, as this is driving me nuts! haha