Search Unity

[Closed] I there an example of local validation/receipt validation

Discussion in 'Unity IAP' started by gegagome, Dec 18, 2016.

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

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    Hi there

    I already set up IAP but need to use Unity's local validation or receipt validation but I can't seem to find an example. I haven't stored any data locally so I would like to see an example of how are people handling nonconsumibles in the case when device is restarted or quit.

    Thanks
     
  2. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    Hey, you can find a sample for the validation in the manual.

    This is validating receipts on purchase, but you can also validate products on start by looping over the controller's products list and calling validator.Validate with each product.receipt. Note that on Apple there is only one shared receipt for all products.
     
  3. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    I am asking, because I haven't store data locally yet on device, so I am assuming I have to store the receipt locally and then do the validation on Start.

    Is that so? Also where would the Obfuscating encryption keys part fit?

    As I said, I already have tested IAP functionality in my app, but need to make sure purchase is available at any time on the user's device.

    Thanks
     
  4. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    Is this what you suggested:

    Code (CSharp):
    1.  
    2.     public void ReceiptCheck ()
    3.     {
    4.         iap.labelCenter.text = "testing\n";
    5.         #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    6.         iap.labelCenter.text += "Entered if\n";
    7.         bool validPurchase = false;
    8.         Product[] products = m_StoreController.products.all;
    9.         CrossPlatformValidator validator = new CrossPlatformValidator (null,
    10.                                               AppleTangle.Data (),
    11.                                               Application.bundleIdentifier);
    12.  
    13.         for (int i = 0; i < products.Length; i++) {
    14.             var result = validator.Validate (products [i].receipt);
    15. //            if (result[i].productID == kProductNameAppleNonConsumable) {
    16.                 iap.IAPBought();
    17.                 iap.labelCenter.text += result[i].productID + "\n";
    18. //            }
    19.         }
    20.         #endif
    21.     }
    22.  
    I can't test it because GooglePlayTangle.Data() is throwing three errors:
    Assets/Purchaser.cs(260,80): error CS0103: The name `GooglePlayTangle' does not exist in the current context

    Assets/Purchaser.cs(262,89): error CS1502: The best overloaded method match for `UnityEngine.Purchasing.Security.CrossPlatformValidator.CrossPlatformValidator(byte[], byte[], string)' has some invalid arguments

    Assets/Purchaser.cs(262,89): error CS1503: Argument `#1' cannot convert `object' expression to type `byte[]'

    Thanks
     
    Last edited: Dec 19, 2016
  5. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    Latest try

    Code (CSharp):
    1.     public void ReceiptCheck ()
    2.     {
    3.         iap.labelCenter.text = "RECEIPT CHECK 2\n";
    4.  
    5.         if (Application.platform == RuntimePlatform.Android ||
    6.             Application.platform == RuntimePlatform.IPhonePlayer ||
    7.             Application.platform == RuntimePlatform.OSXPlayer) {
    8.             iap.labelCenter.text += "Entered if\n";
    9.  
    10.             Product[] products = m_StoreController.products.all;
    11.             iap.labelCenter.text += products.Length + "\n";
    12.             CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
    13.             for (int i = 0; i < products.Length; i++) {
    14.                 var result = validator.Validate (products [i].receipt);
    15.                 if (result[0].productID == kProductIDNonConsumable) {
    16.                     iap.labelCenter.text += result[0].productID + "\n";
    17.                 }
    18.             }
    19.         }
    20.     }
    Somehow this line:
    Code (CSharp):
    1. Product[] products = m_StoreController.products.all;
    breaks the whole method and lines below are never executed.

    Any help would be appreciated.

    Thanks
     
  6. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    https://docs.unity3d.com/Manual/UnityIAPValidatingReceipts.html

    shows how to create the obfuscation keys. They are under Window > Unity IAP > IAP Receipt Validation Obfuscator.

    You don't have to store anything on the device. Product.receipt will not be null in case the user owns the product, so you can call the validation on that after receiving it from the app stores (after billing initialization).
     
  7. ap-unity

    ap-unity

    Unity Technologies

    Joined:
    Aug 3, 2016
    Posts:
    1,519
    @gegagome

    There is also an example of local validation/receipt validation in the IAPDemo.cs that is in the UnityPurchasing folder.

    That shouldn't cause any errors if you are calling this method after the purchasing system is initialized in your IStoreListener implementation. How are you calling the ReceiptCheck method?
     
  8. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    This is what I am doing: https://gist.github.com/gegagome/33a0b61f69928dd712b26c512e835ae9

    I am calling ReceiptCheck on Start right after IsInitialized is executed.

    Curious about why receipt validation is inside ProcessPurchase? In my case users have already purchased a non-consumable product, so I already have RestorePurchases working for when re-enabling their purchases, I do however need to access access to this non-consumable product for when app is restarted or device shut down. Just wanted to clarify my problem.

    Thanks for your help.
     
  9. ap-unity

    ap-unity

    Unity Technologies

    Joined:
    Aug 3, 2016
    Posts:
    1,519
    @gegagome

    You are calling ReceiptCheck before InitializePurchasing, but the actual initialization (UnityPurchasing.Initialize) happens asynchronously. Once Unity purchasing is actually initialized, then the OnInitialized callback will be used. So that might be the best place to do your receipt check.

    Receipt Validation is designed to be a measure to reduce fraudulent purchases. If a user attempts to purchase an item with an invalid receipt, Receipt Validation will let you know, so you won't fulfill that item.

    Restoring purchases is designed for when a user re-installs your app. If they had purchases (non-consumable or subscription) in their previous installation, they are still entitled to those items. Restoring purchases happens automatically on Google Play and Windows Store, this happens automatically. However, for Apple platforms, a Restore button must be provided in the app.
     
  10. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    So you are saying that my ReceiptCheck method is ok and should work if moved after OnInitialized?

    Thanks
     
  11. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
  12. ap-unity

    ap-unity

    Unity Technologies

    Joined:
    Aug 3, 2016
    Posts:
    1,519
    @gegagome

    On line 271 of the gist you are using the same index variable (i) to access two different arrays. In other words, there is no reason that the product in products[1] will be related to results[1].

    You would need to loop through the results array and check for your products from there.
     
  13. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    @ap-unity

    Hi there.

    I updated my gist: https://gist.github.com/gegagome/edee68c476e222aca039e5c87468236f
    ReceiptCheck now looks like this:
    Code (CSharp):
    1. public void ReceiptCheck ()
    2.     {
    3.         iap.labelCenter.text = "RECEIPT CHECK\n";
    4.  
    5.         if (Application.platform == RuntimePlatform.Android ||
    6.             Application.platform == RuntimePlatform.IPhonePlayer ||
    7.             Application.platform == RuntimePlatform.OSXPlayer) {
    8.  
    9.             Product[] products = m_StoreController.products.all;
    10.             iap.labelCenter.text += "products.Length is: " + products.Length + "\n";
    11.             CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
    12.             for (int i = 0; i < products.Length; i++) {
    13.                 iap.labelCenter.text += "Entered for " + i + "\n";
    14.                 IPurchaseReceipt[] result;
    15.                 if (products[i].receipt != null) {
    16.                     result = validator.Validate(products[i].receipt);
    17.                     iap.labelCenter.text += result[i];
    18.                     for (int j = 0; j < result.Length; j++) {
    19.                         if (result[j].productID == kProductNameAppleNonConsumable) {
    20.                             iap.labelCenter.text += "Entered if " + i + "\n";
    21.                             iap.labelCenter.text += result[j].productID + "\n";
    22.                             iap.labelCenter.text = "IAP SUCESS";
    23.                             iap.IAPBought();
    24.                         }
    25.                     }
    26.                 } else {
    27.                     iap.labelCenter.text += "null " + i + "\n";
    28.                 }
    29.  
    30. //                if (result[i].productID == kProductIDNonConsumable) {
    31. //                    iap.labelCenter.text += "Entered if " + i + "\n";
    32. //                    iap.labelCenter.text += result[i].productID + "\n";
    33. //                    iap.labelCenter.text = "IAP SUCESS";
    34. //                    iap.IAPBought();
    35. //                }
    36.             }
    37.         }
    38.     }
    I am getting this from Xcode:
    2017-01-04 23:48:51.439809 alphabet[1631:498198] [DYMTLInitPlatform] platform initialization successful


    2017-01-04 23:48:51.538700 alphabet[1631:498078] -> registered mono modules 0x100e3ffc0

    2017-01-04 23:48:51.668714 alphabet[1631:498078] You've implemented -[<UIApplicationDelegate> application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist.

    -> applicationDidFinishLaunching()

    2017-01-04 23:48:51.725113 alphabet[1631:498078] Metal GPU Frame Capture Enabled

    2017-01-04 23:48:51.725396 alphabet[1631:498078] Metal API Validation Enabled

    -> applicationDidBecomeActive()

    GfxDevice: creating device client; threaded=1

    Init: screen size 1136x640

    Initializing Metal device caps: Apple A9 GPU

    Initialize engine version: 5.4.2f2 (b7e030c65c9b)

    UnloadTime: 1.104416 ms

    2017-01-04 23:48:52.201283 alphabet[1631:498078] UnityIAP:Requesting 1 products

    Setting up 1 worker threads for Enlighten.

    Thread -> id: 16f53f000 -> priority: 1

    2017-01-04 23:48:52.264850 alphabet[1631:498078] UnityIAP:Requesting product data...

    2017-01-04 23:48:53.928255 alphabet[1631:498078] UnityIAP:Received 1 products

    OnInitialized: PASS

    Purchaser:OnInitialized(IStoreController, IExtensionProvider)

    UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()

    UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)

    UnityEngine.Purchasing.AppleStoreImpl:OnProductsRetrieved(String)

    UnityEngine.Purchasing.AppleStoreImpl:processMessage(String, String, String, String)

    UnityEngine.Purchasing.Extension.UnityUtil:Update()


    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/UnityEngineDebugBindings.gen.cpp Line: 42)


    IndexOutOfRangeException: Array index is out of range.

    at Purchaser.OnInitialized (IStoreController controller, IExtensionProvider extensions) [0x00000] in <filename unknown>:0

    at UnityEngine.Purchasing.PurchasingManager.CheckForInitialization () [0x00000] in <filename unknown>:0

    at UnityEngine.Purchasing.PurchasingManager.OnProductsRetrieved (System.Collections.Generic.List`1 products) [0x00000] in <filename unknown>:0

    at UnityEngine.Purchasing.AppleStoreImpl.OnProductsRetrieved (System.String json) [0x00000] in <filename unknown>:0

    at UnityEngine.Purchasing.AppleStoreImpl.ProcessMessage (System.String subject, System.String payload, System.String receipt, System.String transactionId) [0x00000] in <filename unknown>:0

    at UnityEngine.Purchasing.Extension.UnityUtil.Update () [0x00000] in <filename unknown>:0


    (Filename: currently not available on il2cpp Line: -1)


    On my debugging I am getting:
    products.Length is: 1
    Entered for 0
    null 0

    So basically this is never true if(products.receipt !=null)

    Would you please let me know what I am doing wrong. As far as I understand I am following your directions but I am not getting the expected outcome, which is enabling an already purchased product to be available after app is restarted or device has shut down.

    Thanks a lot for your help
     
    Last edited: Jan 8, 2017
  14. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    @ap-unity

    This is my latest try: https://gist.github.com/gegagome/e62130534d7f8f941f11e184a37d6e17
    It is an attempt to log what I am getting from m_StoreController.products.all

    See my screenshot

    It looks like I am not getting anything, especially item.receipt, when this loop runs:
    Code (CSharp):
    1. try {
    2.             CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
    3.             for (int i = 0; i < productsArr.Length; i++) {
    4.                 iap.labelCenter.text += "Entered for " + i + "\n";
    5.                 foreach (var item in productsArr) {
    6.                     iap.labelCenter.text += "item.receipt: " + item.receipt + "\n";
    7.                     iap.labelCenter.text += "item.transactionID: "  + item.transactionID + "\n";
    8.                     iap.labelCenter.text += "item.metadata: " + item.metadata + "\n";
    9.                     iap.labelCenter.text += "item.definition.id: " + item.definition.id + "\n";
    10.                 }
    11.                 IPurchaseReceipt[] result;
    12.  
    13.  
    14.  
    15.                 // start
    16.  
    17. //                if (productsArr [i].receipt != null) {
    18. //                    result = validator.Validate (productsArr [i].receipt);
    19. //                    iap.labelCenter.text += result [i];
    20. //                    for (int j = 0; j < result.Length; j++) {
    21. //                        if (result [j].productID == kProductNameAppleNonConsumable) {
    22. //                            iap.labelCenter.text += "Entered if " + i + "\n";
    23. //                            iap.labelCenter.text += result [j].productID + "\n";
    24. ////                            iap.labelCenter.text = "IAP SUCESS";
    25. //                            iap.IAPBought ();
    26. //                        }
    27. //                    }
    28. //                } else {
    29. //                    iap.labelCenter.text += "null " + productsArr[i].transactionID + "\n";
    30. //                }
    31.  
    32.                 // end
    33.             }
    34.         } catch (Exception ex) {
    35.             iap.labelCenter.text += ex.ToString () + "\n";
    36.         }
    I do get expected outcomes when I buy using Purchase or when I restore purchases using Restore, so I am not sure what I am doing wrong.

    Is what I am trying to do only testable on a live App Store app? I am using my sandbox and hitting dead ends when automatically restoring an already purchased product.

    So confused

    Am I supposed to persist data to accomplish this?

    Thanks again. (sorry for the comments in the code)
     

    Attached Files:

    Last edited: Jan 8, 2017
  15. gegagome

    gegagome

    Joined:
    Oct 11, 2012
    Posts:
    392
    @ap-unity
    I kept trying once more with this:
    Code (CSharp):
    1. try {
    2.             CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
    3.             for (int i = 0; i < productsArr.Length; i++) {
    4.                 iap.labelCenter.text += "Entered for " + i + "\n";
    5.                 foreach (var item in productsArr) {
    6.                     iap.labelCenter.text += "item.receipt: " + item.receipt + "\n";
    7.                     iap.labelCenter.text += "item.transactionID: "  + item.transactionID + "\n";
    8.                     iap.labelCenter.text += "item.metadata: " + item.metadata + "\n";
    9.                     iap.labelCenter.text += "item.definition.id: " + item.definition.id + "\n";
    10.                 }
    11.                 IPurchaseReceipt[] result;
    12.                 result = validator.Validate(productsArr[0].receipt);
    13.                 iap.labelCenter.text += "result is: " + result + "\n";
    14.                 iap.labelCenter.text += "END" + "\n";
    15.             }
    16.         } catch (Exception ex) {
    17.             iap.labelCenter.text += ex.ToString () + "\n";
    18.         }
    and I got the logs in the screenshot attached.

    DISCLOSURE: I have the Receipt Validation Obfuscator empty. Since I am not doing Android just yet I am not worried but let me know otherwise.

    UPDATE: Added GooglePlayTangle.Data() and Google Play public key wand no changes to the above issue ocurred.

    Thanks
     

    Attached Files:

    Last edited: Jan 8, 2017
Thread Status:
Not open for further replies.