The default state of your app is to be locked (free). At every start you check restore for purchased items. If the Pro Upgrade is purchased then you run the unlock code. If not the app remains locked. This takes care of refunds automatically because if they were refunded then purchase = false and app remains locked.
A refund is from something that is owned, so store.restore() will trigger and you can undo anything refunded. But for Android @JonPM’s method works, assume its locked and let the purchased items unlock as needed. The only draw back to this method is if they are off line, they don’t get their benefits.
Rob
Again let me stress that I had a few select users who’s app would lock up while offline, and I myself could never replicate the issue (meaning on my devices the app remained unlocked while offline). Per Google purchase states are stored locally. I would suggest csavalas try it the proper way and assume that there is no difference if the device is online/offline when restoring. Only if you actually have issues then try other solutions (like I did with storing purchase states locally myself).
Jon:
By “proper way”, do you mean the “standard way” in that I would leave it unlocked indefinitely after a user purchases the upgrade, and then only lock up if I ever get a “refunded” state at some point?
Because I am more intrigued by your idea of having the default state being locked, and then relying on restore() to unlock the app when the listener receives “purchased” on the restore call. That being said, I already store the purchase state locally, but if I’m resetting the default state to locked every time anyway, I don’t understand how that ended up being a workaround for your handful of users that where experiencing issues.
Rob mentioned handling suspend/resume as well. How do you handle this? Do you set your local state to locked on every suspend? It seems to me that could get messy since the user could very well be in the middle of something. And have you also decided to not even “handle” the “refunded” state? It seems to me “refunded” becomes irrelevant if you’re using your method.
Rob:
It is my understanding that the “refunded” state is pushed to the device repeatedly until your app handles it with finishtransaction, and once that happens, then “refunded” no longer will be present in the callback. Am I mistaken?
General:
While I don’t want people being able to cheat the app by getting a refund and continuing to use the pro version, it seems like it may be the lesser of two evils, the other being genuine paid users experiencing issues with the app. I don’t know…
I can’t say for certain, but that would make sense.
Rob
No sorry, what I meant was you should call restore() every launch without worrying if the device is online or offline. Because in theory (and according to Google) it should work properly either way.
I still recommend your app defaults to a locked state, and only unlock if purchase is found. This way your refunds are handled automatically. Although as previously discussed it might take a while for the refund state to trigger, and the purchase to be set to false. It’s also worth noting that for In-App Purchases, you the developer have to manually issue refunds. Users cannot obtain refunds for IAPs by any other method.
As for suspend/resume, there is nothing needed there. What I do is set a global variable (isUnlocked) to false by default, and only set to true through the restore() or purchase() methods. This variable will persist while the app in use or in background. I think it is easier to explain through a diagram:
User Opens App >
isUnlocked is false by default >
restore() or purchase() runs, purchase is true, sets isUnlocked to true >
Your app’s items are all unlocked >
User suspends app (hits home button) >
User resumes app (assume the OS hasn’t killed it), isUnlocked retains true state >
User closes app, OS kills app >
Next app launch starts this process over again.
Again, I wouldn’t put too much stress on refunded state. For an IAP, the user has to find you, email you, request the refund, and you decide whether or not to issue it (which you always should IMO). This won’t happen very often.
Thanks for the reply. So as I suspected, on your suspend/resume you’re not checking for refunds as Rob was suggesting, just on app startup. What was your eventual resolution for the users experiencing the locking issue while not connected to the net?
On a personal note, what kind of apps do you develop? I’d love to check them out.
Rob, last question on this. Is it necessary to call restore() to catch a “refunded” coming in, or will it come in any time randomly after an init(). Since it takes so long for “refunded” to come in, I can’t really beta test it, but I’d at least like to try to correctly account for it in the user flow.
Thanks!
Again, if I were you I would completely ignore the “refunded” state, it’s useless. As the developer, all you really care about is is this item purchased? If purchased, app is unlocked, if not app remains locked. There is no concern to the user flow because as I stated earlier a user cannot get a refund for an IAP from within your app nor the Play Store.
The only way I can see a refund being potentially useful is if you issue the user a refund on a consumable item (let’s say they bought 100 coins). In that case you can test for the refund state and subtract 100 coins from their in-app balance.
I was asking Rob about the “refunded” thing because I was debating doing a hybrid of your method and the “refunded” method. From what I understand of your method, if the user never actually turns their phone off or never kills the app manually, it will never lock itself back up since you’re allowing the unlocked state to persist through suspend/resume. I’m considering having the default state of the app be locked at startup before init() and restore() are called. And then in the case that the users gets refunded, but never turns their phone off/kills the app, I’d still like to catch refunded() at some point, hence my question to Rob about how “refunded” comes in. (Or to you if you know the answer lol).
I am reasonably comfortable about setting the default state to locked at every startup, but not really comfortable with locking and unlocking it at every “applicationSuspend”/“applicationResume”, for a variety of reasons. So if “refunded” indeed comes in only when restore() is called, then I’ll simply call restore() every “applicationResume” to try to catch it. I only want to call restore() if necessary though.
The app will eventually be killed by the OS (to free up memory). It will not remain in suspended state indefinitely.
I personally would not advise locking/unlocking at every suspend/resume state.
[quote name=“JonPM” post=“314776” timestamp=“1450747167”]The app will eventually be killed by the OS (to free up memory). It will not remain in suspended state indefinitely. I personally would not advise locking/unlocking at every suspend/resume state. [/quote] Interesting, I knew apps COULD be killed by the OS, but not that they definitely WOULD be at some point. So looks like I’ll take the plunge with your idea. Were you ever able to figure a workaround for your select users. I already have a saved local variable for the paid state, but I can’t see that helping if I’m locking it at startup anyway. You’ve been a lot of help, I’d like to download one of your apps, I promise I won’t ask for a refund ; )
I’ve made some medical apps and one word game called Word Crumbs. I suffer from having too many project ideas and almost zero time
I will send you PM regarding your question.
Rob, I know the thread has gotten fragmented, but I still would love to know if it is necessary to call restore to get “refunded”, or if it comes in at any given time after an init() call. Thanks
You can ignore refunded() and let the user get free stuff. Therefore it’s totally up to you.
JonPM’s system works, do the restore() process every start up and unlock what gets purchased. Then process what events you get. If you get a purchased event that unlocks something unlock it. If you get a refunded event, lock it back. This is a reasonably fail-safe system. The drawback is that if the device doesn’t have a network connection (i.e. Airplane mode), you won’t get any purchased/restored events so you won’t ever unlock things.
The other way to think about is do you want to respect their purchases while they are off line, then you have to leave things that you believe are unlocked and then if they delete the app and reinstall it, give them a restore button (this is the Apple way) to get their purchases back.
Its all a level of trust. Jon’s method is simple and will work as long as you know that they will be locked out while offline.
Rob
Rob
Thanks Rob, that’s not what I was referring to, but I’ve taken enough of your time on this topic already, so no worries!
I think to answer your question, yes you need to call restore() to get the refunded state (to communicate with server and get updated states). There is no Runtime event listener that is constantly checking for changes.
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!