Handling pending consumable transaction

I'm selling consumable products and using Apple's servers for receipt validation via a call from the app to my server.


Twice in past 24 hours, a situation has occurred where the transaction state has updated to SKPaymentTransactionStatePurchased causing the receipt to be sent for validation. However when the response comes back from Apple's server, "body.receipt.in_app" is empty and therefore the purchased product is not added to the user's account.


One of these customers told me that the transaction was showing as "Pending" and a few hours later they received an invoice by email. However the app did not fire another purchased transaction state, so I had to add the purchase manually.


So some questions:

1. Was this unexpected behaviour and likely to be a rare and temporary IAP processing problem?

2. If not, why is the state changing to SKPaymentTransactionStatePurchased if the payment didn't actually complete and is pending?

3. Currently the app is calling finishTransaction when a response is received from my server, but this is NOT dependent on the presence of any inapp purchases in the decoded receipt. I'm thinking that this could be a problem. If the transaction state has not actually completed, is calling finishTransaction going to block any further state changes from being sent to the app, even when that state change is a critically important one when the payment HAS actually been processed and the purchased product should be added to the user's account?

Accepted Reply

I've been told this is a bug so I've filed a report.

Replies

>One of these customers told me that the transaction was showing as "Pending" - I had to add the purchase manually.


You might want to read Rich's comment in this recent thread:


https://forums.developer.apple.com/message/395609#395609


...sounds like you're being scammed.

did the customer send you a copy of the email invoice?.........why do you write that the state changed to 'purchased'?......does your app maintain an observer after a call to finishTransaction so it can receive any future transactions? (Edit- and when you call finishTransaction you must provide a transaction - what transaction did you provide?)

Yes the one customer I had communications with did send me the invoice that he received when the purchase was no longer "pending".


Only the state changing to "purchased" causes the receipt to be sent to our server, that's why I know the state changed to "purchased" even though payment had not actually completed and customer said it was "pending".


Yes the app observes the payment queue all the time.


finishTransaction is passed the transaction that it received on that notification, in the usual way.

So let me try to understand what you are saying happened:


> One of these customers told me that the transaction was showing as "Pending" and a few hours later they received an invoice by email. However the app did not fire another purchased transaction state, so I had to add the purchase manually.


and then you wrote:


>Only the state changing to "purchased" causes the receipt to be sent to our server, that's why I know the state changed to "purchased" even though payment had not actually completed and customer said it was "pending".


So it seems that the pending transaction went through and the app received a call to updatedTransactions with a state of 'purchased' - is that correct????



And this is the problem:


> ....the transaction state has updated to SKPaymentTransactionStatePurchased causing the receipt to be sent for validation. However when the response comes back from Apple's server, "body.receipt.in_app" is empty


This is what you would expect for future receipts if you had called finishTransaction on a receipt that had a consumable IAP reported in it. I don't think you should call finishTransaction on a transaction when the state is SKPaymentTransactionStatePurchasing or SKPaymentTransactionStateDeferred. Is it possible you call finishTransaction on a transaction that actually had the consumable IAP in it thereby wiping out the consumable IAP portion?

Not quite. There was only one change of state to "purchased" and that happened soon after the customer chose to purchase. But the point is that at that moment the purchase was not actually confirmed, it was only pending. It was 9 hours later when the transaction changed from pending to confirmed. That is the moment when it would be reasonable to expect the app payment queue to receive the notification rather than when it did.


As per recommended practice, I'm calling finishTransaction only for purchased/failed/restored states (although restored isn't applicable anyway for consumables). I'm aware that the inapp data only contains "unfinished" purchases (which is why it is useful for adding the purchase to an online account) but I can see there is a timing risk if Apple's server doesn't respond instantly. Since the app finishes the transaction when it receives the 200 OK response from my server, that could possibly cause that purchase to be removed from the receipt by Apple before the decoded receipt is sent back to my server.


Or am I misunderstanding things and the decoded receipt sent from Apple server to my server is simply the receipt uploaded from the app without any modification? In that case, that receipt will always be before the transaction has been finished, so there is no timing risk.


However I feel that scenario isn't applicable here because surely the inapp data should have been empty anyway if the purchase wasn't going to be confirmed for another 9 hours? This is what I'm trying to get to the bottom of.

Ignore whatever you mean by "pending" in "(b)ut the point is that at that moment the purchase was not actually confirmed, it was only pending."


Go solely with the calls to updatedTransactions.


If the state in updatedTransactions is 'purchased' then grab the receipt from [[NSBundle mainBundle] appStoreReceiptURL] at that moment and examine that receipt. "The rest are details."

That's exactly what I am doing!


I will raise a support request to confirm my suspicions but I think that what's going on here is that Apple is incorrectly setting the state to "purchased" when it should still be "purchasing" if the transaction has not actually completed. That feels like a bug.


Even though I'm finishing the transaction on the first state change to "purchased", it doesn't really make any sense that the app should get another notification from "purchased" to "really purchased" does it.

So to make it very simple....


You have a situation in which two users claim that a call was made to

paymentQueue:updatedTransactions:(NSArray *)transactions

and there was a transaction object in transactions that had the value

SKPaymentTransactionStatePurchased for transaction.transactionState

and at that moment, before calling finishTransaction, you grabbed the receiptData in this:

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

NSData *receiptData=[NSData dataWithContentsOfURL:receiptURL];

and sent it to the Apple servers for decoding. Apple responded with an empty in_app field.


If this is what happened then either:


1) you are being scammed by the user who is slipping a fake receipt into your app or otherwise manipulating your code so it appears like this is the case - but it's not


or


2) this is a bug in the system.

I will post the result of the TSI here for future reference.

I've been told this is a bug so I've filed a report.

I've been told by my App Store Server contact that a modification has been made to the App Store's verifyReceipt server process to handle the situation where after a purchased transactionState - the validation of the appStoreReceipt shows that the in_app array is empty. The solution to this issue is to include the applications shared-secret as the "password" key value along with the "receipt-data" in the JSON request payload sent to the verifyReceipt endpoint. The presence of the password key will result in the verifyReceipt endpoint to include unfulfilled purchases. All auto-renewing subscription apps do this now, but now there's reason for apps offering consumable, non-consumable and non-renewing subscriptions to include this key as well.

rich kubota - rkubota@apple.com
developer technical support CoreOS/Hardware/MFI