Hey @Citrus Yan, @Andy, @Brendan, @SethDu (tagging you all as you've all been active on these threads over the years),
Sorry to bring this issue back from the dead, but it's important and I don't think it's been resolved yet:
Threads I could find where this issue has been reported over the years:
- https://community.playfab.com/questions/27917/ios-restore-purchase-receipt-using-unity-iap.html
- https://community.playfab.com/questions/20664/how-to-restore-non-consumable-items-properly-using.html
- https://community.playfab.com/questions/9053/is-there-any-way-to-restore-purchases-without-usin.html
- https://community.playfab.com/questions/5815/receipt-validation-iosandroid-after-restore-purcha.html
While the best practice is of course to try and restore the user's PlayFab account, Apple requires that purchases can be restored so this is an important issue that needs to be addressed.
Are you aware of the longstanding (5 years+ now!) Apple bug where the transaction_id and original_transaction_id are always the same? This issue exists for the iOS7+ "app receipt" format rather than the older and deprecated iOS6 "transactionReceipts".
It's been discussed on numerous Apple developer forum posts, but this one probably has the most info. I've added bold type to highlight what I feel are important points:
"In the earlier receipts the transaction_id was, in fact, the transactionIdentifier. It was unique for all transactions and could be used to detect copied receipts and to differentiate repurchase-for-free and restore transactions from original paid purchases. It was included in the receipt. Now that transaction.transactionReceipt is deprecated we must use the new receipts. The transaction_id in post iOS6 receipts is now the same as original_transaction_id. There is no separate field for the transactionIdentifier for the latest transaction, just for each idividual IAP - and they all reflect the original purchase transaction. This means transaction_id can no longer be used to identifiy a copied receipt nor can it be used to tell whether the user has restored a transaction or repurchased an item for free. What you can do is detect the value of "receipt_creation_date" aka "creation_date" and compare that to the value for "original_purchase_date". If they are within a few seconds of each other then the purchase is a new paid purchase not a restore and not a repurchase-for-free. Also, if "creation_date" is close to [[NSDate alloc] init] then the receipt is not copied - you have to get the NSDate format correct."
This post from the same thread recommends:
"here is a solution that is just as good as before. Use the combination of transaction_id and creation_date in your server to identify a receipt that is a duplicate of another receipt that you have received. Reject any duplicates.
This does not protect you from someone doing a restore and grabbing the receipt before it is sent to your server and using that receipt to allow someone steal the IAP. But that scam could also have been used even when the receipt had unique transaction_id's.
If you want full protection then either decode the receipt yourself relying on the uniqueness of the device's identifierForVendor or check to be sure that the creation_date comes very shortly after the date of the actual purchaseRequest (except for an ask-to-buy purchase - so it's not fool-proof)."
I think "creation_date" refers to "receipt_creation_date" that exists outside of the in_app array but inside the secure Base64/ANS.1 string that must be decoded using the Apple Cert / ReceiptVerification endpoint.
Can you shed any light of what you are comparing in the PlayFab receipt validation code, please? If you are just looking at the transactionIDs from the in_app array then I think that will be the cause of all these "Receipt already used." errors that have been reported over the years!
Thanks for reading, I know it's a long one!
All the best,
Niall