IAP - if server fails to provide content

I have a consumable IAP in an app. The app has users and login/ logout functionality to switch between them. The amount of the consumable product associated with an user is stored on a server. So when the purchase is made a call to the server is made to "inform" it to update with the new amount of product. This call could potentially fail. From what I read what I should do there is to retry in background until it succeeds. I wonder how to handle the following cases:

1. The user logs out and kills the app. Then on start of the app SKPaymentTransactionObserver is called with SKPaymentTransactionStatePurchased because the transactions has not been finished. How do I know for which user of the app this transaction is. Should I store transaction ID-s alongside some user specific information to continue retrying to update with the tokens for this user. Or is there a better approach?

2. The state of the transaction is SKPaymentTransactionStateDeferred. Similar scenario as in point 1. The user logs out and either doesn't log in or logs in with different user. SKPaymentTransactionStatePurchased is received at some point and the app should update the correct user with the purchased product. The app could also be restarted in the process. Should again the transaction id be saved alongside some user information to make the update to the backend? Or is there a better approach.

3. The user logs out. The user logs in the app on another device. SKPaymentTransactionStatePurchased probably won't be called and the user will receive nothing. Maybe I should just block the UI until the purchase is updated on the server also but I worry of some big delays. Also if the user kills the app in the waiting they will be charged but won't receive anything. So can I do something?


Probably there are cases I am missing here, but these are the ones that come to my mind. I don't see similar questions very often and some specific handling is not mentioned so I wonder if things are not easier than I make them to be. The transaction observer is added in didFinishLaunchingWithOptions in AppDelegate and removed in applicationWillTerminate in AppDelegate.

Accepted Reply

You seem to be describing two separate systems so it becomes confusing when you say 'logs out'. The first is the app's interaction with StoreKit to purchase and receive a consumable IAP. The second involves your server and recording the purchase for the user. These are two different issues.


The purchase from StoreKit is controlled by the observer and a call to finishTransaction. If something gets interrupted in that process you don't call finishTransaction and the next time the app adds an observer the purchase will come through again. There are complications if multiple users share a device and share their Apple ID. It is very hard to differentiate between 'different users' who share an Apple ID. The only way to handle this is to continually monitor who is logged into your system and record that during paymentRequest. Use that information to object when they try to credit your server and don't call finishTransactions. (Or you could simply give your IAP credit to any one of them and let them fight over it. They should not be sharing their Apple ID.)


Once the app has obtained the purchase from StoreKit it needs to store it somewhere. If it gets interrupted during that store process it can either rely on not having called finishTransaction (as described above) and get the process restarted at a later time or it can take on the responsibility of retrying itself and dismiss StoreKit by calling finishTransactions. If you have multiple users then the complications mentioned above become double here. If you do not, then you can rely on StoreKit to restart the process from the beginning. I prefer to take over the process myself by storing something in NSUserDefaults or the files system (always calling finishTransactions from within updatedTransactions) and responding to that NSUserDefault/files entry each time the app opens. If there are multuiple users then you can store that information in NSUserDefaults and rely on it next time the app opens. That 'information' could be the user's ID on your system at time of purchase or paymentRequest although they will rarely be different. ('Sign' the entry in NSUserDeafults to prevent theft.)


Regarding the deferred state - you can ignore this. If the transaction comes through tehre will be a new call to updatedTransactions and it will be handled like an original purchase above.


Your #3 is confusing - the user is logging out of which system - if StoreKit then it will sit there awaiting the next time the user logs into StoreKit under their AppleID using that device. If it is your system then everything above still applies. Either you rely on not finishing the transaction and letting the purchase hit updatedTransactions or on the system you created yourself. Again, with multiple users you need to store some information at the time of the paymentRequest.

Replies

1; 2; 3 - Sorry if I missed it... using persistent transaction observer? See the IAP FAQ for hints.

You seem to be describing two separate systems so it becomes confusing when you say 'logs out'. The first is the app's interaction with StoreKit to purchase and receive a consumable IAP. The second involves your server and recording the purchase for the user. These are two different issues.


The purchase from StoreKit is controlled by the observer and a call to finishTransaction. If something gets interrupted in that process you don't call finishTransaction and the next time the app adds an observer the purchase will come through again. There are complications if multiple users share a device and share their Apple ID. It is very hard to differentiate between 'different users' who share an Apple ID. The only way to handle this is to continually monitor who is logged into your system and record that during paymentRequest. Use that information to object when they try to credit your server and don't call finishTransactions. (Or you could simply give your IAP credit to any one of them and let them fight over it. They should not be sharing their Apple ID.)


Once the app has obtained the purchase from StoreKit it needs to store it somewhere. If it gets interrupted during that store process it can either rely on not having called finishTransaction (as described above) and get the process restarted at a later time or it can take on the responsibility of retrying itself and dismiss StoreKit by calling finishTransactions. If you have multiple users then the complications mentioned above become double here. If you do not, then you can rely on StoreKit to restart the process from the beginning. I prefer to take over the process myself by storing something in NSUserDefaults or the files system (always calling finishTransactions from within updatedTransactions) and responding to that NSUserDefault/files entry each time the app opens. If there are multuiple users then you can store that information in NSUserDefaults and rely on it next time the app opens. That 'information' could be the user's ID on your system at time of purchase or paymentRequest although they will rarely be different. ('Sign' the entry in NSUserDeafults to prevent theft.)


Regarding the deferred state - you can ignore this. If the transaction comes through tehre will be a new call to updatedTransactions and it will be handled like an original purchase above.


Your #3 is confusing - the user is logging out of which system - if StoreKit then it will sit there awaiting the next time the user logs into StoreKit under their AppleID using that device. If it is your system then everything above still applies. Either you rely on not finishing the transaction and letting the purchase hit updatedTransactions or on the system you created yourself. Again, with multiple users you need to store some information at the time of the paymentRequest.

Hi, Thank you for your detailed reply.


Yes, you’re right they shouldn’t their share Apple ID, but one person might have two or more accounts in the app for example.


About recording user data during paymentRequest - it sounds like a good option, but I wonder how to match this data to the SKPaymentTransaction when updatedTransactions is called - to make sure this user data matches this transaction. For example:

User A makes purchase. UpdatedTransactions not called yet for some reason (user A goes through a tunnel). User A logs out. User B logs in. User B makes purchase. UpdatedTransactions is called for both transactions and I have both user datas. How do I know which transaction belongs to which user.

When user A makes a paymentRequest in the app you can record, in NSUserDefaults or elsewhere on the device, the username they use on your server. Don't let anyone else make a paymentRequest until this one clears - or warn them that they are displacing the last purchase. When a call is made to updatedTransactions you will know the username at the time of paymentRequest.


While it is useful to handle various what-if scenarios, you really don't have to worry about 'user A purchases - enters tunnel - hands device to user B - second purchase - log-in switch, comes out of tunnel - credit goes to wrong party'. In that case user A and user B are sharing a device, sharing a ride through a tunnel and they can most likely handle the IAP credit going to the wrong one.


While some people report success using applicationUsername for exactly this purpose, Apple has never documented that they will return applicationUsername in updatedTransactions. The documents indicate a very different purpose for applicationUsername - so unless Apple is being obstruse - this may break any moment. In fact, there have been times when it was not returned and it does not work for restoreCompletedTransactions. It is unclear to me whether you can rely on applicationUsername.

Thank you, you helped a lot. We decided to ignore some of the cases and handle the server update fail only if there is a way to identify the user at this point, otherwise an error message will be shown with follow up instructions.