Unity IAP package 3.0.1 error DeObfuscate

Hello! I updated the IAP and the problems started. I added the key. Initialization is successful, but an error occurs during the purchase

NotImplementedException: The method or operation is not implemented.
UnityEngine.Purchasing.Security.Obfuscator.DeObfuscate (System.Byte[ ] data, System.Int32[ ] order, System.Int32 key) (at Library/PackageCache/com.unity.purchasing@3.0.1/Runtime/SecurityStub/Obfuscator.cs:9)
UnityEngine.Purchasing.Security.GooglePlayTangle.Data () (at

#if !UNITY_EDITOR
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);

In game mode, only check ??? Will it work or will it crash

What device are you testing on? Have you created your tangle files? The IAP Sample Project v2 includes receipt validation and works without issue using IAP 3.0.1 https://discussions.unity.com/t/700293/3

Hi @Irina-uk

Seeing “NotImplementedException: The method or operation is not implemented.” is the expected, non-crashing behaviour on platforms that don’t actually use CrossPlatformValidator. You can wrap all calls to it in a try/catch and just handle the exception for platforms that are not Google or Apple.

To be clear the only platforms currently with an implementation of this are GooglePlay, MacOs, iOS and tvOS.

2 Likes

CrossPlatformValidator - can I delete it from the code ??? and the check for validation of the receipt will remain ???

You certainly can delete it. But Apple requires receipt validation, and that is what the validator does. Ideally you will want to wrap it in a Try/Catch block, like in the v2 Sample IAP Project

Thank, error crash, Where is the Sample IAP v2 project ?? I looked at the IAPDemo 3.0.1 file, everything was there as it was and remained

if (Application.platform == RuntimePlatform.Android ||
Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.OSXPlayer ||
Application.platform == RuntimePlatform.tvOS) {
try {
var result = validator.Validate(args.purchasedProduct.receipt);
Debug.Log(“Receipt is valid. Contents:”);
foreach (IPurchaseReceipt productReceipt in result) {
// if (productId == productReceipt.productID)
Debug.Log(productReceipt.productID);
Debug.Log(productReceipt.purchaseDate);
Debug.Log(productReceipt.transactionID);

GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
if (null != google) {
Debug.Log(google.purchaseState);
Debug.Log(google.purchaseToken);
}

AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
if (null != apple) {
Debug.Log(apple.originalTransactionIdentifier);
Debug.Log(apple.subscriptionExpirationDate);
Debug.Log(apple.cancellationDate);
Debug.Log(apple.quantity);
}

// For improved security, consider comparing the signed
// IPurchaseReceipt.productId, IPurchaseReceipt.transactionID, and other data
// embedded in the signed receipt objects to the data which the game is using
// to make this purchase.
}
} catch (IAPSecurityException ex) {
Debug.Log("Invalid receipt, not unlocking content. " + ex);
return true;
}
}

The error continues ! I looked at the IAPDemo 3.0.1 file, everything was there as it was and remained

I provided the link in my first reply

How do I check the validity of a receipt ? Is that enough, or will the whole fake payment thing start again ???

var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
var result = validator.Validate(args.purchasedProduct.receipt);
Debug.Log("Validate = " + result.ToString());
foreach (IPurchaseReceipt productReceipt in result)
{
MyDebug("Valid receipt for " + productReceipt.productID.ToString());
}

I’m not seeing your Try/Catch code? If the validator raises an exception, you don’t give the user the product. That is what Apple will check. They may inject a fake receipt during their testing and ensure that you don’t give the product to the user. And please be precise, what do you mean by the “whole fake payment thing”.

https://docs.unity3d.com/Manual/UnityIAPValidatingReceipts.html

Also, if the Validator raises a NotImplementedException on non-Apple, non-Google platforms, ignore it and continue the payout.

1 Like

I was using the Validator serverside on a non-apple, non-google device to verify that receipts were genuine serverside and not just clientside.
Having the Validator throw a “NotImplementedException” on my server instead of working fine like it used to seems like a step backwards in functionality…
I’m just going to go back to using the old version.

The validator is not expected to work on a server, it’s client/device only. Please show the code you are using on your server.

so is it normal for the editor on mac to throw this error?
should it then be tested on these platforms?

Yes, it’s normal. Use a try/catch block as suggested. It’s only supported on iOS and Android platforms.

Hey so this doesnt work, i have a try and catch before the tangle part and it still throws an error, ive also setup the Obfuscate receipt validator too and have both google and apple tangles in the scripts/generated folder.

Heres my code

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.UI;
using UnityEngine.Purchasing.Security;
using I2.Loc;


public class InAppPurchasing : MonoBehaviour, IStoreListener
{
    public static InAppPurchasing instance;

    private static IStoreController m_StoreController;
    private static IExtensionProvider m_StoreExtensionProvider;

    private IAppleExtensions m_AppleExtensions;
    private IGooglePlayStoreExtensions m_GoogleExtensions;



    public static string tokens1 = "tokens1";
    public static string tokens5 = "tokens5";
    public static string tokens10 = "tokens10";
    public static string caps1000 = "1000caps";
    public static string caps2000 = "2000caps";
    public static string caps4000 = "4000caps";
    public static string crate = "crate";
    public static string ultimate_wanderer = "ultimate_wanderer";
    public static string starterpack = "starterpack";
    public static string weaponpack = "weaponpack";
    public static string van = "van";
    public static string scavengerMaterial = "scavengermaterial";
    public static string remove_banner = "remove_banner";
    public static string max_bag = "max_bag";
    public static string subscription = "wanderer_subscription";
    public static string infinitevan = "vanwarranty";


    void Start()
    {
        instance = this;

        // If we haven't set up the Unity Purchasing reference
        if (m_StoreController == null)
        {
            // Begin to configure our connection to Purchasing
            InitializePurchasing();
        }

#if UNITY_IOS
        RestorePurchases();
#endif
    }


    public void InitializePurchasing()
    {
        // If we have already connected to Purchasing ...
        if (IsInitialized())
        {
            // ... we are done here.
            return;
        }

        // Create a builder, first passing in a suite of Unity provided stores.
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        builder.AddProduct(tokens1, ProductType.Consumable);
        builder.AddProduct(tokens5, ProductType.Consumable);
        builder.AddProduct(tokens10, ProductType.Consumable);
        builder.AddProduct(caps1000, ProductType.Consumable);
        builder.AddProduct(caps2000, ProductType.Consumable);
        builder.AddProduct(caps4000, ProductType.Consumable);
        builder.AddProduct(crate, ProductType.Consumable);
        builder.AddProduct(ultimate_wanderer, ProductType.Consumable);
        builder.AddProduct(starterpack, ProductType.Consumable);
        builder.AddProduct(weaponpack, ProductType.Consumable);
        builder.AddProduct(van, ProductType.Consumable);
        builder.AddProduct(scavengerMaterial, ProductType.NonConsumable);
        builder.AddProduct(remove_banner, ProductType.NonConsumable);
        builder.AddProduct(max_bag, ProductType.NonConsumable);
        builder.AddProduct(infinitevan, ProductType.NonConsumable);
        builder.AddProduct(subscription, ProductType.Subscription);
        //builder.AddProduct(subscription, ProductType.Subscription);

        // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration
        // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
        UnityPurchasing.Initialize(this, builder);
    }

    private bool IsInitialized()
    {
        return m_StoreController != null && m_StoreExtensionProvider != null;
    }

    public void BuySubscription()
    {
        BuyProductID(subscription);
    }


    void BuyProductID(string productId)
    {
        if (!Application.isMobilePlatform && !Application.isEditor)
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/OnlyOnMobile"), 6);
            return;
        }

        if (IsInitialized())
        {
            UnityEngine.Purchasing.Product product = m_StoreController.products.WithID(productId);

            if (product != null && product.availableToPurchase)
            {
                CustomDebug._Log(string.Format("Purchasing product:" + product.definition.id.ToString()));
                m_StoreController.InitiatePurchase(product);
            }
            else
            {
                CustomDebug._Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
            }
        }
        else
        {
            CustomDebug._Log("BuyProductID FAIL. Not initialized.");
        }
    }

    public void ListProducts()
    {

        foreach (UnityEngine.Purchasing.Product item in m_StoreController.products.all)
        {
            if (item.receipt != null)
            {
                CustomDebug._Log("Receipt found for Product = " + item.definition.id.ToString());
            }
        }
    }

    public void RestorePurchases()
    {
        try{
        m_StoreExtensionProvider.GetExtension<IAppleExtensions>().RestoreTransactions(result => {
            if (result)
            {
                CheckProduct(scavengerMaterial);
                CheckProduct(remove_banner);
                CheckProduct(max_bag);
                CheckProduct(subscription);
                CheckProduct(infinitevan);
            }
            else
            {
                Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
            }
        });
        }catch(Exception ex){
Debug.LogError("Failed restoring purchases - error = " + ex);
        }
    }


    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        CustomDebug._Log("OnInitialized: PASS");

        m_StoreController = controller;
        m_StoreExtensionProvider = extensions;
        m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
        m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();

        //m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);

        Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();

        foreach (UnityEngine.Purchasing.Product item in controller.products.all)
        {

            if (item.receipt != null)
            {
                string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];

                if (item.definition.type == ProductType.Subscription)
                {
                    SubscriptionManager p = new SubscriptionManager(item, intro_json);
                    SubscriptionInfo info = p.getSubscriptionInfo();
                    Subscription.instance.Subscribe(bool.Parse(info.isSubscribed().ToString()));
                }
            }
        }

        CheckProduct(scavengerMaterial);
        CheckProduct(remove_banner);
        CheckProduct(max_bag);
        CheckProduct(subscription);
        CheckProduct(infinitevan);
    }

 

    bool BannerSaved()
    {
        foreach (SaveEntry saveEntry in IG_CloudSaving.instance.LocalData.PersistentSaves)
        {
            if (saveEntry.key == "BannerRemoved" && saveEntry.value == "true")
            {
                return true;
            }
        }
        return false;
    }

    void CheckProduct(string id)
    {
        Product product = m_StoreController.products.WithID(id);

        if (product != null)
        {
            var name = product.metadata.localizedTitle;

            //CustomDebug._LogError($"Product = {id} = has receipt = {product.hasReceipt} -------- name given for product = {name} ----- product name = {product} ----- product ID = {product.transactionID}");

            if ((id == remove_banner && product.hasReceipt) || BannerSaved())
            {
                SavedFile.SetBool("BannerRemoved", true, true);
                BannerAdvert.instance.HideBanner();
                BannerAdvert.BannerRemoved = true;
            }

            if (id == scavengerMaterial && product.hasReceipt)
            {
                SavedFile.SetBool("ScavengerMaterial", true, true);
            }

            if (id == max_bag && product.hasReceipt)
            {
                SavedFile.SetBool("MaxBag", true, true);
                UpgradeManager.BagMaxed = true;
            }

            if (id == subscription)
            {
                if (Subscription.instance) Subscription.instance.Subscribe(product.hasReceipt);
                else { CustomDebug._LogError("Subscription manager not initiliazed"); }
                if (product.hasReceipt)
                    BannerAdvert.instance.HideBanner();
            }

            if (id == infinitevan && product.hasReceipt)
            {
                CamperVan.VanWarranty = true;
                SavedFile.SetBool("InfiniteVan", true, true);
            }
        }
    }


    public void OnInitializeFailed(InitializationFailureReason error)
    {
        // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
        Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    }

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        string trgtString = string.Empty;
        bool validPurchase = true; // Presume valid for platforms with no R.V.

        try
        {
            var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
            var result = validator.Validate(args.purchasedProduct.receipt);
            //MyDebug("Validate = " + result.ToString());

            foreach (IPurchaseReceipt productReceipt in result)
            {
                IAPurchase(productReceipt.productID.ToString());
            }

        }
        catch (IAPSecurityException)
        {
            Debug.Log("Invalid receipt, not unlocking content");
            validPurchase = false;
        }

        if (validPurchase)
        {
            // Unlock the appropriate content here.
            Debug.Log(args.purchasedProduct.ToString());
            Debug.Log(args.purchasedProduct.transactionID.ToString());
            IAPurchase(args.purchasedProduct.ToString());
        }

        //MyDebug(string.Format("ProcessPurchase: " + args.purchasedProduct.definition.id));

        return PurchaseProcessingResult.Complete;

    }

    void IAPurchase(string iap)
    {
        if (String.Equals(iap, tokens1, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 token!", 3);
            GameManager.GM.Token += 1;
            GameManager.GM.logText = "You have purchased 1 token!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, tokens5, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 5 tokens!", 3);
            GameManager.GM.Token += 5;
            GameManager.GM.logText = "You have purchased 5 tokens!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps1000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1000 caps!", 3);
            CapsManager.instance.UpdateCaps(1000);
            WandererProgress.TotalCaps += 1000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 1000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps2000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 5000 caps!", 3);
            CapsManager.instance.UpdateCaps(5000);
            WandererProgress.TotalCaps += 5000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 5000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps4000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 10000 caps!", 3);
            CapsManager.instance.UpdateCaps(10000);
            WandererProgress.TotalCaps += 10000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 10000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, tokens10, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 10 tokens!", 3);
            GameManager.GM.Token += 10;
            GameManager.GM.logText = "You have purchased 10 tokens!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, crate, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 WANDERER CRATE!", 3);
            GameManager.GM.logText = "You have purchased a WANDERER CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, ultimate_wanderer, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 ULTIMATE WANDERER CRATE!", 3);
            GameManager.GM.logText = "You have purchased the ULTIMATE WANDERER CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, starterpack, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 STARTER PACK!", 3);
            GameManager.GM.logText = "You have purchased a STARTER PACK!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, weaponpack, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 WEAPON CRATE!", 3);
            GameManager.GM.logText = "You have purchased a WEAPON CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, van, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();

            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 VAN PACKAGE!", 3);
            GameManager.GM.logText = "You have purchased the VAN PACKAGE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, scavengerMaterial, StringComparison.Ordinal))
        {
            SavedFile.SetBool("ScavengerMaterial", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased the") + " material scavenger!", 3);
            GameManager.GM.logText = "You have purchased the material scavenger!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, remove_banner, StringComparison.Ordinal))
        {
            SavedFile.SetBool("BannerRemoved", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have removed the advert banner!... THANKS for your support!"), 3);
            GameManager.GM.logText = "You have removed the advert banner!... THANKS for your support!";
            GameManager.GM.SaveDialogueMessage();
            BannerAdvert.instance.HideBanner();
            SoundHandler.s.Transaction.Play();
            BannerAdvert.BannerRemoved = true;
        }
        else if (String.Equals(iap, max_bag, StringComparison.Ordinal))
        {
            SavedFile.SetBool("MaxBag", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Bag level maxed out!"), 3);
            GameManager.GM.logText = "Bag level maxed out!";
            GameManager.GM.SaveDialogueMessage();
            SoundHandler.s.Transaction.Play();
            UpgradeManager.BagMaxed = true;
        }
        else if (String.Equals(iap, subscription, StringComparison.Ordinal))
        {
            Subscription.instance.Subscribe(true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Subscription enabled!"), 3);
            GameManager.GM.logText = "Subscription enabled!";
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, infinitevan, StringComparison.Ordinal))
        {
            SavedFile.SetBool("InfiniteVan", true, true);
            CamperVan.VanWarranty = true;
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Camper Van Warranty Purchased!"), 3);
            GameManager.GM.logText = "Camper Van Warranty Purchased!";
            SoundHandler.s.Transaction.Play();
        }
    }


    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        // A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing
        // this reason with the user to guide their troubleshooting actions.
        Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    }


    // Update is called once per frame
    public void Token1()
    {
        BuyProductID(tokens1);
    }

    public void Token5()
    {
        BuyProductID(tokens5);
    }

    public void Token10()
    {
        BuyProductID(tokens10);
    }

    public void Cap1000()
    {
        BuyProductID(caps1000);
    }

    public void Cap2000()
    {
        BuyProductID(caps2000);
    }

    public void Cap4000()
    {
        BuyProductID(caps4000);
    }

    public void Crate()
    {
        BuyProductID(crate);
    }
    public void Ultimate_Wanderer()
    {
        BuyProductID(ultimate_wanderer);
    }

    public void Van_Warranty()
    {
        BuyProductID(infinitevan);
    }

    public void StarterPack()
    {
        BuyProductID(starterpack);
    }

    public void WeaponPack()
    {
        BuyProductID(weaponpack);
    }

    public void UnlockVan()
    {
        BuyProductID(van);
    }

    [EasyButtons.Button]
    public void UnlockScavengerMaterial()
    {
        BuyProductID(scavengerMaterial);
    }

    [EasyButtons.Button]
    public void RemoveBanner()
    {
        BuyProductID(remove_banner);
    }

    [EasyButtons.Button]
    public void MaxBag()
    {
        BuyProductID(max_bag);
    }

}

@JParish2590 What line is throwing the exception. Please share the full error, it will include the line number and additional information. Is this on Android or iOS? Or in the Editor? Also, you are trying to purchase multiple items in ProcessPurchase based on each receipt found? Sorry I’m not quite following your code, that’s not how it works. ProcessPurchase only operates on (and therefore purchases) only a single product. A renewing subscription might have multiple receipts, for the same product. I would encourage you to compare to and publish as a new test application using the Sample IAP Project v2 here Sample IAP Project

Heres the error:

NotImplementedException: The method or operation is not implemented.
UnityEngine.Purchasing.Security.GooglePlayTangle.Data () (at Assets/Scripts/UnityPurchasing/generated/GooglePlayTangle.cs:15)
InAppPurchasing.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs args) (at Assets/Scripts/InAppPurchasing.cs:280)
UnityEngine.Purchasing.StoreListenerProxy.ProcessPurchase (UnityEngine.Purchasing.PurchaseEventArgs e) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Purchasing/StoreListenerProxy.cs:34)
UnityEngine.Purchasing.PurchasingManager.ProcessPurchaseIfNew (UnityEngine.Purchasing.Product product) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Purchasing/PurchasingManager.cs:259)
UnityEngine.Purchasing.PurchasingManager.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionId) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Purchasing/PurchasingManager.cs:111)
UnityEngine.Purchasing.JSONStore.OnPurchaseSucceeded (System.String id, System.String receipt, System.String transactionID) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Stores/BaseStore/JSONStore.cs:165)
UnityEngine.Purchasing.FakeStore.<>n__0 (System.String id, System.String receipt, System.String transactionID) (at <8dd15d9eb4c6432688273247c94fa2d9>:0)
UnityEngine.Purchasing.FakeStore+<>c__DisplayClass15_0.<FakePurchase>b__0 (System.Boolean allow, UnityEngine.Purchasing.PurchaseFailureReason failureReason) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Stores/FakeStore/FakeStore.cs:143)
UnityEngine.Purchasing.FakeStore.FakePurchase (UnityEngine.Purchasing.ProductDefinition product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Stores/FakeStore/FakeStore.cs:162)
UnityEngine.Purchasing.FakeStore.Purchase (System.String productJSON, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Stores/FakeStore/FakeStore.cs:128)
UnityEngine.Purchasing.JSONStore.Purchase (UnityEngine.Purchasing.ProductDefinition product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Stores/BaseStore/JSONStore.cs:140)
UnityEngine.Purchasing.PurchasingManager.InitiatePurchase (UnityEngine.Purchasing.Product product, System.String developerPayload) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Purchasing/PurchasingManager.cs:60)
UnityEngine.Purchasing.PurchasingManager.InitiatePurchase (UnityEngine.Purchasing.Product product) (at Library/PackageCache/com.unity.purchasing@4.0.3/Runtime/Purchasing/PurchasingManager.cs:38)
InAppPurchasing.BuyProductID (System.String productId) (at Assets/Scripts/InAppPurchasing.cs:124)
InAppPurchasing.Cap4000 () (at Assets/Scripts/InAppPurchasing.cs:509)
UnityEngine.Events.InvokableCall.Invoke () (at <2a59c552c85f43a9bcaaf39eb0bf8b0d>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <2a59c552c85f43a9bcaaf39eb0bf8b0d>:0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)

Heres the updated script where the error appears on line 280:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.UI;
using UnityEngine.Purchasing.Security;
using I2.Loc;


public class InAppPurchasing : MonoBehaviour, IStoreListener
{
    public static InAppPurchasing instance;

    private static IStoreController m_StoreController;
    private static IExtensionProvider m_StoreExtensionProvider;

    private IAppleExtensions m_AppleExtensions;
    private IGooglePlayStoreExtensions m_GoogleExtensions;



    public static string tokens1 = "tokens1";
    public static string tokens5 = "tokens5";
    public static string tokens10 = "tokens10";
    public static string caps1000 = "1000caps";
    public static string caps2000 = "2000caps";
    public static string caps4000 = "4000caps";
    public static string crate = "crate";
    public static string ultimate_wanderer = "ultimate_wanderer";
    public static string starterpack = "starterpack";
    public static string weaponpack = "weaponpack";
    public static string van = "van";
    public static string scavengerMaterial = "scavengermaterial";
    public static string remove_banner = "remove_banner";
    public static string max_bag = "max_bag";
    public static string subscription = "wanderer_subscription";
    public static string infinitevan = "vanwarranty";


    void Start()
    {
        instance = this;

        // If we haven't set up the Unity Purchasing reference
        if (m_StoreController == null)
        {
            // Begin to configure our connection to Purchasing
            InitializePurchasing();
        }

#if UNITY_IOS
        RestorePurchases();
#endif
    }


    public void InitializePurchasing()
    {
        // If we have already connected to Purchasing ...
        if (IsInitialized())
        {
            // ... we are done here.
            return;
        }

        // Create a builder, first passing in a suite of Unity provided stores.
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        builder.AddProduct(tokens1, ProductType.Consumable);
        builder.AddProduct(tokens5, ProductType.Consumable);
        builder.AddProduct(tokens10, ProductType.Consumable);
        builder.AddProduct(caps1000, ProductType.Consumable);
        builder.AddProduct(caps2000, ProductType.Consumable);
        builder.AddProduct(caps4000, ProductType.Consumable);
        builder.AddProduct(crate, ProductType.Consumable);
        builder.AddProduct(ultimate_wanderer, ProductType.Consumable);
        builder.AddProduct(starterpack, ProductType.Consumable);
        builder.AddProduct(weaponpack, ProductType.Consumable);
        builder.AddProduct(van, ProductType.Consumable);
        builder.AddProduct(scavengerMaterial, ProductType.NonConsumable);
        builder.AddProduct(remove_banner, ProductType.NonConsumable);
        builder.AddProduct(max_bag, ProductType.NonConsumable);
        builder.AddProduct(infinitevan, ProductType.NonConsumable);
        builder.AddProduct(subscription, ProductType.Subscription);
        //builder.AddProduct(subscription, ProductType.Subscription);

        // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration
        // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
        UnityPurchasing.Initialize(this, builder);
    }

    private bool IsInitialized()
    {
        return m_StoreController != null && m_StoreExtensionProvider != null;
    }

    public void BuySubscription()
    {
        BuyProductID(subscription);
    }


    void BuyProductID(string productId)
    {
        if (!Application.isMobilePlatform && !Application.isEditor)
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/OnlyOnMobile"), 6);
            return;
        }

        //if (Application.isEditor)
        //{
        //    IAPurchase(productId);
        //    return;
        //}

        if (IsInitialized())
        {
            UnityEngine.Purchasing.Product product = m_StoreController.products.WithID(productId);

            if (product != null && product.availableToPurchase)
            {
                CustomDebug._Log(string.Format("Purchasing product:" + product.definition.id.ToString()));
                m_StoreController.InitiatePurchase(product);
            }
            else
            {
                CustomDebug._Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
            }
        }
        else
        {
            CustomDebug._Log("BuyProductID FAIL. Not initialized.");
        }
    }

    public void ListProducts()
    {

        foreach (UnityEngine.Purchasing.Product item in m_StoreController.products.all)
        {
            if (item.receipt != null)
            {
                CustomDebug._Log("Receipt found for Product = " + item.definition.id.ToString());
            }
        }
    }

    public void RestorePurchases()
    {
        try{
        m_StoreExtensionProvider.GetExtension<IAppleExtensions>().RestoreTransactions(result => {
            if (result)
            {
                CheckProduct(scavengerMaterial);
                CheckProduct(remove_banner);
                CheckProduct(max_bag);
                CheckProduct(subscription);
                CheckProduct(infinitevan);
            }
            else
            {
                Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
            }
        });
        }catch(Exception ex){
Debug.LogError("Failed restoring purchases - error = " + ex);
        }
    }


    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        CustomDebug._Log("OnInitialized: PASS");

        m_StoreController = controller;
        m_StoreExtensionProvider = extensions;
        m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
        m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();

        //m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);

        Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();

        foreach (UnityEngine.Purchasing.Product item in controller.products.all)
        {

            if (item.receipt != null)
            {
                string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];

                if (item.definition.type == ProductType.Subscription)
                {
                    SubscriptionManager p = new SubscriptionManager(item, intro_json);
                    SubscriptionInfo info = p.getSubscriptionInfo();
                    Subscription.instance.Subscribe(bool.Parse(info.isSubscribed().ToString()));
                }
            }
        }

        CheckProduct(scavengerMaterial);
        CheckProduct(remove_banner);
        CheckProduct(max_bag);
        CheckProduct(subscription);
        CheckProduct(infinitevan);
    }

  

    bool BannerSaved()
    {
        foreach (SaveEntry saveEntry in IG_CloudSaving.instance.LocalData.PersistentSaves)
        {
            if (saveEntry.key == "BannerRemoved" && saveEntry.value == "true")
            {
                return true;
            }
        }
        return false;
    }

    void CheckProduct(string id)
    {
        Product product = m_StoreController.products.WithID(id);

        if (product != null)
        {
            var name = product.metadata.localizedTitle;

            //CustomDebug._LogError($"Product = {id} = has receipt = {product.hasReceipt} -------- name given for product = {name} ----- product name = {product} ----- product ID = {product.transactionID}");

            if ((id == remove_banner && product.hasReceipt) || BannerSaved())
            {
                SavedFile.SetBool("BannerRemoved", true, true);
                BannerAdvert.instance.HideBanner();
                BannerAdvert.BannerRemoved = true;
            }

            if (id == scavengerMaterial && product.hasReceipt)
            {
                SavedFile.SetBool("ScavengerMaterial", true, true);
            }

            if (id == max_bag && product.hasReceipt)
            {
                SavedFile.SetBool("MaxBag", true, true);
                UpgradeManager.BagMaxed = true;
            }

            if (id == subscription)
            {
                if (Subscription.instance) Subscription.instance.Subscribe(product.hasReceipt);
                else { CustomDebug._LogError("Subscription manager not initiliazed"); }
                if (product.hasReceipt)
                    BannerAdvert.instance.HideBanner();
            }

            if (id == infinitevan && product.hasReceipt)
            {
                CamperVan.VanWarranty = true;
                SavedFile.SetBool("InfiniteVan", true, true);
            }
        }
    }


    public void OnInitializeFailed(InitializationFailureReason error)
    {
        // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
        Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    }

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        string trgtString = string.Empty;
        bool validPurchase = true; // Presume valid for platforms with no R.V.

        try
        {
            var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
            var result = validator.Validate(args.purchasedProduct.receipt);
            //MyDebug("Validate = " + result.ToString());

            foreach (IPurchaseReceipt productReceipt in result)
            {
                IAPurchase(productReceipt.productID.ToString());
            }

        }
        catch (IAPSecurityException)
        {
            Debug.Log("Invalid receipt, not unlocking content");
            validPurchase = false;
        }

        if (validPurchase)
        {
            // Unlock the appropriate content here.
            Debug.Log(args.purchasedProduct.ToString());
            Debug.Log(args.purchasedProduct.transactionID.ToString());
            IAPurchase(args.purchasedProduct.ToString());
        }

        //MyDebug(string.Format("ProcessPurchase: " + args.purchasedProduct.definition.id));

        return PurchaseProcessingResult.Complete;

    }

    void IAPurchase(string iap)
    {
        if (String.Equals(iap, tokens1, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 token!", 3);
            GameManager.GM.Token += 1;
            GameManager.GM.logText = "You have purchased 1 token!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, tokens5, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 5 tokens!", 3);
            GameManager.GM.Token += 5;
            GameManager.GM.logText = "You have purchased 5 tokens!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps1000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1000 caps!", 3);
            CapsManager.instance.UpdateCaps(1000);
            WandererProgress.TotalCaps += 1000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 1000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps2000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 5000 caps!", 3);
            CapsManager.instance.UpdateCaps(5000);
            WandererProgress.TotalCaps += 5000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 5000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, caps4000, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 10000 caps!", 3);
            CapsManager.instance.UpdateCaps(10000);
            WandererProgress.TotalCaps += 10000;
            WandererProgress.WP.SavePrefs();
            GameManager.GM.logText = "You have purchased 10000 caps!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, tokens10, StringComparison.Ordinal))
        {
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 10 tokens!", 3);
            GameManager.GM.Token += 10;
            GameManager.GM.logText = "You have purchased 10 tokens!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, crate, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 WANDERER CRATE!", 3);
            GameManager.GM.logText = "You have purchased a WANDERER CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, ultimate_wanderer, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 ULTIMATE WANDERER CRATE!", 3);
            GameManager.GM.logText = "You have purchased the ULTIMATE WANDERER CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, starterpack, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 STARTER PACK!", 3);
            GameManager.GM.logText = "You have purchased a STARTER PACK!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, weaponpack, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 WEAPON CRATE!", 3);
            GameManager.GM.logText = "You have purchased a WEAPON CRATE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, van, StringComparison.Ordinal))
        {
            if (ShopSpecials.instance) ShopSpecials.instance.GiveToPlayer();
            else ShowShopSpecialsPopup.instance.GiveToPlayer();

            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased") + " 1 VAN PACKAGE!", 3);
            GameManager.GM.logText = "You have purchased the VAN PACKAGE!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, scavengerMaterial, StringComparison.Ordinal))
        {
            SavedFile.SetBool("ScavengerMaterial", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have purchased the") + " material scavenger!", 3);
            GameManager.GM.logText = "You have purchased the material scavenger!";
            GameManager.GM.SaveDialogueMessage();
            GameManager.GM.SavePrefs();
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, remove_banner, StringComparison.Ordinal))
        {
            SavedFile.SetBool("BannerRemoved", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/You have removed the advert banner!... THANKS for your support!"), 3);
            GameManager.GM.logText = "You have removed the advert banner!... THANKS for your support!";
            GameManager.GM.SaveDialogueMessage();
            BannerAdvert.instance.HideBanner();
            SoundHandler.s.Transaction.Play();
            BannerAdvert.BannerRemoved = true;
        }
        else if (String.Equals(iap, max_bag, StringComparison.Ordinal))
        {
            SavedFile.SetBool("MaxBag", true, true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Bag level maxed out!"), 3);
            GameManager.GM.logText = "Bag level maxed out!";
            GameManager.GM.SaveDialogueMessage();
            SoundHandler.s.Transaction.Play();
            UpgradeManager.BagMaxed = true;
        }
        else if (String.Equals(iap, subscription, StringComparison.Ordinal))
        {
            Subscription.instance.Subscribe(true);
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Subscription enabled!"), 3);
            GameManager.GM.logText = "Subscription enabled!";
            SoundHandler.s.Transaction.Play();
        }
        else if (String.Equals(iap, infinitevan, StringComparison.Ordinal))
        {
            SavedFile.SetBool("InfiniteVan", true, true);
            CamperVan.VanWarranty = true;
            ScreenWarning.instance.ShowWarning(LocalizationManager.GetTranslation("Popup/Camper Van Warranty Purchased!"), 3);
            GameManager.GM.logText = "Camper Van Warranty Purchased!";
            SoundHandler.s.Transaction.Play();
        }
    }


    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        // A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing
        // this reason with the user to guide their troubleshooting actions.
        Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    }


    // Update is called once per frame
    public void Token1()
    {
        BuyProductID(tokens1);
    }

    public void Token5()
    {

        BuyProductID(tokens5);
    }

    public void Token10()
    {

        BuyProductID(tokens10);
    }

    public void Cap1000()
    {
        BuyProductID(caps1000);
    }

    public void Cap2000()
    {

        BuyProductID(caps2000);
    }

    public void Cap4000()
    {

        BuyProductID(caps4000);
    }

    public void Crate()
    {

        BuyProductID(crate);
    }
    public void Ultimate_Wanderer()
    {
        BuyProductID(ultimate_wanderer);
    }

    public void Van_Warranty()
    {
        BuyProductID(infinitevan);
    }

    public void StarterPack()
    {
        BuyProductID(starterpack);
    }

    public void WeaponPack()
    {
        BuyProductID(weaponpack);
    }

    public void UnlockVan()
    {
        BuyProductID(van);
    }

    [EasyButtons.Button]
    public void UnlockScavengerMaterial()
    {
        BuyProductID(scavengerMaterial);
    }

    [EasyButtons.Button]
    public void RemoveBanner()
    {
        BuyProductID(remove_banner);
    }

    [EasyButtons.Button]
    public void MaxBag()
    {
        BuyProductID(max_bag);
    }

}

@JParish2590 Where are you seeing this error, in the Editor? Please share a screenshot. Please compare to the Sample IAP Project as mentioned. This error is expected in the Editor, but you do show a try/catch block. Please add a Debug.Log statement just before this line, and show the debug output. It’s almost like the code you added is not being executed, the try/catch should catch that exception. And try catching all exceptions, not just IAPSecurityException

catch (Exception e )

You can always just set a boolean value when running in the Editor. Not entirely elegant, but it would allow you to proceed.