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

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

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.

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

Is this what you suggested:

    public void ReceiptCheck ()
    {
        iap.labelCenter.text = "testing\n";
        #if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
        iap.labelCenter.text += "Entered if\n";
        bool validPurchase = false;
        Product[] products = m_StoreController.products.all;
        CrossPlatformValidator validator = new CrossPlatformValidator (null,
                                              AppleTangle.Data (),
                                              Application.bundleIdentifier);

        for (int i = 0; i < products.Length; i++) {
            var result = validator.Validate (products [i].receipt);
//            if (result[i].productID == kProductNameAppleNonConsumable) {
                iap.IAPBought();
                iap.labelCenter.text += result[i].productID + "\n";
//            }
        }
        #endif
    }

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

Latest try

    public void ReceiptCheck ()
    {
        iap.labelCenter.text = "RECEIPT CHECK 2\n";

        if (Application.platform == RuntimePlatform.Android ||
            Application.platform == RuntimePlatform.IPhonePlayer ||
            Application.platform == RuntimePlatform.OSXPlayer) {
            iap.labelCenter.text += "Entered if\n";

            Product[] products = m_StoreController.products.all;
            iap.labelCenter.text += products.Length + "\n";
            CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
            for (int i = 0; i < products.Length; i++) {
                var result = validator.Validate (products [i].receipt);
                if (result[0].productID == kProductIDNonConsumable) {
                    iap.labelCenter.text += result[0].productID + "\n";
                }
            }
        }
    }

Somehow this line:

Product[] products = m_StoreController.products.all;

breaks the whole method and lines below are never executed.

Any help would be appreciated.

Thanks

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).

@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?

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.

@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.

So you are saying that my ReceiptCheck method is ok and should work if moved after OnInitialized?

Thanks

I moved ReceiptCheck at the end of OnInitialized and I only get “Entered for 0” even though products.Length returns 3.

Any ideas?

Thanks

@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.

@ap-unity

Hi there.

I updated my gist: Purchaser2.cs · GitHub
ReceiptCheck now looks like this:

public void ReceiptCheck ()
    {
        iap.labelCenter.text = "RECEIPT CHECK\n";

        if (Application.platform == RuntimePlatform.Android ||
            Application.platform == RuntimePlatform.IPhonePlayer ||
            Application.platform == RuntimePlatform.OSXPlayer) {

            Product[] products = m_StoreController.products.all;
            iap.labelCenter.text += "products.Length is: " + products.Length + "\n";
            CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
            for (int i = 0; i < products.Length; i++) {
                iap.labelCenter.text += "Entered for " + i + "\n";
                IPurchaseReceipt[] result;
                if (products[i].receipt != null) {
                    result = validator.Validate(products[i].receipt);
                    iap.labelCenter.text += result[i];
                    for (int j = 0; j < result.Length; j++) {
                        if (result[j].productID == kProductNameAppleNonConsumable) {
                            iap.labelCenter.text += "Entered if " + i + "\n";
                            iap.labelCenter.text += result[j].productID + "\n";
                            iap.labelCenter.text = "IAP SUCESS";
                            iap.IAPBought();
                        }
                    }
                } else {
                    iap.labelCenter.text += "null " + i + "\n";
                }

//                if (result[i].productID == kProductIDNonConsumable) {
//                    iap.labelCenter.text += "Entered if " + i + "\n";
//                    iap.labelCenter.text += result[i].productID + "\n";
//                    iap.labelCenter.text = "IAP SUCESS";
//                    iap.IAPBought();
//                }
            }
        }
    }

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 -[ 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 :0

at UnityEngine.Purchasing.PurchasingManager.CheckForInitialization () [0x00000] in :0

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

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

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

at UnityEngine.Purchasing.Extension.UnityUtil.Update () [0x00000] in :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

@ap-unity

This is my latest try: Unity IAP · GitHub
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:

try {
            CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
            for (int i = 0; i < productsArr.Length; i++) {
                iap.labelCenter.text += "Entered for " + i + "\n";
                foreach (var item in productsArr) {
                    iap.labelCenter.text += "item.receipt: " + item.receipt + "\n";
                    iap.labelCenter.text += "item.transactionID: "  + item.transactionID + "\n";
                    iap.labelCenter.text += "item.metadata: " + item.metadata + "\n";
                    iap.labelCenter.text += "item.definition.id: " + item.definition.id + "\n";
                }
                IPurchaseReceipt[] result;



                // start

//                if (productsArr [i].receipt != null) {
//                    result = validator.Validate (productsArr [i].receipt);
//                    iap.labelCenter.text += result [i];
//                    for (int j = 0; j < result.Length; j++) {
//                        if (result [j].productID == kProductNameAppleNonConsumable) {
//                            iap.labelCenter.text += "Entered if " + i + "\n";
//                            iap.labelCenter.text += result [j].productID + "\n";
////                            iap.labelCenter.text = "IAP SUCESS";
//                            iap.IAPBought ();
//                        }
//                    }
//                } else {
//                    iap.labelCenter.text += "null " + productsArr[i].transactionID + "\n";
//                }

                // end
            }
        } catch (Exception ex) {
            iap.labelCenter.text += ex.ToString () + "\n";
        }

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)

@ap-unity
I kept trying once more with this:

try {
            CrossPlatformValidator validator = new CrossPlatformValidator (null, AppleTangle.Data (), Application.bundleIdentifier);
            for (int i = 0; i < productsArr.Length; i++) {
                iap.labelCenter.text += "Entered for " + i + "\n";
                foreach (var item in productsArr) {
                    iap.labelCenter.text += "item.receipt: " + item.receipt + "\n";
                    iap.labelCenter.text += "item.transactionID: "  + item.transactionID + "\n";
                    iap.labelCenter.text += "item.metadata: " + item.metadata + "\n";
                    iap.labelCenter.text += "item.definition.id: " + item.definition.id + "\n";
                }
                IPurchaseReceipt[] result;
                result = validator.Validate(productsArr[0].receipt);
                iap.labelCenter.text += "result is: " + result + "\n";
                iap.labelCenter.text += "END" + "\n";
            }
        } catch (Exception ex) {
            iap.labelCenter.text += ex.ToString () + "\n";
        }

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