using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Threading.Tasks;
using WooCommerceNET.Base;
namespace WooCommerceNET
{
public class RestAPI
{
protected string wc_url = string.Empty;
protected string wc_key = "ck_fef6065e15729d76e176fe75fdada8a448f70aa5";
protected string wc_secret = "cs_d9fe6eb1d265a018ae69a22c71a362ac8c3b8045";
//private bool wc_Proxy = false;
protected bool AuthorizedHeader { get; set; }
protected Func<string, string> jsonSeFilter;
protected Func<string, string> jsonDeseFilter;
protected Action<HttpWebRequest> webRequestFilter;
protected Action<HttpWebResponse> webResponseFilter;
/// <summary>
/// For WordPress REST API with OAuth 1.0 ONLY
/// </summary>
public string oauth_token { get; set; }
/// <summary>
/// For WordPress REST API with OAuth 1.0 ONLY
/// </summary>
public string oauth_token_secret { get; set; }
public WP_JWT_Object JWT_Object { get; set; }
/// <summary>
/// Authenticate Woocommerce API with JWT when set to True
/// </summary>
public bool WCAuthWithJWT { get; set; }
/// <summary>
/// Provide a function to modify the json string before deserilizing, this is for JWT Token ONLY!
/// </summary>
public Func<string, string> JWTDeserializeFilter { get; set; }
/// <summary>
/// Provide a function to modify the HttpWebRequest object, this is for JWT Token ONLY!
/// </summary>
public Action<HttpWebRequest> JWTRequestFilter { get; set; }
/// <summary>
/// If running in Debug mode, default is False.
/// NOTE: Beware when setting Debug to True, as exceptions might contain sensetive information.
/// </summary>
public bool Debug { get; set; }
/// <summary>
/// Initialize the RestAPI object
/// </summary>
/// <param name="url">
/// WooCommerce REST API URL, e.g.: http://yourstore/wp-json/wc/v1/
/// WordPress REST API URL, e.g.: http://yourstore/wp-json/
/// </param>
/// <param name="key">WooCommerce REST API Key Or WordPress consumerKey</param>
/// <param name="secret">WooCommerce REST API Secret Or WordPress consumerSecret</param>
/// <param name="authorizedHeader">WHEN using HTTPS, do you prefer to send the Credentials in HTTP HEADER?</param>
/// <param name="jsonSerializeFilter">Provide a function to modify the json string after serilizing.</param>
/// <param name="jsonDeserializeFilter">Provide a function to modify the json string before deserilizing.</param>
/// <param name="requestFilter">Provide a function to modify the HttpWebRequest object.</param>
/// <param name="responseFilter">Provide a function to grab information from the HttpWebResponse object.</param>
public RestAPI(string url, string key, string secret, bool authorizedHeader = true,
Func<string, string> jsonSerializeFilter = null,
Func<string, string> jsonDeserializeFilter = null,
Action<HttpWebRequest> requestFilter = null,
Action<HttpWebResponse> responseFilter = null)//, bool useProxy = false)
{
if (string.IsNullOrEmpty(url))
throw new Exception("Please use a valid WooCommerce Restful API url.");
string urlLower = url.Trim().ToLower().TrimEnd('/');
if (urlLower.EndsWith("wc-api/v1") || urlLower.EndsWith("wc-api/v2") || urlLower.EndsWith("wc-api/v3"))
Version = APIVersion.Legacy;
else if (urlLower.EndsWith("wp-json/wc/v1"))
Version = APIVersion.Version1;
else if (urlLower.EndsWith("wp-json/wc/v2"))
Version = APIVersion.Version2;
else if (urlLower.EndsWith("wp-json/wc/v3"))
Version = APIVersion.Version3;
else if (urlLower.Contains("wp-json/wc-"))
Version = APIVersion.ThirdPartyPlugins;
else if (urlLower.EndsWith("wp-json/wp/v2") || urlLower.EndsWith("wp-json"))
Version = APIVersion.WordPressAPI;
else if (urlLower.EndsWith("jwt-auth/v1/token"))
{
Version = APIVersion.WordPressAPIJWT;
url = urlLower.Replace("jwt-auth/v1/token", "wp/v2");
}
else
{
Version = APIVersion.Unknown;
throw new Exception("Unknown WooCommerce Restful API version.");
}
wc_url = url + (url.EndsWith("/") ? "" : "/");
wc_key = key;
AuthorizedHeader = authorizedHeader;
//Why extra '&'? look here: https://wordpress.org/support/topic/woocommerce-rest-api-v3-problem-woocommerce_api_authentication_error/
if ((url.ToLower().Contains("wc-api/v3") || !IsLegacy) && !wc_url.StartsWith("https", StringComparison.OrdinalIgnoreCase) && !(Version == APIVersion.WordPressAPI || Version == APIVersion.WordPressAPIJWT))
wc_secret = secret + "&";
else
wc_secret = secret;
jsonSeFilter = jsonSerializeFilter;
jsonDeseFilter = jsonDeserializeFilter;
webRequestFilter = requestFilter;
webResponseFilter = responseFilter;
//wc_Proxy = useProxy;
}
public bool IsLegacy
{
get
{
return Version == APIVersion.Legacy;
}
}
public APIVersion Version { get; private set; }
public string Url { get { return wc_url; } }
/// <summary>
/// Make Restful calls
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="endpoint"></param>
/// <param name="method">HEAD, GET, POST, PUT, PATCH, DELETE</param>
/// <param name="requestBody">If your call doesn't have a body, please pass string.Empty, not null.</param>
/// <param name="parms"></param>
/// <returns>json string</returns>
public virtual async Task<string> SendHttpClientRequest<T>(string endpoint, RequestMethod method, T requestBody, Dictionary<string, string> parms = null)
{
HttpWebRequest httpWebRequest = null;
try
{
if (Version == APIVersion.WordPressAPI)
{
if (string.IsNullOrEmpty(oauth_token) || string.IsNullOrEmpty(oauth_token_secret))
throw new Exception($"oauth_token and oauth_token_secret parameters are required when using WordPress REST API.");
}
if ((Version == APIVersion.WordPressAPIJWT || WCAuthWithJWT) && JWT_Object == null)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(wc_url.Replace("wp/v2", "jwt-auth/v1/token")
.Replace("wc/v1", "jwt-auth/v1/token")
.Replace("wc/v2", "jwt-auth/v1/token")
.Replace("wc/v3", "jwt-auth/v1/token"));
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
if (JWTRequestFilter != null)
JWTRequestFilter.Invoke(request);
var buffer = Encoding.UTF8.GetBytes($"username={wc_key}&password={WebUtility.UrlEncode(wc_secret)}");
using (Stream dataStream = await request.GetRequestStreamAsync().ConfigureAwait(false))
{
dataStream.Write(buffer, 0, buffer.Length);
}
WebResponse response = await request.GetResponseAsync().ConfigureAwait(false);
Stream resStream = response.GetResponseStream();
string result = await GetStreamContent(resStream, "UTF-8").ConfigureAwait(false);
if (JWTDeserializeFilter != null)
result = JWTDeserializeFilter.Invoke(result);
JWT_Object = DeserializeJSon<WP_JWT_Object>(result);
}
if (wc_url.StartsWith("https", StringComparison.OrdinalIgnoreCase) && Version != APIVersion.WordPressAPI && Version != APIVersion.WordPressAPIJWT)
{
if (AuthorizedHeader == false)
{
if (parms == null)
parms = new Dictionary<string, string>();
if (!parms.ContainsKey("consumer_key"))
parms.Add("consumer_key", wc_key);
if (!parms.ContainsKey("consumer_secret"))
parms.Add("consumer_secret", wc_secret);
}
//Allow accessing WordPress plugin REST API with WooCommerce secret and key.
//Url should be passed to RestAPI as WooCommerce Rest API url, e.g.: https://mystore.com/wp-json/wc/v3
//Endpoint should be starting with wp-json
if (endpoint.StartsWith("wp-json"))
httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(new Uri($"https://{new Uri(wc_url).Host}"), GetOAuthEndPoint(method.ToString(), endpoint, parms)));
else
httpWebRequest = (HttpWebRequest)WebRequest.Create(wc_url + GetOAuthEndPoint(method.ToString(), endpoint, parms));
if (AuthorizedHeader == true)
{
if (WCAuthWithJWT && JWT_Object != null)
httpWebRequest.Headers["Authorization"] = "Bearer " + JWT_Object.token;
else
httpWebRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(wc_key + ":" + wc_secret));
}
}
else
{
httpWebRequest = (HttpWebRequest)WebRequest.Create(wc_url + GetOAuthEndPoint(method.ToString(), endpoint, parms));
if (Version == APIVersion.WordPressAPIJWT)
httpWebRequest.Headers["Authorization"] = "Bearer " + JWT_Object.token;
}
// start the stream immediately
httpWebRequest.Method = method.ToString();
httpWebRequest.AllowReadStreamBuffering = false;
if (webRequestFilter != null)
webRequestFilter.Invoke(httpWebRequest);
//if (wc_Proxy)
// httpWebRequest.Proxy.Credentials = CredentialCache.DefaultCredentials;
//else
// httpWebRequest.Proxy = null;
if (requestBody != null && requestBody.GetType() != typeof(string))
{
httpWebRequest.ContentType = "application/json";
var buffer = Encoding.UTF8.GetBytes(SerializeJSon(parms));
using (Stream dataStream = await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false))
{
dataStream.Write(buffer, 0, buffer.Length);
}
}
else
{
if (requestBody != null && requestBody.ToString() != string.Empty)
{
if (requestBody.ToString() == "fileupload")
{
httpWebRequest.Headers["Content-Disposition"] = $"form-data; filename=\"{parms["name"]}\"";
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
using (Stream dataStream = await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false))
{
FileStream fileStream = new FileStream(parms["path"], FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
dataStream.Write(buffer, 0, bytesRead);
}
fileStream.Close();
}
}
else
{
httpWebRequest.ContentType = "application/json";
var buffer = Encoding.UTF8.GetBytes(requestBody.ToString());
using (Stream dataStream = await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false))
{
dataStream.Write(buffer, 0, buffer.Length);
}
}
}
}
// asynchronously get a response
WebResponse wr = await httpWebRequest.GetResponseAsync().ConfigureAwait(false);
if (webResponseFilter != null)
webResponseFilter.Invoke((HttpWebResponse)wr);
return await GetStreamContent(wr.GetResponseStream(), wr.ContentType.Contains("=") ? wr.ContentType.Split('=')[1] : "UTF-8").ConfigureAwait(false);
}
catch (WebException we)
{
if (httpWebRequest != null && httpWebRequest.HaveResponse)
if (we.Response != null)
throw new WebException(await GetStreamContent(we.Response.GetResponseStream(), we.Response.ContentType.Contains("=") ? we.Response.ContentType.Split('=')[1] : "UTF-8").ConfigureAwait(false), we.InnerException, we.Status, we.Response);
else
throw we;
else
throw we;
}
catch (Exception e)
{
return e.Message;
}
}
public async Task<string> GetRestful(string endpoint, Dictionary<string, string> parms = null)
{
return await SendHttpClientRequest(endpoint.ToLower(), RequestMethod.GET, string.Empty, parms).ConfigureAwait(false);
}
public async Task<string> PostRestful(string endpoint, object jsonObject, Dictionary<string, string> parms = null)
{
return await SendHttpClientRequest(endpoint.ToLower(), RequestMethod.POST, jsonObject, parms).ConfigureAwait(false);
}
public async Task<string> PutRestful(string endpoint, object jsonObject, Dictionary<string, string> parms = null)
{
return await SendHttpClientRequest(endpoint.ToLower(), RequestMethod.PUT, jsonObject, parms).ConfigureAwait(false);
}
public async Task<string> DeleteRestful(string endpoint, Dictionary<string, string> parms = null)
{
return await SendHttpClientRequest(endpoint.ToLower(), RequestMethod.DELETE, string.Empty, parms).ConfigureAwait(false);
}
public async Task<string> DeleteRestful(string endpoint, object jsonObject, Dictionary<string, string> parms = null)
{
return await SendHttpClientRequest(endpoint.ToLower(), RequestMethod.DELETE, jsonObject, parms).ConfigureAwait(false);
}
protected string GetOAuthEndPoint(string method, string endpoint, Dictionary<string, string> parms = null)
{
if (Version == APIVersion.WordPressAPIJWT || (wc_url.StartsWith("https", StringComparison.OrdinalIgnoreCase) && Version != APIVersion.WordPressAPI))
{
if (parms == null)
return endpoint;
else
{
string requestParms = string.Empty;
foreach (var parm in parms)
requestParms += parm.Key + "=" + parm.Value + "&";
return endpoint + "?" + requestParms.TrimEnd('&');
}
}
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("oauth_consumer_key", wc_key);
if (Version == APIVersion.WordPressAPI)
dic.Add("oauth_token", oauth_token);
dic.Add("oauth_nonce", Guid.NewGuid().ToString("N"));
dic.Add("oauth_signature_method", "HMAC-SHA256");
dic.Add("oauth_timestamp", Common.GetUnixTime(false));
dic.Add("oauth_version", "1.0");
if (parms != null)
foreach (var p in parms)
dic.Add(p.Key, p.Value);
string base_request_uri = method.ToUpper() + "&" + Uri.EscapeDataString(wc_url + endpoint) + "&";
string stringToSign = string.Empty;
foreach (var parm in dic.OrderBy(x => x.Key))
stringToSign += Uri.EscapeDataString(parm.Key) + "=" + Uri.EscapeDataString(parm.Value) + "&";
base_request_uri = base_request_uri + Uri.EscapeDataString(stringToSign.TrimEnd('&'));
if (Version == APIVersion.WordPressAPI)
dic.Add("oauth_signature", Common.GetSHA256(wc_secret + "&" + oauth_token_secret, base_request_uri));
else
dic.Add("oauth_signature", Common.GetSHA256(wc_secret, base_request_uri));
string parmstr = string.Empty;
foreach (var parm in dic)
parmstr += parm.Key + "=" + Uri.EscapeDataString(parm.Value) + "&";
return endpoint + "?" + parmstr.TrimEnd('&');
}
protected async Task<string> GetStreamContent(Stream s, string charset)
{
StringBuilder sb = new StringBuilder();
byte[] Buffer = new byte[512];
int count = 0;
count = await s.ReadAsync(Buffer, 0, Buffer.Length).ConfigureAwait(false);
while (count > 0)
{
sb.Append(Encoding.GetEncoding(charset).GetString(Buffer, 0, count));
count = await s.ReadAsync(Buffer, 0, Buffer.Length).ConfigureAwait(false);
}
return sb.ToString();
}
public virtual string SerializeJSon<T>(T t)
{
DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings()
{
DateTimeFormat = new DateTimeFormat(DateTimeFormat),
UseSimpleDictionaryFormat = true
};
MemoryStream stream = new MemoryStream();
DataContractJsonSerializer ds = new DataContractJsonSerializer(t.GetType(), settings);
ds.WriteObject(stream, t);
byte[] data = stream.ToArray();
string jsonString = Encoding.UTF8.GetString(data, 0, data.Length);
if (t.GetType().GetMethod("FormatJsonS") != null)
{
jsonString = t.GetType().GetMethod("FormatJsonS").Invoke(null, new object[] { jsonString }).ToString();
}
if (IsLegacy)
if (typeof(T).IsArray)
jsonString = "{\"" + typeof(T).Name.ToLower().Replace("[]", "s") + "\":" + jsonString + "}";
else
jsonString = "{\"" + typeof(T).Name.ToLower() + "\":" + jsonString + "}";
stream.Dispose();
if (jsonSeFilter != null)
jsonString = jsonSeFilter.Invoke(jsonString);
return jsonString;
}
public virtual T DeserializeJSon<T>(string jsonString)
{
if (jsonDeseFilter != null)
jsonString = jsonDeseFilter.Invoke(jsonString);
Type dT = typeof(T);
try
{
if (dT.Name.EndsWith("List"))
dT = dT.GetTypeInfo().DeclaredProperties.First().PropertyType.GenericTypeArguments[0];
if (dT.FullName.StartsWith("System.Collections.Generic.List"))
{
dT = dT.GetProperty("Item").PropertyType;
}
if (dT.GetMethod("FormatJsonD") != null)
{
jsonString = dT.GetMethod("FormatJsonD").Invoke(null, new object[] { jsonString }).ToString();
}
DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings()
{
DateTimeFormat = new DateTimeFormat(DateTimeFormat),
UseSimpleDictionaryFormat = true
};
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T), settings);
MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonString));
T obj = (T)ser.ReadObject(stream);
stream.Dispose();
return obj;
}
catch (Exception ex)
{
if (Debug)
throw new Exception(ex.Message + Environment.NewLine + Environment.NewLine + jsonString);
else
throw ex;
}
}
public string DateTimeFormat
{
get
{
return IsLegacy ? "yyyy-MM-ddTHH:mm:ssZ" : "yyyy-MM-ddTHH:mm:ssK";
}
}
}
public class WP_JWT_Object
{
public string token { get; set; }
public string user_email { get; set; }
public string user_nicename { get; set; }
public string user_display_name { get; set; }
}
public enum RequestMethod
{
HEAD = 1,
GET = 2,
POST = 3,
PUT = 4,
PATCH = 5,
DELETE = 6
}
public enum APIVersion
{
Unknown = 0,
Legacy = 1,
Version1 = 2,
Version2 = 3,
Version3 = 4,
WordPressAPI = 90,
WordPressAPIJWT = 91,
ThirdPartyPlugins = 99
}
}
Leave a Reply