Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,29 @@ The execution of integration tests require the following environment variables s
* For Default account systems (OAuth): `CHECKOUT_DEFAULT_OAUTH_CLIENT_ID` & `CHECKOUT_DEFAULT_OAUTH_CLIENT_SECRET`
* For Previous account systems (ABC): `CHECKOUT_PREVIOUS_PUBLIC_KEY` & `CHECKOUT_PREVIOUS_SECRET_KEY`

## Telemetry
Request telemetry is enabled by default in the .NET SDK. Request latency is included in the telemetry data. Recording the request latency allows Checkout.com to continuously monitor and improve the merchant experience.

Request telemetry can be disabled by opting out during CheckoutSdk builder step:
```
ICheckoutApi api = CheckoutSdk.Builder().StaticKeys()
.SecretKey("secret_key")
.RecordTelemetry(false)
.Environment(Environment.Sandbox)
.Build();
```

Or when using `CheckoutSDK.Extensions.Microsoft`:
```
{
"Checkout": {
...
"RecordTelemetry": false,
...
}
}
```

## Code of Conduct

Please refer to [Code of Conduct](CODE_OF_CONDUCT.md)
Expand Down
3 changes: 2 additions & 1 deletion src/CheckoutSdk.Extensions/CheckoutOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class CheckoutOptions
public PlatformType? PlatformType { get; set; }

public IHttpClientFactory HttpClientFactory { get; set; }


public bool RecordTelemetry { get; set; } = true;
}
}
1 change: 1 addition & 0 deletions src/CheckoutSdk.Extensions/CheckoutServiceCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private static void SetCommonAttributes<TB, TC>(
{
builder.HttpClientFactory(httpClientFactory);
}
builder.RecordTelemetry(options.RecordTelemetry);
}
}
}
10 changes: 9 additions & 1 deletion src/CheckoutSdk/AbstractCheckoutSdkBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Checkout
public abstract class AbstractCheckoutSdkBuilder<T>
{
protected Environment Env = Checkout.Environment.Sandbox;

private bool _recordTelemetry = true;
private EnvironmentSubdomain _envSubdomain;
protected IHttpClientFactory ClientFactory = new DefaultHttpClientFactory();

Expand All @@ -24,6 +26,12 @@ public AbstractCheckoutSdkBuilder<T> EnvironmentSubdomain(string subdomain)
return this;
}

public AbstractCheckoutSdkBuilder<T> RecordTelemetry(bool recordTelemetry)
{
_recordTelemetry = recordTelemetry;
return this;
}

#if (NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER)
public AbstractCheckoutSdkBuilder<T> LogProvider(ILoggerFactory loggerFactory)
{
Expand All @@ -40,7 +48,7 @@ public AbstractCheckoutSdkBuilder<T> HttpClientFactory(IHttpClientFactory httpCl

protected CheckoutConfiguration GetCheckoutConfiguration()
{
return new CheckoutConfiguration(GetSdkCredentials(), Env, _envSubdomain, ClientFactory);
return new CheckoutConfiguration(GetSdkCredentials(), Env, _envSubdomain, ClientFactory, _recordTelemetry);
}

protected abstract SdkCredentials GetSdkCredentials();
Expand Down
42 changes: 39 additions & 3 deletions src/CheckoutSdk/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using System.Web;
#endif
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand All @@ -19,16 +21,21 @@ public class ApiClient : IApiClient
private readonly ILogger _log = LogProvider.GetLogger(typeof(ApiClient));
#endif

private const string sdkTelemetryHeader = "cko-sdk-telemetry";
private const int maxCountInTelemetryQueue = 10;
private readonly HttpClient _httpClient;
private readonly Uri _baseUri;
private readonly ISerializer _serializer = new JsonSerializer();
private readonly ConcurrentQueue<RequestMetrics> requestMetricsQueue = new ConcurrentQueue<RequestMetrics>();
private readonly bool _enableTelemetry;

public ApiClient(IHttpClientFactory httpClientFactory, Uri baseUri)
public ApiClient(IHttpClientFactory httpClientFactory, Uri baseUri, bool enableTelemetry)
{
CheckoutUtils.ValidateParams("httpClientFactory", httpClientFactory, "baseUri", baseUri);
var httpClient = httpClientFactory.CreateClient();
_baseUri = baseUri;
_httpClient = httpClient;
_enableTelemetry = enableTelemetry;
}

public async Task<TResult> Get<TResult>(
Expand Down Expand Up @@ -170,7 +177,7 @@ public async Task<TResult> Query<TResult>(
$"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value)}"));
#endif

path = $"{path}?{queryString}";
path = $"{path}?{queryString}";
}
}

Expand Down Expand Up @@ -246,8 +253,37 @@ private async Task<HttpResponseMessage> Invoke(
{
httpRequest.Headers.Add("Cko-Idempotency-Key", idempotencyKey);
}

if (_enableTelemetry)
{
var currentRequestId = Guid.NewGuid().ToString();
if (requestMetricsQueue.TryDequeue(out var lastRequestMetric))
{
lastRequestMetric.RequestId = currentRequestId;
httpRequest.Headers.TryAddWithoutValidation(sdkTelemetryHeader, _serializer.Serialize(lastRequestMetric));
}

return await _httpClient.SendAsync(httpRequest, cancellationToken);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var result = await _httpClient.SendAsync(httpRequest, cancellationToken);
stopwatch.Stop();

if (requestMetricsQueue.Count < maxCountInTelemetryQueue)
{
lastRequestMetric.PrevRequestDuration = stopwatch.ElapsedMilliseconds;
requestMetricsQueue.Enqueue(new RequestMetrics()
{
PrevRequestDuration = stopwatch.ElapsedMilliseconds,
PrevRequestId = currentRequestId
});
}

return result;
}
else
{
return await _httpClient.SendAsync(httpRequest, cancellationToken);
}
}

private async Task ValidateResponseAsync(HttpResponseMessage httpResponse)
Expand Down
12 changes: 8 additions & 4 deletions src/CheckoutSdk/CheckoutApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,25 +79,29 @@
return new ApiClient(configuration.HttpClientFactory,
configuration.EnvironmentSubdomain != null
? configuration.EnvironmentSubdomain.ApiUri
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri);
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri,
configuration.RecordTelemetry);
}

private static ApiClient FilesApiClient(CheckoutConfiguration configuration)
{
return new ApiClient(configuration.HttpClientFactory,
configuration.Environment.GetAttribute<EnvironmentAttribute>().FilesApiUri);
configuration.Environment.GetAttribute<EnvironmentAttribute>().FilesApiUri,
configuration.RecordTelemetry);
}

private static ApiClient TransfersApiClient(CheckoutConfiguration configuration)
{
return new ApiClient(configuration.HttpClientFactory,
configuration.Environment.GetAttribute<EnvironmentAttribute>().TransfersApiUri);
configuration.Environment.GetAttribute<EnvironmentAttribute>().TransfersApiUri,
configuration.RecordTelemetry);
}

private static ApiClient BalancesApiClient(CheckoutConfiguration configuration)
{
return new ApiClient(configuration.HttpClientFactory,
configuration.Environment.GetAttribute<EnvironmentAttribute>().BalancesApiUri);
configuration.Environment.GetAttribute<EnvironmentAttribute>().BalancesApiUri,
configuration.RecordTelemetry);
}


Expand Down Expand Up @@ -126,7 +130,7 @@
return _disputesClient;
}

public IRiskClient RiskClient()

Check warning on line 133 in src/CheckoutSdk/CheckoutApi.cs

View workflow job for this annotation

GitHub Actions / NET 6.0

'IRiskClient' is obsolete: 'Risk endpoints are no longer supported officially, This module will be removed in a future release.'
{
return _riskClient;
}
Expand Down
6 changes: 5 additions & 1 deletion src/CheckoutSdk/CheckoutConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class CheckoutConfiguration

public IHttpClientFactory HttpClientFactory { get; }

public bool RecordTelemetry { get; }

public CheckoutConfiguration(
SdkCredentials sdkCredentials,
Environment environment,
Expand All @@ -28,7 +30,8 @@ public CheckoutConfiguration(
SdkCredentials sdkCredentials,
Environment environment,
EnvironmentSubdomain environmentSubdomain,
IHttpClientFactory httpClientFactory)
IHttpClientFactory httpClientFactory,
bool recordTelemetry)
{
CheckoutUtils.ValidateParams(
"sdkCredentials", sdkCredentials,
Expand All @@ -38,6 +41,7 @@ public CheckoutConfiguration(
Environment = environment;
EnvironmentSubdomain = environmentSubdomain;
HttpClientFactory = httpClientFactory;
RecordTelemetry = recordTelemetry;
}
}
}
3 changes: 2 additions & 1 deletion src/CheckoutSdk/Previous/AbstractCheckoutApmApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ protected AbstractCheckoutApmApi(CheckoutConfiguration configuration)
var apiClient = new ApiClient(configuration.HttpClientFactory,
configuration.EnvironmentSubdomain != null
? configuration.EnvironmentSubdomain.ApiUri
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri);
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri,
configuration.RecordTelemetry);
_idealClient = new IdealClient(apiClient, configuration);
_klarnaClient = new KlarnaClient(apiClient, configuration);
_sepaClient = new SepaClient(apiClient, configuration);
Expand Down
3 changes: 2 additions & 1 deletion src/CheckoutSdk/Previous/CheckoutApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
private readonly IDisputesClient _disputesClient;
private readonly IWebhooksClient _webhooksClient;
private readonly IEventsClient _eventsClient;
private readonly IRiskClient _riskClient;

Check warning on line 26 in src/CheckoutSdk/Previous/CheckoutApi.cs

View workflow job for this annotation

GitHub Actions / NET 5.0

'IRiskClient' is obsolete: 'Risk endpoints are no longer supported officially, This module will be removed in a future release.'
private readonly IPaymentLinksClient _paymentLinksClient;
private readonly IReconciliationClient _reconciliationClient;
private readonly IHostedPaymentsClient _hostedPaymentsClient;
Expand All @@ -33,7 +33,8 @@
var apiClient = new ApiClient(configuration.HttpClientFactory,
configuration.EnvironmentSubdomain != null
? configuration.EnvironmentSubdomain.ApiUri
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri);
: configuration.Environment.GetAttribute<EnvironmentAttribute>().ApiUri,
configuration.RecordTelemetry);
_tokensClient = new TokensClient(apiClient, configuration);
_customersClient = new CustomersClient(apiClient, configuration);
_sourcesClient = new SourcesClient(apiClient, configuration);
Expand Down Expand Up @@ -89,7 +90,7 @@
return _eventsClient;
}

public IRiskClient RiskClient()

Check warning on line 93 in src/CheckoutSdk/Previous/CheckoutApi.cs

View workflow job for this annotation

GitHub Actions / NET 5.0

'IRiskClient' is obsolete: 'Risk endpoints are no longer supported officially, This module will be removed in a future release.'
{
return _riskClient;
}
Expand Down
9 changes: 9 additions & 0 deletions src/CheckoutSdk/RequestMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Checkout
{
public struct RequestMetrics
{
public string PrevRequestId { get; set; }
public string RequestId { get; set; }
public long PrevRequestDuration { get; set; }
}
}
4 changes: 2 additions & 2 deletions test/CheckoutSdkTest/CheckoutApiTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void ShouldInstantiateAndRetrieveClientsPrevious()
httpClientFactoryMock.Setup(mock => mock.CreateClient())
.Returns(new HttpClient());
var checkoutConfiguration = new CheckoutConfiguration(sdkCredentialsMock.Object, Environment.Sandbox, null,
httpClientFactoryMock.Object);
httpClientFactoryMock.Object, false);

//Act
Previous.ICheckoutApi checkoutApi = new Previous.CheckoutApi(checkoutConfiguration);
Expand Down Expand Up @@ -48,7 +48,7 @@ public void ShouldInstantiateAndRetrieveClientsDefault()
httpClientFactoryMock.Setup(mock => mock.CreateClient())
.Returns(new HttpClient());
var checkoutConfiguration = new CheckoutConfiguration(sdkCredentialsMock.Object, Environment.Sandbox, null,
httpClientFactoryMock.Object);
httpClientFactoryMock.Object, false);

//Act
ICheckoutApi checkoutApi = new CheckoutApi(checkoutConfiguration);
Expand Down
6 changes: 3 additions & 3 deletions test/CheckoutSdkTest/CheckoutConfigurationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ private void ShouldCreateConfiguration()
var credentials = new StaticKeysSdkCredentials(ValidDefaultSk, ValidDefaultPk);
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var configuration =
new CheckoutConfiguration(credentials, Environment.Production, null, httpClientFactoryMock.Object);
new CheckoutConfiguration(credentials, Environment.Production, null, httpClientFactoryMock.Object, false);
configuration.Environment.ShouldBe(Environment.Production);
configuration.SdkCredentials.ShouldBeAssignableTo(typeof(StaticKeysSdkCredentials));
}
Expand All @@ -30,7 +30,7 @@ public void ShouldCreateConfigurationWithSubdomain(string subdomain, string expe
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var environmentSubdomain = new EnvironmentSubdomain(Environment.Sandbox, subdomain);
var configuration = new CheckoutConfiguration(credentials, Environment.Sandbox, environmentSubdomain,
httpClientFactoryMock.Object);
httpClientFactoryMock.Object, false);

configuration.Environment.ShouldBe(Environment.Sandbox);
configuration.EnvironmentSubdomain.ApiUri.ToString().ShouldBe(expectedUri);
Expand All @@ -50,7 +50,7 @@ public void ShouldCreateConfigurationWithBadSubdomain(string subdomain, string e
var httpClientFactoryMock = new Mock<IHttpClientFactory>();
var environmentSubdomain = new EnvironmentSubdomain(Environment.Sandbox, subdomain);
var configuration = new CheckoutConfiguration(credentials, Environment.Sandbox, environmentSubdomain,
httpClientFactoryMock.Object);
httpClientFactoryMock.Object, false);

configuration.Environment.ShouldBe(Environment.Sandbox);
configuration.EnvironmentSubdomain.ApiUri.ToString().ShouldBe(expectedUri);
Expand Down
Loading
Loading