Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Solved] CrossPlatformValidator under iOS only works from within ProcessPurchase()?

Discussion in 'Unity IAP' started by myama, Apr 7, 2016.

Thread Status:
Not open for further replies.
  1. myama

    myama

    Joined:
    May 2, 2014
    Posts:
    14
    I’m using Unity 5.3.4f1 on a Mac.

    I’m having an issue with local receipt validation for iOS In-app purchase. When I use CrossPlatformValidator.Validate from inside the ProcessPurchase callback, my receipt validates. This happens when I do a new purchase or restore purchases on my device.

    When I relaunch the app, I read the local app receipt using IAppleConfiguration.appReceipt. But then when pass this into CrossPlatformValidator.Validate it throws the InvalidReceiptDataException.

    I’ve compared the receipt strings, they are the same, the string I pass into CrossPlatformValidator.Validate inside ProcessPurchase() is the same as the string I get using IAppleConfiguration.appReceipt.

    So Is CrossPlatformValidator only supposed to work from within ProcessPurchase?

    So CrossPlatformValidator works inside of PurchaseProcessingResult():
    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
    2. {
    3.     // Unity IAP's validation logic is only included on these platforms.
    4. #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    5.     // Prepare the validator with the secrets we prepared in the Editor
    6.     // obfuscation window.
    7.  
    8.     var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    9.         AppleTangle.Data(), Application.bundleIdentifier);
    10.     try {
    11.         // On Google Play, result will have a single product Id.
    12.         // On Apple stores receipts contain multiple products.
    13.         var result = validator.Validate(e.purchasedProduct.receipt);
    14.         Debug.Log("Receipt is valid. Contents:");
    15.         foreach (IPurchaseReceipt productReceipt in result) {
    16.             Debug.Log(productReceipt.productID);
    17.             Debug.Log(productReceipt.purchaseDate);
    18.             Debug.Log(productReceipt.transactionID);
    19.         }
    20.  
    21.         // Unlock the appropriate content here.
    22.     } catch (IAPSecurityException) {
    23.         Debug.Log("Invalid receipt, not unlocking content");
    24.     }
    25. #endif
    26.  
    27.     return PurchaseProcessingResult.Complete;
    28. }
    But if I do something like this, it throws the IAPSecurityException, with the same receipt string passed in on the same device:

    Code (CSharp):
    1. public ValidateReceiptAtStartup ()
    2. {
    3.     var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
    4.     string appReceipt = builder.Configure<IAppleConfiguration>().appReceipt;
    5.  
    6.     // Unity IAP's validation logic is only included on these platforms.
    7. #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    8.     // Prepare the validator with the secrets we prepared in the Editor
    9.     // obfuscation window.
    10.     var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    11.         AppleTangle.Data(), Application.bundleIdentifier);
    12.     try {
    13.         // On Google Play, result will have a single product Id.
    14.         // On Apple stores receipts contain multiple products.
    15.         var result = validator.Validate(appReceipt);
    16.         Debug.Log("Receipt is valid. Contents:");
    17.         foreach (IPurchaseReceipt productReceipt in result) {
    18.             Debug.Log(productReceipt.productID);
    19.             Debug.Log(productReceipt.purchaseDate);
    20.             Debug.Log(productReceipt.transactionID);
    21.         }
    22.  
    23.         // Unlock the appropriate content here.
    24.     } catch (IAPSecurityException) {
    25.         Debug.Log("Invalid receipt, not unlocking content");
    26.     }
    27. #endif
    28. }
    Here is the exception I get when not called from inside ProcessPurchase:
    ValidateLocalReceipt: EXCEPTION=UnityEngine.Purchasing.Security.InvalidReceiptDataException: Exception of type 'UnityEngine.Purchasing.Security.InvalidReceiptDataException' was thrown.
    at UnityEngine.Purchasing.Security.CrossPlatformValidator.Validate (System.String unityIAPReceipt) [0x00000] in <filename unknown>:0

    On relaunch of the app, I’ve tried using the AppleValidation class on the receipt loaded from IAppleConfiguration.appReceipt, and it does return an AppleReceipt object. But it was not clear to me if the AppleValidation class does all the same checks as CrossPlatformValidator. Should I be confident that if AppleValidation.Validate returns an AppleReceipt, that the receipt string I passed in is valid and its “Receipt authenticity is checked via signature validation” like the CrossPlatformValidator does?

    Thanks
     
    rainbowmimizu likes this.
  2. nicholasr

    nicholasr

    Joined:
    Aug 15, 2015
    Posts:
    183
    @myama this is a limitation where the CrossPlatformValidator API expects a platform neutral Unity IAP receipt. This Unity IAP "unified" JSON receipt is passed back in "e.purchasedProduct.receipt" and described more fully here http://docs.unity3d.com/Manual/UnityIAPPurchaseReceipts.html.

    A workaround would be to wrap that Apple receipt and pass it in. Please let me know if this doesn't fix your ValidateReceiptAtStartup method.

    1. First format the 'appReceipt' in a JSON string:
    string unityIAPReceipt = "{ \"Store\": \"AppleAppStore\", \"Payload\": \"" + appReceipt + "\" }";
    2. Then use unityIAPReceipt as the parameter to validator.Validate(unityIAPReceipt) instead of appReceipt.

    To reiterate:

    The IAppleConfiguration receipt data is a base-64 encoded ASN.1 from Apple. However the e.purchasedProduct.receipt is a unified JSON receipt from Unity IAP which wraps the platform receipt under a JSON key called "Payload". Also the JSON receipt has a "Store" key.

    The "builder.Configure<IAppleConfiguration>().appReceipt" differs from the "e.purchasedProduct.receipt" - the latter is a wrapped receipt and the former is the raw Apple receipt.

    Also, the docs could be clearer, calling out when Unity IAP is expecting a platform neutral unified receipt. Here's more on the receipts one may see when using IAP: http://docs.unity3d.com/Manual/UnityIAPPurchaseReceipts.html
     
    Last edited: Apr 7, 2016
  3. myama

    myama

    Joined:
    May 2, 2014
    Posts:
    14
    Thank you, that worked.
     
    rainbowmimizu, erika_d and nicholasr like this.
Thread Status:
Not open for further replies.