question

dreadwolf avatar image
dreadwolf asked

How do you get a receipt that is compatible with Unity SDK's PlayFabClientAPI.RestoreIOSPurchases?

Whenever I try to use RestoreIOSPurchases with the Unity SDK, I get "Receipt already used." Does anyone have a working example on how to do this? I understand that I need to call restoreCompletedTransactions and pass the receipt to RestoreIOSPurchases, but I can't figure out how to get the proper receipt with Unity/C# or Objective-C.

None of the documentation is helpful:

This Unity Documentation does not explain how to get the receipt:

Unity: Restoring Transactions

This Unity post blames Playfab:

ios Restore Transactions appears to refresh receipts rather than restore.

This Playfab post blames Unity:

iOS Restore Purchase Receipt using Unity IAP

I am 100% certain that the Unity IAP Plugin is calling restoreCompletedTransations under the hood because I located the place where it was being called in UnityPurchasing.m.

This code called from ProcessPurchase gives me a "Receipt Already Used" error: The transaction id for the product is different every time so it does seem like a restore receipt rather than a refresh receipt

JSONNode json = JSONNode.Parse(product.receipt);
var request = new RestoreIOSPurchasesRequest();
request.ReceiptData = json["Payload"].Value;
PlayFabClientAPI.RestoreIOSPurchases(request, OnRestoreIOSPurchase, OnRestoreIOSPurchaseError); 

I also tried using the app receipt after restore is finished, but this also gives me a "Receipt Already Used" error.

var request = new RestoreIOSPurchasesRequest();
request.ReceiptData = _appleConfig.appReceipt;
PlayFabClientAPI.RestoreIOSPurchases(request, OnRestoreIOSPurchase, OnRestoreIOSPurchaseError); 

I could try to bypass Unity entirely and get the receipt in Objective-C where I have direct access to SKPaymentQueue, but this is the callback for when the restore is finished:

-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue 

SKPaymentQueue has a list of SKPaymentTransactions transactions where "transactionReceipt" is deprecated, so how do I get a single PlayFab compatible receipt out of this function?

I have no idea whether the problem I'm having is, Apple, PlayFab, Unity, or my own bad code, so I'm wondering if anyone has managed to get this working. I've been banging my head against the wall for 2 days trying to get this working and I'm almost to the point of just adding a totally hackable CloudScript function to grant the items or switching to consumable. Please help! Anyone?

unity3dsdks
2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

dreadwolf avatar image dreadwolf commented ·

I finally found a post that helped me:

https://community.playfab.com/questions/9957/ios-7-style-itunes-receipt-failing-restoreiospurch.html

I modified the Unity bridge code to pass the deprecated transactionReceipt instead of the recommended appStoreReceiptURL receipt and was finally able to get RestoreIOSPurchases to succeed. This is not a good solution and it's disturbing that this post is two years old and still applicable. I saw some other posts that suggest the problem could also be related to a corrupted sandbox iTunes account so I will try a new sandbox account to see if I really have to use deprecated variables.

0 Likes 0 ·
Jimmie Tyrrell avatar image Jimmie Tyrrell dreadwolf commented ·

Trying to work around this same issue in 2020. @dreadwolf do you know if the transactionReceipt solution still works? Is Apple still accepting apps that use it? Do you have an example of the Unity bridge code you can share? I'm not much of an Objective-C developer :(

Any help you can provide would be greatly appreciated

0 Likes 0 ·
Citrus Yan avatar image
Citrus Yan answered

Hi,

After carefully reading your post, it seems that the core problem is on the side of Apple. There isn’t any response from Apple stating how to use appStoreReceiptURL to get a Restore receipt instead of a Refresh receipt, so Unity IAP can’t successfully get the Restore Receipt. Playfab will mark the receipts in the databases as “used” to prevent cheating, no doubt in your case the Restore receipts(actually is Refresh receipts) can’t successfully pass through the RestoreIOSPurchases request.

Playfab is a backend platform for live games which needs cooperation with many other platforms, we do what we are capable of, and other platforms do what they expert in.

We suggest that you should reach out to Apple to see if they can provide any update, if they can ensure that Refresh receipt can be safely used to restore purchased, we can do our part to adjust their update.

At present, you may have to use the deprecated way to get Restore Receipt, sorry for your inconvenience.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Niall Muldoon avatar image
Niall Muldoon answered

Hey @Citrus Yan, @Andy, @Brendan (tagging you all as you've all been active on these threads over the years),

Sorry to bring this 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:

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

1 comment
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Seth Du avatar image Seth Du ♦ commented ·

Hi, @Niall. Since this is a closed thread, we highly recommend creating a new one and paste your questions there, which will help us track the progress and provide technical support. You may add a link to refer this thread as well. Thanks for your understanding.

0 Likes 0 ·
Niall Muldoon avatar image
Niall Muldoon answered

Hey @dreadwolf,

Thank you for looking into this so thoroughly, I have been fighting the same battle now for quite some time! Please see my other recent post on this thread that perhaps sheds some light on the issue?

Can you provide any more information on exactly what you did to solve this?

You mention "I modified the Unity bridge code to pass the deprecated transactionReceipt instead of the recommended appStoreReceiptURL receipt and was finally able to get RestoreIOSPurchases to succeed." but I'm unsure of what exactly this means so any further information you could give would be very useful!

Thank you,

Niall

2 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

dreadwolf avatar image dreadwolf commented ·

Ultimately, I ended up just converting the in-app purchase to a consumable so I didn't have to restore it. If I recall correctly I was able to get a receipt I could use with Playfab by modifying Unity's purchasing code:

In UnityPurchasing.m I changed this line:

[self UnitySendMessage:@"OnPurchaseSucceeded" payload:transaction.payment.productIdentifier receipt:[self selectReceipt:transaction] transactionId:transactionId]; 

to this:

NSString* receipt; 
#if MAC_APPSTORE 
	// There is no transactionReceipt on Mac 
	receipt = @""; 
#else 
	// The transactionReceipt field is deprecated, but this is the only way to get a receipt that is compatible with PlayFab when restoring purchases
	receipt = [transaction.transactionReceipt base64EncodedStringWithOptions:0]; 
#endif 
[self UnitySendMessage:@"OnPurchaseSucceeded" payload:transaction.payment.productIdentifier receipt:receipt transactionId:transactionId]; 

I could then use that receipt for ValidateIOSReceipt. I'm not sure why my code wasn't calling RestoreIOSPurchase instead, but theoretically, the receipt would work for that too.

I looked up these code changes in my git logs from 2 years ago, so I have no idea if they still work.

0 Likes 0 ·
dreadwolf avatar image dreadwolf commented ·
0 Likes 0 ·

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.