16 Replies
      Latest reply on Apr 16, 2019 4:19 PM by Mr. Bruce Wayne
      Mr. Bruce Wayne Level 1 Level 1 (0 points)

        I'm testing restoring purchases, and in paymentQueue(queue: updatedTransactions transactions:) I print the amount of transactions to process. This number is all over the place. I get anywhere from 5 to over 100. Why is this?

        • Re: restoreCompletedTransactions returns varying amount transactions
          PBK Level 7 Level 7 (3,105 points)

          Is the number varying with the same testuser or with different testusers?

            • Re: restoreCompletedTransactions returns varying amount transactions
              Mr. Bruce Wayne Level 1 Level 1 (0 points)

              Only the same test user. I created a second test user to work around temporarily and it's not having issues so far.

                • Re: restoreCompletedTransactions returns varying amount transactions
                  PBK Level 7 Level 7 (3,105 points)

                  So with one test user is it 'jumping around' or is it constant?  Are you calling finishTransaction in between purchases?  How many times are you calling finishTransactions?  Could that explain the 'jumping around'?

                    • Re: restoreCompletedTransactions returns varying amount transactions
                      Mr. Bruce Wayne Level 1 Level 1 (0 points)

                      OK. With one test user, it is jumping around.

                       

                      func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
                      
                              print ("Number of transactions to process   \(transactions.count)")
                              
                              var xyz = 0
                              
                              for transaction in transactions{
                                  
                                  xyz += 1
                                  
                                  switch transaction.transactionState{
                                      
                                  case .purchased:
                                      print ("purchased")  
                                      //call my payed function
                                      SKPaymentQueue.default().finishTransaction(transaction)
                                      SKPaymentQueue.default().remove(self)
                                      statusLabel.text = "Suscribed"
                                      statusLabel.textColor = .softGreen()
                                      break
                                  
                                  case .restored:
                                      print ("restored")
                                      SKPaymentQueue.default().finishTransaction(transaction)
                      
                                     break
                                      
                                  case .failed:
                                      
                                      print ("failed")
                                      
                                      print ("transaction error   \(transaction.error!.localizedDescription)")                
                      
                                      let failedAlert = UIAlertController(title: "Failed", message: "Your payment failed!", preferredStyle: .alert)
                                      let okAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
                                      failedAlert.addAction(okAction)
                                      self.present(failedAlert, animated: true, completion: nil)
                                      SKPaymentQueue.default().finishTransaction(transaction)
                                      SKPaymentQueue.default().remove(self)
                                      break
                                  
                                  case .deferred:
                                      print ("deferred")
                                      break
                                  default:
                                      print ("default")
                                      break
                                  }
                                  print (xyz)
                              }
                          }
                      

                       

                      As you can see at line 3, I am printing the number of transactions to process. That is the number that jumps around when I test payments using a specific test user.

                        • Re: restoreCompletedTransactions returns varying amount transactions
                          PBK Level 7 Level 7 (3,105 points)

                          It seems like you are referring to one user having many transactions for which the app has not yet called finishTransaction.  I think it unwise to not call finishTransaction within the updatedTransaction method when the transaction is first reported or, at most, shortly after that first call - giving you just enough time to interact with your own server.  You will find that a user cannot restore a transaction before the first transaction is finished and various other issues arise before that transaction is finished.  Don't rely on the StoreKit queue to store transactions for you. 

                            • Re: restoreCompletedTransactions returns varying amount transactions
                              Mr. Bruce Wayne Level 1 Level 1 (0 points)

                              I'm not using my own server. I use the Apple servers to verify the receipt, and then the app handles what happens next. If I can't rely on the StoreKit to store transactions for me what else can I do?

                               

                               

                              P.S.

                              Sorry if I'm being troublesome. I'm only an amateur...for now...

                                • Re: restoreCompletedTransactions returns varying amount transactions
                                  PBK Level 7 Level 7 (3,105 points)

                                  You need to learn how to store information in the app.  You can use NSUserDefaults or the file system.  You need to store the information contained in the transaction and then call finishTransaction.  Use your stored information to set teh state of the app.

                                   

                                  For example, just store a receipt in NSUserDefaults.  When you want to get information, send that stored receipt to the Apple servers.  No need to rely on StoreKit's queue to redeliver that receipt.

                                    • Re: restoreCompletedTransactions returns varying amount transactions
                                      Mr. Bruce Wayne Level 1 Level 1 (0 points)

                                      The store receipt is stored in the StoreKit folder tho. Which is never deleted as far as I know. I simply use this on startup:

                                       

                                      if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path){
                                                  barTitle.text = "Checking your subscription"
                                                  privateReceiptValidation(url: "https://buy.itunes.apple.com/verifyReceipt", VC: self)
                                      
                                              }
                                      

                                       

                                      The privateReceiptValidation function sends the receipt to the Apple servers to verify it and decomplite it as a json file.

                                       

                                      Unless you mean save the json file in the app so I don't have to connect to the servers each startup? And if the subscription ended download a new receipt, etc.

                                       

                                      I know how to use UserDefaults, and my use of the apps documents folder is key to this app.

                                       

                                      BTW I should have stated that this takes place on iOS.

                                        • Re: restoreCompletedTransactions returns varying amount transactions
                                          PBK Level 7 Level 7 (3,105 points)

                                          First there is no need to check the recipt each time the user starts the app.  Check it once, record the expiration date and then check it again only after that expiration date.  Getting the receipt from bundle.main.appStoreReceiptURL will work fine.   If there is no receipt at that location then show a button for the user to tap marked 'check my subscription status'.  Then do a restoreCompletedTransactions.  That will trigger a log in to the App Store and will send a transaction to updatedTransactions. 

                                           

                                          If you do that there is no reason not to call finishTransaction on all transactions at the time of purchase - the issue at question in this thread.

                                            • Re: restoreCompletedTransactions returns varying amount transactions
                                              Mr. Bruce Wayne Level 1 Level 1 (0 points)

                                              If I don't call finishTransaction on each transaction at the time of purchase, where else do I do it? Every example I've seen of how to setup IAP has a switch statement which finishes the transaction in each case of purchased, restored, and failed.

                                                • Re: restoreCompletedTransactions returns varying amount transactions
                                                  PBK Level 7 Level 7 (3,105 points)

                                                  You may want to verify the receipt using your server.  After your server verifies the receipt it sends a message back to your app saying 'ok'.  Apple suggests ("Best practice") that you retain the transaction and finish it only after you have secured the subscription.  That way, as you envision, the next time you open a transactionObserver the transaction will come back to the app and restart that process in the unlikely situation where your server or Apple's server have not responded in a timely fashion.  Otherwise, in the unlikely situation where your server or the Apple server fails, if you have called finishTransaction you will forever lose the transaction.

                                                   

                                                  Because of myriad problems with not finishing the transaction promptly, I think that 'Better practice" is to finishTransaction in updatedTransactions and take on the responsibility of following up in that unlikely situation by recovering the receipt and restarting the verification process.

                                                   

                                                  Your original post suggested that you don't finishTransaction in updatedTransactions.  It also suggested another way of doing that.

                                                    • Re: restoreCompletedTransactions returns varying amount transactions
                                                      Mr. Bruce Wayne Level 1 Level 1 (0 points)

                                                      I do call finishTransaction in updatedTransactions. I posted the code in a previous post but I'll post it again. Please look at it and tell me if I'm simply misusing terms or what. What I mean when I say that each transaction is finished once the purchase is made is exactly what happens in my code below. After each case where the transaction state is purchase I call SKPaymentQueue.default().finishTransaction(transaction). I call that statement if the case is restored or failed as well. Is that wrong? If each transaction has been finished which would the number of transactions change?

                                                       

                                                       

                                                       

                                                       

                                                       

                                                      func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {  
                                                      
                                                              print ("Number of transactions to process   \(transactions.count)")  
                                                      
                                                              var xyz = 0  
                                                      
                                                              for transaction in transactions{  
                                                      
                                                                  xyz += 1  
                                                      
                                                                  switch transaction.transactionState{  
                                                      
                                                                  case .purchased:  
                                                                      print ("purchased")    
                                                                      //call my payed function  
                                                                      SKPaymentQueue.default().finishTransaction(transaction)  
                                                                      SKPaymentQueue.default().remove(self)  
                                                                      statusLabel.text = "Suscribed"  
                                                                      statusLabel.textColor = .softGreen()  
                                                                      break  
                                                      
                                                                  case .restored:  
                                                                      print ("restored")  
                                                                      SKPaymentQueue.default().finishTransaction(transaction)  
                                                      
                                                                     break  
                                                      
                                                                  case .failed:  
                                                      
                                                                      print ("failed")  
                                                      
                                                                      print ("transaction error   \(transaction.error!.localizedDescription)")                  
                                                      
                                                                      let failedAlert = UIAlertController(title: "Failed", message: "Your payment failed!", preferredStyle: .alert)  
                                                                      let okAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)  
                                                                      failedAlert.addAction(okAction)  
                                                                      self.present(failedAlert, animated: true, completion: nil)  
                                                                      SKPaymentQueue.default().finishTransaction(transaction)  
                                                                      SKPaymentQueue.default().remove(self)  
                                                                      break  
                                                      
                                                                  case .deferred:  
                                                                      print ("deferred")  
                                                                      break  
                                                                  default:  
                                                                      print ("default")  
                                                                      break  
                                                                  }  
                                                                  print (xyz)  
                                                              }  
                                                          }  
                                                      
                                                      
                                • Re: restoreCompletedTransactions returns varying amount transactions
                                  KMT Level 9 Level 9 (13,965 points)

                                  Seem to recall hearing this was a sandbox issue in the past - you might want to file bugs and see what comes back.