Users unable to purchase consumables after initial purchase fails.

To be clearer.

The table posted is not returned during the game play session. It appears the NEXT time the app is opened and store.init() is called. 

So from Apple’s point of view, yes the item actually did get purchased. From the app’s point of view, it knows nothing about that successful purchase because the app was closed when iTunes finished the transaction.

Then whenever you try to purchase this consumable item again, you get the “This In-App purchase has already been bought. It will be restored for free” message, but at this point the IAP listener (the one provided to store.init) is not called at all.

And yes you are correct, in our listener we unlock items or add game currency.

Hopefully this summary is a bit clearer.

  • Start purchase process.
  • Suspend app - iTunes popups continue appearing.
  • Complete IAP process.
  • Fully kill app.
  • Open app.
  • Purchase has failed.
  • Can no longer purchase that consumable item or finish the transaction.

Hi Alan,

So in your most recent list of “steps”, is there anything which happens between 3 and 4? Are you saying that you’re force-killing the app from a background state, i.e. by double-clicking the main button and sliding off (closing) the app?

Brent

Hi Brent, I understand that’s correct. We are killing the app at this point to simulate a crash that could happen. It is a requirement for the publisher we are working with and whilst I know it’s hopefully a rare occurrence we must be able to recover from this state.

That’s exactly correct.  

App is backgrounded but the iTunes IAP alerts continue as if the app was still active. After the final “purchase successful” alert, double tap the button and force kill the app.

OK, and you said you were able to get a response sometime after you call store.finishTransaction(). When does that occur in your series of steps? When it does, is there any event data to handle?

What happens after you get the message that it’s already been purchased, if you call finishTransaction()??  Can you buy the item again?  Consumables should be able to continue to be purchased.   I had talked to Brent about doing a store.restore(), but that’s pointless since that’s only for non-consumables. 

Rob

@Brent When I try again this morning, there doesn’t seem to be anything that’s returning the failed transaction data some time after finishTransaction() is called. Now it just fails and that’s it. Reopening the app or trying to make the purchase again doesn’t ever return the data I posted earlier, it just shows the error message and then the user is stuck. I’m sure that it was showing that data after calling store.init() yesterday, but I’ll check with one of my colleagues when they start this morning. At any rate, the data returned was the data in my first post:

\<Warning\>:event.transaction.state: failed \<Warning\>:event.transaction.date: nil \<Warning\>:event.transaction.identifier: ABCDEFG-1234-5678-XXXX-1234567890AB \<Warning\>:event.transaction.productIdentifier: quiztix.mygame.consumable1 \<Warning\>:event.transaction.errorType: none \<Warning\>:event.transaction.errorString: nil

This WAS being printed inside the listener which is passed to store.init, and the listener was calling finish.transaction(). In my eyes that should mean that apple get a response saying that the purchase had now been handled by the app. It’s possible that because the event.transaction.state is “failed” that this is why it doesn’t…but then if Apple think it’s a failed transaction, why does it not allow another transaction to be made? It’s also odd that the state is failed, yet there is no error type or errorString. With the state being “failed”, as it stands right now we would not have processed the user’s item anyway because as far as we know it means exactly that - the transaction failed.

@Rob do you mean just randomly call finishTransaction at some other time, because there is NO callback at the point where you try to make the purchase again and it says it’s already purchased. I’ll give it a try and see what happens. We tried store.restore as well, just in case but as you say it only returns non-consumables.

Sorry for the delayed replies, we’re in the UK so the time difference makes it difficult. We have video footage from the QA team of this happening. Can we send it to one of you via email/skype/etc so you can see what’s happening as they’ve captured it pretty clearly. I would paste a link here but there are some passwords being typed in the video so I’d rather send it directly.

We think that the failed transaction data only appears the first time store.init is called after the initial purchase->kill app happens, but we’re not 100% sure.

Hi Alan,

Do you ever get a successful transaction state in your force-kill-from-background testing process?

Not so far, it’s failed each time.

Hi Alan,

Are you then calling .finishTransaction() following a failed state? Perhaps you should not call that unless you get a successful transaction.

Another question: if you do NOT do this force-kill testing process (in other words, you simply try to purchase an item without killing the app forcibly), do you get a successful transaction?

Actually my suggest was to call finishTransaction() after you get this:

<Warning>:event.transaction.state: failed
<Warning>:event.transaction.date: nil
<Warning>:event.transaction.identifier: ABCDEFG-1234-5678-XXXX-1234567890AB
<Warning>:event.transaction.productIdentifier: quiztix.mygame.consumable1
<Warning>:event.transaction.errorType: none
<Warning>:event.transaction.errorString: nil

in an attempt to clear the transaction before you purchase things again.

finishTransaction() was called after that data was received:

This WAS being printed inside the listener which is passed to store.init, and the listener was calling finish.transaction(). In my eyes that should mean that apple get a response saying that the purchase had now been handled by the app. It’s possible that because the event.transaction.state is “failed” that this is why it doesn’t…but then if Apple think it’s a failed transaction, why does it not allow another transaction to be made

I think I can actually simplify this problem, requires 2 builds but makes the cause (finishTransaction not being called) much easier to replicate.  

  • Setup code so that finishTransaction is NOT called (to simulate crash/error before it reaches that point), and also remove the code that grants the item.
  • Build and install.
  • Perform a consumable purchase - iTunes will show success message, but code will not give you the item and will not call finishTransaction()
  • Replace finishTransaction() + item handling in code.
  • Build and install.
  • Try to buy consumable item again - because finishTransaction() was not called the first time this was purchased, it will tell you that you already own this item.
  • Be stuck forever - cannot purchase this item, and there does not seem to be any way to close the transaction.

Hi Alan,

After step 3 in NOT calling .finishTransaction(), what happens if you just restart the app? Does the iTunes store attempt to resume the purchase in any way, considering that it should regard the purchase as interrupted? I guess I don’t see how the store is considering this as a “successful purchase” if you haven’t told the store that it was successful by calling .finishTransaction().

Brent

Yes it will bring up the “sign into iTunes” popup as soon as store.init is called (which we do at startup), but as far as we can tell it will only show the failed message data the very first time this happens.

It will also still fail to successfully conclude the transaction even if it calls finishTransaction(), so you still can’t purchase the product ever again.

Our QA team actually managed to encounter the same issue when a new bug introduced to our listener prevented finishTransaction() from being called, and after the bug was resolved the transaction is still stuck in this limbo state.

As mentioned in my very first post, we are currently testing with sandbox accounts as the app is not live yet, so it’s possible that this is a sandbox-only error but we have no way of knowing until the game is live.

Hi Alan,

To clarify, when exactly does the iTunes popup show? Only on first launch, or does it happen again when you restart the app after force-killing it and not calling finishTransaction()? Is there any way you can post some screenshots of how it looks in each case?

Thanks,

Brent

I’m in the same boat.  I was doing a simple update to my app, and my binary got rejected because of this.  Here’s my code (note: I only edited out things that might be sensitive information or garbage in the logs like gesture warnings):

function IAPContainer:create() print("store init...") self.store.init( "apple", self.transactionCallback ) end function IAPContainer:transactionCallback( event ) print("=============okay?") print(event) if event then local transaction = event.transaction if event.transaction.state == "purchased" then if (transaction.productIdentifier == self.productIdentifiers[1]) then fortuneContainer:add() end elseif event.transaction.state == "cancelled" then elseif event.transaction.state == "failed" then -- something went wrong native.showAlert( "Transaction Failed", "Try again later.", { "OK" } ) else -- something went wrong native.showAlert( "Transaction Failed", "Try again later.", { "OK" } ) end self.store.finishTransaction( event.transaction ) else print("!!! not okay") end end function IAPContainer:buy(productID) productId = self.productIdentifiers[1] if self.store.isActive == false then native.showAlert("Store purchases are not available. Verify that in-app purchases are enabled and try again later.", {"OK"}) elseif self.store.canMakePurchases == false then native.showAlert("Store purchases are not available. Verify that in-app purchases are enabled and try again later.", {"OK"}) elseif productId then -- print("Purchasing " .. tostring(productId)) self.store.purchase( {productId} ) end end

This is what I get from the console output:

Aug 21 14:06:20 MyDeviceName MyAppName[295] \<Error\>: assertion failed: 12H321: libxpc.dylib + 51867 [3C761F5E-F2FD-315B-895A-4054CAE2232E]: 0x7d Aug 21 14:06:20 MyDeviceName Unknown[295] \<Error\>: Aug 21 14:06:20 MyDeviceName MyAppName[295] \<Warning\>: Platform: iPod touch / iPod5,1 / 8.4.1 / PowerVR SGX 543 / OpenGL ES 2.0 IMGSGX543-115.5 / 2015.2697 Aug 21 14:06:20 MyDeviceName MyAppName[295] \<Warning\>: making store... Aug 21 14:06:20 MyDeviceName MyAppName[295] \<Warning\>: store init... Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: =============okay? Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: nil Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: !!! not okay Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: =============okay? Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: nil Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: !!! not okay Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: =============okay? Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: nil Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: !!! not okay Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: =============okay? Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: nil Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: !!! not okay Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: =============okay? Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: nil Aug 21 14:06:22 MyDeviceName MyAppName[295] \<Warning\>: !!! not okay Aug 21 14:06:23 MyDeviceName itunescloudd[148] \<Warning\>: Updating media purchase history for initial load... Aug 21 14:06:24 MyDeviceName ubd[157] \<Error\>: account is still enabled for ubiquity! Aug 21 14:06:24 MyDeviceName ubd[157] \<Error\>: account is still enabled for ubiquity! Aug 21 14:06:37 MyDeviceName ubd[157] \<Error\>: account is still enabled for ubiquity! Aug 21 14:06:37 MyDeviceName ubd[157] \<Error\>: account is still enabled for ubiquity! Aug 21 14:06:37 MyDeviceName syncdefaultsd[287] \<Notice\>: (Note ) SYDAccount: no account Aug 21 14:06:37 MyDeviceName itunesstored[83] \<Warning\>: Could not load library [21] Aug 21 14:06:37 MyDeviceName kernel[0] \<Notice\>: AppleKeyStore: operation failed \<removed possibly sensitive data\> Aug 21 14:06:37 MyDeviceName itunescloudd[148] \<Warning\>: Updating media purchase history for initial load... Aug 21 14:06:38 MyDeviceName itunescloudd[148] \<Warning\>: Updating media purchase history for initial load... Aug 21 14:06:38 MyDeviceName accountsd[82] \<Warning\>: [Warning] Unhandled server key: alert Aug 21 14:06:38 MyDeviceName accountsd[82] \<Warning\>: AIDA Notification plugin running Aug 21 14:06:38 MyDeviceName kernel[0] \<Notice\>: AppleAP3GDL::handleInterrupt2 FIFO is Empty Aug 21 14:06:39 MyDeviceName kernel[0] \<Notice\>: AppleKeyStore: operation failed \<removed possibly sensitive data\> Aug 21 14:06:39 MyDeviceName backupd[300] \<Warning\>: INFO: Account changed (enabled=0, accountID=219936675) Aug 21 14:06:39 MyDeviceName kernel[0] \<Notice\>: AppleKeyStore: operation failed \<removed possibly sensitive data\> Aug 21 14:06:39 MyDeviceName syncdefaultsd[287] \<Notice\>: (Note ) SYDAccount: no account Aug 21 14:06:39 MyDeviceName syncdefaultsd[287] \<Notice\>: (Note ) SYDAccount: no account

Basically, something’s going wrong in the startup, and it’s calling the callback with “nil” instead of the event, which probably breaks things under the hood with Corona as well.  After that, when I attempt to buy stuff, everything looks like it’s working.  I get the “Buy items for $.99” screen and accept, but then, I get an the screen about “In-apps will be restored for free.”  The “store.isActive” is returning true, but it’s failing to buy.  The buy calls never trigger the transactionCallback (like, not even to pass nil for the event like it does at startup).

Oh, btw, IAPContainer is an implementation of a simple class in my code and “store” is this:

IAPContainer.store = require("store")

Hi @tjsamson,

How are you calling that “:create()” function? Is there a specific reason you decided to implement the store in this class setup?

Thanks,

Brent

@brent,  the entirety of my class setup is for ease of programming for me.  I prefer OOP for modeling the types of stuff I make, and it makes things like IAP very simple, modular, and self-contained.  I use this same class throughout all my products.  This is called once, right at the start of my app when I set things up in the main.lua.  It looks like this:

local storyboard = require "storyboard" require("theme") -- my class for themeing my app require("transitioncover") -- for transitions require("iapcontainer") -- the aforementioned IAP class require("advertiser") -- a class I made for using ads (which I'm not using right now) iapContainer = IAPContainer() iapContainer:create()

As you can see, it’s only called once, right at the start. I could throw in a debug log to prove that, if that would be of use.

As a note, it was working up until I updated to this version (I would have to do some digging to verify exactly when it broke, and that might be a product of iOS 8.4, not Corona).  Though, I don’t believe it worked with the latest prod build before I upgraded.

I’m building with 2015.8.15, targeting Mobile/iPad, with iOS SDK 8.4, btw.