Restore purchase vs. receipt validation

I have a macOS app with auto-renewable subscription. I'm reading the docs and cannot fully understand the concept. According to the docs I have to add and observer to the payment queue. I have to provide a restore function to restore a purchase if the app was reinstalled. My observer is listening to the queue and it gets restored transactions once I call SKPaymentQueue.default().restoreCompletedTransactions() method. However, I cannot see if the subscription expired or not just using the observer. Transaction objects which I reecive in the payment queue have only some abstract transaction ID.


The docs recommend to validate a receipt. I'm sending base64 encoded receipt to my server which in turns sends it to verifyReceipt endpoint. Even if I send an old receipt, Apple responds with JSON containing latest_receipt_info where I can see the current status of subscription and it's expiration date. I can assume that subscription is not active if expiration date is earlier than the current date.


The question is why I have to call SKPaymentQueue.default().restoreCompletedTransactions() method to restore a purchase if I can refresh the receipt (if it's missing) and send it to my server and get the recent info? It seems to me that it is redundant. So my usage is:

  • Listen to the payment queue
  • Perform receipt validation as soon as I get transaction with purchased state and unlock paid functionality if I see a transaction with specific productId and expiration date later than the current date
  • Perform receipt validation when user clicks Restore button and unlock paid functionality if I see a transaction with specific productId and expiration date later than the current date
  • Perform receipt validation on every launch of the application to check the subscription status
  • Perform receipt validation every 12 or 24 hours to disable paid functionality if the app was not closed but the subscription already expired


Did I clearly understood the concept or am I missing something here because I don't see the benefit of calling SKPaymentQueue.default().restoreCompletedTransactions() method?

Replies

The short answer to basing the restore process on the use of validating the appStoreReceipt is that it will fail in App Review. The issue here stems from the fact that in the sandbox, when the app is installed by Xcode, TestFlight or in App Review, there is never an appStoreReceipt until a purchase is made, or transactions are restored using the restoreCompletedTransactions method. If your app behaves as described, then when the reviewer presses the "Restore" button, the appStoreReceiptURL will be nil and the app will make use of the SKReceiptRefreshRequest. When the Authentication dialog is presented, the App Reviewer always cancels the request because it requires a password which the reviewer doesn't have. If the reviewer cannot verify that an in-app purchase can be restored, I'm told that the app will be rejected. If the app uses restoreCompletedTransactions, prior transactions will be restored.


In the production environment, this is not normally an issue as the app is always installed with the appStoreReceipt current for the user who installs the app.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

Post not yet marked as solved Up vote reply of rich Down vote reply of rich

I just noticed that your question involves a macOS app. I apologize, my answer above only applies to iOS apps. If the macOS StoreKit app detects that the appStoreReceiptURL is nil and calls exit (173), then the appStoreReceipt for the reviewer will be updated to reflect prior transactions.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

I see Rich believes that there will be a current receipt on a Mac device upon installation. I do not dispute that, I just do not know. Same issue with an iOS app. But this does not address the scenario - "I own two devices and just purchased the subscription on one device; now how do I get that subscription on my other device?" But you ask the following....


"The question is why I have to call SKPaymentQueue.default().restoreCompletedTransactions() method to restore a purchase if I can refresh the receipt (if it's missing) and send it to my server and get the recent info?"


There is no substantive difference between restoreCompletedTransactions and an SKReceiptRefreshRequest. Both require a recent log-in to the App Store. Both result in a new receipt. The restore does a call to updatedTransactions when it completes, the refresh does a call to its delegate's requestdidFinish: method. You only need to call one of these and only once on each device owned by the user. Keep in mind that the user will be expecting this requirement since they just loaded the app in the device or just purchased the subscription on their other device.


You certainly do not want to be sending receipts from every user to the app store unless you suspect the user might have an active subscription.

It seems like an error on appreviews' part that they don't have a password to their own test account. Do your statements imply they can never test any functionality which uses receipt refreshing?