diff --git a/src/Stripe.net/Entities/_base/StripeEntity.cs b/src/Stripe.net/Entities/_base/StripeEntity.cs index 4acfe4ed2d..ab80067180 100644 --- a/src/Stripe.net/Entities/_base/StripeEntity.cs +++ b/src/Stripe.net/Entities/_base/StripeEntity.cs @@ -26,7 +26,7 @@ public static IHasObject FromJson(string value) /// The object to deserialize. /// The deserialized Stripe object from the JSON string. public static T FromJson(string value) - where T : StripeEntity + where T : IStripeEntity { return JsonConvert.DeserializeObject(value, StripeConfiguration.SerializerSettings); } diff --git a/src/Stripe.net/Infrastructure/Client.cs b/src/Stripe.net/Infrastructure/Client.cs deleted file mode 100644 index 635cb146f6..0000000000 --- a/src/Stripe.net/Infrastructure/Client.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace Stripe.Infrastructure -{ - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Reflection; - using Newtonsoft.Json; -#if NET45 - using Microsoft.Win32; -#else - using System.Runtime.InteropServices; -#endif - - internal class Client - { - public Client(HttpRequestMessage requestMessage) - { - this.RequestMessage = requestMessage; - } - - private HttpRequestMessage RequestMessage { get; set; } - - public void ApplyUserAgent() - { - this.RequestMessage.Headers.UserAgent.ParseAdd($"Stripe/v1 .NetBindings/{StripeConfiguration.StripeNetVersion}"); - } - - public void ApplyClientData() - { - this.RequestMessage.Headers.Add("X-Stripe-Client-User-Agent", this.GetClientUserAgentString()); - } - -#if NET45 - private static string GetMonoVersion() - { - Type monoRuntimeType = typeof(object).Assembly.GetType("Mono.Runtime"); - - if (monoRuntimeType != null) - { - MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod( - "GetDisplayName", - BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding, - null, - Type.EmptyTypes, - null); - - if (getDisplayNameMethod != null) - { - return (string)getDisplayNameMethod.Invoke(null, null); - } - } - - return null; - } -#endif - - private string GetClientUserAgentString() - { - var values = new Dictionary - { - { "bindings_version", StripeConfiguration.StripeNetVersion }, - { "lang", ".net" }, - { "publisher", "stripe" }, - }; - -#if NET45 - values.Add("lang_version", ".NET Framework 4.5+"); - values.Add("os_version", Environment.OSVersion.ToString()); - - string monoVersion = Client.GetMonoVersion(); - if (monoVersion != null) - { - values.Add("mono_version", monoVersion); - } -#else - values.Add("lang_version", RuntimeInformation.FrameworkDescription); - values.Add("os_version", RuntimeInformation.OSDescription); -#endif - - return JsonConvert.SerializeObject(values, Formatting.None); - } - } -} diff --git a/src/Stripe.net/Infrastructure/Extensions/ServiceExtensions.cs b/src/Stripe.net/Infrastructure/Extensions/ServiceExtensions.cs index 559c320333..e8c64d8130 100644 --- a/src/Stripe.net/Infrastructure/Extensions/ServiceExtensions.cs +++ b/src/Stripe.net/Infrastructure/Extensions/ServiceExtensions.cs @@ -8,28 +8,6 @@ namespace Stripe.Infrastructure.Extensions internal static class ServiceExtensions { - /// Creates the full URL for a request. - /// Entity type returned by the service. - /// The service sending the request. - /// The request parameters. - /// The base URL for the request. - /// Whether the request is a list request or not. - /// The full URL for the request. - public static string ApplyAllParameters(this Service service, BaseOptions options, string baseUrl, bool isListMethod = false) - where T : IStripeEntity - { - var expansions = service.Expansions(isListMethod); - if (options != null) - { - expansions.AddRange(options.Expand); - } - - return FormEncoder.AppendQueries( - baseUrl, - options?.ToQueryString(includeExtraParams: true, includeExpandParams: false), - FormEncoder.EncodeList(expansions, "expand")); - } - /// /// Returns the list of attributes to expand in requests sent by the service. /// diff --git a/src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs b/src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs index 2926741b6c..20f9cd205d 100644 --- a/src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs +++ b/src/Stripe.net/Infrastructure/FormEncoding/FormEncoder.cs @@ -4,10 +4,13 @@ namespace Stripe.Infrastructure.FormEncoding using System.Collections; using System.Collections.Generic; using System.Globalization; + using System.IO; using System.Linq; using System.Net; + using System.Net.Http; using System.Reflection; using Newtonsoft.Json; + using Stripe.Infrastructure.Http; /// /// This class provides methods to serialize various objects with @@ -24,6 +27,60 @@ public static string EncodeOptions(BaseOptions options) return EncodeValue(options, null); } + public static HttpContent EncodeOptionsContent(BaseOptions options) + { + if (options is MultipartOptions multipartOptions) + { + return EncodeOptionsMultipart(multipartOptions); + } + + return new FormUrlEncodedUTF8Content(options); + } + + public static MultipartFormDataContent EncodeOptionsMultipart(MultipartOptions options) + { + var content = new MultipartFormDataContent(); + + foreach (var property in options.GetType().GetRuntimeProperties()) + { + // Skip properties not annotated with `[JsonProperty]` + var attribute = property.GetCustomAttribute(); + if (attribute == null) + { + continue; + } + + var key = attribute.PropertyName; + var value = property.GetValue(options); + + if (value is Stream stream) + { + string fileName = "blob"; +#if NET45 || NETSTANDARD2_0 + FileStream fileStream = stream as FileStream; + if ((fileStream != null) && (!string.IsNullOrEmpty(fileStream.Name))) + { + fileName = fileStream.Name; + } +#endif + var streamContent = new StreamContent(stream); + + content.Add(streamContent, key, fileName); + } + else + { + var flatParams = FlattenParamsValue(value, key); + + foreach (var flatParam in flatParams) + { + content.Add(new StringContent(flatParam.Value), flatParam.Key); + } + } + } + + return content; + } + /// Creates the HTTP query string for a given dictionary. /// The dictionary for which to create the query string. /// The query string. @@ -60,22 +117,6 @@ public static string JoinQueries(params string[] queries) return string.Join("&", queries.Where(q => !string.IsNullOrEmpty(q))); } - /// Append one or more query strings to a URL. - /// The base URL. - /// One or more query strings to be appended to the URL. - /// The full URL with all the query strings. - public static string AppendQueries(string url, params string[] queries) - { - var fullQuery = JoinQueries(queries); - - if (!string.IsNullOrEmpty(fullQuery)) - { - url += "?" + fullQuery; - } - - return url; - } - /// Creates the HTTP query string for a given value. /// The value to encode. /// diff --git a/src/Stripe.net/Infrastructure/Http/FormUrlEncodedUTF8Content.cs b/src/Stripe.net/Infrastructure/Http/FormUrlEncodedUTF8Content.cs new file mode 100644 index 0000000000..1e9d92b1f5 --- /dev/null +++ b/src/Stripe.net/Infrastructure/Http/FormUrlEncodedUTF8Content.cs @@ -0,0 +1,27 @@ +namespace Stripe.Infrastructure.Http +{ + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using Stripe.Infrastructure.Extensions; + + public class FormUrlEncodedUTF8Content : ByteArrayContent + { + public FormUrlEncodedUTF8Content(BaseOptions options) + : base(GetContentByteArray(options)) + { + this.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + this.Headers.ContentType.CharSet = "utf-8"; + } + + private static byte[] GetContentByteArray(BaseOptions options) + { + if (options == null) + { + return new byte[0]; + } + + return Encoding.UTF8.GetBytes(options.ToQueryString()); + } + } +} diff --git a/src/Stripe.net/Infrastructure/Http/HttpClient.cs b/src/Stripe.net/Infrastructure/Http/HttpClient.cs new file mode 100644 index 0000000000..018a78d67b --- /dev/null +++ b/src/Stripe.net/Infrastructure/Http/HttpClient.cs @@ -0,0 +1,25 @@ +namespace Stripe.Infrastructure.Http +{ + using System.Threading; + using System.Threading.Tasks; + + /// + /// Abstract base class for HTTP clients used to make requests to Stripe's API. + /// + public abstract class HttpClient + { + /// The last request made by this client. + public Request LastRequest { get; protected set; } + + /// The last response received by this client. + public Response LastResponse { get; protected set; } + + /// Sends a request to Stripe's API as an asynchronous operation. + /// The parameters of the request to send. + /// The cancellation token to cancel operation. + /// The task object representing the asynchronous operation. + public abstract Task MakeRequestAsync( + Request request, + CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/Stripe.net/Infrastructure/Http/Request.cs b/src/Stripe.net/Infrastructure/Http/Request.cs new file mode 100644 index 0000000000..8b9b494b8f --- /dev/null +++ b/src/Stripe.net/Infrastructure/Http/Request.cs @@ -0,0 +1,119 @@ +namespace Stripe.Infrastructure.Http +{ + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Text; + using Stripe.Infrastructure.Extensions; + using Stripe.Infrastructure.FormEncoding; + + /// + /// Represents a request to Stripe's API. + /// + public class Request + { + /// Initializes a new instance of the class. + /// The HTTP method. + /// The path of the request. + /// The parameters of the request. + /// The special modifiers of the request. + public Request( + HttpMethod method, + string path, + BaseOptions options, + RequestOptions requestOptions) + { + this.Method = method; + + this.Uri = BuildUri(method, path, options, requestOptions); + + this.AuthorizationHeader = new AuthenticationHeaderValue( + "Bearer", + requestOptions?.ApiKey ?? StripeConfiguration.ApiKey); + + this.StripeHeaders = BuildStripeHeaders(requestOptions); + + this.Content = BuildContent(method, options); + } + + /// The HTTP method for the request (GET, POST or DELETE). + public HttpMethod Method { get; } + + /// + /// The URL for the request. If this is a GET or DELETE request, the URL also includes + /// the parameters in the query string. + /// + public Uri Uri { get; } + + /// The value of the Authorization header with the API key. + public AuthenticationHeaderValue AuthorizationHeader { get; } + + /// + /// Dictionary containing Stripe custom headers (Stripe-Version, + /// Stripe-Account, Idempotency-Key...). + /// + public Dictionary StripeHeaders { get; } + + /// + /// The body of the request. For POST requests, this will be either a + /// application/x-www-form-urlencoded or a multipart/form-data encoded + /// payload. For non-POST requests, this will be null. + /// + public HttpContent Content { get; } + + private static Uri BuildUri( + HttpMethod method, + string path, + BaseOptions options, + RequestOptions requestOptions) + { + var b = new StringBuilder(); + + b.Append(requestOptions?.BaseUrl ?? StripeConfiguration.ApiBase); + b.Append(path); + + if (method != HttpMethod.Post) + { + var queryString = options?.ToQueryString(); + if (!string.IsNullOrEmpty(queryString)) + { + b.Append("?"); + b.Append(queryString); + } + } + + return new Uri(b.ToString()); + } + + private static Dictionary BuildStripeHeaders(RequestOptions requestOptions) + { + var stripeHeaders = new Dictionary + { + { "Stripe-Version", requestOptions?.StripeVersion ?? StripeConfiguration.ApiVersion }, + }; + + if (!string.IsNullOrEmpty(requestOptions?.StripeConnectAccountId)) + { + stripeHeaders.Add("Stripe-Account", requestOptions.StripeConnectAccountId); + } + + if (!string.IsNullOrEmpty(requestOptions?.IdempotencyKey)) + { + stripeHeaders.Add("Idempotency-Key", requestOptions.IdempotencyKey); + } + + return stripeHeaders; + } + + private static HttpContent BuildContent(HttpMethod method, BaseOptions options) + { + if (method != HttpMethod.Post) + { + return null; + } + + return FormEncoder.EncodeOptionsContent(options); + } + } +} diff --git a/src/Stripe.net/Infrastructure/Http/Response.cs b/src/Stripe.net/Infrastructure/Http/Response.cs new file mode 100644 index 0000000000..7b7a82f5dd --- /dev/null +++ b/src/Stripe.net/Infrastructure/Http/Response.cs @@ -0,0 +1,31 @@ +namespace Stripe.Infrastructure.Http +{ + using System.Net; + using System.Net.Http.Headers; + + /// + /// Represents a response from Stripe's API. + /// + public class Response + { + /// Initializes a new instance of the class. + /// The HTTP status code. + /// The HTTP headers of the response. + /// The body of the response. + public Response(HttpStatusCode statusCode, HttpHeaders headers, string content) + { + this.StatusCode = statusCode; + this.Headers = headers; + this.Content = content; + } + + /// The HTTP status code of the response. + public HttpStatusCode StatusCode { get; } + + /// The HTTP headers of the response. + public HttpHeaders Headers { get; } + + /// The body of the response. + public string Content { get; } + } +} diff --git a/src/Stripe.net/Infrastructure/Http/SystemNetHttpClient.cs b/src/Stripe.net/Infrastructure/Http/SystemNetHttpClient.cs new file mode 100644 index 0000000000..2cc3bc2f02 --- /dev/null +++ b/src/Stripe.net/Infrastructure/Http/SystemNetHttpClient.cs @@ -0,0 +1,123 @@ +namespace Stripe.Infrastructure.Http +{ + using System.Collections.Generic; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using Newtonsoft.Json; + + /// + /// Standard client to make requests to Stripe's API, using + /// to send HTTP requests. + /// + public class SystemNetHttpClient : HttpClient + { + private static readonly string UserAgentString + = $"Stripe/v1 .NetBindings/{StripeConfiguration.StripeNetVersion}"; + + private readonly System.Net.Http.HttpClient httpClient; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The client to use. If null, an HTTP + /// client will be created with default parameters. + /// + public SystemNetHttpClient(System.Net.Http.HttpClient httpClient = null) + { + this.httpClient = httpClient ?? BuildDefaultSystemNetHttpClient(); + } + + /// + /// Initializes a new instance of the class + /// with default parameters. + /// + /// The new instance of the class. + public static System.Net.Http.HttpClient BuildDefaultSystemNetHttpClient() + { + // We set the User-Agent and X-Stripe-Client-User-Agent headers in each request + // message rather than through the client's DefaultRequestHeaders because we + // want these headers to be present even when a custom HTTP client is used. + return new System.Net.Http.HttpClient() + { + Timeout = StripeConfiguration.DefaultHttpTimeout, + }; + } + + /// Sends a request to Stripe's API as an asynchronous operation. + /// The parameters of the request to send. + /// The cancellation token to cancel operation. + /// The task object representing the asynchronous operation. + public override async Task MakeRequestAsync( + Request request, + CancellationToken cancellationToken = default(CancellationToken)) + { + var httpRequest = BuildRequestMessage(request); + + this.LastRequest = request; + this.LastResponse = null; + + // TODO: telemetry + // TODO: request retries + var response = await this.httpClient.SendAsync(httpRequest, cancellationToken) + .ConfigureAwait(false); + var reader = new StreamReader( + await response.Content.ReadAsStreamAsync().ConfigureAwait(false)); + this.LastResponse = new Response( + response.StatusCode, + response.Headers, + await reader.ReadToEndAsync().ConfigureAwait(false)); + + return this.LastResponse; + } + + private static System.Net.Http.HttpRequestMessage BuildRequestMessage(Request request) + { + var requestMessage = new System.Net.Http.HttpRequestMessage(request.Method, request.Uri); + + // Standard headers + requestMessage.Headers.UserAgent.ParseAdd(UserAgentString); + requestMessage.Headers.Authorization = request.AuthorizationHeader; + + // Custom headers + requestMessage.Headers.Add( + "X-Stripe-Client-User-Agent", + BuildStripeClientUserAgentString()); + foreach (var header in request.StripeHeaders) + { + requestMessage.Headers.Add(header.Key, header.Value); + } + + // Request body + if (request.Content != null) + { + requestMessage.Content = request.Content; + } + + return requestMessage; + } + + private static string BuildStripeClientUserAgentString() + { + var values = new Dictionary + { + { "bindings_version", StripeConfiguration.StripeNetVersion }, + { "lang", ".net" }, + { "publisher", "stripe" }, + { "lang_version", RuntimeInformation.GetLanguageVersion() }, + { "os_version", RuntimeInformation.GetOSVersion() }, + }; + +#if NET45 + string monoVersion = RuntimeInformation.GetMonoVersion(); + if (!string.IsNullOrEmpty(monoVersion)) + { + values.Add("mono_version", monoVersion); + } +#endif + + return JsonConvert.SerializeObject(values, Formatting.None); + } + } +} diff --git a/src/Stripe.net/Infrastructure/IStripeClient.cs b/src/Stripe.net/Infrastructure/IStripeClient.cs new file mode 100644 index 0000000000..e8192ab978 --- /dev/null +++ b/src/Stripe.net/Infrastructure/IStripeClient.cs @@ -0,0 +1,25 @@ +namespace Stripe.Infrastructure +{ + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + public interface IStripeClient + { + /// Sends a request to Stripe's API as an asynchronous operation. + /// Type of the Stripe entity returned by the API. + /// The HTTP method. + /// The path of the request. + /// The parameters of the request. + /// The special modifiers of the request. + /// The cancellation token to cancel operation. + /// The task object representing the asynchronous operation. + Task RequestAsync( + HttpMethod method, + string path, + BaseOptions options, + RequestOptions requestOptions, + CancellationToken cancellationToken = default(CancellationToken)) + where T : IStripeEntity; + } +} diff --git a/src/Stripe.net/Infrastructure/Public/Mapper.cs b/src/Stripe.net/Infrastructure/Public/Mapper.cs deleted file mode 100644 index 1d94a204d9..0000000000 --- a/src/Stripe.net/Infrastructure/Public/Mapper.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Stripe -{ - using System.Reflection; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - public static class Mapper - { - // the ResponseJson on a list method is the entire list (as json) returned from stripe. - // the ObjectJson is so we can store only the json for a single object in the list on that entity for - // logging and/or debugging - public static T MapFromJson(string json, string parentToken = null, StripeResponse stripeResponse = null) - { - var jsonToParse = string.IsNullOrEmpty(parentToken) ? json : JObject.Parse(json).SelectToken(parentToken).ToString(); - - var result = JsonConvert.DeserializeObject(jsonToParse, StripeConfiguration.SerializerSettings); - - // if necessary, we might need to apply the stripe response to nested properties for StripeList - ApplyStripeResponse(json, stripeResponse, result); - - return result; - } - - public static T MapFromJson(StripeResponse stripeResponse, string parentToken = null) - { - return MapFromJson(stripeResponse.ResponseJson, parentToken, stripeResponse); - } - - private static void ApplyStripeResponse(string json, StripeResponse stripeResponse, object obj) - { - if (stripeResponse == null) - { - return; - } - - foreach (var property in obj.GetType().GetRuntimeProperties()) - { - if (property.Name == nameof(StripeResponse)) - { - property.SetValue(obj, stripeResponse); - } - } - - stripeResponse.ObjectJson = json; - } - } -} diff --git a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs index 9397a4c20d..bbcb9d1cde 100644 --- a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs +++ b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs @@ -2,7 +2,6 @@ namespace Stripe { using System; using System.Collections.Generic; - using System.Net.Http; using System.Reflection; using Newtonsoft.Json; using Stripe.Infrastructure; @@ -14,9 +13,11 @@ public static class StripeConfiguration { private static string apiKey; + private static IStripeClient stripeClient; + static StripeConfiguration() { - StripeNetVersion = new AssemblyName(typeof(Requestor).GetTypeInfo().Assembly.FullName).Version.ToString(3); + StripeNetVersion = new AssemblyName(typeof(StripeConfiguration).GetTypeInfo().Assembly.FullName).Version.ToString(3); } /// API version used by Stripe.net. @@ -71,12 +72,6 @@ public static string ApiKey /// Gets or sets the base URL for Stripe's Files API. public static string FilesBase { get; set; } = DefaultFilesBase; - /// Gets or sets a custom . - public static HttpMessageHandler HttpMessageHandler { get; set; } - - /// Gets or sets the timespan to wait before the request times out. - public static TimeSpan HttpTimeout { get; set; } = DefaultHttpTimeout; - /// /// Gets or sets the settings used for deserializing JSON objects returned by Stripe's API. /// It is highly recommended you do not change these settings, as doing so can produce @@ -87,22 +82,41 @@ public static string ApiKey /// public static JsonSerializerSettings SerializerSettings { get; set; } = DefaultSerializerSettings(); - /// Gets the version of the Stripe.net client library. - public static string StripeNetVersion { get; } - /// - /// Gets or sets the timespan to wait before the request times out. - /// This property is deprecated and will be removed in a future version, please use the - /// property instead. + /// Gets or sets a custom for sending requests to Stripe's + /// API. You can use this to use a custom message handler, set proxy parameters, etc. /// - // TODO: remove this property in a future major version - [Obsolete("Use StripeConfiguration.HttpTimeout instead.")] - public static TimeSpan? HttpTimeSpan + /// + /// To use a custom message handler: + /// + /// System.Net.Http.HttpMessageHandler messageHandler = ...; + /// var httpClient = new System.Net.HttpClient(messageHandler); + /// var stripeClient = new StripeClient(new Stripe.Client.SystemNetHttpClient(httpClient)); + /// StripeConfiguration.StripeClient = stripeClient; + /// + /// + // TODO fix example with final namespaces + public static IStripeClient StripeClient { - get { return HttpTimeout; } - set { HttpTimeout = value ?? DefaultHttpTimeout; } + get + { + if (stripeClient == null) + { + stripeClient = new StripeClient(); + } + + return stripeClient; + } + + set + { + stripeClient = value; + } } + /// Gets the version of the Stripe.net client library. + public static string StripeNetVersion { get; } + /// /// Returns a new instance of with /// the default settings used by Stripe.net. diff --git a/src/Stripe.net/Infrastructure/Public/StripeResponse.cs b/src/Stripe.net/Infrastructure/Public/StripeResponse.cs index ca69a43200..05851194cd 100644 --- a/src/Stripe.net/Infrastructure/Public/StripeResponse.cs +++ b/src/Stripe.net/Infrastructure/Public/StripeResponse.cs @@ -6,10 +6,17 @@ public class StripeResponse { public string ResponseJson { get; set; } - public string ObjectJson { get; set; } - public string RequestId { get; set; } public DateTime RequestDate { get; set; } + + public override string ToString() + { + return string.Format( + "{0} {{ RequestId={1}, RequestDate={2} }}", + this.GetType().FullName, + this.RequestId, + this.RequestDate.ToString("s")); + } } } diff --git a/src/Stripe.net/Infrastructure/Requestor.cs b/src/Stripe.net/Infrastructure/Requestor.cs deleted file mode 100644 index 3d0156140a..0000000000 --- a/src/Stripe.net/Infrastructure/Requestor.cs +++ /dev/null @@ -1,251 +0,0 @@ -namespace Stripe.Infrastructure -{ - using System; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - - internal static class Requestor - { - static Requestor() - { - HttpClient = - StripeConfiguration.HttpMessageHandler != null - ? new HttpClient(StripeConfiguration.HttpMessageHandler) - : new HttpClient(); - - HttpClient.Timeout = StripeConfiguration.HttpTimeout; - } - - internal static HttpClient HttpClient { get; private set; } - - public static StripeResponse GetString(string url, RequestOptions requestOptions) - { - var wr = GetRequestMessage(url, HttpMethod.Get, requestOptions); - - return ExecuteRequest(wr); - } - - public static StripeResponse PostString(string url, RequestOptions requestOptions) - { - var wr = GetRequestMessage(url, HttpMethod.Post, requestOptions); - - return ExecuteRequest(wr); - } - - public static StripeResponse Delete(string url, RequestOptions requestOptions) - { - var wr = GetRequestMessage(url, HttpMethod.Delete, requestOptions); - - return ExecuteRequest(wr); - } - - public static StripeResponse PostFile(string url, Stream stream, string purpose, RequestOptions requestOptions) - { - var wr = GetRequestMessage(url, HttpMethod.Post, requestOptions); - - ApplyMultiPartFileToRequest(wr, stream, purpose); - - return ExecuteRequest(wr); - } - - private static StripeResponse ExecuteRequest(HttpRequestMessage requestMessage) - { - var response = HttpClient.SendAsync(requestMessage).ConfigureAwait(false).GetAwaiter().GetResult(); - var responseText = response.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - - var result = BuildResponseData(response, responseText); - - if (response.IsSuccessStatusCode) - { - return result; - } - - throw BuildStripeException(result, response.StatusCode, requestMessage.RequestUri.AbsoluteUri, responseText); - } - - public static Task GetStringAsync(string url, RequestOptions requestOptions, CancellationToken cancellationToken = default(CancellationToken)) - { - var wr = GetRequestMessage(url, HttpMethod.Get, requestOptions); - - return ExecuteRequestAsync(wr, cancellationToken); - } - - public static Task PostStringAsync(string url, RequestOptions requestOptions, CancellationToken cancellationToken = default(CancellationToken)) - { - var wr = GetRequestMessage(url, HttpMethod.Post, requestOptions); - - return ExecuteRequestAsync(wr, cancellationToken); - } - - public static Task DeleteAsync(string url, RequestOptions requestOptions, CancellationToken cancellationToken = default(CancellationToken)) - { - var wr = GetRequestMessage(url, HttpMethod.Delete, requestOptions); - - return ExecuteRequestAsync(wr, cancellationToken); - } - - public static Task PostFileAsync(string url, Stream stream, string purpose, RequestOptions requestOptions, CancellationToken cancellationToken = default(CancellationToken)) - { - var wr = GetRequestMessage(url, HttpMethod.Post, requestOptions); - - ApplyMultiPartFileToRequest(wr, stream, purpose); - - return ExecuteRequestAsync(wr, cancellationToken); - } - - internal static async Task ExecuteRequestAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken = default(CancellationToken)) - { - var response = await HttpClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false); - var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - var result = BuildResponseData(response, responseText); - - if (response.IsSuccessStatusCode) - { - return result; - } - - throw BuildStripeException(result, response.StatusCode, requestMessage.RequestUri.AbsoluteUri, responseText); - } - - internal static HttpRequestMessage GetRequestMessage(string url, HttpMethod method, RequestOptions requestOptions) - { - requestOptions.ApiKey = requestOptions.ApiKey ?? StripeConfiguration.ApiKey; - -#if NET45 - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; -#endif - - var request = BuildRequest(method, url); - - request.Headers.Add( - "Authorization", - GetAuthorizationHeaderValue(requestOptions.ApiKey)); - - if (requestOptions.StripeConnectAccountId != null) - { - request.Headers.Add("Stripe-Account", requestOptions.StripeConnectAccountId); - } - - if (requestOptions.IdempotencyKey != null) - { - request.Headers.Add("Idempotency-Key", requestOptions.IdempotencyKey); - } - - if (requestOptions.StripeVersion != null) - { - request.Headers.Add("Stripe-Version", requestOptions.StripeVersion); - } - else - { - request.Headers.Add("Stripe-Version", StripeConfiguration.ApiVersion); - } - - var client = new Client(request); - client.ApplyUserAgent(); - client.ApplyClientData(); - - return request; - } - - private static HttpRequestMessage BuildRequest(HttpMethod method, string url) - { - if (method != HttpMethod.Post) - { - return new HttpRequestMessage(method, new Uri(url)); - } - - var postData = string.Empty; - var newUrl = url; - - if (!string.IsNullOrEmpty(new Uri(url).Query)) - { - postData = new Uri(url).Query.Substring(1); - newUrl = url.Substring(0, url.IndexOf("?", StringComparison.CurrentCultureIgnoreCase)); - } - - var request = new HttpRequestMessage(method, new Uri(newUrl)) - { - Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded") - }; - - return request; - } - - private static string GetAuthorizationHeaderValue(string apiKey) - { - return $"Bearer {apiKey}"; - } - - private static void ApplyMultiPartFileToRequest(HttpRequestMessage requestMessage, Stream stream, string purpose) - { - requestMessage.Headers.ExpectContinue = true; - - string fileName = "blob"; - - #if NET45 - // Doing this on .NET Standard would require us to bump the minimum framework version - // to .NET Standard 1.3, which isn't worth it since the filename is basically ignored - // by the server. - FileStream fileStream = stream as FileStream; - if ((fileStream != null) && (!string.IsNullOrEmpty(fileStream.Name))) - { - fileName = fileStream.Name; - } - #endif - - var fileContent = new StreamContent(stream); - fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") - { - Name = "\"file\"", - FileName = $"\"{fileName}\"" - }; - - fileContent.Headers.ContentType = new MediaTypeHeaderValue(MimeTypes.GetMimeType(fileName)); - - var multiPartContent = - new MultipartFormDataContent($"----------Upload:{DateTime.UtcNow.Ticks :x}") - { - { new StringContent(purpose), "\"purpose\"" }, - fileContent - }; - - requestMessage.Content = multiPartContent; - } - - private static StripeException BuildStripeException(StripeResponse response, HttpStatusCode statusCode, string requestUri, string responseContent) - { - var stripeError = requestUri.Contains("oauth") - ? Mapper.MapFromJson(responseContent, null, response) - : Mapper.MapFromJson(responseContent, "error", response); - - return new StripeException(statusCode, stripeError, stripeError.Message) - { - StripeResponse = response - }; - } - - private static StripeResponse BuildResponseData(HttpResponseMessage response, string responseText) - { - var result = new StripeResponse - { - RequestId = response.Headers.Contains("Request-Id") ? - response.Headers.GetValues("Request-Id").First() : - "n/a", - RequestDate = response.Headers.Contains("Date") ? - Convert.ToDateTime(response.Headers.GetValues("Date").First(), CultureInfo.InvariantCulture) : - default(DateTime), - ResponseJson = responseText, - }; - - return result; - } - } -} diff --git a/src/Stripe.net/Infrastructure/RuntimeInformation.cs b/src/Stripe.net/Infrastructure/RuntimeInformation.cs new file mode 100644 index 0000000000..d234dda8f0 --- /dev/null +++ b/src/Stripe.net/Infrastructure/RuntimeInformation.cs @@ -0,0 +1,53 @@ +namespace Stripe.Infrastructure +{ +#if NET45 + using System; + using System.Reflection; + using Microsoft.Win32; +#endif + + internal static class RuntimeInformation + { + public static string GetLanguageVersion() + { +#if NET45 + return ".NET Framework 4.5+"; +#else + return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; +#endif + } + + public static string GetOSVersion() + { +#if NET45 + return Environment.OSVersion.ToString(); +#else + return System.Runtime.InteropServices.RuntimeInformation.OSDescription; +#endif + } + +#if NET45 + public static string GetMonoVersion() + { + Type monoRuntimeType = typeof(object).Assembly.GetType("Mono.Runtime"); + + if (monoRuntimeType != null) + { + MethodInfo getDisplayNameMethod = monoRuntimeType.GetMethod( + "GetDisplayName", + BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.ExactBinding, + null, + Type.EmptyTypes, + null); + + if (getDisplayNameMethod != null) + { + return (string)getDisplayNameMethod.Invoke(null, null); + } + } + + return null; + } +#endif + } +} diff --git a/src/Stripe.net/Infrastructure/StripeClient.cs b/src/Stripe.net/Infrastructure/StripeClient.cs new file mode 100644 index 0000000000..eae6ad5087 --- /dev/null +++ b/src/Stripe.net/Infrastructure/StripeClient.cs @@ -0,0 +1,105 @@ +namespace Stripe.Infrastructure +{ + using System; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Newtonsoft.Json.Linq; + using Stripe.Infrastructure.Http; + + public class StripeClient : IStripeClient + { + /// Initializes a new instance of the class. + /// + /// The client to use. If null, + /// an HTTP client will be created with default parameters. + /// + public StripeClient(Stripe.Infrastructure.Http.HttpClient httpClient = null) + { + this.HttpClient = httpClient ?? BuildDefaultHttpClient(); + } + + public Stripe.Infrastructure.Http.HttpClient HttpClient { get; } + + /// Sends a request to Stripe's API as an asynchronous operation. + /// Type of the Stripe entity returned by the API. + /// The HTTP method. + /// The path of the request. + /// The parameters of the request. + /// The special modifiers of the request. + /// The cancellation token to cancel operation. + /// The task object representing the asynchronous operation. + public async Task RequestAsync( + HttpMethod method, + string path, + BaseOptions options, + RequestOptions requestOptions, + CancellationToken cancellationToken = default(CancellationToken)) + where T : IStripeEntity + { + var request = new Request(method, path, options, requestOptions); + + var response = await this.HttpClient.MakeRequestAsync(request); + + return ProcessResponse(response); + } + + private static Stripe.Infrastructure.Http.HttpClient BuildDefaultHttpClient() + { + return new SystemNetHttpClient(); + } + + private static T ProcessResponse(Response response) + where T : IStripeEntity + { + var stripeResponse = BuildStripeResponse(response); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw BuildStripeException(response, stripeResponse); + } + + var obj = StripeEntity.FromJson(response.Content); + obj.StripeResponse = stripeResponse; + + return obj; + } + + private static StripeResponse BuildStripeResponse(Response response) + { + return new StripeResponse + { + RequestId = response.Headers.Contains("Request-Id") + ? response.Headers.GetValues("Request-Id").First() + : "n/a", + RequestDate = response.Headers.Contains("Date") + ? Convert.ToDateTime( + response.Headers.GetValues("Date").First(), + CultureInfo.InvariantCulture) + : default(DateTime), + ResponseJson = response.Content, + }; + } + + private static StripeException BuildStripeException( + Response response, + StripeResponse stripeResponse) + { + var stripeError = false // TODO + ? StripeError.FromJson(response.Content) + : StripeError.FromJson(JObject.Parse(response.Content)["error"].ToString()); // TODO + stripeError.StripeResponse = stripeResponse; + + return new StripeException( + response.StatusCode, + stripeError, + stripeError.Message) + { + StripeResponse = stripeResponse, + }; + } + } +} diff --git a/src/Stripe.net/Services/Files/FileCreateOptions.cs b/src/Stripe.net/Services/Files/FileCreateOptions.cs index aa36be5b43..6affcfdb43 100644 --- a/src/Stripe.net/Services/Files/FileCreateOptions.cs +++ b/src/Stripe.net/Services/Files/FileCreateOptions.cs @@ -1,10 +1,9 @@ namespace Stripe { - using System.Collections.Generic; using System.IO; using Newtonsoft.Json; - public class FileCreateOptions : BaseOptions + public class FileCreateOptions : MultipartOptions { /// /// REQUIRED. A file to upload. The file should follow the specifications of RFC 2388 diff --git a/src/Stripe.net/Services/Files/FileService.cs b/src/Stripe.net/Services/Files/FileService.cs index 20622f7dca..9fb9530212 100644 --- a/src/Stripe.net/Services/Files/FileService.cs +++ b/src/Stripe.net/Services/Files/FileService.cs @@ -25,26 +25,15 @@ public FileService(string apiKey) public virtual File Create(FileCreateOptions options, RequestOptions requestOptions = null) { requestOptions = this.SetupRequestOptions(requestOptions); - requestOptions.BaseUrl = StripeConfiguration.FilesBase; - return Mapper.MapFromJson( - Requestor.PostFile( - StripeConfiguration.FilesBase + this.ClassUrl(), - options.File, - options.Purpose, - requestOptions)); + requestOptions.BaseUrl = requestOptions.BaseUrl ?? StripeConfiguration.FilesBase; + return this.CreateEntity(options, requestOptions); } - public virtual async Task CreateAsync(FileCreateOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken)) + public virtual Task CreateAsync(FileCreateOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken)) { requestOptions = this.SetupRequestOptions(requestOptions); - requestOptions.BaseUrl = StripeConfiguration.FilesBase; - return Mapper.MapFromJson( - await Requestor.PostFileAsync( - StripeConfiguration.FilesBase + this.ClassUrl(), - options.File, - options.Purpose, - requestOptions, - cancellationToken).ConfigureAwait(false)); + requestOptions.BaseUrl = requestOptions.BaseUrl ?? StripeConfiguration.FilesBase; + return this.CreateEntityAsync(options, requestOptions, cancellationToken); } public virtual File Get(string fileId, RequestOptions requestOptions = null) diff --git a/src/Stripe.net/Services/OAuth/OAuthTokenService.cs b/src/Stripe.net/Services/OAuth/OAuthTokenService.cs index 7c00c38c05..e6f83cc31d 100644 --- a/src/Stripe.net/Services/OAuth/OAuthTokenService.cs +++ b/src/Stripe.net/Services/OAuth/OAuthTokenService.cs @@ -17,18 +17,18 @@ public OAuthTokenService(string apiKey) { } - public override string BasePath => null; + public override string BasePath => "/oauth/token"; public override string BaseUrl => StripeConfiguration.ConnectBase; public virtual OAuthToken Create(OAuthTokenCreateOptions options, RequestOptions requestOptions = null) { - return this.Request(HttpMethod.Post, "/oauth/token", options, requestOptions); + return this.CreateEntity(options, requestOptions); } public virtual Task CreateAsync(OAuthTokenCreateOptions options, RequestOptions requestOptions = null, CancellationToken cancellationToken = default(CancellationToken)) { - return this.RequestAsync(HttpMethod.Post, "/oauth/token", options, requestOptions, cancellationToken); + return this.CreateEntityAsync(options, requestOptions, cancellationToken); } public virtual OAuthDeauthorize Deauthorize(OAuthTokenDeauthorizeOptions options, RequestOptions requestOptions = null) diff --git a/src/Stripe.net/Services/_base/MultipartOptions.cs b/src/Stripe.net/Services/_base/MultipartOptions.cs new file mode 100644 index 0000000000..97bd5ac739 --- /dev/null +++ b/src/Stripe.net/Services/_base/MultipartOptions.cs @@ -0,0 +1,10 @@ +namespace Stripe +{ + /// + /// Base class for Stripe options classes for which parameters should be encoded as + /// multipart/form-data rather than the usual application/x-www-form-urlencoded. + /// + public class MultipartOptions : BaseOptions + { + } +} diff --git a/src/Stripe.net/Services/_base/Service.cs b/src/Stripe.net/Services/_base/Service.cs index 5cd46c5f3a..9da1bbba61 100644 --- a/src/Stripe.net/Services/_base/Service.cs +++ b/src/Stripe.net/Services/_base/Service.cs @@ -1,6 +1,7 @@ namespace Stripe { using System.Collections.Generic; + using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; @@ -208,13 +209,14 @@ protected async Task RequestAsync( CancellationToken cancellationToken = default(CancellationToken)) where T : IStripeEntity { + options = this.SetupOptions(options, IsStripeList()); requestOptions = this.SetupRequestOptions(requestOptions); - var url = requestOptions.BaseUrl + path; - var wr = Requestor.GetRequestMessage( - this.ApplyAllParameters(options, url, IsStripeList()), + return await StripeConfiguration.StripeClient.RequestAsync( method, - requestOptions); - return Mapper.MapFromJson(await Requestor.ExecuteRequestAsync(wr)); + path, + options, + requestOptions, + cancellationToken); } protected IEnumerable ListRequestAutoPaging( @@ -259,7 +261,7 @@ protected RequestOptions SetupRequestOptions(RequestOptions requestOptions) requestOptions = new RequestOptions(); } - if (!string.IsNullOrEmpty(this.ApiKey)) + if (requestOptions.ApiKey == null && !string.IsNullOrEmpty(this.ApiKey)) { requestOptions.ApiKey = this.ApiKey; } @@ -269,6 +271,21 @@ protected RequestOptions SetupRequestOptions(RequestOptions requestOptions) return requestOptions; } + protected BaseOptions SetupOptions(BaseOptions options, bool isListMethod) + { + var expansions = this.Expansions(isListMethod); + + if (!expansions.Any()) + { + return options; + } + + options = options ?? new BaseOptions(); + options.Expand.AddRange(expansions); + + return options; + } + protected virtual string ClassUrl() { return this.BasePath; diff --git a/src/StripeTests/Infrastructure/ClientTest.cs b/src/StripeTests/Infrastructure/ClientTest.cs deleted file mode 100644 index 91b9e877b9..0000000000 --- a/src/StripeTests/Infrastructure/ClientTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace StripeTests -{ - using System.Linq; - using System.Net.Http; - using Newtonsoft.Json.Linq; - using Stripe; - using Stripe.Infrastructure; - using Xunit; - - public class ClientTest : BaseStripeTest - { - [Fact] - public void SetsUserAgent() - { - var request = new HttpRequestMessage(); - Assert.NotNull(request); - - var client = new Client(request); - client.ApplyClientData(); - client.ApplyUserAgent(); - - var expectedUserAgent = $"Stripe/v1 .NetBindings/{StripeConfiguration.StripeNetVersion}"; - Assert.Equal(expectedUserAgent, request.Headers.UserAgent.ToString()); - Assert.NotNull(request.Headers.GetValues("X-Stripe-Client-User-Agent")); - - var userAgentJson = JToken.Parse(request.Headers.GetValues("X-Stripe-Client-User-Agent").FirstOrDefault()); - Assert.NotNull(userAgentJson); - Assert.Equal(StripeConfiguration.StripeNetVersion, userAgentJson["bindings_version"]); - Assert.Equal(".net", userAgentJson["lang"]); - Assert.Equal("stripe", userAgentJson["publisher"]); - Assert.NotNull(userAgentJson["lang_version"]); - Assert.NotNull(userAgentJson["os_version"]); - } - } -} diff --git a/src/StripeTests/Infrastructure/Extensions/ServiceExtensionsTest.cs b/src/StripeTests/Infrastructure/Extensions/ServiceExtensionsTest.cs index 6c9cf71d37..e36c79b66e 100644 --- a/src/StripeTests/Infrastructure/Extensions/ServiceExtensionsTest.cs +++ b/src/StripeTests/Infrastructure/Extensions/ServiceExtensionsTest.cs @@ -9,46 +9,31 @@ public class ServiceExtensionsTest : BaseStripeTest private readonly TestService service = new TestService(); [Fact] - public void ExpandServiceResource() + public void Expansions() { this.service.ExpandSimple = true; this.service.ExpandMultiWordProperty = true; - var url = this.service.ApplyAllParameters(null, string.Empty, false); - Assert.Equal("?expand[0]=simple&expand[1]=multi_word_property", url); + var expansions = this.service.Expansions(false); + Assert.NotNull(expansions); + Assert.Collection( + expansions, + i1 => Assert.Equal("simple", i1), + i2 => Assert.Equal("multi_word_property", i2)); } [Fact] - public void ExpandServiceList() + public void ExpansionsForListMethod() { this.service.ExpandSimple = true; this.service.ExpandMultiWordProperty = true; - var url = this.service.ApplyAllParameters(null, string.Empty, true); - Assert.Equal("?expand[0]=data.simple&expand[1]=data.multi_word_property", url); - } - - [Fact] - public void SetsUrl() - { - var url = this.service.ApplyAllParameters(null, "base_url", false); - Assert.Equal("base_url", url); - } - - [Fact] - public void ExpandViaServicePropertiesAndOptionsClass() - { - this.service.ExpandSimple = true; - this.service.ExpandMultiWordProperty = true; - - var options = new TestOptions(); - options.AddExpand("foo"); - options.AddExpand("bar.baz"); - - var url = this.service.ApplyAllParameters(options, string.Empty, false); - Assert.Equal( - "?expand[0]=simple&expand[1]=multi_word_property&expand[2]=foo&expand[3]=bar.baz", - url); + var expansions = this.service.Expansions(true); + Assert.NotNull(expansions); + Assert.Collection( + expansions, + i1 => Assert.Equal("data.simple", i1), + i2 => Assert.Equal("data.multi_word_property", i2)); } } } diff --git a/src/StripeTests/Infrastructure/MapperTest.cs b/src/StripeTests/Infrastructure/MapperTest.cs deleted file mode 100644 index 00f36fc5d5..0000000000 --- a/src/StripeTests/Infrastructure/MapperTest.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace StripeTests -{ - using Stripe; - using Xunit; - - public class MapperTest : BaseStripeTest - { - [Fact] - public void DeserializeISODates() - { - var json = GetResourceAsString("api_fixtures.customer_iso_dates.json"); - var customer = Mapper.MapFromJson(json); - - Assert.Equal("2018-01-01T12:34:56-07:00", customer.Description); - Assert.Equal("2018-02-02T12:34:56+02:00", customer.Metadata["some_iso_date"]); - Assert.Equal("2018-03-03T12:34:56+08:30", customer.Subscriptions.Data[0].Metadata["another_iso_date"]); - Assert.Equal("2018-04-04T12:34:56Z", ((Card)customer.DefaultSource).Metadata["yet_another_iso_date"]); - Assert.Equal("2018-04-04T12:34:56Z", ((Card)customer.Sources.Data[0]).Metadata["yet_another_iso_date"]); - } - } -} diff --git a/src/StripeTests/Infrastructure/RequestorTest.cs b/src/StripeTests/Infrastructure/RequestorTest.cs deleted file mode 100644 index 06f6cd520b..0000000000 --- a/src/StripeTests/Infrastructure/RequestorTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace StripeTests -{ - using System.Linq; - using System.Net.Http; - using Newtonsoft.Json.Linq; - using Stripe; - using Stripe.Infrastructure; - using Xunit; - - public class RequestorTest : BaseStripeTest - { - [Fact] - public void SetsHeaders() - { - RequestOptions options = new RequestOptions - { - ApiKey = "sk_key", - StripeConnectAccountId = "acct_123", - IdempotencyKey = "123", - }; - var request = Requestor.GetRequestMessage("http://localhost", HttpMethod.Get, options); - Assert.NotNull(request); - Assert.Equal($"Bearer {options.ApiKey}", request.Headers.GetValues("Authorization").FirstOrDefault()); - Assert.Equal(options.IdempotencyKey, request.Headers.GetValues("Idempotency-Key").FirstOrDefault()); - Assert.Equal(options.StripeConnectAccountId, request.Headers.GetValues("Stripe-Account").FirstOrDefault()); - Assert.Equal(StripeConfiguration.ApiVersion, request.Headers.GetValues("Stripe-Version").FirstOrDefault()); - } - } -} diff --git a/src/StripeTests/Infrastructure/StripeResponseTest.cs b/src/StripeTests/Infrastructure/StripeResponseTest.cs index 78f9a250b7..218ed585dc 100644 --- a/src/StripeTests/Infrastructure/StripeResponseTest.cs +++ b/src/StripeTests/Infrastructure/StripeResponseTest.cs @@ -20,7 +20,6 @@ public void Initializes() Assert.NotNull(this.charges.StripeResponse); Assert.NotNull(this.charges.StripeResponse.RequestId); Assert.NotNull(this.charges.StripeResponse.ResponseJson); - Assert.NotNull(this.charges.StripeResponse.ObjectJson); Assert.True(this.charges.StripeResponse.RequestDate.Year > 0); } } diff --git a/src/StripeTests/MockHttpClientFixture.cs b/src/StripeTests/MockHttpClientFixture.cs index c251087d11..04dd4e5767 100644 --- a/src/StripeTests/MockHttpClientFixture.cs +++ b/src/StripeTests/MockHttpClientFixture.cs @@ -8,10 +8,11 @@ namespace StripeTests using Moq; using Moq.Protected; using Stripe; + using Stripe.Infrastructure; public class MockHttpClientFixture : IDisposable { - private readonly HttpMessageHandler origHandler; + private readonly IStripeClient origClient; public MockHttpClientFixture() { @@ -19,16 +20,19 @@ public MockHttpClientFixture() { CallBase = true }; + var httpClient = new System.Net.Http.HttpClient(this.MockHandler.Object); + var stripeClient = new StripeClient( + new Stripe.Infrastructure.Http.SystemNetHttpClient(httpClient)); - this.origHandler = StripeConfiguration.HttpMessageHandler; - StripeConfiguration.HttpMessageHandler = this.MockHandler.Object; + this.origClient = StripeConfiguration.StripeClient; + StripeConfiguration.StripeClient = stripeClient; } public Mock MockHandler { get; } public void Dispose() { - StripeConfiguration.HttpMessageHandler = this.origHandler; + StripeConfiguration.StripeClient = this.origClient; } ///