JSONSerializer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. using System;
  2. using UnityEngine.Purchasing.Extension;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5. namespace UnityEngine.Purchasing
  6. {
  7. static class SerializationExtensions
  8. {
  9. public static string TryGetString(this Dictionary<string, object> dic, string key)
  10. {
  11. if (dic.ContainsKey(key))
  12. {
  13. if (dic[key] != null)
  14. {
  15. return dic[key].ToString();
  16. }
  17. }
  18. return null;
  19. }
  20. }
  21. internal class JSONSerializer
  22. {
  23. public static string SerializeProductDef(ProductDefinition product)
  24. {
  25. return MiniJson.JsonEncode(EncodeProductDef(product));
  26. }
  27. public static string SerializeProductDefs(IEnumerable<ProductDefinition> products)
  28. {
  29. List<object> result = new List<object>();
  30. foreach (var product in products)
  31. {
  32. result.Add(EncodeProductDef(product));
  33. }
  34. return MiniJson.JsonEncode(result);
  35. }
  36. public static string SerializeProductDescs(ProductDescription product)
  37. {
  38. return MiniJson.JsonEncode(EncodeProductDesc(product));
  39. }
  40. public static string SerializeProductDescs(IEnumerable<ProductDescription> products)
  41. {
  42. List<object> result = new List<object>();
  43. foreach (var product in products)
  44. {
  45. result.Add(EncodeProductDesc(product));
  46. }
  47. return MiniJson.JsonEncode(result);
  48. }
  49. public static List<ProductDescription> DeserializeProductDescriptions(string json)
  50. {
  51. var objects = (List<object>)MiniJson.JsonDecode(json);
  52. var result = new List<ProductDescription>();
  53. foreach (Dictionary<string, object> obj in objects)
  54. {
  55. var metadata = DeserializeMetadata((Dictionary<string, object>)obj["metadata"]);
  56. var product = new ProductDescription(
  57. (string)obj["storeSpecificId"],
  58. metadata,
  59. obj.TryGetString("receipt"),
  60. obj.TryGetString("transactionId"),
  61. ProductType.NonConsumable);
  62. result.Add(product);
  63. }
  64. return result;
  65. }
  66. public static Dictionary<string, string> DeserializeSubscriptionDescriptions(string json)
  67. {
  68. var objects = (List<object>)MiniJson.JsonDecode(json);
  69. var result = new Dictionary<string, string>();
  70. foreach (Dictionary<string, object> obj in objects)
  71. {
  72. var subscription = new Dictionary<string, string>();
  73. if (obj.TryGetValue("metadata", out var metadata))
  74. {
  75. var metadataDict = (Dictionary<string, object>)metadata;
  76. subscription["introductoryPrice"] = metadataDict.TryGetString("introductoryPrice");
  77. subscription["introductoryPriceLocale"] = metadataDict.TryGetString("introductoryPriceLocale");
  78. subscription["introductoryPriceNumberOfPeriods"] = metadataDict.TryGetString("introductoryPriceNumberOfPeriods");
  79. subscription["numberOfUnits"] = metadataDict.TryGetString("numberOfUnits");
  80. subscription["unit"] = metadataDict.TryGetString("unit");
  81. // this is a double check for Apple side's bug
  82. if (!string.IsNullOrEmpty(subscription["numberOfUnits"]) && string.IsNullOrEmpty(subscription["unit"]))
  83. {
  84. subscription["unit"] = "0";
  85. }
  86. }
  87. else
  88. {
  89. Debug.LogWarning("metadata key not found in subscription description json");
  90. }
  91. if (obj.TryGetValue("storeSpecificId", out var id))
  92. {
  93. var idStr = (string)id;
  94. result.Add(idStr, MiniJson.JsonEncode(subscription));
  95. }
  96. else
  97. {
  98. Debug.LogWarning("storeSpecificId key not found in subscription description json");
  99. }
  100. }
  101. return result;
  102. }
  103. public static Dictionary<string, string> DeserializeProductDetails(string json)
  104. {
  105. var objects = (List<object>)MiniJson.JsonDecode(json);
  106. var result = new Dictionary<string, string>();
  107. foreach (Dictionary<string, object> obj in objects)
  108. {
  109. var details = new Dictionary<string, string>();
  110. if (obj.TryGetValue("metadata", out var metadata))
  111. {
  112. var metadataStr = (Dictionary<string, object>)metadata;
  113. details["subscriptionNumberOfUnits"] = metadataStr.TryGetString("subscriptionNumberOfUnits");
  114. details["subscriptionPeriodUnit"] = metadataStr.TryGetString("subscriptionPeriodUnit");
  115. details["localizedPrice"] = metadataStr.TryGetString("localizedPrice");
  116. details["isoCurrencyCode"] = metadataStr.TryGetString("isoCurrencyCode");
  117. details["localizedPriceString"] = metadataStr.TryGetString("localizedPriceString");
  118. details["localizedTitle"] = metadataStr.TryGetString("localizedTitle");
  119. details["localizedDescription"] = metadataStr.TryGetString("localizedDescription");
  120. details["introductoryPrice"] = metadataStr.TryGetString("introductoryPrice");
  121. details["introductoryPriceLocale"] = metadataStr.TryGetString("introductoryPriceLocale");
  122. details["introductoryPriceNumberOfPeriods"] = metadataStr.TryGetString("introductoryPriceNumberOfPeriods");
  123. details["numberOfUnits"] = metadataStr.TryGetString("numberOfUnits");
  124. details["unit"] = metadataStr.TryGetString("unit");
  125. // this is a double check for Apple side's bug
  126. if (!string.IsNullOrEmpty(details["subscriptionNumberOfUnits"]) && string.IsNullOrEmpty(details["subscriptionPeriodUnit"]))
  127. {
  128. details["subscriptionPeriodUnit"] = "0";
  129. }
  130. // this is a double check for Apple side's bug
  131. if (!string.IsNullOrEmpty(details["numberOfUnits"]) && string.IsNullOrEmpty(details["unit"]))
  132. {
  133. details["unit"] = "0";
  134. }
  135. }
  136. else
  137. {
  138. Debug.LogWarning("metadata key not found in product details json");
  139. }
  140. if (obj.TryGetValue("storeSpecificId", out var id))
  141. {
  142. var idStr = (string)id;
  143. result.Add(idStr, MiniJson.JsonEncode(details));
  144. }
  145. else
  146. {
  147. Debug.LogWarning("storeSpecificId key not found in product details json");
  148. }
  149. }
  150. return result;
  151. }
  152. public static PurchaseFailureDescription DeserializeFailureReason(string json)
  153. {
  154. var dic = (Dictionary<string, object>)MiniJson.JsonDecode(json);
  155. var reason = PurchaseFailureReason.Unknown;
  156. if (dic.TryGetValue("reason", out var reasonStr))
  157. {
  158. if (Enum.IsDefined(typeof(PurchaseFailureReason), (string)reasonStr))
  159. {
  160. reason = (PurchaseFailureReason)Enum.Parse(typeof(PurchaseFailureReason), (string)reasonStr);
  161. }
  162. if (dic.TryGetValue("productId", out var productId))
  163. {
  164. return new PurchaseFailureDescription((string)productId, reason, dic.TryGetString("message"));
  165. }
  166. }
  167. else
  168. {
  169. Debug.LogWarning("Reason key not found in purchase failure json: " + json);
  170. }
  171. return new PurchaseFailureDescription("Unknown ProductID", reason, dic.TryGetString("message"));
  172. }
  173. private static ProductMetadata DeserializeMetadata(Dictionary<string, object> data)
  174. {
  175. // We are seeing an occasional exception when converting a string to a decimal here. It may be related to
  176. // a mono bug with certain cultures' number formatters: https://bugzilla.xamarin.com/show_bug.cgi?id=4814
  177. //
  178. // It's not a great idea to set the price to 0 when this happens, but it's probably better than throwing
  179. // an exception. The best solution is to pass a number for localizedPrice when possible, to avoid any string
  180. // parsing issues.
  181. decimal localizedPrice = 0.0m;
  182. try
  183. {
  184. localizedPrice = Convert.ToDecimal(data["localizedPrice"]);
  185. }
  186. catch
  187. {
  188. localizedPrice = 0.0m;
  189. }
  190. return new ProductMetadata(
  191. data.TryGetString("localizedPriceString"),
  192. data.TryGetString("localizedTitle"),
  193. data.TryGetString("localizedDescription"),
  194. data.TryGetString("isoCurrencyCode"),
  195. localizedPrice);
  196. }
  197. private static Dictionary<string, object> EncodeProductDef(ProductDefinition product)
  198. {
  199. var prod = new Dictionary<string, object>
  200. {
  201. {"id", product.id}, {"storeSpecificId", product.storeSpecificId}, {"type", product.type.ToString()}
  202. };
  203. bool enabled = true;
  204. var enabledProp = typeof(ProductDefinition).GetProperty("enabled");
  205. if (enabledProp != null)
  206. {
  207. try
  208. {
  209. enabled = Convert.ToBoolean(enabledProp.GetValue(product, null));
  210. }
  211. catch
  212. {
  213. enabled = true;
  214. }
  215. }
  216. prod.Add("enabled", enabled);
  217. var payoutsArray = new List<object>();
  218. var payoutsProp = typeof(ProductDefinition).GetProperty("payouts");
  219. if (payoutsProp != null)
  220. {
  221. var payoutsObject = payoutsProp.GetValue(product, null);
  222. Array payouts = payoutsObject as Array;
  223. if (payouts != null)
  224. {
  225. foreach (object payout in payouts)
  226. {
  227. var payoutDict = new Dictionary<string, object>();
  228. var payoutType = payout.GetType();
  229. payoutDict["t"] = payoutType.GetField("typeString").GetValue(payout);
  230. payoutDict["st"] = payoutType.GetField("subtype").GetValue(payout);
  231. payoutDict["q"] = payoutType.GetField("quantity").GetValue(payout);
  232. payoutDict["d"] = payoutType.GetField("data").GetValue(payout);
  233. payoutsArray.Add(payoutDict);
  234. }
  235. }
  236. }
  237. prod.Add("payouts", payoutsArray);
  238. return prod;
  239. }
  240. private static Dictionary<string, object> EncodeProductDesc(ProductDescription product)
  241. {
  242. var prod = new Dictionary<string, object> { { "storeSpecificId", product.storeSpecificId } };
  243. // ProductDescription.type field available in Unity 5.4+. Access by reflection here.
  244. Type pdClassType = typeof(ProductDescription);
  245. FieldInfo pdClassFieldType = pdClassType.GetField("type");
  246. if (pdClassFieldType != null)
  247. {
  248. var typeValue = pdClassFieldType.GetValue(product);
  249. prod.Add("type", typeValue.ToString());
  250. }
  251. prod.Add("metadata", EncodeProductMeta(product.metadata));
  252. prod.Add("receipt", product.receipt);
  253. prod.Add("transactionId", product.transactionId);
  254. return prod;
  255. }
  256. private static Dictionary<string, object> EncodeProductMeta(ProductMetadata product)
  257. {
  258. var prod = new Dictionary<string, object>
  259. {
  260. {"localizedPriceString", product.localizedPriceString},
  261. {"localizedTitle", product.localizedTitle},
  262. {"localizedDescription", product.localizedDescription},
  263. {"isoCurrencyCode", product.isoCurrencyCode},
  264. {"localizedPrice", Convert.ToDouble(product.localizedPrice)}
  265. };
  266. return prod;
  267. }
  268. }
  269. }