Iap, Android, And Restores

I’ve been testing IAP for Android recently.  All of my purchases work fine and the transaction handler works like a charm.  When I went to test a managed purchase restore, it doesn’t process through the “purchased” state (at least that I can tell).  What I am doing is calling store.restore() after the init to automatically start the restore process.

A few other notes:

  1. yes this is with a test user account and purchases have gone through fine.

  2. When I try to purchase the managed item again it says that it has already been purchased.

Any thoughts or items I might be missing?  From what I understood, as long as I process items through the purchased state, restores should just work.

I’m in the same situation right now with a managed product. The user wants to restore the lost data by buying it again but android doesn’t allow them to do so… as Android doesn’t allow buying it again so no callback is called.

I just dumped the log on one of my games that I’m adding Google Play IAP to using:

        if store.availableStores.google then             store.init( "google", transactionCallback )             store.restore()         end

And I got all the messages I would expect this code to print:

function transactionCallback( event )     local transaction = event.transaction     if transaction.state == "purchased" then         print("Transaction succuessful!")         print("productIdentifier", transaction.productIdentifier)         print("receipt", transaction.receipt)         print("transactionIdentifier", transaction.identifier)         print("date", transaction.date)     end     store.finishTransaction( transaction ) end

So it’s working as expected for me.

@Rob:

as described here: http://forums.coronalabs.com/topic/35721-android-inapp-purchase-transaction-purchased-when-transaction-has-failed/

When calling the purchase() function, when I do have the product bought, I would expect the “purchased” transaction state and not “failed”.

Could you please comment on that?

What I’m getting at, is that I want to have a single button to purchase/restore. When testing on iOS it works fine with just store.purchase, but on Android I would have to try to restore first and then purchase?

I don’t have a lot of experience with Google’s IAP.  But my understanding is that we have a .purcahse() and a .restore() call for a reason.  You should call .restore() upon initing the store and it will update you will what all the customer has purchased.  For iOS you get a restore event, for Google a purchase event.   But if you try to purchase something you already bought, it should fail.  I’m surprised Apple isn’t returning a failure.

@Rob: on iOS, if I call .restore() function for a user, who doesn’t have the addon on their account, it will only ask for credentials and will “hang” - won’t call the transaction listener with any transaction status. If I call .purchase() it is fine.

I’m in the process of testing .restore() on Android, just waiting for google to update my alpha build.

I’m in the same situation right now with a managed product. The user wants to restore the lost data by buying it again but android doesn’t allow them to do so… as Android doesn’t allow buying it again so no callback is called.

I just dumped the log on one of my games that I’m adding Google Play IAP to using:

        if store.availableStores.google then             store.init( "google", transactionCallback )             store.restore()         end

And I got all the messages I would expect this code to print:

function transactionCallback( event )     local transaction = event.transaction     if transaction.state == "purchased" then         print("Transaction succuessful!")         print("productIdentifier", transaction.productIdentifier)         print("receipt", transaction.receipt)         print("transactionIdentifier", transaction.identifier)         print("date", transaction.date)     end     store.finishTransaction( transaction ) end

So it’s working as expected for me.

@Rob:

as described here: http://forums.coronalabs.com/topic/35721-android-inapp-purchase-transaction-purchased-when-transaction-has-failed/

When calling the purchase() function, when I do have the product bought, I would expect the “purchased” transaction state and not “failed”.

Could you please comment on that?

What I’m getting at, is that I want to have a single button to purchase/restore. When testing on iOS it works fine with just store.purchase, but on Android I would have to try to restore first and then purchase?

I don’t have a lot of experience with Google’s IAP.  But my understanding is that we have a .purcahse() and a .restore() call for a reason.  You should call .restore() upon initing the store and it will update you will what all the customer has purchased.  For iOS you get a restore event, for Google a purchase event.   But if you try to purchase something you already bought, it should fail.  I’m surprised Apple isn’t returning a failure.

@Rob: on iOS, if I call .restore() function for a user, who doesn’t have the addon on their account, it will only ask for credentials and will “hang” - won’t call the transaction listener with any transaction status. If I call .purchase() it is fine.

I’m in the process of testing .restore() on Android, just waiting for google to update my alpha build.

I’m also encountering something like this right now… I wonder with a managed product if it’s possible somehow to catch the info of an already purchased product with the Google Android version? For example: In my app you can unlock levels via one in-app purchase (managed product). Successfully purchased I save the information in a file on the device. Now, if a user is deleting the app and reinstalls it or want to get the levels on another device he tries to purchase and gets the information he already has purchased this product… and I don’t get back an event I can use in the app to save this information again. So the user can’t restore AND can’t buy again. Is that right?

What is the right way to do this?

Can anyone help me please?

Thank you!

Daniela

That’s correct.

I think there’s something missing in Corona when it comes to in app purchases/billing.

It’s even worse on iOS, where if you try to restore a purchase, which was never made, you will not receive any notification…

I handle it this way:

In main menu I’ve got a button, which can be used to remove ads/unlock multiplayer.

When Android user uses this button to restore the purchase, he will get an error from Play store, and then I will display an error message, as part of which I tell user that if they made the purchase before, they can restore it in options.

Then in options I have a restore button.

Because of a bug in Corona [yup… both Google and Apple provide calls to handle that], restore is screwed and you won’t get response from server, if user cannot restore a purchase. So I simply have a ‘please wait’ popup, and if popup stays open for longer than 10 seconds I allow user to close it (I just show them a cancel button after 10 seconds).

Terrible solution if you ask me, but I haven’t figured out any better way to handle it.

I’ve been following this thread, but I haven’t been able to replicate the problems others seem to be facing.  Things seem to be working fine for me on both iOS and Google Play.

Here’s my setup.  I have some managed products that the user can purchase, and I have a separate button to restore purchases.  I don’t call store.restore() until the user actually presses the restore purchases button.

Here’s what I’m seeing.

On Android, let’s say the user buys a product, deletes the app, and then reinstalls the app.  If they try to buy the product again, my transaction callback receives a “failed” transaction.  The user also sees an automatic popup from Google Play telling them they already own the product.  True, it’s up to the user to then realize they need to go and select restore purchases, but I don’t think that’s a big issue.  If it’s something you’re worried about, then in response to a failed state, you can show your own popup reminding the user to try restoring purchases, which, Krystian, it sounds like is what you’re doing, and is what I do too.  Either way, if the user then does hit the restore purchases button, initiating a call to store.restore(), my transaction callback gets a “purchased” transaction for each restored product as expected.  So that all seems fine to me.

On iOS, let’s again say the user buys a product, deletes the app, and then reinstalls the app.  If they try to buy the product again, the user gets a popup from iOS indicating that they already own the product and can download it again for free.  If they say yes, then my transaction callback gets a “purchased” transaction.  If they so no, it gets a “cancelled” transaction.  If instead of all this, they happen to go directly to the restore purchases button, initiating a call to store.restore(), my transaction callback gets a “restored” transaction.  So again, that all seems fine to me.

It sounds like many on this thread are calling store.restore() immediately after store.init() and not receiving any transactions in your transaction listener that you think should be restored.  Just a thought, but try calling store.restore() after a few second delay or by making a separate button for it.  Notwithstanding that the sample code in the API documentation shows store.restore() being called immediately after store.init(), I wonder if there’s something asynchronous about store.init() that could prevent it from working.

Separately, @krystian6, you raised a separate point about what should happen when store.restore() is called if the user hasn’t previously purchased anything and there’s nothing to restore.  It sounds like you’re saying that you expect your transaction callback to receive an event indicating that there was nothing to restore, but instead your callback isn’t receiving anything.  First, from a user standpoint, I don’t see that as a big problem, since a user wouldn’t be expected to press a restore purchases button unless they had something in mind that they were trying to restore.  Second, on the question of whether this behavior is a Corona bug or not, I don’t think it is.  For iOS, according to the bottom of this page – http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/MakingaPurchase/MakingaPurchase.html – “the App Store generates a new restore transaction for each transaction that was previously completed”.  This implies to me that it generates nothing if no products were previously purchased.  The Android documentation isn’t quite as clear, but this page – http://developer.android.com/google/play/billing/v2/api.html – does say that RESTORE_TRANSACTIONS “retrieves a user’s transaction status for managed purchases and subscriptions”, which again implies to me that if the user has no managed purchases or subscriptions, it retrieves nothing.

I guess the problem is if, after a user presses the button and you call store.restore(), you activate the system’s activity indicator or some modal dialog that prevents the user from using the app.  You’d turn it off in your transaction callback, but since it never gets called, the user is stuck.  The solution is to either use a timeout, which, @krystrian6, it sounds like is what you do, or you not show an activity indicator / modal dialog in the first place.  But again, it seems to me like this would be the same thing on iOS or Android natively.

  • Andrew

Hi Andrew,

thanks for taking interest in this thread and for sharing your thoughts.

Indeed, the issue I have is with the negative scenarios, when user tries to restore a product they don’t own.

I think we read the same docs and get different ideas on how this should work.

For iOS I would expect to receive “paymentQueueRestoreCompletedTransactionsFinished” call, which indicates that app store finished sending restores. When getting this call without any restores I would assume user purchased nothing.

For Android I would expect to receive a response for RESTORE_TRANSACTIONS, with a response code indicating user didn’t purchase any products.

Never mind that, I do have this work around in place and for now this have to suffice. I will be starting a new project within a week or two, and as a preparation exercise I will simply build a sample app for android and ios to check and see how this works natively :slight_smile:

Again, thanks for taking part in this thread.

Hi Krystian,

Sounds good, I’ll be interested to hear what you find out when you try the “nothing to restore” scenario in native code

  • Andrew

Is there a reason you simply don’t call store.restore() when your app boots up and unlock things automatically?  If there’s nothing to restore, you simply go on with your app.  If you get call backs, unlock them and go on (downloading content as necessary).

The reason I don’t call store.restore() as soon as the app starts is that iOS will sometimes prompt the user to re-enter their password.  I thought that would be a strange user experience for a user running the app for the first time after having uninstalled it previously or after their device was wiped.

  • Andrew

I think that may just be a testing behavior.  Have we seen that happen on live apps?  If they are logged into iTunes on their device, they should only be prompted for their PW when they go to buy things… I think.  

You might be right that it’s only something that happens in the sandbox with test accounts.  But the … is what worries me.  :-)

  • Andrew