Skip to content

Commit

Permalink
#347 Add support for setting custom idempotency keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Viincenttt committed Mar 29, 2024
1 parent 465cab5 commit 68ecdab
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 4 deletions.
9 changes: 9 additions & 0 deletions src/Mollie.Api/Client/Abstract/IBaseMollieClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Mollie.Api.Client.Abstract
{
public interface IBaseMollieClient : IDisposable
{
IDisposable WithIdempotencyKey(string value);
}
}
5 changes: 2 additions & 3 deletions src/Mollie.Api/Client/Abstract/IPaymentClient.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Mollie.Api.Models;
using Mollie.Api.Models.List;
using Mollie.Api.Models.Payment.Request;
using Mollie.Api.Models.Payment.Response;
using Mollie.Api.Models.Url;

namespace Mollie.Api.Client.Abstract {
public interface IPaymentClient : IDisposable {
public interface IPaymentClient : IBaseMollieClient {
Task<PaymentResponse> CreatePaymentAsync(PaymentRequest paymentRequest, bool includeQrCode = false);

/// <summary>
Expand Down
11 changes: 10 additions & 1 deletion src/Mollie.Api/Client/BaseMollieClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Mollie.Api.Extensions;
using Mollie.Api.Framework;
using Mollie.Api.Framework.Idempotency;
using Mollie.Api.Models.Url;

namespace Mollie.Api.Client {
Expand All @@ -17,6 +18,8 @@ public abstract class BaseMollieClient : IDisposable {
private readonly string _apiKey;
private readonly HttpClient _httpClient;
private readonly JsonConverterService _jsonConverterService;

private readonly AsyncLocalVariable<string> _idempotencyKey = new AsyncLocalVariable<string>(null);

private readonly bool _createdHttpClient = false;

Expand All @@ -37,6 +40,11 @@ protected BaseMollieClient(HttpClient httpClient = null, string apiEndpoint = Ap
this._createdHttpClient = httpClient == null;
this._httpClient = httpClient ?? new HttpClient();
}

public IDisposable WithIdempotencyKey(string value) {
_idempotencyKey.Value = value;
return _idempotencyKey;
}

private async Task<T> SendHttpRequest<T>(HttpMethod httpMethod, string relativeUri, object data = null) {
HttpRequestMessage httpRequest = this.CreateHttpRequest(httpMethod, relativeUri);
Expand Down Expand Up @@ -127,7 +135,8 @@ protected virtual HttpRequestMessage CreateHttpRequest(HttpMethod method, string
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this._apiKey);
httpRequest.Headers.Add("User-Agent", this.GetUserAgent());
httpRequest.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var idemPotencyKey = _idempotencyKey.Value ?? Guid.NewGuid().ToString();
httpRequest.Headers.Add("Idempotency-Key", idemPotencyKey);
httpRequest.Content = content;

return httpRequest;
Expand Down
26 changes: 26 additions & 0 deletions src/Mollie.Api/Framework/Idempotency/AsyncLocalVariable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Threading;

namespace Mollie.Api.Framework.Idempotency
{
public class AsyncLocalVariable<T> : IDisposable where T : class
{
private readonly AsyncLocal<T> _asyncLocal = new AsyncLocal<T>();

public T Value
{
get => _asyncLocal.Value;
set => _asyncLocal.Value = value;
}

public AsyncLocalVariable(T value)
{
_asyncLocal.Value = value;
}

public void Dispose()
{
_asyncLocal.Value = null;
}
}
}
27 changes: 27 additions & 0 deletions tests/Mollie.Tests.Unit/Client/PaymentClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Mollie.Api.Models.Payment.Response;
using RichardSzalay.MockHttp;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
Expand All @@ -16,6 +17,32 @@
namespace Mollie.Tests.Unit.Client;

public class PaymentClientTests : BaseClientTests {
[Fact]
public async Task CreatePaymentAsync_WithCustomIdempotencyKey_CustomIdemPotencyKeyIsSent()
{
// Given: We create a payment request with only the required parameters
PaymentRequest paymentRequest = new PaymentRequest()
{
Amount = new Amount(Currency.EUR, "100.00"),
Description = "Description",
RedirectUrl = "http://www.mollie.com"
};
const string customIdempotencyKey = "my-idempotency-key";
const string jsonToReturnInMockResponse = defaultPaymentJsonResponse;
var mockHttp = new MockHttpMessageHandler();
mockHttp.When($"{BaseMollieClient.ApiEndPoint}*")
.With(request => request.Headers.Contains("Idempotency-Key") && request.Headers.GetValues("Idempotency-Key").Single() == customIdempotencyKey)
.Respond("application/json", jsonToReturnInMockResponse);
HttpClient httpClient = mockHttp.ToHttpClient();
PaymentClient paymentClient = new PaymentClient("abcde", httpClient);

// Arrange & Act
using (paymentClient.WithIdempotencyKey(customIdempotencyKey))
{
await paymentClient.CreatePaymentAsync(paymentRequest);
}
}

[Fact]
public async Task CreatePaymentAsync_PaymentWithRequiredParameters_ResponseIsDeserializedInExpectedFormat() {
// Given: we create a payment request with only the required parameters
Expand Down

0 comments on commit 68ecdab

Please sign in to comment.