diff --git a/src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs b/src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs
index e8b80f217f..c2a366da5d 100644
--- a/src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs
+++ b/src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs
@@ -35,6 +35,11 @@ internal static ApiRequestor Adapt(IStripeClient client)
return stripeClient.Requestor;
}
+ if (client is DefaultStripeClient defaultStripeClient)
+ {
+ return defaultStripeClient.Requestor;
+ }
+
return new ApiRequestorAdapter(client);
}
diff --git a/src/Stripe.net/Infrastructure/Public/DefaultStripeClient.cs b/src/Stripe.net/Infrastructure/Public/DefaultStripeClient.cs
new file mode 100644
index 0000000000..449a4b9c45
--- /dev/null
+++ b/src/Stripe.net/Infrastructure/Public/DefaultStripeClient.cs
@@ -0,0 +1,80 @@
+namespace Stripe
+{
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net.Http;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ ///
+ /// A default implementation of the interface. This is used by
+ /// StripeConfiguration to provide a default client, which is used in conjuction with Service
+ /// instances when no client is provided.
+ ///
+ internal class DefaultStripeClient : IStripeClient
+ {
+ public DefaultStripeClient(string apiKey, string clientId, IHttpClient httpClient)
+ {
+ this.Requestor = new LiveApiRequestor(
+ new StripeClientOptions
+ {
+ ApiKey = apiKey,
+ ClientId = clientId,
+ HttpClient = httpClient,
+ }, new List());
+ }
+
+ /// Gets the base URL for Stripe's API.
+ /// The base URL for Stripe's API.
+ public string ApiBase => this.Requestor?.ApiBase;
+
+ /// Gets the API key used by the client to send requests.
+ /// The API key used by the client to send requests.
+ public string ApiKey => this.Requestor?.ApiKey;
+
+ /// Gets the client ID used by the client in OAuth requests.
+ /// The client ID used by the client in OAuth requests.
+ public string ClientId => this.Requestor?.ClientId;
+
+ /// Gets the base URL for Stripe's OAuth API.
+ /// The base URL for Stripe's OAuth API.
+ public string ConnectBase => this.Requestor?.ConnectBase;
+
+ /// Gets the base URL for Stripe's Files API.
+ /// The base URL for Stripe's Files API.
+ public string FilesBase => this.Requestor?.FilesBase;
+
+ /// Gets the base URL for Stripe's Meter Events API.
+ /// The base URL for Stripe's Meter Events API.
+ public string MeterEventsBase => this.Requestor?.MeterEventsBase;
+
+ /// Gets the used to send HTTP requests.
+ /// The used to send HTTP requests.
+ public IHttpClient HttpClient => this.Requestor?.HttpClient;
+
+ internal ApiRequestor Requestor { get; }
+
+ ///
+ public async Task RequestAsync(
+ HttpMethod method,
+ string path,
+ BaseOptions options,
+ RequestOptions requestOptions,
+ CancellationToken cancellationToken = default)
+ where T : IStripeEntity
+ {
+ return await this.Requestor.RequestAsync(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ public async Task RequestStreamingAsync(
+ HttpMethod method,
+ string path,
+ BaseOptions options,
+ RequestOptions requestOptions,
+ CancellationToken cancellationToken = default)
+ {
+ return await this.Requestor.RequestStreamingAsync(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs b/src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs
index d877c8fe16..652b563171 100644
--- a/src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs
+++ b/src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs
@@ -3,6 +3,7 @@ namespace Stripe
using System;
using System.Collections.Generic;
using System.IO;
+ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
@@ -17,8 +18,9 @@ internal class LiveApiRequestor : ApiRequestor
internal static readonly List RawRequestUsage = new List { "raw_request" };
private JsonSerializerSettings jsonSerializerSettings;
private StripeClientOptions clientOptions;
+ private List defaultUsage;
- public LiveApiRequestor(StripeClientOptions options)
+ public LiveApiRequestor(StripeClientOptions options, List defaultUsage = null)
{
// Clone the object passed in, or use an empty option object if it is null
options = options?.Clone() ?? new StripeClientOptions();
@@ -41,6 +43,7 @@ public LiveApiRequestor(StripeClientOptions options)
this.ConnectBase = options.ConnectBase ?? DefaultConnectBase;
this.FilesBase = options.FilesBase ?? DefaultFilesBase;
this.MeterEventsBase = options.MeterEventsBase ?? DefaultMeterEventsBase;
+ this.defaultUsage = defaultUsage ?? new List();
this.jsonSerializerSettings = StripeConfiguration.DefaultSerializerSettings(this);
}
@@ -216,6 +219,17 @@ private StripeRequest MakeStripeRequest(
RequestOptions requestOptions,
ApiMode apiMode)
{
+ if (this.defaultUsage.Count > 0)
+ {
+ var usage = this.defaultUsage;
+ if (requestOptions?.Usage?.Count > 0)
+ {
+ usage = usage.Concat(requestOptions.Usage).ToList();
+ }
+
+ requestOptions = requestOptions.WithUsage(usage);
+ }
+
var uri = StripeRequest.BuildUri(
requestOptions?.BaseUrl ?? this.GetBaseUrl(baseAddress),
method,
@@ -297,7 +311,7 @@ public override async Task RawRequestAsync(
throw new InvalidOperationException("content is not allowed for non-POST requests.");
}
- requestOptions = requestOptions.WithUsage(RawRequestUsage);
+ requestOptions = requestOptions.WithUsage(this.defaultUsage.Concat(RawRequestUsage).ToList());
var apiMode = ApiModeUtils.GetApiMode(path);
var uri = StripeRequest.BuildUri(
requestOptions?.BaseUrl ?? this.GetBaseUrl(BaseAddress.Api),
diff --git a/src/Stripe.net/Infrastructure/Public/StripeClient.cs b/src/Stripe.net/Infrastructure/Public/StripeClient.cs
index 4251488e9e..9919800c8f 100644
--- a/src/Stripe.net/Infrastructure/Public/StripeClient.cs
+++ b/src/Stripe.net/Infrastructure/Public/StripeClient.cs
@@ -1,6 +1,7 @@
namespace Stripe
{
using System;
+ using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
@@ -13,6 +14,8 @@ namespace Stripe
///
public class StripeClient : IStripeClient
{
+ internal static readonly List StripeClientUsage = new List { "stripe_client" };
+
private JsonSerializerSettings jsonSerializerSettings;
// Fields: The beginning of the section generated from our OpenAPI spec
@@ -71,7 +74,7 @@ public StripeClient(
}
public StripeClient(StripeClientOptions options)
- : this(new LiveApiRequestor(options))
+ : this(new LiveApiRequestor(options, StripeClientUsage))
{
}
diff --git a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs
index 033b06ee56..0ac501f181 100644
--- a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs
+++ b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs
@@ -3,7 +3,6 @@ namespace Stripe
using System;
using System.Collections.Generic;
using System.Configuration;
- using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Stripe.Infrastructure;
@@ -243,7 +242,7 @@ public static void SetApiKey(string newApiKey)
ApiKey = newApiKey;
}
- private static StripeClient BuildDefaultStripeClient()
+ private static IStripeClient BuildDefaultStripeClient()
{
if (ApiKey != null && ApiKey.Length == 0)
{
@@ -268,7 +267,7 @@ private static StripeClient BuildDefaultStripeClient()
maxNetworkRetries: MaxNetworkRetries,
appInfo: AppInfo,
enableTelemetry: EnableTelemetry);
- return new StripeClient(ApiKey, ClientId, httpClient: httpClient);
+ return new DefaultStripeClient(ApiKey, ClientId, httpClient);
}
}
}
diff --git a/src/StripeTests/BaseStripeTest.cs b/src/StripeTests/BaseStripeTest.cs
index 6eccea6a96..1c12a69cbc 100644
--- a/src/StripeTests/BaseStripeTest.cs
+++ b/src/StripeTests/BaseStripeTest.cs
@@ -69,9 +69,10 @@ public BaseStripeTest(
if ((this.StripeMockFixture != null) && (this.MockHttpClientFixture != null))
{
// Set up StripeClient to use stripe-mock with the mock HTTP client
- var requestor = this.StripeMockFixture.BuildApiRequestor(this.MockHttpClientFixture.MockHandler.Object);
- this.StripeClient = new StripeClient(requestor);
- this.Requestor = requestor;
+ var clientOptions = this.StripeMockFixture.BuildStripeClientOptions(this.MockHttpClientFixture.MockHandler.Object);
+ var client = new StripeClient(clientOptions);
+ this.StripeClient = client;
+ this.Requestor = client.Requestor;
// Reset the mock before each test
this.MockHttpClientFixture.Reset();
@@ -79,22 +80,24 @@ public BaseStripeTest(
else if (this.StripeMockFixture != null)
{
// Set up StripeClient to use stripe-mock
- var requestor = this.StripeMockFixture.BuildApiRequestor();
- this.StripeClient = new StripeClient(requestor);
- this.Requestor = requestor;
+ var clientOptions = this.StripeMockFixture.BuildStripeClientOptions();
+ var client = new StripeClient(clientOptions);
+ this.StripeClient = client;
+ this.Requestor = client.Requestor;
}
else if (this.MockHttpClientFixture != null)
{
// Set up StripeClient with the mock HTTP client
var httpClient = new SystemNetHttpClient(
new HttpClient(this.MockHttpClientFixture.MockHandler.Object));
- var requestor = new LiveApiRequestor(new StripeClientOptions
+ var clientOptions = new StripeClientOptions
{
ApiKey = "sk_test_123",
HttpClient = httpClient,
- });
- this.StripeClient = new StripeClient(requestor);
- this.Requestor = requestor;
+ };
+ var client = new StripeClient(clientOptions);
+ this.StripeClient = client;
+ this.Requestor = client.Requestor;
// Reset the mock before each test
this.MockHttpClientFixture.Reset();
@@ -102,9 +105,10 @@ public BaseStripeTest(
else
{
// Use the default StripeClient
- var requestor = new LiveApiRequestor(new StripeClientOptions { ApiKey = "sk_test_123" });
- this.StripeClient = new StripeClient(requestor);
- this.Requestor = requestor;
+ var clientOptions = new StripeClientOptions { ApiKey = "sk_test_123" };
+ var client = new StripeClient(clientOptions);
+ this.StripeClient = client;
+ this.Requestor = client.Requestor;
}
}
diff --git a/src/StripeTests/Functional/TelemetryTest.cs b/src/StripeTests/Functional/TelemetryTest.cs
index 2a82cb5c66..b6c3ef014d 100644
--- a/src/StripeTests/Functional/TelemetryTest.cs
+++ b/src/StripeTests/Functional/TelemetryTest.cs
@@ -15,6 +15,8 @@ namespace StripeTests
using Stripe;
using Xunit;
+ using static TelemetryTestUtils;
+
public class TelemetryTest : BaseStripeTest
{
public TelemetryTest(MockHttpClientFixture mockHttpClientFixture)
@@ -95,7 +97,7 @@ public void TelemetryIncludesUsage()
m.Headers,
(_) => true,
(_) => true,
- (t) => t != null && t.Count == 2 && t.Contains("llama") && t.Contains("bufo"))),
+ (t) => t != null && t.Count >= 2 && t.Contains("llama") && t.Contains("bufo"))),
ItExpr.IsAny());
}
@@ -175,33 +177,6 @@ public void NoTelemetryWhenDisabled()
ItExpr.IsAny());
}
- private static bool TelemetryHeaderMatcher(
- HttpHeaders headers,
- Func requestIdMatcher,
- Func durationMatcher,
- Func, bool> usageMatcher)
- {
- if (!headers.Contains("X-Stripe-Client-Telemetry"))
- {
- return false;
- }
-
- var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();
-
- var deserialized = JToken.Parse(payload);
- var requestId = (string)deserialized["last_request_metrics"]["request_id"];
- var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
- var usageRaw = deserialized["last_request_metrics"]["usage"];
-
- List usage = null;
- if (usageRaw != null)
- {
- usage = usageRaw.Select(x => (string)x).ToList();
- }
-
- return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
- }
-
private class TestEntity : StripeEntity
{
}
diff --git a/src/StripeTests/Functional/TelemetryTestUtils.cs b/src/StripeTests/Functional/TelemetryTestUtils.cs
new file mode 100644
index 0000000000..3bc3e98456
--- /dev/null
+++ b/src/StripeTests/Functional/TelemetryTestUtils.cs
@@ -0,0 +1,47 @@
+namespace StripeTests
+{
+ using System;
+ using System.Collections.Generic;
+ 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;
+ using Moq;
+ using Moq.Protected;
+ using Newtonsoft.Json.Linq;
+ using Stripe;
+ using Xunit;
+
+ internal class TelemetryTestUtils
+ {
+ public static bool TelemetryHeaderMatcher(
+ HttpHeaders headers,
+ Func requestIdMatcher,
+ Func durationMatcher,
+ Func, bool> usageMatcher)
+ {
+ if (!headers.Contains("X-Stripe-Client-Telemetry"))
+ {
+ return false;
+ }
+
+ var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();
+
+ var deserialized = JToken.Parse(payload);
+ var requestId = (string)deserialized["last_request_metrics"]["request_id"];
+ var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
+ var usageRaw = deserialized["last_request_metrics"]["usage"];
+
+ List usage = null;
+ if (usageRaw != null)
+ {
+ usage = usageRaw.Select(x => (string)x).ToList();
+ }
+
+ return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
+ }
+ }
+}
diff --git a/src/StripeTests/Infrastructure/Public/StripeClientTest.cs b/src/StripeTests/Infrastructure/Public/StripeClientTest.cs
index 6b252add8d..a02d3c64e4 100644
--- a/src/StripeTests/Infrastructure/Public/StripeClientTest.cs
+++ b/src/StripeTests/Infrastructure/Public/StripeClientTest.cs
@@ -1,16 +1,20 @@
namespace StripeTests
{
using System;
+ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
+ using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
+ using Newtonsoft.Json.Linq;
using Stripe;
- using StripeTests.V2;
using Xunit;
+ using static TelemetryTestUtils;
+
public class StripeClientTest : BaseStripeTest
{
private StripeClient stripeClient;
@@ -73,6 +77,58 @@ public void Ctr_StripeClientOptions_ChangesAfterConstruction()
Assert.Equal(goodApiKey, client.ApiKey);
}
+ [Fact]
+ public void StripeClientRequestorIncludesCorrectUsage()
+ {
+ var client = this.StripeClient as StripeClient;
+ client.V1.Customers.Get("cus_123");
+ client.V1.Customers.Get("cus_123");
+
+ this.MockHttpClientFixture.MockHandler.Protected()
+ .Verify(
+ "SendAsync",
+ Times.Once(),
+ ItExpr.Is(m =>
+ TelemetryHeaderMatcher(
+ m.Headers,
+ (_) => true,
+ (_) => true,
+ (t) => t != null && t.Count == 1 && t.Contains(Stripe.StripeClient.StripeClientUsage[0]))),
+ ItExpr.IsAny());
+ }
+
+ [Fact]
+ public void DefaultStripeClientRequestorDoesNotIncludeUsage()
+ {
+ var stripeClient = this.StripeClient as StripeClient;
+
+ // This mimics StripeConfiguration.BuildDefaultStripeClient, which is used
+ // by StripeConfiguration.StripeClient when a client is not set
+ StripeConfiguration.StripeClient = new DefaultStripeClient(
+ stripeClient.ApiKey, stripeClient.ClientId, stripeClient.HttpClient);
+
+ var customers = new CustomerService();
+ customers.Get("cus_123");
+ customers.Get("cus_123");
+
+ this.MockHttpClientFixture.MockHandler.Protected()
+ .Verify(
+ "SendAsync",
+ Times.Once(),
+ ItExpr.Is(m =>
+ TelemetryHeaderMatcher(
+ m.Headers,
+ (_) => true,
+ (_) => true,
+ (t) => t != null && t.Count == 0)),
+ ItExpr.IsAny());
+
+ // unfortunately we cannot check at the top of the test because
+ // StripeConfiguration.StripeClient always returns a value; but
+ // we should assume it was not set
+ StripeConfiguration.StripeClient = null;
+ }
+
[Fact]
public async Task RawRequestAsync_Json()
{
@@ -108,6 +164,38 @@ public async Task RawRequestAsync_Json()
Assert.Equal("mes_123", obj.Id);
}
+ [Fact]
+ public async Task RawRequestAsyncIncludesCorrectUsage()
+ {
+ await this.stripeClient.RawRequestAsync(
+ HttpMethod.Get,
+ "/v1/customers/cus_123",
+ null,
+ new RawRequestOptions
+ {
+ });
+
+ await this.stripeClient.RawRequestAsync(
+ HttpMethod.Get,
+ "/v1/customers/cus_123",
+ null,
+ new RawRequestOptions
+ {
+ });
+
+ this.MockHttpClientFixture.MockHandler.Protected()
+ .Verify(
+ "SendAsync",
+ Times.Once(),
+ ItExpr.Is(m =>
+ TelemetryHeaderMatcher(
+ m.Headers,
+ (_) => true,
+ (_) => true,
+ (t) => t != null && t.Count == 2 && t.Contains(Stripe.StripeClient.StripeClientUsage[0]) && t.Contains(LiveApiRequestor.RawRequestUsage[0]))),
+ ItExpr.IsAny());
+ }
+
[Fact]
public void ConstructThinEvent()
{
diff --git a/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs b/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs
index 3d66619557..54d6720059 100644
--- a/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs
+++ b/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs
@@ -24,6 +24,7 @@ public void StripeClient_Getter_CreatesNewStripeClientIfNullAndApiKeyIsSet()
var client = StripeConfiguration.StripeClient;
Assert.NotNull(client);
+ Assert.IsType(client);
Assert.Equal(StripeConfiguration.ApiKey, client.ApiKey);
Assert.Equal(StripeConfiguration.ClientId, client.ClientId);
}
@@ -46,6 +47,7 @@ public void StripeClient_Getter_CreatesNewStripeClientIfNullAndApiKeyIsNull()
var client = StripeConfiguration.StripeClient;
Assert.NotNull(client);
+ Assert.IsType(client);
Assert.Null(client.ApiKey);
}
finally
diff --git a/src/StripeTests/StripeMockFixture.cs b/src/StripeTests/StripeMockFixture.cs
index 244dd867dc..a521ac1dc7 100644
--- a/src/StripeTests/StripeMockFixture.cs
+++ b/src/StripeTests/StripeMockFixture.cs
@@ -43,14 +43,14 @@ public void Dispose()
/// created with default parameters.
///
/// The new instance.
- internal ApiRequestor BuildApiRequestor(HttpClientHandler innerHandler = null)
+ internal StripeClientOptions BuildStripeClientOptions(HttpClientHandler innerHandler = null)
{
- return new LiveApiRequestor(new StripeClientOptions
+ return new StripeClientOptions
{
ApiKey = "sk_test_123",
ClientId = "ca_123",
HttpClient = new SystemNetHttpClient(new ForwardingHttpClient(innerHandler, this.port)),
- });
+ };
}
///