9 Replies
      Latest reply on Sep 11, 2019 2:13 AM by roron
      roron Level 1 Level 1 (0 points)

        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.

        • Re: IAP - if server fails to provide content
          KMT Level 9 Level 9 (14,665 points)

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

            • Re: IAP - if server fails to provide content
              roron Level 1 Level 1 (0 points)

              Hi. If you mean my question is too long, here is the short version - How to associate a transaction with an app user (not apple id) in case they logout before the transaction is completely handled meaning before the server has provided the content for the user. But I figured more explanation will be better. I looked in the FAQ you pointed me to, but don't see an answer to this. I am adding and removing the transaction observer as suggested in the documentation - at start and killing of the app.

                • Re: IAP - if server fails to provide content
                  KMT Level 9 Level 9 (14,665 points)

                       > I am adding and removing the transaction observer as suggested

                   

                  Yep, I expected the FAQ to only help in that example if you weren't.

                   

                       >How to...

                   

                  Clearer, thanks. If the user actually logs out before they receive content tied to a completed transaction, that process will stay in the queue for a short time, waiting for them to come back. Your process would normally wait on apple's process to complete, rather than anticipate completion on it's own...don't assume the backend will allow the purchase, otherwise.

                   

                       >How to associate a transaction with an app user (not apple id)

                   

                  Can I ask why you don't want to rely on apple ID in your example?

                    • Re: IAP - if server fails to provide content
                      roron Level 1 Level 1 (0 points)

                      Thanks for the reply. What I mean is logging in/ out of the application, not with the Apple ID. I’d like to associate app level users with the content purchased from the AppStore, but the users can logout (in the app) before purchase is successful or before they “get” the content from our server. I am looking for a solution to the different cases that may occur because of the multiple users option. Example: User A logs in the app, makes Successful purchase to the App Store, but fails to update our server => finishTransaction not called. User A logs out of the app. User B logs in the app, makes Failed purchase to the App Store. UpdatedTransactions is called with both transactions (at least I presume it will be that way). Now how do I know which transaction update belongs to which user.

                • Re: IAP - if server fails to provide content
                  PBK Level 7 Level 7 (3,235 points)

                  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.

                    • Re: IAP - if server fails to provide content
                      roron Level 1 Level 1 (0 points)

                      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.

                        • Re: IAP - if server fails to provide content
                          PBK Level 7 Level 7 (3,235 points)

                          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.