550 lines
18 KiB
C#
550 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.Purchasing;
|
|
|
|
namespace UnityEditor.Purchasing
|
|
{
|
|
/// <summary>
|
|
/// Synchronize store data from UDP and IAP
|
|
/// </summary>
|
|
public static class UdpSynchronizationApi
|
|
{
|
|
|
|
internal const string kOAuthClientId = "channel_editor";
|
|
|
|
// Although a client secret is here, it doesn't matter
|
|
// because the user information is also secured by user's token
|
|
private const string kOAuthClientSecret = "B63AFB324DE3D12A13827340019D1EE3";
|
|
|
|
private const string kHttpVerbGET = "GET";
|
|
private const string kHttpVerbPOST = "POST";
|
|
private const string kHttpVerbPUT = "PUT";
|
|
|
|
private const string kContentType = "Content-Type";
|
|
private const string kApplicationJson = "application/json";
|
|
private const string kAuthHeader = "Authorization";
|
|
|
|
private static void CheckUdpBuildConfig()
|
|
{
|
|
var udpBuildConfig = BuildConfigInterface.GetClassType();
|
|
if (udpBuildConfig == null)
|
|
{
|
|
Debug.LogError("Cannot Retrieve Build Config Endpoints for UDP. Please make sure the UDP package is installed");
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get Access Token according to authCode.
|
|
/// </summary>
|
|
/// <param name="authCode"> Acquired by UnityOAuth</param>
|
|
/// <returns></returns>
|
|
[Obsolete("Internal API, it will be removed soon.")]
|
|
public static object GetAccessToken(string authCode)
|
|
{
|
|
return CreateGetAccessTokenRequest(authCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a Web Request to get the UDP Access Token according to authCode.
|
|
/// </summary>
|
|
/// <param name="authCode">Acquired by UnityOAuth</param>
|
|
/// <returns></returns>
|
|
internal static UnityWebRequest CreateGetAccessTokenRequest(string authCode)
|
|
{
|
|
CheckUdpBuildConfig();
|
|
|
|
var req = new TokenRequest
|
|
{
|
|
code = authCode,
|
|
client_id = kOAuthClientId,
|
|
client_secret = kOAuthClientSecret,
|
|
grant_type = "authorization_code",
|
|
redirect_uri = BuildConfigInterface.GetIdEndpoint()
|
|
};
|
|
return AsyncRequest(kHttpVerbPOST, BuildConfigInterface.GetApiEndpoint(), "/v1/oauth2/token", null, req);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to retrieve the Organization Identifier.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="projectGuid">The project id.</param>
|
|
/// <returns>The HTTP GET Request to get the organization identifier.</returns>
|
|
[Obsolete("Internal API, it will be removed soon.")]
|
|
public static object GetOrgId(string accessToken, string projectGuid)
|
|
{
|
|
return CreateGetOrgIdRequest(accessToken, projectGuid);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to retrieve the Organization Identifier.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="projectGuid">The project id.</param>
|
|
/// <returns>The HTTP GET Request to get the organization identifier.</returns>
|
|
internal static UnityWebRequest CreateGetOrgIdRequest(string accessToken, string projectGuid)
|
|
{
|
|
CheckUdpBuildConfig();
|
|
|
|
var api = "/v1/core/api/projects/" + projectGuid;
|
|
return AsyncRequest(kHttpVerbGET, BuildConfigInterface.GetApiEndpoint(), api, accessToken, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to create a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="orgId">The organization identifier to create the store item under.</param>
|
|
/// <param name="iapItem">The store item to create.</param>
|
|
/// <returns>The HTTP POST Request to create a store item.</returns>
|
|
[Obsolete("Internal API, it will be removed soon.")]
|
|
public static object CreateStoreItem(string accessToken, string orgId, IapItem iapItem)
|
|
{
|
|
return CreateAddStoreItemRequest(accessToken, orgId, iapItem);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to create a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="orgId">The organization identifier to create the store item under.</param>
|
|
/// <param name="iapItem">The store item to create.</param>
|
|
/// <returns>The HTTP POST Request to create a store item.</returns>
|
|
internal static UnityWebRequest CreateAddStoreItemRequest(string accessToken, string orgId, IapItem iapItem)
|
|
{
|
|
CheckUdpBuildConfig();
|
|
|
|
var api = "/v1/store/items";
|
|
iapItem.ownerId = orgId;
|
|
return AsyncRequest(kHttpVerbPOST, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, iapItem);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to update a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="iapItem">The updated store item.</param>
|
|
/// <returns>The HTTP PUT Request to update a store item.</returns>
|
|
[Obsolete("Internal API, it will be removed soon.")]
|
|
public static object UpdateStoreItem(string accessToken, IapItem iapItem)
|
|
{
|
|
return CreateUpdateStoreItemRequest(accessToken, iapItem);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to update a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="iapItem">The updated store item.</param>
|
|
/// <returns>The HTTP PUT Request to update a store item.</returns>
|
|
internal static UnityWebRequest CreateUpdateStoreItemRequest(string accessToken, IapItem iapItem)
|
|
{
|
|
CheckUdpBuildConfig();
|
|
|
|
var api = "/v1/store/items/" + iapItem.id;
|
|
return AsyncRequest(kHttpVerbPUT, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, iapItem);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to search for a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="orgId">The organization identifier where to find the store item.</param>
|
|
/// <param name="appItemSlug">The store item slug name.</param>
|
|
/// <returns>The HTTP GET Request to update a store item.</returns>
|
|
[Obsolete("Internal API, it will be removed soon.")]
|
|
public static object SearchStoreItem(string accessToken, string orgId, string appItemSlug)
|
|
{
|
|
return CreateSearchStoreItemRequest(accessToken, orgId, appItemSlug);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call UDP store asynchronously to search for a store item.
|
|
/// </summary>
|
|
/// <param name="accessToken">The bearer token to UDP.</param>
|
|
/// <param name="orgId">The organization identifier where to find the store item.</param>
|
|
/// <param name="appItemSlug">The store item slug name.</param>
|
|
/// <returns>The HTTP GET Request to update a store item.</returns>
|
|
internal static UnityWebRequest CreateSearchStoreItemRequest(string accessToken, string orgId, string appItemSlug)
|
|
{
|
|
CheckUdpBuildConfig();
|
|
|
|
var api = "/v1/store/items/search?ownerId=" + orgId +
|
|
"&ownerType=ORGANIZATION&start=0&count=20&type=IAP&masterItemSlug=" + appItemSlug;
|
|
return AsyncRequest(kHttpVerbGET, BuildConfigInterface.GetUdpEndpoint(), api, accessToken, null);
|
|
}
|
|
|
|
// Return UnityWebRequest instance
|
|
static UnityWebRequest AsyncRequest(string method, string url, string api, string token, object postObject)
|
|
{
|
|
var request = new UnityWebRequest(url + api, method);
|
|
|
|
if (postObject != null)
|
|
{
|
|
var postData = HandlePostData(JsonUtility.ToJson(postObject));
|
|
var postDataBytes = Encoding.UTF8.GetBytes(postData);
|
|
|
|
request.uploadHandler = new UploadHandlerRaw(postDataBytes);
|
|
}
|
|
|
|
request.downloadHandler = new DownloadHandlerBuffer();
|
|
|
|
request.SetRequestHeader(kContentType, kApplicationJson);
|
|
if (token != null)
|
|
{
|
|
request.SetRequestHeader(kAuthHeader, "Bearer " + token);
|
|
}
|
|
|
|
request.SendWebRequest();
|
|
|
|
return request;
|
|
}
|
|
|
|
internal static bool CheckUdpAvailability()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
internal static bool CheckUdpCompatibility()
|
|
{
|
|
var udpBuildConfig = BuildConfigInterface.GetClassType();
|
|
if (udpBuildConfig == null)
|
|
{
|
|
Debug.LogError("Cannot Retrieve Build Config Endpoints for UDP. Please make sure the UDP package is installed");
|
|
return false;
|
|
}
|
|
|
|
var udpVersion = BuildConfigInterface.GetVersion();
|
|
int.TryParse(udpVersion.Split('.')[0], out var majorVersion);
|
|
|
|
return majorVersion >= 2;
|
|
}
|
|
|
|
// A very tricky way to deal with the json string, need to be improved
|
|
// en-US and zh-CN will appear in the JSON and Unity JsonUtility cannot
|
|
// recognize them to variables. So we change this to a string (remove "-").
|
|
private static string HandlePostData(string oldData)
|
|
{
|
|
var newData = oldData.Replace("thisShouldBeENHyphenUS", "en-US");
|
|
newData = newData.Replace("thisShouldBeZHHyphenCN", "zh-CN");
|
|
var re = new Regex("\"\\w+?\":\"\",");
|
|
newData = re.Replace(newData, "");
|
|
re = new Regex(",\"\\w+?\":\"\"");
|
|
newData = re.Replace(newData, "");
|
|
re = new Regex("\"\\w+?\":\"\"");
|
|
newData = re.Replace(newData, "");
|
|
return newData;
|
|
}
|
|
}
|
|
|
|
#region model
|
|
|
|
/// <summary>
|
|
/// This class is used to authenticate the API call to UDP. In OAuth2.0 authentication format.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class TokenRequest
|
|
{
|
|
/// <summary>
|
|
/// The access token. Acquired by UnityOAuth
|
|
/// </summary>
|
|
public string code;
|
|
/// <summary>
|
|
/// The client identifier
|
|
/// </summary>
|
|
public string client_id;
|
|
/// <summary>
|
|
/// The client secret key
|
|
/// </summary>
|
|
public string client_secret;
|
|
/// <summary>
|
|
/// The type of OAuth2.0 code granting.
|
|
/// </summary>
|
|
public string grant_type;
|
|
/// <summary>
|
|
/// Redirect use after a successful authorization.
|
|
/// </summary>
|
|
public string redirect_uri;
|
|
/// <summary>
|
|
/// When the access token is expire. This token is used to renew it.
|
|
/// </summary>
|
|
public string refresh_token;
|
|
}
|
|
|
|
/// <summary>
|
|
/// PriceSets holds the PurchaseFee. Used for IapItem.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PriceSets
|
|
{
|
|
/// <summary>
|
|
/// Get the PurchaseFee
|
|
/// </summary>
|
|
public PurchaseFee PurchaseFee = new PurchaseFee();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A PurchaseFee contains the PriceMap which contains the prices and currencies.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PurchaseFee
|
|
{
|
|
/// <summary>
|
|
/// The PurchaseFee type
|
|
/// </summary>
|
|
public string priceType = "CUSTOM";
|
|
/// <summary>
|
|
/// Holds a list of prices with their currencies
|
|
/// </summary>
|
|
public PriceMap priceMap = new PriceMap();
|
|
}
|
|
|
|
/// <summary>
|
|
/// PriceMap hold a list of PriceDetail.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PriceMap
|
|
{
|
|
/// <summary>
|
|
/// List of prices with their currencies.
|
|
/// </summary>
|
|
public List<PriceDetail> DEFAULT = new List<PriceDetail>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Price and the currency of a IAPItem.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PriceDetail
|
|
{
|
|
/// <summary>
|
|
/// Price of a IAPItem.
|
|
/// </summary>
|
|
public string price;
|
|
/// <summary>
|
|
/// Currency of the price.
|
|
/// </summary>
|
|
public string currency = "USD";
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Response from and HTTP response converted into an object.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The body from the HTTP response.
|
|
/// </summary>
|
|
public string message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The properties of a IAPItem.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class Properties
|
|
{
|
|
/// <summary>
|
|
/// The description of a IAPItem.
|
|
/// </summary>
|
|
public string description;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The response used when creating/updating IAP item succeeds
|
|
/// </summary>
|
|
[Serializable]
|
|
public class IapItemResponse : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The IapItem identifier.
|
|
/// </summary>
|
|
public string id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// IapItem is the representation of a purchasable product from the UDP store
|
|
/// </summary>
|
|
[Serializable]
|
|
public class IapItem
|
|
{
|
|
/// <summary>
|
|
/// A unique identifier to identify the product.
|
|
/// </summary>
|
|
public string id;
|
|
/// <summary>
|
|
/// The product url stripped of all unsafe characters.
|
|
/// </summary>
|
|
public string slug;
|
|
/// <summary>
|
|
/// The product name.
|
|
/// </summary>
|
|
public string name;
|
|
/// <summary>
|
|
/// The organization url stripped of all unsafe characters.
|
|
/// </summary>
|
|
public string masterItemSlug;
|
|
/// <summary>
|
|
/// Is product a consumable type. If set to false it is a subscriptions.
|
|
/// Consumables may be purchased more than once.
|
|
/// Subscriptions have a finite window of validity.
|
|
/// </summary>
|
|
public bool consumable = true;
|
|
/// <summary>
|
|
/// The product type.
|
|
/// </summary>
|
|
public string type = "IAP";
|
|
/// <summary>
|
|
/// The product status.
|
|
/// </summary>
|
|
public string status = "STAGE";
|
|
/// <summary>
|
|
/// The organization id.
|
|
/// </summary>
|
|
public string ownerId;
|
|
/// <summary>
|
|
/// The organization type.
|
|
/// </summary>
|
|
public string ownerType = "ORGANIZATION";
|
|
|
|
/// <summary>
|
|
/// The product's prices with currencies.
|
|
/// </summary>
|
|
public PriceSets priceSets = new PriceSets();
|
|
|
|
/// <summary>
|
|
/// The properties of the product.
|
|
/// </summary>
|
|
public Properties properties = new Properties();
|
|
|
|
/// <summary>
|
|
/// Validates that the IapItem has at least the minimum amount of information set.
|
|
/// </summary>
|
|
/// <returns>A string error of missing information to the IapItem.</returns>
|
|
public string ValidationCheck()
|
|
{
|
|
if (string.IsNullOrEmpty(slug))
|
|
{
|
|
return "Please fill in the ID";
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
return "Please fill in the title";
|
|
}
|
|
|
|
if (properties == null || string.IsNullOrEmpty(properties.description))
|
|
{
|
|
return "Please fill in the description";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// TokenInfo holds all the authentication token required to authenticate the API call.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class TokenInfo : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The OAuth2.0 access token.
|
|
/// </summary>
|
|
public string access_token;
|
|
/// <summary>
|
|
/// The OAuth2.0 refresh token.
|
|
/// </summary>
|
|
public string refresh_token;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The response used when searching for IAP item.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class IapItemSearchResponse : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The total amount of IAP item found.
|
|
/// </summary>
|
|
public int total;
|
|
/// <summary>
|
|
/// The list of IAP item found.
|
|
/// </summary>
|
|
public List<IapItem> results;
|
|
}
|
|
|
|
struct ReqStruct
|
|
{
|
|
public UnityWebRequest request;
|
|
public GeneralResponse resp;
|
|
public ProductCatalogEditor.ProductCatalogItemEditor itemEditor;
|
|
public IapItem iapItem;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The response used when searching for Organization identifier.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class OrgIdResponse : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The organization identifier.
|
|
/// </summary>
|
|
public string org_foreign_key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The response used when searching for Organization roles.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class OrgRoleResponse : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The organization roles.
|
|
/// </summary>
|
|
public List<string> roles;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The response used when getting an error.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ErrorResponse : GeneralResponse
|
|
{
|
|
/// <summary>
|
|
/// The http error code.
|
|
/// </summary>
|
|
public string code;
|
|
/// <summary>
|
|
/// The details of an error.
|
|
/// </summary>
|
|
public ErrorDetail[] details;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The details of an error return from the api.
|
|
/// </summary>
|
|
[Serializable]
|
|
public class ErrorDetail
|
|
{
|
|
/// <summary>
|
|
/// The error context where it occured.
|
|
/// </summary>
|
|
public string field;
|
|
/// <summary>
|
|
/// The error message reason.
|
|
/// </summary>
|
|
public string reason;
|
|
}
|
|
|
|
#endregion
|
|
}
|