/// <summary>
        /// Updates the receipt.
        /// </summary>
        /// <param name="pp">Pp.</param>
        /// <param name="p">P.</param>
        /// <param name="introductory_info_dict">Introductory info dict.</param>
        private void _UpdateReceipt(Product pp, ref IAPProduct p, Dictionary <string, string> introductory_info_dict)
        {
#if RECEIPT_VALIDATION
#if UNITY_EDITOR
#else
            // Get subscription information
            if (pp.definition.type == ProductType.Subscription)
            {
                // Get the json which store the introductory info
                string intro_json = (introductory_info_dict == null || !introductory_info_dict.ContainsKey(pp.definition.storeSpecificId)) ? null : introductory_info_dict[pp.definition.storeSpecificId];
                if (pp.receipt != null && checkIfProductIsAvailableForSubscriptionManager(pp.receipt))
                {
                    SubscriptionManager manager = new SubscriptionManager(pp, intro_json);
                    SubscriptionInfo    info    = manager.getSubscriptionInfo();
                    p.subsciprtionInfo = IAPSubscriptionInfo.GetInstance(info);
                    // Debug.Log("[SubscriptionInfo] input: " + pp.receipt);
                    // Debug.Log("[SubscriptionInfo]: " + info);
                    // Debug.Log("[SubscriptionInfo] result: " + p.subsciprtionInfo);
                    // Debug.Log("[SubscriptionInfo] purchaseDate: " + p.subsciprtionInfo.purchaseDate);
                    // Debug.Log("[SubscriptionInfo] expireDate: " + p.subsciprtionInfo.expireDate);
                    // Debug.Log("[SubscriptionInfo] introductoryPrice: " + p.subsciprtionInfo.introductoryPrice);
                }
                // Decode introductoryPrice
                if (intro_json != null)
                {
                    IntroductoryPrice ip = JsonUtility.FromJson <IntroductoryPrice>(intro_json);
                    //Debug.Log("IntroductoryPrice> " + ip);
                    if (ip != null)
                    {
                        if (string.IsNullOrEmpty(ip.introductoryPrice) || ip.introductoryPrice == "0")
                        {
                            // No introductory price
                        }
                        else
                        {
                            //p.priceString = ip.introductoryPrice;
                            //p.price = Convert.ToDecimal(Regex.Replace(ip.introductoryPrice, "[^0-9.]", ""));
                            p.introductoryPriceString = ip.introductoryPrice;
                            p.introductoryPrice       = Convert.ToDecimal(Regex.Replace(ip.introductoryPrice, "[^0-9.]", ""));
                        }
                    }
                }
            }
#endif
#endif
        }
        // Private Helper Methods

        /// <summary>
        /// Converts the product to IAP product.
        /// </summary>
        /// <returns>The product to IAP product.</returns>
        /// <param name="product">Product.</param>

        private static IAPProduct ConvertFromProduct(Product product)
        {
            IAPProduct p = null;

            if (products.ContainsKey(product.definition.id))
            {
                p                     = products[product.definition.id];
                p.priceString         = product.metadata.localizedPriceString;
                p.price               = product.metadata.localizedPrice;
                p.title               = product.metadata.localizedTitle;
                p.description         = product.metadata.localizedDescription;
                p.isoCurrencyCode     = product.metadata.isoCurrencyCode;
                p.availableToPurchase = product.availableToPurchase;
                p.hasReceipt          = product.hasReceipt;
                p.rawProduct          = product;
            }
            return(p);
        }
        /// <summary>
        /// Processes the purchase.
        /// </summary>
        /// <returns>The purchase.</returns>
        /// <param name="args">Arguments.</param>
        public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
        {
            if (args != null && args.purchasedProduct != null)
            {
                IAPProduct product = ConvertFromProduct(args.purchasedProduct);

#if RECEIPT_VALIDATION
                if (Application.platform == RuntimePlatform.Android ||
                    Application.platform == RuntimePlatform.IPhonePlayer ||
                    Application.platform == RuntimePlatform.OSXPlayer)
                {
                    try {
                        //var result =
                        _ReceiptValidator.Validate(args.purchasedProduct.receipt);
                        //Debug.Log("Receipt is valid. Contents:");
                        //foreach (IPurchaseReceipt productReceipt in result) {
                        //	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);
                        //	}
                        //}
                    } catch (IAPSecurityException) {
                        Debug.Log("Invalid receipt, not unlocking content");
                        if (OnIAPProcessPurchase != null)
                        {
                            OnIAPProcessPurchase(null, null, null);
                        }
                        return(PurchaseProcessingResult.Complete);
                    }
                }

                // update subscription info
#if UNITY_IOS
                Dictionary <string, string> introductory_info_dict = _AppleExtensions.GetIntroductoryPriceDictionary();
                _UpdateReceipt(args.purchasedProduct, ref product, introductory_info_dict);
#endif
#if UNITY_ANDROID
                Dictionary <string, string> introductory_info_dict = _GooglePlayStoreExtensions.GetProductJSONDictionary();
                _UpdateReceipt(args.purchasedProduct, ref product, introductory_info_dict);
#endif
#endif

                // CloudMoolah purchase completion / finishing currently requires using the API
                // extension IMoolahExtension.RequestPayout to finish a transaction.
                if (_isCloudMoolahStore)
                {
                    // Finish transaction with CloudMoolah server
                    _MoolahExtensions.ValidateReceipt(args.purchasedProduct.transactionID,
                                                      args.purchasedProduct.receipt,
                                                      (string transactionID, ValidateReceiptState state, string message) =>
                    {
                        if (state == ValidateReceiptState.ValidateSucceed)
                        {
                            // Finally, finish transaction with Unity IAP's local
                            // transaction log, recording the transaction id there
                            _StoreController.ConfirmPendingPurchase(args.purchasedProduct);

                            // Unlock content here.
                        }
                        else
                        {
                            Debug.Log("RequestPayOut: failed. transactionID: " + transactionID +
                                      ", state: " + state + ", message: " + message);
                            // Finishing failed. Retry later.
                        }
                    });
                }

                if (OnIAPProcessPurchase != null)
                {
                    OnIAPProcessPurchase(
                        product,
                        args.purchasedProduct.transactionID,
                        args.purchasedProduct.receipt);
                }
                return(PurchaseProcessingResult.Complete);
            }
            else
            {
                if (OnIAPProcessPurchase != null)
                {
                    OnIAPProcessPurchase(null, null, null);
                }
            }

            // still pending
            return(PurchaseProcessingResult.Pending);
        }
        //
        // --- IStoreListener
        //


        public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
        {
            // Purchasing has succeeded initializing. Collect our Purchasing references.
            Debug.Log("[IAPManager] OnInitialized.");

            // Overall Purchasing system, configured with products for this application.
            _StoreController = controller;
            // Store specific subsystem, for accessing device-specific store features.
            _StoreExtensionProvider = extensions;

            _AppleExtensions   = extensions.GetExtension <IAppleExtensions>();
            _SamsungExtensions = extensions.GetExtension <ISamsungAppsExtensions>();
            _MoolahExtensions  = extensions.GetExtension <IMoolahExtension>();

            // On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
            // On non-Apple platforms this will have no effect; OnDeferred will never be called.
#if UNITY_IOS
            _AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
#endif
#if UNITY_ANDROID
            _GooglePlayStoreExtensions = extensions.GetExtension <IGooglePlayStoreExtensions>();
#endif

#if RECEIPT_VALIDATION
    #if UNITY_EDITOR
    #else
        #if UNITY_IOS
            Dictionary <string, string> introductory_info_dict = _AppleExtensions.GetIntroductoryPriceDictionary();
        #endif
        #if UNITY_ANDROID
            Dictionary <string, string> introductory_info_dict = _GooglePlayStoreExtensions.GetProductJSONDictionary();
        #endif
    #endif
#endif
            // Store products
            for (int i = 0; i < controller.products.all.Length; i++)
            {
                Product    pp = controller.products.all[i];
                IAPProduct p  = null;
                if (products.ContainsKey(pp.definition.id))
                {
                    p                     = products[pp.definition.id];
                    p.priceString         = pp.metadata.localizedPriceString;
                    p.price               = pp.metadata.localizedPrice;
                    p.title               = pp.metadata.localizedTitle;
                    p.description         = pp.metadata.localizedDescription;
                    p.isoCurrencyCode     = pp.metadata.isoCurrencyCode;
                    p.hasReceipt          = pp.hasReceipt;
                    p.availableToPurchase = pp.availableToPurchase;
                    p.receipt             = pp.receipt;
                    p.transactionID       = pp.transactionID;
                    p.rawProduct          = pp;
                    p.hasReceipt          = pp.hasReceipt;

#if RECEIPT_VALIDATION
#if UNITY_EDITOR
#else
                    _UpdateReceipt(pp, ref p, introductory_info_dict);
#endif
#endif
                }
            }
            // callback
            if (OnIAPInitialized != null)
            {
                OnIAPInitialized(products);
            }
        }
        // Private Methods

        private static void _init(List <IAPProductSetting> productSettings)
        {
            // build the product list
            products = new Dictionary <string, IAPProduct>();
            foreach (IAPProductSetting setting in productSettings)
            {
                string         id   = setting.productId;
                IAPProductType type = setting.productType;
                IAPProduct     p    = new IAPProduct(id, type);
                p.amazonProductId  = (setting.amazonProductId != null && setting.amazonProductId != "") ? setting.amazonProductId : setting.productId;
                p.googleProductId  = (setting.googleProductId != null && setting.googleProductId != "") ? setting.googleProductId : setting.productId;
                p.appleProductId   = (setting.appleProductId != null && setting.appleProductId != "") ? setting.appleProductId : setting.productId;
                p.samsungProductId = (setting.samsungProductId != null && setting.samsungProductId != "") ? setting.samsungProductId : setting.productId;
                p.macProductId     = (setting.macProductId != null && setting.macProductId != "") ? setting.macProductId : setting.productId;
                p.tizenProductId   = (setting.tizenProductId != null && setting.tizenProductId != "") ? setting.tizenProductId : setting.productId;
                p.moolahProductId  = (setting.moolahProductId != null && setting.moolahProductId != "") ? setting.moolahProductId : setting.productId;

                p.buyButtonText      = setting.buyButtonText;
                p.disabledButtonText = setting.disabledButtonText;

                if (products.ContainsKey(id))
                {
                    Debug.Log("[IAPManager] Product id \"" + id + "\" already exist.");
                }
                else
                {
                    Debug.Log("[IAPManager] Add product with id \"" + id + "\"");
                    products.Add(id, p);
                }
                //				Debug.Log("product id: " + p.id);
            }

            var module = StandardPurchasingModule.Instance();

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

            // CloudMoolah Configuration setings
            // All games must set the configuration. the configuration need to apply on the CloudMoolah Portal.
            // CloudMoolah APP Key
            if (_setting.MoolahAppKey != null && _setting.MoolahAppKey != "")
            {
                builder.Configure <IMoolahConfiguration>().appKey = _setting.MoolahAppKey;
            }
            // CloudMoolah Hash Key
            if (_setting.MoolahHashKey != null && _setting.MoolahHashKey != "")
            {
                builder.Configure <IMoolahConfiguration>().hashKey = _setting.MoolahHashKey;
            }

            // Test Mode Enbled
            if (_setting.testMode)
            {
                // This enables the Microsoft IAP simulator for local testing.
                builder.Configure <IMicrosoftConfiguration>().useMockBillingSystem = true;
                if (_setting.GooglePublicKey != null && _setting.GooglePublicKey != "")
                {
                    builder.Configure <IGooglePlayConfiguration>().SetPublicKey(_setting.GooglePublicKey);
                }
                builder.Configure <IMoolahConfiguration>().SetMode(CloudMoolahMode.AlwaysSucceed);
                // This records whether we are using Cloud Moolah IAP.
                // Cloud Moolah requires logging in to access your Digital Wallet, so:
                // A) IAPDemo (this) displays the Cloud Moolah GUI button for Cloud Moolah
            }

            // Add Product to builder
            foreach (KeyValuePair <string, IAPProduct> item in products)
            {
                IAPProduct p   = item.Value;
                IDs        ids = new IDs();

                if (p.appleProductId != null)
                {
                    ids.Add(p.appleProductId, AppleAppStore.Name);
                }
                if (p.googleProductId != null)
                {
                    ids.Add(p.googleProductId, GooglePlay.Name);
                }
                if (p.amazonProductId != null)
                {
                    ids.Add(p.amazonProductId, AmazonApps.Name);
                }
                if (p.macProductId != null)
                {
                    ids.Add(p.macProductId, MacAppStore.Name);
                }
                if (p.samsungProductId != null)
                {
                    ids.Add(p.samsungProductId, SamsungApps.Name);
                }
                if (p.tizenProductId != null)
                {
                    ids.Add(p.tizenProductId, TizenStore.Name);
                }
                if (p.moolahProductId != null)
                {
                    ids.Add(p.moolahProductId, MoolahAppStore.Name);
                }
                //				Debug.Log("builder id: " + p.id);
                builder.AddProduct(p.id, ConvertFromIAPProductType(p.productType), ids);
            }

            // Test Mode Enabled
            if (_setting.testMode)
            {
                // Write Amazon's JSON description of our products to storage when using Amazon's local sandbox.
                // This should be removed from a production build.
                builder.Configure <IAmazonConfiguration>().WriteSandboxJSON(builder.products);

                // This enables simulated purchase success for Samsung IAP.
                // You would remove this, or set to SamsungAppsMode.Production, before building your release package.
                builder.Configure <ISamsungAppsConfiguration>().SetMode(SamsungAppsMode.AlwaysSucceed);
                // This records whether we are using Samsung IAP. Currently ISamsungAppsExtensions.RestoreTransactions
                // displays a blocking Android Activity, so:
                // A) Unity IAP does not automatically restore purchases on Samsung Galaxy Apps
                // B) IAPDemo (this) displays the "Restore" GUI button for Samsung Galaxy Apps
            }


            // This selects the GroupId that was created in the Tizen Store for this set of products
            // An empty or non-matching GroupId here will result in no products available for purchase
            builder.Configure <ITizenStoreConfiguration>().SetGroupId(_setting.TizenGroupId);


            // Check Platform
            _isGooglePlayStore  = Application.platform == RuntimePlatform.Android && module.appStore == AppStore.GooglePlay;
            _isCloudMoolahStore = Application.platform == RuntimePlatform.Android && module.appStore == AppStore.CloudMoolah;
            _isSamsungAppsStore = module.appStore == AppStore.SamsungApps;

            // build validator
#if RECEIPT_VALIDATION
            string appIdentifier;
#if UNITY_5_6_OR_NEWER
            appIdentifier = Application.identifier;
#else
            appIdentifier = Application.bundleIdentifier;
#endif
            _ReceiptValidator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(),
                                                           UnityChannelTangle.Data(), appIdentifier);
#endif

            UnityPurchasing.Initialize(_instance, builder);
        }