Our app implements 100+ IAPs that use Apple-hosted content. As one cannot restore individual products in the StoreKit API we must call [[SKPaymentQueue defaultQueue[ restoreCompletedTransactions] and handle all SKPaymentTransactionRestored transactions returned in the context of the user's expectations.
This typically involves starting the download of one or more products and then waiting for them to complete before finishing these transactions. Users inevitably background the app while they wait. We observe that if the app is backgrounded and then foregrounded, StoreKit will deliver an SKPaymentTransactionPurchased transaction to the queue for every restored product that is currently being handled (i.e. still in the queue).
This is very confusing and hard to handle in code as we have to be able to discern the difference between a genuine purchased transaction and a restored transaction in order to deliver adequate user experience. What are we expected to do with these 'purchased' duplicates? Typically we are already downloading content associated with the 'restored' versions. Critically, finishing these 'purchased' duplicates to get rid of them causes the payment queue to 'cancel' the downloads of the corresponding 'restored' transactions. However, the 'purchased' transactions seem to have their own functionally independent downloads (i.e. the restored progress can be 57%, but the purchased progress is 0% - they have different SKDownloads for the same product).
Our expectation would be that a 'restored' transaction that has not been finished remains in the queue and that if the app is background/foregrounded or terminated and relaunched that SKPaymentQueue would redeliver the same 'restored' transaction, which could easily be identified, de-duped and handled appropriately. Instead we seem to be given multiple independent transactions for the same product restoration request, with different transaction states and different downloads.
An important addendum to this feedback is this:
Upon finishing of SKPaymentTransactions, iOS 14 is 10x slower than iOS 13 in reporting the removal of the transaction from the queue via paymentQueue:removedTransactions:. When 100+ transactions are returned from restoreCompletedTransactions this creates quite a lot of lag in terms of being able to update the UI (and thus allow the user to restore a different product if they need to). Finishing ~100 SKPaymentTransactions in a for-loop results in paymentQueue:removedTransactions: being called 100 times with a single transaction. On iOS 13 this occurs in a fraction of a second, but on iOS 14 can take 10-20 seconds to play out. How come this got so slow? Can it be sped up (drastically!) or can we get faster API methods to batch finish transactions?
Reproduction:
paymentQueue:updatedTransactions: is called with the same set of SKPaymentTransactionRestored transactions already in the queue.
paymentQueue:removedTransactions: is called in rapid succession after finishing multiple transactions in a for-loop, or once with all transactions.
Observation:
paymentQueue:updatedTransactions: is called with an SKPaymentTransactionPurchased transaction for every product with an unfinished SKPaymentTransactionRestored in the queue. Finishing any of these purchased transactions will cancel download for the restored transaction corresponding to the same product, if indeed a download is active.
paymentQueue:removedTransactions: is called very slowly (one-by-one) after finishing multiple transactions in a for-loop.
This typically involves starting the download of one or more products and then waiting for them to complete before finishing these transactions. Users inevitably background the app while they wait. We observe that if the app is backgrounded and then foregrounded, StoreKit will deliver an SKPaymentTransactionPurchased transaction to the queue for every restored product that is currently being handled (i.e. still in the queue).
This is very confusing and hard to handle in code as we have to be able to discern the difference between a genuine purchased transaction and a restored transaction in order to deliver adequate user experience. What are we expected to do with these 'purchased' duplicates? Typically we are already downloading content associated with the 'restored' versions. Critically, finishing these 'purchased' duplicates to get rid of them causes the payment queue to 'cancel' the downloads of the corresponding 'restored' transactions. However, the 'purchased' transactions seem to have their own functionally independent downloads (i.e. the restored progress can be 57%, but the purchased progress is 0% - they have different SKDownloads for the same product).
Our expectation would be that a 'restored' transaction that has not been finished remains in the queue and that if the app is background/foregrounded or terminated and relaunched that SKPaymentQueue would redeliver the same 'restored' transaction, which could easily be identified, de-duped and handled appropriately. Instead we seem to be given multiple independent transactions for the same product restoration request, with different transaction states and different downloads.
An important addendum to this feedback is this:
Upon finishing of SKPaymentTransactions, iOS 14 is 10x slower than iOS 13 in reporting the removal of the transaction from the queue via paymentQueue:removedTransactions:. When 100+ transactions are returned from restoreCompletedTransactions this creates quite a lot of lag in terms of being able to update the UI (and thus allow the user to restore a different product if they need to). Finishing ~100 SKPaymentTransactions in a for-loop results in paymentQueue:removedTransactions: being called 100 times with a single transaction. On iOS 13 this occurs in a fraction of a second, but on iOS 14 can take 10-20 seconds to play out. How come this got so slow? Can it be sped up (drastically!) or can we get faster API methods to batch finish transactions?
Reproduction:
Use iOS 14.x, iPhone or iPad.
Call paymentQueue:restoredTransactions:.
For one or many SKPaymentTransactionRestored transactions returned, begin an Apple-hosted content download.
Background the app mid-download.
Foreground the app prior to completion of the download. You may need to try this once or twice, but we've observed it both when the app execution is and is not suspended during the background phase.
paymentQueue:updatedTransactions: is called with the same set of SKPaymentTransactionRestored transactions already in the queue.
paymentQueue:removedTransactions: is called in rapid succession after finishing multiple transactions in a for-loop, or once with all transactions.
Observation:
paymentQueue:updatedTransactions: is called with an SKPaymentTransactionPurchased transaction for every product with an unfinished SKPaymentTransactionRestored in the queue. Finishing any of these purchased transactions will cancel download for the restored transaction corresponding to the same product, if indeed a download is active.
paymentQueue:removedTransactions: is called very slowly (one-by-one) after finishing multiple transactions in a for-loop.