Hi there,
We're tracking an issue both in the sandbox environment and in production whereby download of App Store hosted in-app purchase content is not working properly. This is something that only happens on iOS 14 - all beta versions up to and including beta 3 - and has occurred with no change in our in-app purchase code. Either there is a bug in iOS 14 StoreKit, or something changed which breaks our implementation.
In production our logs show that in-app purchase SKPaymentTransaction object flow occurs nominally, with purchased and restored transactions presented and handled as expected.
Ultimately we provide content by calling
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads]
with a non-nil transaction whose first downloads array object is non-nil and in the 'Waiting' SKDownload state. Ultimately a 'Finished' download object is returned via updatedDownloads:, but on processing the (supposedly) completed download NSFileManager fails to move the content files into the app directory.
Investigating in the Sandbox allows us to see more via the debugger.
It appears that the updatedDownloads: StoreKit method does not return 'Active' SKDownload objects at any time. We are starting a 'Waiting' SKDownload object and then seeing nothing until one of two cryptic iOS error message appears in the debugger:
SKDownload encountered a sandboxing error: 0 or
SKDownload encountered a sandboxing error: 35 The SKDownload is then returned to the updatedDownloads: method in the 'Finished' state, and not the 'Failed' state as one might have expected. When attempting to process the finished download we have verified via NSFileManager that the source files we expect DO exist at the source location (the temporary content download directory) and DO NOT exist at the destination location prior to attempting to move the files across. It would appear that the download is actually executing, but we are not being informed of its progress, and cannot move the resultant files into our app directory.
NSFileManager gives the following error on attempting a file move:
Error Domain=NSCocoaErrorDomain Code=513 "“redactedFilename.jpg” couldn’t be moved because you don’t have permission to access “redactedFolderName”." UserInfo={NSSourceFilePathErrorKey=/private/var/mobile/Library/OnDemandResources/AssetPacks/StoreKit/6050167547064785133/Contents/Content/25/redactedFilename.jpg, NSUserStringVariant=(
Move
), NSDestinationFilePath=/var/mobile/Containers/Data/Application/63151311-068D-4421-89BB-0E7AAEF65C62/Library/Application Support/Packs/redactedFolderName/redactedFilename.jpg, NSFilePath=/private/var/mobile/Library/OnDemandResources/AssetPacks/StoreKit/6050167547064785133/Contents/Content/25/redactedFilename.jpg, NSUnderlyingError=0x281aae2b0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}} We have double checked, and the target folder has been created and exists:
/var/mobile/Containers/Data/Application/63151311-068D-4421-89BB-0E7AAEF65C62/Library/Application Support/Packs/redactedFolderName Can anyone shed any light on what is occurring here? Why do I not have permission to move files into the application support directory of my own app, into a folder which I created successfully moments prior? I've not found any hint of other people experiencing similar issues so far. If it is a simple case of needing to attain more permissions, that doesn't explain why I am not seeing 'Active' SKDownloads returned via StoreKit's updatedDownloads: method.
I would really appreciate any insight from anyone - thank you in advance.
Kind regards,
Alex
Post
Replies
Boosts
Views
Activity
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:
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.
Expectation:
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.
Attempts to purchase an SKProduct or restore all completed transactions via the usual method calls in StoreKit leads to failure in the beta environment with the following error:
Error Domain=SKErrorDomain Code=0 "An unknown error occurred" UserInfo={NSLocalizedDescription=An unknown error occurred, NSUnderlyingError=0x300b10210 {Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={NSUnderlyingError=0x300b10090 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?REDACTED, AMSStatusCode=500, NSLocalizedFailureReason=The response has an invalid status code}}}}}
We've tried with existing sandbox user accounts, and created fresh ones which also fail in a similar way. Our beta testers report the same thing. Nothing has changed in our IAP code, and this started happening about 24-48 hours ago.
It seems to be a server-side error - please can someone verify on Apple's side?