IAP - transactionListener is not called after interruption during transaction

Hello, I have following issue with the Apple IAP. All purchases (consumable and non-consumable) are working correctly with this logic:

1) store.init( transactionListener )

  1. Buy button -> store.purchase({item})

3) function transactionListener( event )

  1. the app connects to my server with “event.transaction” details

  2. my server connect Apple authentication server

  3. my server get authentication from Apple server -> add purchased item

7) store.finishTransaction( event.transaction )

Everything works fine and it is possible to successfully buy any item/get authentication and finish the transaction until I skip the step 7), eg. I finish the app before my server get back with the result - for example - if the connection is lost during transaction. In this case - Apple register my purchase, the credit card is charged, the receipt is sent to my server, but the transaction isn’t finished with “store.finishTransaction( event.transaction )”. When I try to purchase the item again after restarting the app, Apple respond with message “This In-App purchase has already been bought. It will be restored for free.”, but anything happen after the message - the “transactionListener” is not called - I tried to modify the code to make sure the transactionListener is not called (eg. get notice when the transactionListener is called). Also, if I remember correctly - last time I have tried to test purchases - if I did the same (skip “store.finishTransaction( event.transaction )”), as soon as I have restarted the app and called “store.init( transactionListener )”, the Apple tried to deliver the purchase again automatically through the “transactionListener”, but it is not happening anymore.

Thank you in advance

Martin

Edit: “store.init( event.transaction )” changed to “store.init( transactionListener )”.

Hi Martin,

Are you detecting the “restored” transaction state in your transaction listener function?

Brent

Hello Brent, thank you for the response. Yes - I am detecting the “restored” state, the biggest issue is that the listener is not called at all. I have realized that it is just an issue of “consumable”, because if I purchase non-consumable (unlock), the Apple response with the same message “This In-App purchase has already been bought. It will be restored for free.” and the listener get called with transaction.sate = “purchased” and everything is working as supposed. I believe the biggest issue is that store.init is not calling the listener after unfinished transaction and relaunch.

Martin

Hi Martin,

I assume you’re calling the “finishTransaction()” immediately upon confirmation that the purchase was successful? Apple is adamant about that and you should call it immediately once you know a purchase went through and the item is “obtained” in your app. I suspect you already are doing this, but you mentioned earlier that you were skipping that step for various testing reasons or something?

Best regards,

Brent

Yes - the “finishTransaction()” is called as soon as I get the confirmation that the item was added and all purchases work as supposed under normal circumstances. Users are buying all items without any issues (both consumables and non-consumables). The reason for this issue is because I am testing all possible scenarios - eg. losing connection (or any other interruption) during the transaction. If I skip the “finishTransaction()” (if - for example - user get connection lost during the authentication on my server), the transaction get “stuck” and I am not able to get information about transaction and successfully finish the transaction. If I remember correctly, last time I have tested it (few months ago), in case of this interruption, the purchase of “consumable item” was delivered as soon as I relaunched the app and called “store.init()”.

You need to call finishTransaction() regardless of the result of the communications with your server. You can call it before you make your server calls, or make sure you trap the error where communications to your server failed to always make sure finishTransaction() gets called. If not the server gets out of sync.  

Rob

Hello Rob, thank you for the response. I believe it is better practice to call the “finishTransaction()” after the purchased item is delivered - I am doing it this way in all of my non-Corona apps and also the Corona documentation support it (https://docs.coronalabs.com/api/library/store/finishTransaction.html):

You must call this function following every successful transaction. If you don’t, Apple will assume that the transaction was interrupted and will attempt to resume it sometime after the next application launch.

If you’re offering the item as downloadable content, do not call this function until the download is complete.

The main issue is - if the transaction is interrupted. Even if I would be calling the “finishTransaction()” as soon as I get the “event.transaction” from Apple (without making sure the item was delivered by my server), it would still be possible the transaction being interrupted somewhere between “the credit card was charged” and “finishTransaction()”. In any way, the Apple should “attempt to resume it sometime after the next application launch”.

Considering the fact that it was working correctly a few months ago, I have tried to remove all plugins and everything that I have added during last few months and it has started to work again (eg. Apple attempt to resume the transaction as soon as I relaunch the app and call “store.init()”). By adding plugins one by one I have found the plugin that makes this issue - “plugin.admob”. Just by including the plugin in “build.settings” (even if I don’t “require( “plugin.admob” )”), this issue is happening. My knowledge of Corona is very limited, but I can see only two options:

  1. Admob plugin is somehow interacting with the “store” plugin.

  2. Admob plugin (not my Admob account, because it happens even if I don’t call “admob.init()” with my account credentials) is somehow interacting with my Apple/Corona account/plugin/device or there is something wrong in my build.settings or something.

My build.settings with correctly working “store” plugin:

plugins = { -- ["plugin.admob"] = { -- publisherId = "com.coronalabs" -- }, ["plugin.toast"] = { publisherId = "com.spiralcodestudio" }, ["plugin.utf8"] = { publisherId = "com.coronalabs" }, ["CoronaProvider.gameNetwork.apple"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } }, },

My build.settings where “store” plugin works only partly with the issue described before:

plugins = { ["plugin.admob"] = { publisherId = "com.coronalabs" }, ["plugin.toast"] = { publisherId = "com.spiralcodestudio" }, ["plugin.utf8"] = { publisherId = "com.coronalabs" }, ["CoronaProvider.gameNetwork.apple"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } }, },

Note: It is the only difference between two builds, and “require( “plugin.admob” )” is not called anywhere.

I kindly ask if you could investigate the issue to help me find where is the issue - I believe there is only very little I can do about plugins interaction and it is very unlikely that I could make “store” plugin behave this way just by adding “admob” plugin, but anything is possible. Not using the “admob” plugin is not an option for me, but it is currently the only way to make the “store” plugin work correctly.

I am using “2017.3068”.

Steps to reproduce:

Launch App > “store.init()” > Buy anything > Apple message “You are all set - Your purchase was successful.” > skip “finishTransaction()” > Relaunch App > “store.init()”

Apple should now attempt to resume the purchase and “transactionListener” should be called and you can easily get the “even.transaction” of the interrupted transaction and it is possible to finish the transaction/deliver the item.

Now include the “plugin.admob” and repeat the steps, now after:

Launch App > “store.init()” > Buy anything > Apple message “You are all set - Your purchase was successful.” > skip “finishTransaction()” > Relaunch App > “store.init()”

The apple doesn’t attempt to resume the purchase and “transactionListener” is not called. After going to buy the item again:

Buy the item again > Apple message “This In-App purchase has already been bought. - It will be restored for free” > “transactionListener” is not called and thus it is not possible to finish the transaction and it is not possible to buy the item anymore. On the other hand, if I try to purchase any other item (where “finishTransaction()” wasn’t skipped), everything works as supposed. So the purchase of interrupted item “stuck” and it is not possible to “clear” it, until I remove the “plugin.admob” and then - automatically after launch, the “stuck” item is delivered after “store.init()”.

Please note: It is only happening with “consumables”, in the case of “non-consumables” it works as supposed.

I know it sounds really strange, maybe I am missing something, but I am not able to narrow the issue because just including the “plugin.admob” is making the issue for me.

Thank you in advance

Martin

I’ve asked an engineer to look at this.

Rob

@Martin77

The AdMob plugin is completely self-contained and doesn’t interact with any other parts of Corona. The fact that you get this issue by merely including the AdMob plugin in build.settings (without any require or init) makes this even more bizarre.

Can you do one more test and instead of including plugin.admob, try including plugin.mediabrix (another monetization plugin) in your build.settings only, without require or init, and see if you get the same result?

Hello ingemar_cl, thank you for the response. I agree it sounds really bizarre - if it would be happening after “require” or “init”, I would be able to check for unfinished/interrupted transactions first and “require”/“init” admob after.

I have tried to include the “plugin.mediaBrix” instead of “plugin.admob” and everything works correctly.

Working (note: I haven’t added “NSAppTransportSecurity = { NSAllowsArbitraryLoads=true },” as stated in documentation, but I think it is probably not that important in this case):

plugins = { ["plugin.mediaBrix"] = { publisherId = "com.coronalabs" }, -- ["plugin.admob"] = { -- publisherId = "com.coronalabs" -- }, ["plugin.toast"] = { publisherId = "com.spiralcodestudio" }, ["plugin.utf8"] = { publisherId = "com.coronalabs" }, ["CoronaProvider.gameNetwork.apple"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } }, },

Not working (I have tried to move the plugin to the end, just for case):

plugins = { -- ["plugin.mediaBrix"] = -- { -- publisherId = "com.coronalabs" -- }, ["plugin.toast"] = { publisherId = "com.spiralcodestudio" }, ["plugin.utf8"] = { publisherId = "com.coronalabs" }, ["CoronaProvider.gameNetwork.apple"] = { publisherId = "com.coronalabs", supportedPlatforms = { iphone=true } }, ["plugin.admob"] = { publisherId = "com.coronalabs" }, },

I have also tried if the issue is present on the App store version with different iPhone device and different Apple ID (not connected to my developer account in any way) and the issue is still there, so the issue can’t be because of my Apple account/developer/sandbox/testFlight/settings. Fortunately, there is currently only around 10-20 in app purchases a day, so chance it will happen is quite small and nobody complained about it yet (well, it doesn’t have to mean it didn’t happen yet), but even if somebody would complain - I can add the item manually, but because of the transaction is “stuck” - it will not be possible to buy the consumable item anymore without successfully “finishTransaction()”.

Just to assure you:

  • I have searched all the project by Sublime Text > Find > Find in Files… > “plugin.admob” to make sure the plugin is not called anywhere except for “build.settings”.

  • The change in “build.settings” is the only change between working and non-working build, so I think it shouldn’t be possible there could be any mistake in the code, if everything is working correctly without “plugin.admob”.

Could you please try if it is possible to reproduce this issue? It would help if it is worth it to try different plugin combination or any other “bizarre” way to find the solution for the issue.

Thank you in advance

Martin

@Martin77

Can you try with the AdMob plugin again?

I made one change, but you will need to require the admob plugin and call admob.init() before the change takes effect.

Also, in this case please make sure admob.init() is called before store.init().

After looking through the AdMob documentation I found that their SDK by default has activated some built-in IAP reporting. I’m suspecting that it might be interfering with the Corona IAP plugin. The good news is that it’s possible to deactivate, however to do that admob.init() must be called.

Hopefully this was the culprit.

This happens to me a few times a week on Google.  I have to refund the order so the customer can purchase it again (otherwise it remains locked).  I get it around 1 in 500 purchases fail and it is nearly always due to dodgy internet.

I have tried to rebuild with “plugin.admob” included and “require”/“init” before calling “store.init()” and the issue is still there. I have also tried to wait a bit before “store.init()” and also build with “2017.3077”

@Sphere Games Studios

I have no game made by Corona published on Google, so I can’t try that, but it is happening on Google quite often that the purchase is delivered after relaunch (most likely due to connection lost). It would help if you could check if “plugin.admob” is responsible for your issue too.

I don’t use admob

Is there any news? I have tried to re-build on 2017.3078 and the issue is still present. Have you tried to replicate the issue? I will have to publish a new update soon and I would definitely like to fix this issue or know where the issue is, and eventually when it will be fixed, because sooner or later with increasing number of purchases, the issue will be more and more annoying.

Thank you in advance for response

To be honest, I am quite confused. Does it mean the issue is not going to be fixed/checked?

It’s easy for us to miss posts and questions. We probably are going to need a formal bug report on this. We will need a test case that demonstrates the problem so we can see it happen to identify the cause. 

Can you narrow it down?

Rob

Hello Rob, thank you for the response. I really appreciate it. I could make some test case that demonstrates it if needed, but It will probably be problematic because it is needed to have the app uploaded on iTunnes (at least in testFlight) - so, it is needed to have real IAP item (consumable) in order to test it - the issue is in communication between Apple purchase server and Corona (most likely the Admob plugin is interrupting the communication in specific cases). On the other hand, it should be quite easy to replicate the issue with a very short guide because almost no code is needed - just default Apple IAP code and the issue is there just by simply including the “plugin.admob” in the “build.settings” (without even calling it).

The simplest description of the issue is:

If you take a look at this page https://docs.coronalabs.com/api/library/store/finishTransaction.html, there is a sentence “Apple will assume that the transaction was interrupted and will attempt to resume it sometime after the next application launch.”, just try if you are able to resume the transaction if it was interrupted (simply do not call the finishTransaction() and relaunch the app) - with and without “plugin.admob” included in the “build.settings”. In my case - if the “plugin.admob” is not included, the transaction resume after the relaunch, but if I include the “plugin.admob” - it doesn’t resume and it is not even possible to buy the item again.

It would be great if somebody could try/test it to confirm it is the bug in Corona.

Note: I have tried it last time with build “2017.3077”, I will try it tomorrow on the newest “2017.3082”.

I have tried to upload build on the newest version “CoronaSDK 2017.3082” and the issue is still there. Could anybody check the issue? It is quite easy to replicate the issue.

Here is the guide:

If you take a look at this page https://docs.coronalabs.com/api/library/store/finishTransaction.html, there is a sentence “Apple will assume that the transaction was interrupted and will attempt to resume it sometime after the next application launch.”, just try if you are able to resume the transaction if it was interrupted (simply do not call the finishTransaction() and relaunch the app) - with and without "plugin. admob**" included in the “build.settings”. In my case - if the “plugin.admob” is not included, the transaction resume after the relaunch, but if I include the “plugin. admob” - it doesn’t resume and it is not even possible to buy the item again because of it “stuck” and the transaction listener is not called even if the Apple respond with the message “This In-App purchase has already been bought. It will be restored for free.”.**

Please let me know, so I will know you haven’t missed the post again.

Thank you in advance

Martin, at this point we are going to need a bug report that has a small sample project that demonstrates the problem. I have to have a project complete with main.lua, build.settings and config.lua and any assets to build and run it before I can get it to an engineer. 

Once you have a bug that demonstrates the problem, write up a good description of the problem, what purchase items we will need to create. It should match what you are using. 

From there I can get someone to look at it.  Please email the .zip file (Please use .zip, not other formats) and the steps to reproduce the problem to support@coronalabs.com

Thanks

Rob