using System; using System.Collections.Generic; using System.Reflection; using UnityEngine.Purchasing.Extension; namespace UnityEngine.Purchasing { static class SerializationExtensions { public static string TryGetString(this Dictionary dic, string key) { if (dic.ContainsKey(key)) { if (dic[key] != null) { return dic[key].ToString(); } } return null; } } internal class JSONSerializer { public static string SerializeProductDef(ProductDefinition product) { return MiniJson.JsonEncode(EncodeProductDef(product)); } public static string SerializeProductDefs(IEnumerable products) { var result = new List(); foreach (var product in products) { result.Add(EncodeProductDef(product)); } return MiniJson.JsonEncode(result); } public static string SerializeProductDescs(ProductDescription product) { return MiniJson.JsonEncode(EncodeProductDesc(product)); } public static string SerializeProductDescs(IEnumerable products) { var result = new List(); foreach (var product in products) { result.Add(EncodeProductDesc(product)); } return MiniJson.JsonEncode(result); } public static Dictionary DeserializeSubscriptionDescriptions(string json) { var objects = (List)MiniJson.JsonDecode(json); var result = new Dictionary(); foreach (Dictionary obj in objects) { var subscription = new Dictionary(); if (obj.TryGetValue("metadata", out var metadata)) { var metadataDict = (Dictionary)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; } public static Dictionary DeserializeProductDetails(string json) { var objects = (List)MiniJson.JsonDecode(json); var result = new Dictionary(); foreach (Dictionary obj in objects) { var details = new Dictionary(); if (obj.TryGetValue("metadata", out var metadata)) { var metadataStr = (Dictionary)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; } public static PurchaseFailureDescription DeserializeFailureReason(string json) { var dic = (Dictionary)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, BuildPurchaseFailureDescriptionMessage(dic)); } } else { Debug.LogWarning("Reason key not found in purchase failure json: " + json); } return new PurchaseFailureDescription("Unknown ProductID", reason, BuildPurchaseFailureDescriptionMessage(dic)); } static string BuildPurchaseFailureDescriptionMessage(Dictionary dic) { var message = dic.TryGetString("message"); var storeSpecificErrorCode = dic.TryGetString("storeSpecificErrorCode"); if (message == null && storeSpecificErrorCode == null) { return null; } if (storeSpecificErrorCode != null) { storeSpecificErrorCode = " storeSpecificErrorCode: " + storeSpecificErrorCode; } return message + storeSpecificErrorCode; } private static Dictionary EncodeProductDef(ProductDefinition product) { var prod = new Dictionary { { "id", product.id }, { "storeSpecificId", product.storeSpecificId }, { "type", product.type.ToString() } }; var enabled = true; var enabledProp = typeof(ProductDefinition).GetProperty("enabled"); if (enabledProp != null) { try { enabled = Convert.ToBoolean(enabledProp.GetValue(product, null)); } catch { enabled = true; } } prod.Add("enabled", enabled); var payoutsArray = new List(); var payoutsProp = typeof(ProductDefinition).GetProperty("payouts"); if (payoutsProp != null) { var payoutsObject = payoutsProp.GetValue(product, null); var payouts = payoutsObject as Array; if (payouts != null) { foreach (var payout in payouts) { var payoutDict = new Dictionary(); var payoutType = payout.GetType(); payoutDict["t"] = payoutType.GetField("typeString").GetValue(payout); payoutDict["st"] = payoutType.GetField("subtype").GetValue(payout); payoutDict["q"] = payoutType.GetField("quantity").GetValue(payout); payoutDict["d"] = payoutType.GetField("data").GetValue(payout); payoutsArray.Add(payoutDict); } } } prod.Add("payouts", payoutsArray); return prod; } private static Dictionary EncodeProductDesc(ProductDescription product) { var prod = new Dictionary { { "storeSpecificId", product.storeSpecificId } }; // ProductDescription.type field available in Unity 5.4+. Access by reflection here. var pdClassType = typeof(ProductDescription); var pdClassFieldType = pdClassType.GetField("type"); if (pdClassFieldType != null) { var typeValue = pdClassFieldType.GetValue(product); prod.Add("type", typeValue.ToString()); } prod.Add("metadata", EncodeProductMeta(product.metadata)); prod.Add("receipt", product.receipt); prod.Add("transactionId", product.transactionId); return prod; } private static Dictionary EncodeProductMeta(ProductMetadata product) { var prod = new Dictionary { { "localizedPriceString", product.localizedPriceString }, { "localizedTitle", product.localizedTitle }, { "localizedDescription", product.localizedDescription }, { "isoCurrencyCode", product.isoCurrencyCode }, { "localizedPrice", Convert.ToDouble(product.localizedPrice) } }; return prod; } } }