Unity IAP - Restoring subscriptions

Hi,

We need to implement restore functionality for purchases on iOS. We offer subscriptions and consumables in our products, so all the restored purchases should be subscriptions (it looks like they are by looking at the product ids).
I have followed the documentation and added a method that triggers the purchase restore functionality for ios using the IAppleExtensions.

IStoreListener.ProcessPurchase(PurchaseEventArgs purchaseEvent) is successfully invoked, however it is invoked for a bunch of transactions that we cannot validate against our backend records using the transaction Id.
Also, recently purchased subscriptions doesn’t seem to be coming back.

  1. Is the ProcessPurchase callback being invoked for previous purchases in any particular order? (i.e. most recent first)
  2. Does unity IAP pick up sandbox payments properly ?
  3. Does unity IAP pick up subscriptions on trial ?

NOTE: I Also noticed that ProcessPurchase was triggers as part of the initialisation process on iOS, however the documentations states that that shouldn’t be the case?

unity version : 2020.3.38f1
iap version: 4.1.5

Apple bundles all receipts. ProcessPurchase is not called in any order. Not sure what you mean by “pick up payments”. But yes, if I understand correctly. What is your observation with subscriptions on trial? We are working on improved subscription support including trials as we move to StoreKit2 by the end of the year.

Thanks for your promt response.

When I say pick up i basically ask whether ProcessPurchase will be invoked for sandbox payments/ subscription on trial.
The problem I am trying to resolve is, how can I detect what’s the latest/valid purchase from all these subscription purchase transactions ?

When i try to test the functionaity I :

  1. buy a subscription product (sandbox)
  2. delete the app
  3. re-install
  4. try to restore

At this point, none of the transaction ids that are being restored maps to the latest purchase.
Also it takes some time before unity IAP allows a subscription purchase to go through again (but i think that’s a problem with the nature of sandbox subscriptions that don’t last long)

We use purchaseToken, this post should help https://discussions.unity.com/t/854991

Isn’t purchaseToken Android specific ?

I am getting wrong transactionIds on iOS. I am querying the transaction id that can be found in purchaseEvent.purchasedProduct.transactionID. Am i missing something ?

Oops, sorry about that, you are correct. I will follow up with the team here. I did find this https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid

This is how you can find the originalTransactionIdentifier from an AppleInAppPurchaseReceipt. That receipt can be found in the purchaseProduct.receipt.

    private void LogAppleReceiptValidationInfo(IPurchaseReceipt productReceipt)
    {
        var appleReceipt = productReceipt as AppleInAppPurchaseReceipt;
        if (appleReceipt != null)
        {
            LogConsole($"Apple - Original Transaction: '{appleReceipt.originalTransactionIdentifier}', Expiration Date : '{appleReceipt.subscriptionExpirationDate}', Cancellation Date : '{appleReceipt.cancellationDate}', Quantity : '{appleReceipt.quantity}'");
        }
    }

When you say purchaseProduct.receipt are you referring to the purchasedProduct property in PurchaseEventArgs that is of type Product ?

The receipt in there is a string. Is this the same receipt documented here? Unity - Manual: Purchase Receipts

If so, how do we get the IPurchaseReceipt (and therefore the AppleInAppPurchaseReceipt) from the string receipt ?

Can I deserialise the payload from the receipt into an AppleInAppPurchaseReceipt?

The receipt you have is a string as a JSON hash, the same as the link you posted.

From that receipt (found in purchaseProduct.receipt), you can use the CrossPlatformValidator.Validate which will return an array of IPurchaseReceipt.

Alternatively, you can also use the AppleValidator.Validate to obtain an AppleReceipt which has inAppPurchaseReceipts containing an array of IPurchaseReceipt.

See both examples (Store-specific details and Parsing raw Apple receipts):

Thanks! I used cloud verification instead and managed to get the original transaction id.

When attempting to restore purchases (subscriptions in my case) do we need to call StoreController.ConfirmPendingPurchase for any result we get back?

I get some weird behaviour where, when i try to restore purchases and nothing comes back from Unity IAP, and then I try purchase the product, unity IAP fails the purchase with a reason Duplicate Transaction.

No, there is no need to call ConfirmPendingPurchase during restore. Basically treat it like a brand new purchase. You should see ProcessPurchase triggered for each product that is restored.

We have this exact same issue on iOS with Unity IAP 4.4.1

To avoid confusion, please provide your steps to reproduce.

But it doesn’t. Here is my case:

I got 2 subscriptions which must be verified on app start. I was able to test it on iOS only using Test Flight using my actual Apple ID account (not a sandbox user).

By some reason, each time starting the app I am forced to make the subscription purchase because these 2 subscription products just doesn’t have a receipt so I can’t verify, is subscription active or not.
Even knowing the fact it’s not related to purchase restore feature (because app wasn’t re-isntalled) but trying to restore purchases using appleStore.RestoreTransactions method, I receive only true as a result, but never receive
ProcessPurchase callback. Also, checking the subscriptions receipts after restore I can’t get any info, because receipts were not found.
Furthermore, once I see these logs trying to restore the purchase - by some reason there wre unfinished transaction

XCode log

2022-12-18 14:04:17.671886+0800 ScienceKids[2234:607847] UnityIAP: Restore transactions
2022-12-18 14:04:17.672052+0800 ScienceKids[2234:607847] UnityIAP: RestorePurchase
2022-12-18 14:04:21.486094+0800 ScienceKids[2234:607847] UnityIAP: UpdatedTransactions
2022-12-18 14:04:21.520672+0800 ScienceKids[2234:607847] UnityIAP: PaymentQueueRestoreCompletedTransactionsFinished
2022-12-18 14:04:21.535421+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646413
2022-12-18 14:04:21.535686+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646414
2022-12-18 14:04:21.535896+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646415
2022-12-18 14:04:21.536102+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646416
2022-12-18 14:04:21.536299+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646417
2022-12-18 14:04:21.536497+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646418
2022-12-18 14:04:21.536693+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646419
2022-12-18 14:04:21.536890+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646420
2022-12-18 14:04:21.537086+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646421
2022-12-18 14:04:21.537285+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646422
2022-12-18 14:04:21.537503+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646423
2022-12-18 14:04:21.537726+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646424
2022-12-18 14:04:21.537927+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646425
2022-12-18 14:04:21.538119+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646426
2022-12-18 14:04:21.538314+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646427
2022-12-18 14:04:21.538504+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646428
2022-12-18 14:04:21.538701+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646429
2022-12-18 14:04:21.538893+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646430
2022-12-18 14:04:21.539087+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646431
2022-12-18 14:04:21.539278+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646432
2022-12-18 14:04:21.539468+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646433
2022-12-18 14:04:21.539655+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646434
2022-12-18 14:04:21.539843+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646435
2022-12-18 14:04:21.540034+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646436
2022-12-18 14:04:21.540225+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646437
2022-12-18 14:04:21.540447+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646438
2022-12-18 14:04:21.540672+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646439
2022-12-18 14:04:21.540945+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646440
2022-12-18 14:04:21.541133+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646441
2022-12-18 14:04:21.541323+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646442
2022-12-18 14:04:21.541513+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646443
2022-12-18 14:04:21.541745+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646444
2022-12-18 14:04:21.541949+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646445
2022-12-18 14:04:21.542146+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646446
2022-12-18 14:04:21.542340+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646447
2022-12-18 14:04:21.542540+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646448
2022-12-18 14:04:21.542736+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646449
2022-12-18 14:04:21.542929+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646450
2022-12-18 14:04:21.543121+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646451
2022-12-18 14:04:21.543313+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646452
2022-12-18 14:04:21.543511+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646453
2022-12-18 14:04:21.543707+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646454
2022-12-18 14:04:21.543921+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646455
2022-12-18 14:04:21.544152+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646456
2022-12-18 14:04:21.544370+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646457
2022-12-18 14:04:21.544564+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646458
2022-12-18 14:04:21.544757+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646459
2022-12-18 14:04:21.544948+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646460
2022-12-18 14:04:21.545141+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646461
2022-12-18 14:04:21.545330+0800 ScienceKids[2234:607847] UnityIAP: Finishing transaction 2000000230646462
Restoring IAP purchases result: True

How to get the ProcessPurchase callback and make the things works as expected?

I am on Unity 2021.3 and IAP 4.5.1

UPDATE
Finally, I could make it work using Sandbox user login in AppStore app settings. However, there are still no callback after purchase restore request - if user without any subscription tries to restore purchased, I still will get true as a response. Sounds logically if that callback represents just a restore purchase request result, but in terms of app development it’s impossible to understand in right time does user has a purchase or not. If he has, I will get the process purchase callback what is fine, but if not, I need to check the receipt again after getting true in purchase restore request, but I need to do it in some delayed time, becuase can be the case that
process purchase callback can be delayed…