Users unable to purchase consumables after initial purchase fails.

We have come across an error which we can reproduce 100%, but before filing a bug report maybe someone here can see something that we may have overlooked. It only happens on iOS as far as we know, and we are testing with our sandbox test accounts as the app is not live yet.

Here is the chain of events to reproduce.

  • Open game, go to store.

  • Store loads products - this works fine.

  • Press one of our store buttons which calls store.purchase(theProduct)

  • Before the iTunes popup appears - background the app.

  • Fill in password/press ok on the iTunes popup.

  • Complete the purchase, get the “purchase successful” popup.

  • Kill the app (this may seem like an odd thing to do, but we were trying to simulate the app crashing at this point).

  • Open app - the following table data is received by store lib:

    <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 is printed inside out IAP listener that is passed into store.init(). The store.finishTransaction function is called here, and we’ve printed a line underneath that to be sure that the code gets that far. However the transaction returned has a “failed” state, so I’m not sure if that transaction being passed into store.finishTransaction is causing this problem:

  • Press the purchase button again.
  • Get this error “This In-App purchase has already been bought. It will be restored for free”. However this does not trigger the listener function at all, so no data is passed from this purchase attempt, and store.finishTransaction() is not called.

It might seem like an unusual usage case, but as I mentioned earlier the reason for suspending/killing during the purchase process was to simulate the app crashing for unrelated reasons. As it stands the user will have made a purchase and not received the item they paid for, and cannot even make the purchase in future if they wanted to.

Is there something we’ve missed because our deadline to submit is in the next 18 hours and it would be a massive help if someone can spot something. 

Hi Alan,

I’m a little confused. You shut down the app during the purchase, but it sounds like the item actually did get purchased, because it reaches the point after store.finishTransaction. In your listener, what else are you doing? Presumably unlocking some feature in your Lua code?

Best regards,

Brent

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

Hi Alan,

I’m a little confused. You shut down the app during the purchase, but it sounds like the item actually did get purchased, because it reaches the point after store.finishTransaction. In your listener, what else are you doing? Presumably unlocking some feature in your Lua code?

Best regards,

Brent