설명 없음
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UdpSynchronizationApi.cs 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reflection;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using UnityEngine;
  7. using UnityEngine.Networking;
  8. using UnityEngine.Purchasing;
  9. namespace UnityEditor.Purchasing
  10. {
  11. [Obsolete("UDP support will be removed in the next major update of In-App Purchasing. Right now, the UDP SDK will still function normally in tandem with IAP.")]
  12. /// <summary>
  13. /// Synchronize store data from UDP and IAP
  14. /// </summary>
  15. public static class UdpSynchronizationApi
  16. {
  17. internal const string kOAuthClientId = "channel_editor";
  18. // Although a client secret is here, it doesn't matter
  19. // because the user information is also secured by user's token
  20. private const string kOAuthClientSecret = "B63AFB324DE3D12A13827340019D1EE3";
  21. private const string kHttpVerbGET = "GET";
  22. private const string kHttpVerbPOST = "POST";
  23. private const string kHttpVerbPUT = "PUT";
  24. private const string kContentType = "Content-Type";
  25. private const string kApplicationJson = "application/json";
  26. private const string kAuthHeader = "Authorization";
  27. private static void CheckUdpBuildConfig()
  28. {
  29. var udpBuildConfig = BuildConfigInterface.GetClassType();
  30. if (udpBuildConfig == null)
  31. {
  32. Debug.LogError("Cannot Retrieve Build Config Endpoints for UDP. Please make sure the UDP package is installed");
  33. throw new NotImplementedException();
  34. }
  35. }
  36. /// <summary>
  37. /// Get Access Token according to authCode.
  38. /// </summary>
  39. /// <param name="authCode"> Acquired by UnityOAuth</param>
  40. /// <returns></returns>
  41. [Obsolete("Internal API, it will be removed soon.")]
  42. public static object GetAccessToken(string authCode)
  43. {
  44. return CreateGetAccessTokenRequest(authCode);
  45. }
  46. /// <summary>
  47. /// Create a Web Request to get the UDP Access Token according to authCode.
  48. /// </summary>
  49. /// <param name="authCode">Acquired by UnityOAuth</param>
  50. /// <returns></returns>
  51. internal static UnityWebRequest CreateGetAccessTokenRequest(string authCode)
  52. {
  53. CheckUdpBuildConfig();
  54. var req = new TokenRequest
  55. {
  56. code = authCode,
  57. client_id = kOAuthClientId,
  58. client_secret = kOAuthClientSecret,
  59. grant_type = "authorization_code",
  60. redirect_uri = BuildConfigInterface.GetIdEndpoint()
  61. };
  62. return AsyncRequest(kHttpVerbPOST, BuildConfigInterface.GetApiEndpoint(), "/v1/oauth2/token", null, req);
  63. }
  64. /// <summary>
  65. /// Call UDP store asynchronously to retrieve the Organization Identifier.
  66. /// </summary>
  67. /// <param name="accessToken">The bearer token to UDP.</param>
  68. /// <param name="projectGuid">The project id.</param>
  69. /// <returns>The HTTP GET Request to get the organization identifier.</returns>
  70. [Obsolete("Internal API, it will be removed soon.")]
  71. public static object GetOrgId(string accessToken, string projectGuid)
  72. {
  73. return CreateGetOrgIdRequest(accessToken, projectGuid);
  74. }
  75. /// <summary>
  76. /// Call UDP store asynchronously to retrieve the Organization Identifier.
  77. /// </summary>
  78. /// <param name="accessToken">The bearer token to UDP.</param>
  79. /// <param name="projectGuid">The project id.</param>
  80. /// <returns>The HTTP GET Request to get the organization identifier.</returns>
  81. internal static UnityWebRequest CreateGetOrgIdRequest(string accessToken, string projectGuid)
  82. {
  83. CheckUdpBuildConfig();
  84. var api = "/v1/core/api/projects/" + projectGuid;
  85. return AsyncRequest(kHttpVerbGET, BuildConfigInterface.GetApiEndpoint(), api, accessToken, null);
  86. }
  87. /// <summary>
  88. /// Call UDP store asynchronously to create a store item.
  89. /// </summary>
  90. /// <param name="accessToken">The bearer token to UDP.</param>
  91. /// <param name="orgId">The organization identifier to create the store item under.</param>
  92. /// <param name="iapItem">The store item to create.</param>
  93. /// <returns>The HTTP POST Request to create a store item.</returns>
  94. [Obsolete("Internal API, it will be removed soon.")]
  95. public static object CreateStoreItem(string accessToken, string orgId, IapItem iapItem)
  96. {
  97. return CreateAddStoreItemRequest(accessToken, orgId, iapItem);
  98. }
  99. /// <summary>
  100. /// Call UDP store asynchronously to create a store item.
  101. /// </summary>
  102. /// <param name="accessToken">The bearer token to UDP.</param>
  103. /// <param name="orgId">The organization identifier to create the store item under.</param>
  104. /// <param name="iapItem">The store item to create.</param>
  105. /// <returns>The HTTP POST Request to create a store item.</returns>
  106. internal static UnityWebRequest CreateAddStoreItemRequest(string accessToken, string orgId, IapItem iapItem)
  107. {
  108. CheckUdpBuildConfig();
  109. var api = "/v1/store/items";
  110. iapItem.ownerId = orgId;
  111. return AsyncRequest(kHttpVerbPOST, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, iapItem);
  112. }
  113. /// <summary>
  114. /// Call UDP store asynchronously to update a store item.
  115. /// </summary>
  116. /// <param name="accessToken">The bearer token to UDP.</param>
  117. /// <param name="iapItem">The updated store item.</param>
  118. /// <returns>The HTTP PUT Request to update a store item.</returns>
  119. [Obsolete("Internal API, it will be removed soon.")]
  120. public static object UpdateStoreItem(string accessToken, IapItem iapItem)
  121. {
  122. return CreateUpdateStoreItemRequest(accessToken, iapItem);
  123. }
  124. /// <summary>
  125. /// Call UDP store asynchronously to update a store item.
  126. /// </summary>
  127. /// <param name="accessToken">The bearer token to UDP.</param>
  128. /// <param name="iapItem">The updated store item.</param>
  129. /// <returns>The HTTP PUT Request to update a store item.</returns>
  130. internal static UnityWebRequest CreateUpdateStoreItemRequest(string accessToken, IapItem iapItem)
  131. {
  132. CheckUdpBuildConfig();
  133. var api = "/v1/store/items/" + iapItem.id;
  134. return AsyncRequest(kHttpVerbPUT, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, iapItem);
  135. }
  136. /// <summary>
  137. /// Call UDP store asynchronously to search for a store item.
  138. /// </summary>
  139. /// <param name="accessToken">The bearer token to UDP.</param>
  140. /// <param name="orgId">The organization identifier where to find the store item.</param>
  141. /// <param name="appItemSlug">The store item slug name.</param>
  142. /// <returns>The HTTP GET Request to update a store item.</returns>
  143. [Obsolete("Internal API, it will be removed soon.")]
  144. public static object SearchStoreItem(string accessToken, string orgId, string appItemSlug)
  145. {
  146. return CreateSearchStoreItemRequest(accessToken, orgId, appItemSlug);
  147. }
  148. /// <summary>
  149. /// Call UDP store asynchronously to search for a store item.
  150. /// </summary>
  151. /// <param name="accessToken">The bearer token to UDP.</param>
  152. /// <param name="orgId">The organization identifier where to find the store item.</param>
  153. /// <param name="appItemSlug">The store item slug name.</param>
  154. /// <returns>The HTTP GET Request to update a store item.</returns>
  155. internal static UnityWebRequest CreateSearchStoreItemRequest(string accessToken, string orgId, string appItemSlug)
  156. {
  157. CheckUdpBuildConfig();
  158. var api = "/v1/store/items/search?ownerId=" + orgId +
  159. "&ownerType=ORGANIZATION&start=0&count=20&type=IAP&masterItemSlug=" + appItemSlug;
  160. return AsyncRequest(kHttpVerbGET, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, null);
  161. }
  162. // Return UnityWebRequest instance
  163. static UnityWebRequest AsyncRequest(string method, string url, string api, string token, object postObject)
  164. {
  165. var request = new UnityWebRequest(url + api, method);
  166. if (postObject != null)
  167. {
  168. var postData = HandlePostData(JsonUtility.ToJson(postObject));
  169. var postDataBytes = Encoding.UTF8.GetBytes(postData);
  170. request.uploadHandler = new UploadHandlerRaw(postDataBytes);
  171. }
  172. request.downloadHandler = new DownloadHandlerBuffer();
  173. request.SetRequestHeader(kContentType, kApplicationJson);
  174. if (token != null)
  175. {
  176. request.SetRequestHeader(kAuthHeader, "Bearer " + token);
  177. }
  178. request.SendWebRequest();
  179. return request;
  180. }
  181. internal static bool CheckUdpAvailability()
  182. {
  183. return true;
  184. }
  185. internal static bool CheckUdpCompatibility()
  186. {
  187. var udpBuildConfig = BuildConfigInterface.GetClassType();
  188. if (udpBuildConfig == null)
  189. {
  190. Debug.LogError("Cannot Retrieve Build Config Endpoints for UDP. Please make sure the UDP package is installed");
  191. return false;
  192. }
  193. var udpVersion = BuildConfigInterface.GetVersion();
  194. int.TryParse(udpVersion.Split('.')[0], out var majorVersion);
  195. return majorVersion >= 2;
  196. }
  197. // A very tricky way to deal with the json string, need to be improved
  198. // en-US and zh-CN will appear in the JSON and Unity JsonUtility cannot
  199. // recognize them to variables. So we change this to a string (remove "-").
  200. private static string HandlePostData(string oldData)
  201. {
  202. var newData = oldData.Replace("thisShouldBeENHyphenUS", "en-US");
  203. newData = newData.Replace("thisShouldBeZHHyphenCN", "zh-CN");
  204. var re = new Regex("\"\\w+?\":\"\",");
  205. newData = re.Replace(newData, "");
  206. re = new Regex(",\"\\w+?\":\"\"");
  207. newData = re.Replace(newData, "");
  208. re = new Regex("\"\\w+?\":\"\"");
  209. newData = re.Replace(newData, "");
  210. return newData;
  211. }
  212. }
  213. #region model
  214. /// <summary>
  215. /// This class is used to authenticate the API call to UDP. In OAuth2.0 authentication format.
  216. /// </summary>
  217. [Serializable]
  218. public class TokenRequest
  219. {
  220. /// <summary>
  221. /// The access token. Acquired by UnityOAuth
  222. /// </summary>
  223. public string code;
  224. /// <summary>
  225. /// The client identifier
  226. /// </summary>
  227. public string client_id;
  228. /// <summary>
  229. /// The client secret key
  230. /// </summary>
  231. public string client_secret;
  232. /// <summary>
  233. /// The type of OAuth2.0 code granting.
  234. /// </summary>
  235. public string grant_type;
  236. /// <summary>
  237. /// Redirect use after a successful authorization.
  238. /// </summary>
  239. public string redirect_uri;
  240. /// <summary>
  241. /// When the access token is expire. This token is used to renew it.
  242. /// </summary>
  243. public string refresh_token;
  244. }
  245. /// <summary>
  246. /// PriceSets holds the PurchaseFee. Used for IapItem.
  247. /// </summary>
  248. [Serializable]
  249. public class PriceSets
  250. {
  251. /// <summary>
  252. /// Get the PurchaseFee
  253. /// </summary>
  254. public PurchaseFee PurchaseFee = new PurchaseFee();
  255. }
  256. /// <summary>
  257. /// A PurchaseFee contains the PriceMap which contains the prices and currencies.
  258. /// </summary>
  259. [Serializable]
  260. public class PurchaseFee
  261. {
  262. /// <summary>
  263. /// The PurchaseFee type
  264. /// </summary>
  265. public string priceType = "CUSTOM";
  266. /// <summary>
  267. /// Holds a list of prices with their currencies
  268. /// </summary>
  269. public PriceMap priceMap = new PriceMap();
  270. }
  271. /// <summary>
  272. /// PriceMap hold a list of PriceDetail.
  273. /// </summary>
  274. [Serializable]
  275. public class PriceMap
  276. {
  277. /// <summary>
  278. /// List of prices with their currencies.
  279. /// </summary>
  280. public List<PriceDetail> DEFAULT = new List<PriceDetail>();
  281. }
  282. /// <summary>
  283. /// Price and the currency of a IAPItem.
  284. /// </summary>
  285. [Serializable]
  286. public class PriceDetail
  287. {
  288. /// <summary>
  289. /// Price of a IAPItem.
  290. /// </summary>
  291. public string price;
  292. /// <summary>
  293. /// Currency of the price.
  294. /// </summary>
  295. public string currency = "USD";
  296. }
  297. /// <summary>
  298. /// The Response from and HTTP response converted into an object.
  299. /// </summary>
  300. [Serializable]
  301. public class GeneralResponse
  302. {
  303. /// <summary>
  304. /// The body from the HTTP response.
  305. /// </summary>
  306. public string message;
  307. }
  308. /// <summary>
  309. /// The properties of a IAPItem.
  310. /// </summary>
  311. [Serializable]
  312. public class Properties
  313. {
  314. /// <summary>
  315. /// The description of a IAPItem.
  316. /// </summary>
  317. public string description;
  318. }
  319. /// <summary>
  320. /// The response used when creating/updating IAP item succeeds
  321. /// </summary>
  322. [Serializable]
  323. public class IapItemResponse : GeneralResponse
  324. {
  325. /// <summary>
  326. /// The IapItem identifier.
  327. /// </summary>
  328. public string id;
  329. }
  330. /// <summary>
  331. /// IapItem is the representation of a purchasable product from the UDP store
  332. /// </summary>
  333. [Serializable]
  334. public class IapItem
  335. {
  336. /// <summary>
  337. /// A unique identifier to identify the product.
  338. /// </summary>
  339. public string id;
  340. /// <summary>
  341. /// The product url stripped of all unsafe characters.
  342. /// </summary>
  343. public string slug;
  344. /// <summary>
  345. /// The product name.
  346. /// </summary>
  347. public string name;
  348. /// <summary>
  349. /// The organization url stripped of all unsafe characters.
  350. /// </summary>
  351. public string masterItemSlug;
  352. /// <summary>
  353. /// Is product a consumable type. If set to false it is a subscriptions.
  354. /// Consumables may be purchased more than once.
  355. /// Subscriptions have a finite window of validity.
  356. /// </summary>
  357. public bool consumable = true;
  358. /// <summary>
  359. /// The product type.
  360. /// </summary>
  361. public string type = "IAP";
  362. /// <summary>
  363. /// The product status.
  364. /// </summary>
  365. public string status = "STAGE";
  366. /// <summary>
  367. /// The organization id.
  368. /// </summary>
  369. public string ownerId;
  370. /// <summary>
  371. /// The organization type.
  372. /// </summary>
  373. public string ownerType = "ORGANIZATION";
  374. /// <summary>
  375. /// The product's prices with currencies.
  376. /// </summary>
  377. public PriceSets priceSets = new PriceSets();
  378. /// <summary>
  379. /// The properties of the product.
  380. /// </summary>
  381. public Properties properties = new Properties();
  382. /// <summary>
  383. /// Validates that the IapItem has at least the minimum amount of information set.
  384. /// </summary>
  385. /// <returns>A string error of missing information to the IapItem.</returns>
  386. public string ValidationCheck()
  387. {
  388. if (string.IsNullOrEmpty(slug))
  389. {
  390. return "Please fill in the ID";
  391. }
  392. if (string.IsNullOrEmpty(name))
  393. {
  394. return "Please fill in the title";
  395. }
  396. if (properties == null || string.IsNullOrEmpty(properties.description))
  397. {
  398. return "Please fill in the description";
  399. }
  400. return "";
  401. }
  402. }
  403. /// <summary>
  404. /// TokenInfo holds all the authentication token required to authenticate the API call.
  405. /// </summary>
  406. [Serializable]
  407. public class TokenInfo : GeneralResponse
  408. {
  409. /// <summary>
  410. /// The OAuth2.0 access token.
  411. /// </summary>
  412. public string access_token;
  413. /// <summary>
  414. /// The OAuth2.0 refresh token.
  415. /// </summary>
  416. public string refresh_token;
  417. }
  418. /// <summary>
  419. /// The response used when searching for IAP item.
  420. /// </summary>
  421. [Serializable]
  422. public class IapItemSearchResponse : GeneralResponse
  423. {
  424. /// <summary>
  425. /// The total amount of IAP item found.
  426. /// </summary>
  427. public int total;
  428. /// <summary>
  429. /// The list of IAP item found.
  430. /// </summary>
  431. public List<IapItem> results;
  432. }
  433. struct ReqStruct
  434. {
  435. public UnityWebRequest request;
  436. public GeneralResponse resp;
  437. public ProductCatalogEditor.ProductCatalogItemEditor itemEditor;
  438. public IapItem iapItem;
  439. }
  440. /// <summary>
  441. /// The response used when searching for Organization identifier.
  442. /// </summary>
  443. [Serializable]
  444. public class OrgIdResponse : GeneralResponse
  445. {
  446. /// <summary>
  447. /// The organization identifier.
  448. /// </summary>
  449. public string org_foreign_key;
  450. }
  451. /// <summary>
  452. /// The response used when searching for Organization roles.
  453. /// </summary>
  454. [Serializable]
  455. public class OrgRoleResponse : GeneralResponse
  456. {
  457. /// <summary>
  458. /// The organization roles.
  459. /// </summary>
  460. public List<string> roles;
  461. }
  462. /// <summary>
  463. /// The response used when getting an error.
  464. /// </summary>
  465. [Serializable]
  466. public class ErrorResponse : GeneralResponse
  467. {
  468. /// <summary>
  469. /// The http error code.
  470. /// </summary>
  471. public string code;
  472. /// <summary>
  473. /// The details of an error.
  474. /// </summary>
  475. public ErrorDetail[] details;
  476. }
  477. /// <summary>
  478. /// The details of an error return from the api.
  479. /// </summary>
  480. [Serializable]
  481. public class ErrorDetail
  482. {
  483. /// <summary>
  484. /// The error context where it occured.
  485. /// </summary>
  486. public string field;
  487. /// <summary>
  488. /// The error message reason.
  489. /// </summary>
  490. public string reason;
  491. }
  492. #endregion
  493. }