// INativeStore
        public void Purchase(string productJSON, string developerPayload)
        {
            var         dic = (Dictionary <string, object>)MiniJson.JsonDecode(productJSON);
            object      obj;
            string      id, storeId, type;
            ProductType itemType;

            dic.TryGetValue("id", out obj);
            id = obj.ToString();
            dic.TryGetValue("storeSpecificId", out obj);
            storeId = obj.ToString();
            dic.TryGetValue("type", out obj);
            type = obj.ToString();
            if (Enum.IsDefined(typeof(ProductType), type))
            {
                itemType = (ProductType)Enum.Parse(typeof(ProductType), type);
            }
            else
            {
                itemType = ProductType.Consumable;
            }

            // This doesn't currently deal with "enabled" and "payouts" that could be included in the JSON
            var product = new ProductDefinition(id, storeId, itemType);

            FakePurchase(product, developerPayload);
        }
        // INativeStore
        public void RetrieveProducts(string json)
        {
            var jsonList           = (List <object>)MiniJson.JsonDecode(json);
            var productDefinitions = jsonList.DecodeJSON(Name);

            StoreRetrieveProducts(new ReadOnlyCollection <ProductDefinition>(productDefinitions.ToList()));
        }
        /// <summary>
        /// Parse product definitions from JSON, selecting store specific identifiers
        /// for the specified store name.
        /// </summary>
        internal static List <ProductDefinition> ParseProductsFromJSON(string json, string storeName, ILogger logger)
        {
            if (String.IsNullOrEmpty(json))
            {
                return(null);
            }

            var result = new HashSet <ProductDefinition>();

            try
            {
                var    container = (Dictionary <string, object>)MiniJson.JsonDecode(json);
                object storeCatalog;
                object abGroupObject;
                container.TryGetValue("catalog", out storeCatalog);
                if (container.TryGetValue("abGroup", out abGroupObject))
                {
                    if (profile != null)
                    {
                        profile.SetStoreABGroup(Convert.ToInt32(abGroupObject));
                    }
                }
                var    cat = storeCatalog as Dictionary <string, object>;
                object catid;
                if (cat.TryGetValue("id", out catid))
                {
                    if (profile != null)
                    {
                        var id = catid as string;
                        if (id == "")
                        {
                            id = "empty-catalog-id";
                        }
                        profile.SetCatalogId(id);
                    }
                }
                else
                {
                    // Tracking the case when store catalog returns without an "id" field
                    // May need to be an errr at some point
                    if (profile != null)
                    {
                        profile.SetCatalogId("no-catalog-id-present");
                    }
                }
                object products;
                cat.TryGetValue("products", out products);
                var jsonList = (List <object>)products;
                return(jsonList.DecodeJSON(storeName));
            }
            catch (Exception e) {
                if (logger != null)
                {
                    logger.LogWarning("UnityIAP", "Error parsing catalog, exception " + e);
                }
                return(null);
            }
        }
Exemple #4
0
        private double computeExtraTime(string metadata, double new_sku_period_in_seconds)
        {
            var  wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(metadata);
            long old_sku_remaining_seconds = (long)wrapper["old_sku_remaining_seconds"];
            long old_sku_price_in_micros   = (long)wrapper["old_sku_price_in_micros"];

            double old_sku_period_in_seconds = (parseTimeSpan((string)wrapper["old_sku_period_string"])).TotalSeconds;
            long   new_sku_price_in_micros   = (long)wrapper["new_sku_price_in_micros"];
            double result = ((((double)old_sku_remaining_seconds / (double)old_sku_period_in_seconds) * (double)old_sku_price_in_micros) / (double)new_sku_price_in_micros) * new_sku_period_in_seconds;

            return(result);
        }
        public static Dictionary <string, string> DeserializeProductDetails(string json)
        {
            var objects = (List <object>)MiniJson.JsonDecode(json);
            var result  = new Dictionary <string, string>();

            foreach (Dictionary <string, object> obj in objects)
            {
                var details = new Dictionary <string, string>();
                if (obj.TryGetValue("metadata", out var metadata))
                {
                    var metadataStr = (Dictionary <string, object>)metadata;
                    details["subscriptionNumberOfUnits"]        = metadataStr.TryGetString("subscriptionNumberOfUnits");
                    details["subscriptionPeriodUnit"]           = metadataStr.TryGetString("subscriptionPeriodUnit");
                    details["localizedPrice"]                   = metadataStr.TryGetString("localizedPrice");
                    details["isoCurrencyCode"]                  = metadataStr.TryGetString("isoCurrencyCode");
                    details["localizedPriceString"]             = metadataStr.TryGetString("localizedPriceString");
                    details["localizedTitle"]                   = metadataStr.TryGetString("localizedTitle");
                    details["localizedDescription"]             = metadataStr.TryGetString("localizedDescription");
                    details["introductoryPrice"]                = metadataStr.TryGetString("introductoryPrice");
                    details["introductoryPriceLocale"]          = metadataStr.TryGetString("introductoryPriceLocale");
                    details["introductoryPriceNumberOfPeriods"] = metadataStr.TryGetString("introductoryPriceNumberOfPeriods");
                    details["numberOfUnits"] = metadataStr.TryGetString("numberOfUnits");
                    details["unit"]          = metadataStr.TryGetString("unit");

                    // this is a double check for Apple side's bug
                    if (!string.IsNullOrEmpty(details["subscriptionNumberOfUnits"]) && string.IsNullOrEmpty(details["subscriptionPeriodUnit"]))
                    {
                        details["subscriptionPeriodUnit"] = "0";
                    }

                    // this is a double check for Apple side's bug
                    if (!string.IsNullOrEmpty(details["numberOfUnits"]) && string.IsNullOrEmpty(details["unit"]))
                    {
                        details["unit"] = "0";
                    }
                }
                else
                {
                    Debug.LogWarning("metadata key not found in product details json");
                }

                if (obj.TryGetValue("storeSpecificId", out var id))
                {
                    var idStr = (string)id;
                    result.Add(idStr, MiniJson.JsonEncode(details));
                }
                else
                {
                    Debug.LogWarning("storeSpecificId key not found in product details json");
                }
            }
            return(result);
        }
Exemple #6
0
        public static void UpdateSubscription(Product newProduct, Product oldProduct, string developerPayload, Action <Product, string> appleStore, Action <string, string> googleStore)
        {
            if (oldProduct.receipt == null)
            {
                Debug.Log("The product has not been purchased, a subscription can only be upgrade/downgrade when has already been purchased");
                return;
            }
            var receipt_wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(oldProduct.receipt);

            if (receipt_wrapper == null || !receipt_wrapper.ContainsKey("Store") || !receipt_wrapper.ContainsKey("Payload"))
            {
                Debug.Log("The product receipt does not contain enough information");
                return;
            }
            var store   = (string)receipt_wrapper ["Store"];
            var payload = (string)receipt_wrapper ["Payload"];

            if (payload != null)
            {
                switch (store)
                {
                case "GooglePlay":
                {
                    SubscriptionManager oldSubscriptionManager = new SubscriptionManager(oldProduct, null);
                    SubscriptionInfo    oldSubscriptionInfo    = null;
                    try {
                        oldSubscriptionInfo = oldSubscriptionManager.getSubscriptionInfo();
                    } catch (Exception e) {
                        Debug.unityLogger.Log("Error: the product that will be updated does not have a valid receipt", e);
                        return;
                    }
                    string newSubscriptionId = newProduct.definition.storeSpecificId;
                    googleStore(oldSubscriptionInfo.getSubscriptionInfoJsonString(), newSubscriptionId);
                    return;
                }

                case "AppleAppStore":
                case "MacAppStore":
                {
                    appleStore(newProduct, developerPayload);
                    return;
                }

                default:
                {
                    Debug.Log("This store does not support update subscriptions");
                    return;
                }
                }
            }
        }
Exemple #7
0
        // parse the "payload" part to get the subscription
        // info from the platform based native receipt
        public SubscriptionInfo getSubscriptionInfo()
        {
            if (this.receipt != null)
            {
                var receipt_wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(receipt);

                var validPayload = receipt_wrapper.TryGetValue("Payload", out var payloadAsObject);
                var validStore   = receipt_wrapper.TryGetValue("Store", out var storeAsObject);

                if (validPayload && validStore)
                {
                    var payload = payloadAsObject as string;
                    var store   = storeAsObject as string;

                    switch (store)
                    {
                    case GooglePlay.Name:
                    {
                        return(getGooglePlayStoreSubInfo(payload));
                    }

                    case AppleAppStore.Name:
                    case MacAppStore.Name:
                    {
                        if (this.productId == null)
                        {
                            throw new NullProductIdException();
                        }
                        return(getAppleAppStoreSubInfo(payload, this.productId));
                    }

                    case AmazonApps.Name:
                    {
                        return(getAmazonAppStoreSubInfo(this.productId));
                    }

                    default:
                    {
                        throw new StoreSubscriptionInfoNotSupportedException("Store not supported: " + store);
                    }
                    }
                }
            }

            throw new NullReceiptException();
        }
Exemple #8
0
        /// <exception cref="System.ArgumentException">Thrown when parsing fails</exception>
        public static StoreConfiguration Deserialize(string json)
        {
            var dic = (Dictionary <string, object>)MiniJson.JsonDecode(json);

            AppStore store;
            var      key = (string)dic ["androidStore"];

            if (!Enum.IsDefined(typeof(AppStore), key))
            {
                store = AppStore.GooglePlay;
            }
            else
            {
                store = (AppStore)Enum.Parse(typeof(AppStore), (string)dic["androidStore"], true);
            }

            return(new StoreConfiguration(store));
        }
        public static List <ProductDescription> DeserializeProductDescriptions(string json)
        {
            var objects = (List <object>)MiniJson.JsonDecode(json);
            var result  = new List <ProductDescription>();

            foreach (Dictionary <string, object> obj in objects)
            {
                var metadata = DeserializeMetadata((Dictionary <string, object>)obj["metadata"]);
                var product  = new ProductDescription(
                    (string)obj["storeSpecificId"],
                    metadata,
                    obj.TryGetString("receipt"),
                    obj.TryGetString("transactionId"),
                    ProductType.NonConsumable);
                result.Add(product);
            }
            return(result);
        }
Exemple #10
0
        private StoreSpecificPurchaseErrorCode ParseStoreSpecificPurchaseErrorCode(string json)
        {
            // If we didn't get any JSON just return Unknown.
            if (json == null)
            {
                return(StoreSpecificPurchaseErrorCode.Unknown);
            }

            // If the dictionary contains a storeSpecificErrorCode, return it, otherwise return Unknown.
            var purchaseFailureDictionary = MiniJson.JsonDecode(json) as Dictionary <string, object>;

            if (purchaseFailureDictionary != null && purchaseFailureDictionary.ContainsKey(kStoreSpecificErrorCodeKey) && Enum.IsDefined(typeof(StoreSpecificPurchaseErrorCode), (string)purchaseFailureDictionary[kStoreSpecificErrorCodeKey]))
            {
                string storeSpecificErrorCodeString = (string)purchaseFailureDictionary[kStoreSpecificErrorCodeKey];
                return((StoreSpecificPurchaseErrorCode)Enum.Parse(typeof(StoreSpecificPurchaseErrorCode),
                                                                  storeSpecificErrorCodeString));
            }
            return(StoreSpecificPurchaseErrorCode.Unknown);
        }
        public static Dictionary <string, string> DeserializeSubscriptionDescriptions(string json)
        {
            var objects = (List <object>)MiniJson.JsonDecode(json);
            var result  = new Dictionary <string, string>();

            foreach (Dictionary <string, object> obj in objects)
            {
                var subscription = new Dictionary <string, string>();
                if (obj.TryGetValue("metadata", out var metadata))
                {
                    var metadataDict = (Dictionary <string, object>)metadata;
                    subscription["introductoryPrice"]                = metadataDict.TryGetString("introductoryPrice");
                    subscription["introductoryPriceLocale"]          = metadataDict.TryGetString("introductoryPriceLocale");
                    subscription["introductoryPriceNumberOfPeriods"] = metadataDict.TryGetString("introductoryPriceNumberOfPeriods");
                    subscription["numberOfUnits"] = metadataDict.TryGetString("numberOfUnits");
                    subscription["unit"]          = metadataDict.TryGetString("unit");

                    // this is a double check for Apple side's bug
                    if (!string.IsNullOrEmpty(subscription["numberOfUnits"]) && string.IsNullOrEmpty(subscription["unit"]))
                    {
                        subscription["unit"] = "0";
                    }
                }
                else
                {
                    Debug.LogWarning("metadata key not found in subscription description json");
                }

                if (obj.TryGetValue("storeSpecificId", out var id))
                {
                    var idStr = (string)id;
                    result.Add(idStr, MiniJson.JsonEncode(subscription));
                }
                else
                {
                    Debug.LogWarning("storeSpecificId key not found in subscription description json");
                }
            }

            return(result);
        }
Exemple #12
0
        public bool IsPurchasedProductDeferred(Product product)
        {
            try
            {
                var unifiedReceipt = MiniJson.JsonDecode(product.receipt) as Dictionary <string, object>;
                var payloadStr     = unifiedReceipt["Payload"] as string;

                var payload = MiniJson.JsonDecode(payloadStr) as Dictionary <string, object>;
                var jsonStr = payload["json"] as string;

                var jsonDic       = MiniJson.JsonDecode(jsonStr) as Dictionary <string, object>;
                var purchaseState = (long)jsonDic["purchaseState"];

                //PurchaseState codes: https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState#pending
                return(purchaseState == 2 || purchaseState == 4);
            }
            catch
            {
                Debug.LogWarning("Cannot parse Google receipt for transaction " + product.transactionID);
                return(false);
            }
        }
        public static PurchaseFailureDescription DeserializeFailureReason(string json)
        {
            var dic    = (Dictionary <string, object>)MiniJson.JsonDecode(json);
            var reason = PurchaseFailureReason.Unknown;

            if (dic.TryGetValue("reason", out var reasonStr))
            {
                if (Enum.IsDefined(typeof(PurchaseFailureReason), (string)reasonStr))
                {
                    reason = (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), (string)reasonStr);
                }

                if (dic.TryGetValue("productId", out var productId))
                {
                    return(new PurchaseFailureDescription((string)productId, reason, dic.TryGetString("message")));
                }
            }
            else
            {
                Debug.LogWarning("Reason key not found in purchase failure json: " + json);
            }

            return(new PurchaseFailureDescription("Unknown ProductID", reason, dic.TryGetString("message")));
        }
Exemple #14
0
        private SubscriptionInfo getGooglePlayStoreSubInfo(string payload)
        {
            var payload_wrapper    = (Dictionary <string, object>)MiniJson.JsonDecode(payload);
            var validSkuDetailsKey = payload_wrapper.TryGetValue("skuDetails", out var skuDetailsObject);

            string skuDetails = null;

            if (validSkuDetailsKey)
            {
                skuDetails = skuDetailsObject as string;
            }

            var purchaseHistorySupported = false;

            var original_json_payload_wrapper =
                (Dictionary <string, object>)MiniJson.JsonDecode((string)payload_wrapper["json"]);

            var validIsAutoRenewingKey =
                original_json_payload_wrapper.TryGetValue("autoRenewing", out var autoRenewingObject);

            var isAutoRenewing = false;

            if (validIsAutoRenewingKey)
            {
                isAutoRenewing = (bool)autoRenewingObject;
            }

            // Google specifies times in milliseconds since 1970.
            DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

            var validPurchaseTimeKey =
                original_json_payload_wrapper.TryGetValue("purchaseTime", out var purchaseTimeObject);

            long purchaseTime = 0;

            if (validPurchaseTimeKey)
            {
                purchaseTime = (long)purchaseTimeObject;
            }

            var purchaseDate = epoch.AddMilliseconds(purchaseTime);

            var validDeveloperPayloadKey =
                original_json_payload_wrapper.TryGetValue("developerPayload", out var developerPayloadObject);

            var    isFreeTrial          = false;
            var    hasIntroductoryPrice = false;
            string updateMetadata       = null;

            if (validDeveloperPayloadKey)
            {
                var developerPayloadJSON     = (string)developerPayloadObject;
                var developerPayload_wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(developerPayloadJSON);
                var validIsFreeTrialKey      =
                    developerPayload_wrapper.TryGetValue("is_free_trial", out var isFreeTrialObject);
                if (validIsFreeTrialKey)
                {
                    isFreeTrial = (bool)isFreeTrialObject;
                }

                var validHasIntroductoryPriceKey =
                    developerPayload_wrapper.TryGetValue("has_introductory_price_trial",
                                                         out var hasIntroductoryPriceObject);

                if (validHasIntroductoryPriceKey)
                {
                    hasIntroductoryPrice = (bool)hasIntroductoryPriceObject;
                }

                var validIsUpdatedKey = developerPayload_wrapper.TryGetValue("is_updated", out var isUpdatedObject);

                var isUpdated = false;

                if (validIsUpdatedKey)
                {
                    isUpdated = (bool)isUpdatedObject;
                }

                if (isUpdated)
                {
                    var isValidUpdateMetaKey = developerPayload_wrapper.TryGetValue("update_subscription_metadata",
                                                                                    out var updateMetadataObject);

                    if (isValidUpdateMetaKey)
                    {
                        updateMetadata = (string)updateMetadataObject;
                    }
                }
            }

            return(new SubscriptionInfo(skuDetails, isAutoRenewing, purchaseDate, isFreeTrial, hasIntroductoryPrice,
                                        purchaseHistorySupported, updateMetadata));
        }
Exemple #15
0
        public SubscriptionInfo(AppleInAppPurchaseReceipt r, string intro_json)
        {
            var productType = (AppleStoreProductType)Enum.Parse(typeof(AppleStoreProductType), r.productType.ToString());

            if (productType == AppleStoreProductType.Consumable || productType == AppleStoreProductType.NonConsumable)
            {
                throw new InvalidProductTypeException();
            }

            if (!string.IsNullOrEmpty(intro_json))
            {
                var intro_wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(intro_json);
                var nunit         = -1;
                var unit          = SubscriptionPeriodUnit.NotAvailable;
                this.introductory_price = intro_wrapper.TryGetString("introductoryPrice") + intro_wrapper.TryGetString("introductoryPriceLocale");
                if (string.IsNullOrEmpty(this.introductory_price))
                {
                    this.introductory_price = "not available";
                }
                else
                {
                    try {
                        this.introductory_price_cycles = Convert.ToInt64(intro_wrapper.TryGetString("introductoryPriceNumberOfPeriods"));
                        nunit = Convert.ToInt32(intro_wrapper.TryGetString("numberOfUnits"));
                        unit  = (SubscriptionPeriodUnit)Convert.ToInt32(intro_wrapper.TryGetString("unit"));
                    } catch (Exception e) {
                        Debug.unityLogger.Log("Unable to parse introductory period cycles and duration, this product does not have configuration of introductory price period", e);
                        unit = SubscriptionPeriodUnit.NotAvailable;
                    }
                }
                DateTime now = DateTime.Now;
                switch (unit)
                {
                case SubscriptionPeriodUnit.Day:
                    this.introductory_price_period = TimeSpan.FromTicks(TimeSpan.FromDays(1).Ticks *nunit);
                    break;

                case SubscriptionPeriodUnit.Month:
                    TimeSpan month_span = now.AddMonths(1) - now;
                    this.introductory_price_period = TimeSpan.FromTicks(month_span.Ticks * nunit);
                    break;

                case SubscriptionPeriodUnit.Week:
                    this.introductory_price_period = TimeSpan.FromTicks(TimeSpan.FromDays(7).Ticks *nunit);
                    break;

                case SubscriptionPeriodUnit.Year:
                    TimeSpan year_span = now.AddYears(1) - now;
                    this.introductory_price_period = TimeSpan.FromTicks(year_span.Ticks * nunit);
                    break;

                case SubscriptionPeriodUnit.NotAvailable:
                    this.introductory_price_period = TimeSpan.Zero;
                    this.introductory_price_cycles = 0;
                    break;
                }
            }
            else
            {
                this.introductory_price        = "not available";
                this.introductory_price_period = TimeSpan.Zero;
                this.introductory_price_cycles = 0;
            }

            DateTime current_date = DateTime.UtcNow;

            this.purchaseDate = r.purchaseDate;
            this.productId    = r.productID;

            this.subscriptionExpireDate = r.subscriptionExpirationDate;
            this.subscriptionCancelDate = r.cancellationDate;

            // if the product is non-renewing subscription, apple store will not return expiration date for this product
            if (productType == AppleStoreProductType.NonRenewingSubscription)
            {
                this.is_subscribed                = Result.Unsupported;
                this.is_expired                   = Result.Unsupported;
                this.is_cancelled                 = Result.Unsupported;
                this.is_free_trial                = Result.Unsupported;
                this.is_auto_renewing             = Result.Unsupported;
                this.is_introductory_price_period = Result.Unsupported;
            }
            else
            {
                this.is_cancelled     = (r.cancellationDate.Ticks > 0) && (r.cancellationDate.Ticks < current_date.Ticks) ? Result.True : Result.False;
                this.is_subscribed    = r.subscriptionExpirationDate.Ticks >= current_date.Ticks ? Result.True : Result.False;
                this.is_expired       = (r.subscriptionExpirationDate.Ticks > 0 && r.subscriptionExpirationDate.Ticks < current_date.Ticks) ? Result.True : Result.False;
                this.is_free_trial    = (r.isFreeTrial == 1) ? Result.True : Result.False;
                this.is_auto_renewing = ((productType == AppleStoreProductType.AutoRenewingSubscription) && this.is_cancelled == Result.False &&
                                         this.is_expired == Result.False) ? Result.True : Result.False;
                this.is_introductory_price_period = r.isIntroductoryPricePeriod == 1 ? Result.True : Result.False;
            }

            if (this.is_subscribed == Result.True)
            {
                this.remainedTime = r.subscriptionExpirationDate.Subtract(current_date);
            }
            else
            {
                this.remainedTime = TimeSpan.Zero;
            }
        }
Exemple #16
0
        public SubscriptionInfo(string skuDetails, bool isAutoRenewing, DateTime purchaseDate, bool isFreeTrial,
                                bool hasIntroductoryPriceTrial, bool purchaseHistorySupported, string updateMetadata)
        {
            var skuDetails_wrapper = (Dictionary <string, object>)MiniJson.JsonDecode(skuDetails);
            var validTypeKey       = skuDetails_wrapper.TryGetValue("type", out var typeObject);

            if (!validTypeKey || (string)typeObject == "inapp")
            {
                throw new InvalidProductTypeException();
            }

            var validProductIdKey = skuDetails_wrapper.TryGetValue("productId", out var productIdObject);

            productId = null;
            if (validProductIdKey)
            {
                productId = productIdObject as string;
            }

            this.purchaseDate     = purchaseDate;
            this.is_subscribed    = Result.True;
            this.is_auto_renewing = isAutoRenewing ? Result.True : Result.False;
            this.is_expired       = Result.False;
            this.is_cancelled     = isAutoRenewing ? Result.False : Result.True;
            this.is_free_trial    = Result.False;


            string sub_period = null;

            if (skuDetails_wrapper.ContainsKey("subscriptionPeriod"))
            {
                sub_period = (string)skuDetails_wrapper["subscriptionPeriod"];
            }
            string free_trial_period = null;

            if (skuDetails_wrapper.ContainsKey("freeTrialPeriod"))
            {
                free_trial_period = (string)skuDetails_wrapper["freeTrialPeriod"];
            }
            string introductory_price = null;

            if (skuDetails_wrapper.ContainsKey("introductoryPrice"))
            {
                introductory_price = (string)skuDetails_wrapper["introductoryPrice"];
            }
            string introductory_price_period_string = null;

            if (skuDetails_wrapper.ContainsKey("introductoryPricePeriod"))
            {
                introductory_price_period_string = (string)skuDetails_wrapper["introductoryPricePeriod"];
            }
            long introductory_price_cycles = 0;

            if (skuDetails_wrapper.ContainsKey("introductoryPriceCycles"))
            {
                introductory_price_cycles = (long)skuDetails_wrapper["introductoryPriceCycles"];
            }

            // for test
            free_trial_period_string = free_trial_period;

            this.subscriptionPeriod = computePeriodTimeSpan(parsePeriodTimeSpanUnits(sub_period));

            this.freeTrialPeriod = TimeSpan.Zero;
            if (isFreeTrial)
            {
                this.freeTrialPeriod = parseTimeSpan(free_trial_period);
            }

            this.introductory_price           = introductory_price;
            this.introductory_price_cycles    = introductory_price_cycles;
            this.introductory_price_period    = TimeSpan.Zero;
            this.is_introductory_price_period = Result.False;
            TimeSpan total_introductory_duration = TimeSpan.Zero;

            if (hasIntroductoryPriceTrial)
            {
                if (introductory_price_period_string != null && introductory_price_period_string.Equals(sub_period))
                {
                    this.introductory_price_period = this.subscriptionPeriod;
                }
                else
                {
                    this.introductory_price_period = parseTimeSpan(introductory_price_period_string);
                }
                // compute the total introductory duration according to the introductory price period and period cycles
                total_introductory_duration = accumulateIntroductoryDuration(parsePeriodTimeSpanUnits(introductory_price_period_string), this.introductory_price_cycles);
            }

            // if this subscription is updated from other subscription, the remaining time will be applied to this subscription
            TimeSpan extra_time = TimeSpan.FromSeconds(updateMetadata == null ? 0.0 : computeExtraTime(updateMetadata, this.subscriptionPeriod.TotalSeconds));

            TimeSpan time_since_purchased = DateTime.UtcNow.Subtract(purchaseDate);


            // this subscription is still in the extra time (the time left by the previous subscription when updated to the current one)
            if (time_since_purchased <= extra_time)
            {
                // this subscription is in the remaining credits from the previous updated one
                this.subscriptionExpireDate = purchaseDate.Add(extra_time);
            }
            else if (time_since_purchased <= this.freeTrialPeriod.Add(extra_time))
            {
                // this subscription is in the free trial period
                // this product will be valid until free trial ends, the beginning of next billing date
                this.is_free_trial          = Result.True;
                this.subscriptionExpireDate = purchaseDate.Add(this.freeTrialPeriod.Add(extra_time));
            }
            else if (time_since_purchased < this.freeTrialPeriod.Add(extra_time).Add(total_introductory_duration))
            {
                // this subscription is in the introductory price period
                this.is_introductory_price_period = Result.True;
                DateTime introductory_price_begin_date = this.purchaseDate.Add(this.freeTrialPeriod.Add(extra_time));
                this.subscriptionExpireDate = nextBillingDate(introductory_price_begin_date, parsePeriodTimeSpanUnits(introductory_price_period_string));
            }
            else
            {
                // no matter sub is cancelled or not, the expire date will be next billing date
                DateTime billing_begin_date = this.purchaseDate.Add(this.freeTrialPeriod.Add(extra_time).Add(total_introductory_duration));
                this.subscriptionExpireDate = nextBillingDate(billing_begin_date, parsePeriodTimeSpanUnits(sub_period));
            }

            this.remainedTime = this.subscriptionExpireDate.Subtract(DateTime.UtcNow);
            this.sku_details  = skuDetails;
        }
        /// <summary>
        /// Parse product definitions from JSON, selecting store specific identifiers
        /// for the specified store name.
        /// </summary>
        internal static HashSet <ProductDefinition> ParseProductsFromJSON(string json, string storeName)
        {
            var result = new HashSet <ProductDefinition>();

            try
            {
                var    container = (Dictionary <string, object>)MiniJson.JsonDecode(json);
                object products;
                container.TryGetValue("products", out products);
                var productsList = products as List <object>;

                var snakeCaseStoreName = CamelCaseToSnakeCase(storeName);
                foreach (object product in productsList)
                {
                    var    productDict = (Dictionary <string, object>)product;
                    object id, storeIDs, typeString;
                    // Remove payouts and enabled references for 1.13.0.
                    //object enabled, payoutsJson;
                    productDict.TryGetValue("id", out id);
                    productDict.TryGetValue("store_ids", out storeIDs);
                    productDict.TryGetValue("type", out typeString);
                    // Remove payouts and enabled references for 1.13.0.
                    //productDict.TryGetValue("enabled", out enabled);
                    //productDict.TryGetValue("payouts", out payoutsJson);

                    var    idHash          = storeIDs as Dictionary <string, object>;
                    string storeSpecificId = (string)id;
                    if (null != idHash && idHash.ContainsKey(snakeCaseStoreName))
                    {
                        object storeId = null;
                        idHash.TryGetValue(snakeCaseStoreName, out storeId);
                        if (null != storeId)
                        {
                            storeSpecificId = (string)storeId;
                        }
                    }

                    var type = (ProductType)Enum.Parse(typeof(ProductType), (string)typeString);

                    // Remove payouts and enabled references for 1.13.0.
                    //bool enabledBool = true;
                    //if (enabled != null && (enabled is bool || enabled is Boolean)) {
                    //    enabledBool = (bool)enabled;
                    //}
                    //var definition = new ProductDefinition((string)id, storeSpecificId, type, enabledBool);
                    var definition = new ProductDefinition((string)id, storeSpecificId, type);

                    // Remove payouts and enabled references for 1.13.0.
                    //List<object> payoutsJsonArray = payoutsJson as List<object>;
                    //if (payoutsJsonArray != null) {
                    //    var payouts = new List<PayoutDefinition>();
                    //    foreach (object payoutJson in payoutsJsonArray) {
                    //        Dictionary<string, object> payoutJsonDict = payoutJson as Dictionary<string, object>;
                    //        object payoutTypeString, subtype, quantity, data;
                    //        payoutJsonDict.TryGetValue("t", out payoutTypeString);
                    //        payoutJsonDict.TryGetValue("st", out subtype);
                    //        payoutJsonDict.TryGetValue("q", out quantity);
                    //        payoutJsonDict.TryGetValue("d", out data);
                    //
                    //        double q = quantity == null ? 0 : Convert.ToDouble (quantity);
                    //
                    //        payouts.Add(new PayoutDefinition((string)payoutTypeString, (string)subtype, q, (string)data));
                    //    }
                    //    definition.SetPayouts(payouts);
                    //}

                    result.Add(definition);
                }
                return(result);
            }
            catch (Exception e)
            {
                throw new SerializationException("Error parsing JSON", e);
            }
        }