diff --git a/src/Mollie.Api/Client/InvoicesClient.cs b/src/Mollie.Api/Client/InvoicesClient.cs index b83ed84f..350682d4 100644 --- a/src/Mollie.Api/Client/InvoicesClient.cs +++ b/src/Mollie.Api/Client/InvoicesClient.cs @@ -36,12 +36,5 @@ public async Task> GetInvoiceListAsync( public async Task> GetInvoiceListAsync(UrlObjectLink> url) { return await this.GetAsync(url).ConfigureAwait(false); } - - private Dictionary BuildQueryParameters(string profileId, bool testmode) { - var result = new Dictionary(); - result.AddValueIfNotNullOrEmpty("profileId", profileId); - result.AddValueIfTrue("testmode", testmode); - return result; - } } } \ No newline at end of file diff --git a/src/Mollie.Api/Client/OrderClient.cs b/src/Mollie.Api/Client/OrderClient.cs index 18849a0c..a0c99e2d 100644 --- a/src/Mollie.Api/Client/OrderClient.cs +++ b/src/Mollie.Api/Client/OrderClient.cs @@ -6,7 +6,6 @@ using Mollie.Api.Models; using Mollie.Api.Models.List; using Mollie.Api.Models.Order; -using Mollie.Api.Models.Order.Request; using Mollie.Api.Models.Order.Request.ManageOrderLines; using Mollie.Api.Models.Payment.Response; using Mollie.Api.Models.Refund; diff --git a/src/Mollie.Api/ContractResolvers/DeliminatorSeparatedPropertyNamesContractResolver.cs b/src/Mollie.Api/ContractResolvers/DeliminatorSeparatedPropertyNamesContractResolver.cs deleted file mode 100644 index 0faf37f7..00000000 --- a/src/Mollie.Api/ContractResolvers/DeliminatorSeparatedPropertyNamesContractResolver.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Newtonsoft.Json.Serialization; - -namespace Mollie.Api.ContractResolvers { - public class DeliminatorSeparatedPropertyNamesContractResolver : DefaultContractResolver { - private readonly string _separator; - - protected DeliminatorSeparatedPropertyNamesContractResolver(char separator) { - this._separator = separator.ToString(); - } - - protected override string ResolvePropertyName(string propertyName) { - for (var j = propertyName.Length - 1; j > 0; j--) - if (j > 0 && char.IsUpper(propertyName[j]) || j > 0 && char.IsNumber(propertyName[j]) && - !char.IsNumber(propertyName[j - 1])) - propertyName = propertyName.Insert(j, this._separator); - return propertyName.ToLower(); - } - } -} \ No newline at end of file diff --git a/src/Mollie.Api/ContractResolvers/SnakeCasePropertyNamesContractResolver.cs b/src/Mollie.Api/ContractResolvers/SnakeCasePropertyNamesContractResolver.cs deleted file mode 100644 index 428cb074..00000000 --- a/src/Mollie.Api/ContractResolvers/SnakeCasePropertyNamesContractResolver.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Mollie.Api.ContractResolvers { - public class SnakeCasePropertyNamesContractResolver : DeliminatorSeparatedPropertyNamesContractResolver { - public SnakeCasePropertyNamesContractResolver() : base('_') { - } - } -} \ No newline at end of file diff --git a/src/Mollie.Api/Extensions/DictionaryExtensions.cs b/src/Mollie.Api/Extensions/DictionaryExtensions.cs index 56d00ab7..e2ca6da7 100644 --- a/src/Mollie.Api/Extensions/DictionaryExtensions.cs +++ b/src/Mollie.Api/Extensions/DictionaryExtensions.cs @@ -25,11 +25,5 @@ public static void AddValueIfTrue(this IDictionary dictionary, s dictionary.Add(key, bool.TrueString.ToLower()); } } - - public static void AddValueIfTrue(this IDictionary dictionary, string key, bool? value) { - if (value == true) { - dictionary.Add(key, bool.TrueString.ToLower()); - } - } } } \ No newline at end of file diff --git a/src/Mollie.Api/Extensions/JsonConvertExtensions.cs b/src/Mollie.Api/Extensions/JsonConvertExtensions.cs deleted file mode 100644 index cf08ee3e..00000000 --- a/src/Mollie.Api/Extensions/JsonConvertExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Mollie.Api.ContractResolvers; -using Newtonsoft.Json; - -namespace Mollie.Api.Extensions { - internal static class JsonConvertExtensions { - public static string SerializeObjectSnakeCase(object value) { - return JsonConvert.SerializeObject(value, - new JsonSerializerSettings { - DateFormatString = "yyyy-MM-dd", - ContractResolver = new SnakeCasePropertyNamesContractResolver(), - NullValueHandling = NullValueHandling.Ignore - }); - } - } -} \ No newline at end of file diff --git a/src/Mollie.Api/JsonConverters/BalanceReportResponseJsonConverter.cs b/src/Mollie.Api/JsonConverters/BalanceReportResponseJsonConverter.cs index aa7e285c..114b6c37 100644 --- a/src/Mollie.Api/JsonConverters/BalanceReportResponseJsonConverter.cs +++ b/src/Mollie.Api/JsonConverters/BalanceReportResponseJsonConverter.cs @@ -1,6 +1,5 @@ using System; using Mollie.Api.Framework.Factories; -using Mollie.Api.Models.Balance.Response; using Mollie.Api.Models.Balance.Response.BalanceReport; using Newtonsoft.Json.Linq; diff --git a/src/Mollie.Api/Models/Capture/Response/CaptureResponseLinks.cs b/src/Mollie.Api/Models/Capture/Response/CaptureResponseLinks.cs index 7534b603..1fe8d91b 100644 --- a/src/Mollie.Api/Models/Capture/Response/CaptureResponseLinks.cs +++ b/src/Mollie.Api/Models/Capture/Response/CaptureResponseLinks.cs @@ -1,18 +1,30 @@ -using Mollie.Api.Models.Url; +using Mollie.Api.Models.Payment.Response; +using Mollie.Api.Models.Settlement; +using Mollie.Api.Models.Shipment; +using Mollie.Api.Models.Url; namespace Mollie.Api.Models.Capture { public class CaptureResponseLinks { /// - /// The API resource URL of the order itself. + /// The API resource URL of the capture itself. /// public UrlObjectLink Self { get; set; } /// - /// The URL your customer should visit to make the payment for the order. - /// This is where you should redirect the customer to after creating the order. + /// The API resource URL of the payment the capture belongs to. /// - public UrlLink Checkout { get; set; } + public UrlObjectLink Payment { get; set; } + /// + /// The API resource URL of the shipment that triggered the capture to be created. + /// + public UrlObjectLink Shipment { get; set; } + + /// + /// The API resource URL of the settlement this capture has been settled with. Not present if not yet settled. + /// + public UrlObjectLink Settlement { get; set; } + /// /// The URL to the order retrieval endpoint documentation. /// diff --git a/src/Mollie.Api/Models/Payment/Response/Specific/BankTransferPaymentResponse.cs b/src/Mollie.Api/Models/Payment/Response/Specific/BankTransferPaymentResponse.cs index f18c9305..2b1b38a6 100644 --- a/src/Mollie.Api/Models/Payment/Response/Specific/BankTransferPaymentResponse.cs +++ b/src/Mollie.Api/Models/Payment/Response/Specific/BankTransferPaymentResponse.cs @@ -1,4 +1,5 @@ using Mollie.Api.Models.Url; +using Newtonsoft.Json; namespace Mollie.Api.Models.Payment.Response.Specific { public class BankTransferPaymentResponse : PaymentResponse { @@ -7,6 +8,7 @@ public class BankTransferPaymentResponse : PaymentResponse { /// /// For bank transfer payments, the _links object will contain some additional URL objects relevant to the payment. /// + [JsonProperty("_links")] public new BankTransferPaymentResponseLinks Links { get; set; } } @@ -33,23 +35,23 @@ public class BankTransferPaymentResponseDetails { public string TransferReference { get; set; } /// - /// Only available if the payment has been completed – The consumer's name. + /// Only available if the payment has been completed � The consumer's name. /// public string ConsumerName { get; set; } /// - /// Only available if the payment has been completed – The consumer's bank account. This may be an IBAN, or it may be a + /// Only available if the payment has been completed � The consumer's bank account. This may be an IBAN, or it may be a /// domestic account number. /// public string ConsumerAccount { get; set; } /// - /// Only available if the payment has been completed – The consumer's bank's BIC / SWIFT code. + /// Only available if the payment has been completed � The consumer's bank's BIC / SWIFT code. /// public string ConsumerBic { get; set; } /// - /// Only available if filled out in the API or by the consumer – The email address which the consumer asked the payment + /// Only available if filled out in the API or by the consumer � The email address which the consumer asked the payment /// instructions to be sent to. /// public string BillingEmail { get; set; } diff --git a/src/Mollie.Api/Models/Payment/Response/Specific/PayPalPaymentResponse.cs b/src/Mollie.Api/Models/Payment/Response/Specific/PayPalPaymentResponse.cs index 0b11d698..a10f90cd 100644 --- a/src/Mollie.Api/Models/Payment/Response/Specific/PayPalPaymentResponse.cs +++ b/src/Mollie.Api/Models/Payment/Response/Specific/PayPalPaymentResponse.cs @@ -31,6 +31,11 @@ public class PayPalPaymentResponseDetails { /// public string SellerProtection { get; set; } + /// + /// The shipping address details. + /// + public AddressObject ShippingAddress { get; set; } + /// /// The amount of fee PayPal will charge for this transaction. This field is omitted if PayPal will not charge a fee /// for this transaction. diff --git a/src/Mollie.Api/Models/Profile/Response/ProfileResponseLinks.cs b/src/Mollie.Api/Models/Profile/Response/ProfileResponseLinks.cs index 900a94da..b2b1eedc 100644 --- a/src/Mollie.Api/Models/Profile/Response/ProfileResponseLinks.cs +++ b/src/Mollie.Api/Models/Profile/Response/ProfileResponseLinks.cs @@ -8,6 +8,7 @@ namespace Mollie.Api.Models.Profile.Response { public class ProfileResponseLinks { public UrlObjectLink Self { get; set; } + public UrlLink Dashboard { get; set; } public UrlObjectLink> Chargebacks { get; set; } public UrlObjectLink> Methods { get; set; } public UrlObjectLink> Payments { get; set; } diff --git a/tests/Mollie.Tests.Unit/Client/BalanceClientTests.cs b/tests/Mollie.Tests.Unit/Client/BalanceClientTests.cs index 5fa2ea46..bd2b5a19 100644 --- a/tests/Mollie.Tests.Unit/Client/BalanceClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/BalanceClientTests.cs @@ -47,6 +47,11 @@ public async Task GetBalanceAsync_DefaultBehaviour_ResponseIsParsed() { balanceResponse.TransferDestination.Type.Should().Be(getBalanceResponseFactory.TransferDestination.Type); balanceResponse.TransferDestination.BankAccount.Should().Be(getBalanceResponseFactory.TransferDestination.BankAccount); balanceResponse.TransferDestination.BeneficiaryName.Should().Be(getBalanceResponseFactory.TransferDestination.BeneficiaryName); + balanceResponse.Links.Should().NotBeNull(); + balanceResponse.Links.Self.Href.Should().Be($"https://api.mollie.com/v2/balances/{getBalanceResponseFactory.BalanceId}"); + balanceResponse.Links.Self.Type.Should().Be("application/hal+json"); + balanceResponse.Links.Documentation.Href.Should().Be($"https://docs.mollie.com/reference/v2/balances-api/get-balance"); + balanceResponse.Links.Documentation.Type.Should().Be("text/html"); } [Theory] diff --git a/tests/Mollie.Tests.Unit/Client/BaseClientTests.cs b/tests/Mollie.Tests.Unit/Client/BaseClientTests.cs index fcc2f770..670c90a8 100644 --- a/tests/Mollie.Tests.Unit/Client/BaseClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/BaseClientTests.cs @@ -3,16 +3,31 @@ namespace Mollie.Tests.Unit.Client { public abstract class BaseClientTests { + protected readonly string DefaultRedirectUrl = "http://mysite.com"; + protected MockHttpMessageHandler CreateMockHttpMessageHandler(HttpMethod httpMethod, string url, string response, string expectedPartialContent = null) { MockHttpMessageHandler mockHttp = new MockHttpMessageHandler(); MockedRequest mockedRequest = mockHttp.Expect(httpMethod, url) .Respond("application/json", response); - if (!string.IsNullOrEmpty(expectedPartialContent)) { - mockedRequest.WithPartialContent(expectedPartialContent); + if (!string.IsNullOrEmpty(expectedPartialContent)) + { + mockedRequest.With(x => + { + string expectedContent = RemoveWhiteSpaces(expectedPartialContent); + string content = RemoveWhiteSpaces(x.Content!.ReadAsStringAsync().Result); + return content.Contains(expectedContent); + }); } return mockHttp; } + + private string RemoveWhiteSpaces(string input) + { + return input + .Replace(System.Environment.NewLine, string.Empty) + .Replace(" ", string.Empty); + } } } diff --git a/tests/Mollie.Tests.Unit/Client/ConnectClientTests.cs b/tests/Mollie.Tests.Unit/Client/ConnectClientTests.cs new file mode 100644 index 00000000..6771b3e5 --- /dev/null +++ b/tests/Mollie.Tests.Unit/Client/ConnectClientTests.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Mollie.Api.Client; +using Mollie.Api.Models.Connect; +using RichardSzalay.MockHttp; +using Xunit; + +namespace Mollie.Tests.Unit.Client; + +public class ConnectClientTests : BaseClientTests +{ + private const string ClientId = "client-id"; + private const string ClientSecret = "client-secret"; + + [Fact] + public void GetAuthorizationUrl_WithSingleScope_GeneratesAuthorizationUrl() + { + // Arrange + HttpClient httpClient = new HttpClient(); + ConnectClient connectClient = new ConnectClient(ClientId, ClientSecret, httpClient); + var scopes = new List {AppPermissions.PaymentsRead}; + + // Act + string authorizationUrl = connectClient.GetAuthorizationUrl("abcde", scopes); + + // Assert + string expectedUrl = $"https://www.mollie.com/oauth2/authorize?client_id={ClientId}&state=abcde&scope=payments.read&response_type=code&approval_prompt=auto"; + authorizationUrl.Should().Be(expectedUrl); + } + + [Theory] + [InlineData("refresh_abcde", "refresh_token")] + [InlineData("abcde", "authorization_code")] + public async Task GetAccessTokenAsync_WithRefreshToken_ResponseIsDeserializedInExpectedFormat(string refreshToken, string expectedGrantType) + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Post, "https://api.mollie.nl/oauth2/tokens") + .Respond("application/json", defaultGetTokenResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + ConnectClient connectClient = new ConnectClient(ClientId, ClientSecret, httpClient); + var tokenRequest = new TokenRequest(refreshToken, DefaultRedirectUrl); + + // Act + TokenResponse tokenResponse = await connectClient.GetAccessTokenAsync(tokenRequest); + + // Assert + mockHttp.VerifyNoOutstandingExpectation(); + tokenRequest.GrantType.Should().Be(expectedGrantType); + tokenResponse.Should().NotBeNull(); + tokenResponse.AccessToken.Should().Be("access_46EUJ6x8jFJZZeAvhNH4JVey6qVpqR"); + tokenResponse.RefreshToken.Should().Be("refresh_FS4xc3Mgci2xQ5s5DzaLXh3HhaTZOP"); + tokenResponse.ExpiresIn.Should().Be(3600); + tokenResponse.TokenType.Should().Be("bearer"); + tokenResponse.Scope.Should().Be("payments.read organizations.read"); + } + + [Fact] + public async Task RevokeTokenAsync_SendsRequest() + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Delete, "https://api.mollie.nl/oauth2/tokens") + .Respond(HttpStatusCode.NoContent); + HttpClient httpClient = mockHttp.ToHttpClient(); + ConnectClient connectClient = new ConnectClient(ClientId, ClientSecret, httpClient); + var revokeTokenRequest = new RevokeTokenRequest + { + Token = "access_46EUJ6x8jFJZZeAvhNH4JVey6qVpqR", + TokenTypeHint = "refresh_token" + }; + + // Act + await connectClient.RevokeTokenAsync(revokeTokenRequest); + + // Assert + mockHttp.VerifyNoOutstandingExpectation(); + } + + private const string defaultGetTokenResponse = @" +{ + ""access_token"": ""access_46EUJ6x8jFJZZeAvhNH4JVey6qVpqR"", + ""refresh_token"": ""refresh_FS4xc3Mgci2xQ5s5DzaLXh3HhaTZOP"", + ""expires_in"": 3600, + ""token_type"": ""bearer"", + ""scope"": ""payments.read organizations.read"" +}"; +} \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/OrderClientTests.cs b/tests/Mollie.Tests.Unit/Client/OrderClientTests.cs index 3c5308a0..7bbe2552 100644 --- a/tests/Mollie.Tests.Unit/Client/OrderClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/OrderClientTests.cs @@ -7,31 +7,13 @@ using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Extensions; using Mollie.Api.Models.Order.Request.ManageOrderLines; using RichardSzalay.MockHttp; using Xunit; namespace Mollie.Tests.Unit.Client { public class OrderClientTests : BaseClientTests { - private const string defaultOrderJsonResponse = @"{ - ""resource"": ""order"", - ""id"": ""ord_kEn1PlbGa"", - ""profileId"": ""pfl_URR55HPMGx"", - ""method"": ""ideal"", - ""amount"": { - ""value"": ""1027.99"", - ""currency"": ""EUR"" - }, - }"; - - private const string defaultPaymentJsonResponse = @"{ - ""amount"":{ - ""currency"":""EUR"", - ""value"":""100.00"" - }, - ""description"":""Description"", - ""redirectUrl"":""http://www.mollie.com""}"; - [Fact] public async Task GetOrderAsync_NoEmbedParameters_QueryStringIsEmpty() { // Given: We make a request to retrieve a order without wanting any extra data @@ -222,6 +204,45 @@ public async Task CreateOrderPaymentAsync_PaymentWithMultiplePaymentMethods_Requ // Then mockHttp.VerifyNoOutstandingExpectation(); } + + [Fact] + public async Task CreateOrderRefundAsync_WithRequiredParameters_ResponseIsDeserializedInExpectedFormat() + { + // Given: We create a refund request with only the required parameters + const string orderId = "ord_stTC2WHAuS"; + OrderRefundRequest orderRefundRequest = new OrderRefundRequest() + { + Description = "description", + Lines = new[] + { + new OrderLineDetails() + { + Id = "odl_dgtxyl", + Quantity = 1, + Amount = new Amount(Currency.EUR, "399.00") + } + }, + Metadata = "my-metadata" + }; + string url = $"{BaseMollieClient.ApiEndPoint}orders/{orderId}/refunds"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, url, defaultOrderRefundJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + OrderClient orderClient = new OrderClient("abcde", httpClient); + + // When: We send the request + var response = await orderClient.CreateOrderRefundAsync(orderId, orderRefundRequest); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + response.Resource.Should().Be("refund"); + response.Id.Should().Be("re_4qqhO89gsT"); + response.Description.Should().Be("description"); + response.Status.Should().Be("pending"); + response.CreatedAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(14.March(2018).At(17, 09, 02), DateTimeKind.Utc)); + response.PaymentId.Should().Be("tr_WDqYK6vllg"); + response.OrderId.Should().Be(orderId); + response.Lines.Should().HaveCount(1); + } [Theory] [InlineData("")] @@ -404,5 +425,101 @@ private OrderRequest CreateOrderRequestWithOnlyRequiredFields() { Locale = Locale.nl_NL }; } + + private const string defaultOrderRefundJsonResponse = @"{ + ""resource"": ""refund"", + ""id"": ""re_4qqhO89gsT"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""698.00"" + }, + ""status"": ""pending"", + ""createdAt"": ""2018-03-14T17:09:02.0Z"", + ""description"": ""description"", + ""metadata"": { + ""bookkeeping_id"": 12345 + }, + ""paymentId"": ""tr_WDqYK6vllg"", + ""orderId"": ""ord_stTC2WHAuS"", + ""lines"": [ + { + ""resource"": ""orderline"", + ""id"": ""odl_dgtxyl"", + ""orderId"": ""ord_stTC2WHAuS"", + ""name"": ""LEGO 42083 Bugatti Chiron"", + ""sku"": ""5702016116977"", + ""type"": ""physical"", + ""status"": ""paid"", + ""metadata"": null, + ""quantity"": 1, + ""unitPrice"": { + ""value"": ""399.00"", + ""currency"": ""EUR"" + }, + ""vatRate"": ""21.00"", + ""vatAmount"": { + ""value"": ""51.89"", + ""currency"": ""EUR"" + }, + ""discountAmount"": { + ""value"": ""100.00"", + ""currency"": ""EUR"" + }, + ""totalAmount"": { + ""value"": ""299.00"", + ""currency"": ""EUR"" + }, + ""createdAt"": ""2018-08-02T09:29:56+00:00"", + ""_links"": { + ""productUrl"": { + ""href"": ""https://shop.lego.com/nl-NL/Bugatti-Chiron-42083"", + ""type"": ""text/html"" + }, + ""imageUrl"": { + ""href"": ""https://sh-s7-live-s.legocdn.com/is/image//LEGO/42083_alt1?$main$"", + ""type"": ""text/html"" + } + } + } + ], + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg/refunds/re_4qqhO89gsT"", + ""type"": ""application/hal+json"" + }, + ""payment"": { + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg"", + ""type"": ""application/hal+json"" + }, + ""order"": { + ""href"": ""https://api.mollie.com/v2/orders/ord_stTC2WHAuS"", + ""type"": ""application/hal+json"" + }, + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/refunds-api/create-order-refund"", + ""type"": ""text/html"" + } + } +}"; + + private const string defaultOrderJsonResponse = @"{ + ""resource"": ""order"", + ""id"": ""ord_kEn1PlbGa"", + ""profileId"": ""pfl_URR55HPMGx"", + ""method"": ""ideal"", + ""amount"": { + ""value"": ""1027.99"", + ""currency"": ""EUR"" + }, + }"; + + private const string defaultPaymentJsonResponse = @"{ + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""redirectUrl"":""http://www.mollie.com""}"; + } } \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/PaymentClientTests.cs b/tests/Mollie.Tests.Unit/Client/PaymentClientTests.cs index 952640aa..3897811e 100644 --- a/tests/Mollie.Tests.Unit/Client/PaymentClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/PaymentClientTests.cs @@ -9,54 +9,69 @@ using System.Net.Http; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Extensions; +using Mollie.Api.Models.Payment.Response.Specific; using Xunit; +namespace Mollie.Tests.Unit.Client; -namespace Mollie.Tests.Unit.Client { - public class PaymentClientTests : BaseClientTests { - private const string defaultPaymentJsonResponse = @"{ - ""amount"":{ - ""currency"":""EUR"", - ""value"":""100.00"" - }, - ""description"":""Description"", - ""redirectUrl"":""http://www.mollie.com""}"; - - [Fact] - public async Task CreatePaymentAsync_PaymentWithRequiredParameters_ResponseIsDeserializedInExpectedFormat() { - // 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 jsonToReturnInMockResponse = defaultPaymentJsonResponse; - - var mockHttp = new MockHttpMessageHandler(); - mockHttp.When($"{BaseMollieClient.ApiEndPoint}*") - .With(request => request.Headers.Contains("Idempotency-Key")) - .Respond("application/json", jsonToReturnInMockResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - - // When: We send the request - PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); - - // Then - this.AssertPaymentIsEqual(paymentRequest, paymentResponse); - } +public class PaymentClientTests : BaseClientTests { + [Fact] + public async Task CreatePaymentAsync_PaymentWithRequiredParameters_ResponseIsDeserializedInExpectedFormat() { + // 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 jsonToReturnInMockResponse = defaultPaymentJsonResponse; + + var mockHttp = new MockHttpMessageHandler(); + mockHttp.When($"{BaseMollieClient.ApiEndPoint}*") + .With(request => request.Headers.Contains("Idempotency-Key")) + .Respond("application/json", jsonToReturnInMockResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task CreatePaymentAsync_PaymentWithSinglePaymentMethod_RequestIsSerializedInExpectedFormat() { - // Given: We create a payment request with a single payment method - PaymentRequest paymentRequest = new PaymentRequest() { - Amount = new Amount(Currency.EUR, "100.00"), - Description = "Description", - RedirectUrl = "http://www.mollie.com", - Method = PaymentMethod.Ideal - }; - string expectedPaymentMethodJson = $"\"method\":[\"{PaymentMethod.Ideal}\""; - const string jsonResponse = @"{ + // When: We send the request + PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); + + // Then + this.AssertPaymentIsEqual(paymentRequest, paymentResponse); + paymentResponse.AuthorizedAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(19.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.CreatedAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(20.March(2018).At(13, 13, 37), DateTimeKind.Utc)); + paymentResponse.PaidAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(21.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.CanceledAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(22.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.ExpiredAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(23.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.FailedAt!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(24.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.CaptureBefore!.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(25.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + paymentResponse.AmountRefunded.Value.Should().Be("10.00"); + paymentResponse.AmountRefunded.Currency.Should().Be(Currency.EUR); + paymentResponse.AmountRemaining.Value.Should().Be("90.00"); + paymentResponse.AmountRemaining.Currency.Should().Be(Currency.EUR); + paymentResponse.AmountChargedBack.Value.Should().Be("10.00"); + paymentResponse.AmountChargedBack.Currency.Should().Be(Currency.EUR); + paymentResponse.CancelUrl.Should().Be("https://webshop.example.org/order/12345/cancel"); + paymentResponse.CountryCode.Should().Be("NL"); + paymentResponse.SettlementId.Should().Be("stl_jDk30akdN"); + paymentResponse.SubscriptionId.Should().Be("sub_rVKGtNd6s3"); + paymentResponse.ApplicationFee.Should().NotBeNull(); + paymentResponse.ApplicationFee.Amount.Value.Should().Be("1.00"); + paymentResponse.ApplicationFee.Amount.Currency.Should().Be(Currency.EUR); + paymentResponse.ApplicationFee.Description.Should().Be("description"); + } + + [Fact] + public async Task CreatePaymentAsync_PaymentWithSinglePaymentMethod_RequestIsSerializedInExpectedFormat() { + // Given: We create a payment request with a single payment method + PaymentRequest paymentRequest = new PaymentRequest() { + Amount = new Amount(Currency.EUR, "100.00"), + Description = "Description", + RedirectUrl = "http://www.mollie.com", + Method = PaymentMethod.Ideal + }; + string expectedPaymentMethodJson = $"\"method\":[\"{PaymentMethod.Ideal}\""; + const string jsonResponse = @"{ ""amount"":{ ""currency"":""EUR"", ""value"":""100.00"" @@ -64,34 +79,34 @@ public async Task CreatePaymentAsync_PaymentWithSinglePaymentMethod_RequestIsSer ""description"":""Description"", ""method"":""ideal"", ""redirectUrl"":""http://www.mollie.com""}"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, expectedPaymentMethodJson); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, expectedPaymentMethodJson); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - // When: We send the request - PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); + // When: We send the request + PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); - // Then - mockHttp.VerifyNoOutstandingExpectation(); - this.AssertPaymentIsEqual(paymentRequest, paymentResponse); - paymentResponse.Method.Should().Be(paymentRequest.Method); - } + // Then + mockHttp.VerifyNoOutstandingExpectation(); + this.AssertPaymentIsEqual(paymentRequest, paymentResponse); + paymentResponse.Method.Should().Be(paymentRequest.Method); + } - [Fact] - public async Task CreatePaymentAsync_PaymentWithMultiplePaymentMethods_RequestIsSerializedInExpectedFormat() { - // Given: We create a payment request with multiple payment methods - PaymentRequest paymentRequest = new PaymentRequest() { - Amount = new Amount(Currency.EUR, "100.00"), - Description = "Description", - RedirectUrl = "http://www.mollie.com", - Methods = new List() { - PaymentMethod.Ideal, - PaymentMethod.CreditCard, - PaymentMethod.DirectDebit - } - }; - string expectedPaymentMethodJson = $"\"method\":[\"{PaymentMethod.Ideal}\",\"{PaymentMethod.CreditCard}\",\"{PaymentMethod.DirectDebit}\"]"; - const string expectedJsonResponse = @"{ + [Fact] + public async Task CreatePaymentAsync_PaymentWithMultiplePaymentMethods_RequestIsSerializedInExpectedFormat() { + // Given: We create a payment request with multiple payment methods + PaymentRequest paymentRequest = new PaymentRequest() { + Amount = new Amount(Currency.EUR, "100.00"), + Description = "Description", + RedirectUrl = "http://www.mollie.com", + Methods = new List() { + PaymentMethod.Ideal, + PaymentMethod.CreditCard, + PaymentMethod.DirectDebit + } + }; + string expectedPaymentMethodJson = $"\"method\":[\"{PaymentMethod.Ideal}\",\"{PaymentMethod.CreditCard}\",\"{PaymentMethod.DirectDebit}\"]"; + const string expectedJsonResponse = @"{ ""amount"":{ ""currency"":""EUR"", ""value"":""100.00"" @@ -99,40 +114,40 @@ public async Task CreatePaymentAsync_PaymentWithMultiplePaymentMethods_RequestIs ""description"":""Description"", ""method"": null, ""redirectUrl"":""http://www.mollie.com""}"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", expectedJsonResponse, expectedPaymentMethodJson); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", expectedJsonResponse, expectedPaymentMethodJson); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - // When: We send the request - PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); + // When: We send the request + PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); - // Then - mockHttp.VerifyNoOutstandingExpectation(); - this.AssertPaymentIsEqual(paymentRequest, paymentResponse); - paymentResponse.Method.Should().BeNull(); - } + // Then + mockHttp.VerifyNoOutstandingExpectation(); + this.AssertPaymentIsEqual(paymentRequest, paymentResponse); + paymentResponse.Method.Should().BeNull(); + } - [Fact] - public async Task CreatePayment_WithRoutingInformation_RequestIsSerializedInExpectedFormat() { - // Given: We create a payment request with the routing request - PaymentRoutingRequest routingRequest = new PaymentRoutingRequest { - Amount = new Amount("EUR", 100), - Destination = new RoutingDestination { - Type = "organization", - OrganizationId = "organization-id" - }, - ReleaseDate = new DateTime(2022, 1, 14) - }; - PaymentRequest paymentRequest = new PaymentRequest() { - Amount = new Amount(Currency.EUR, "100.00"), - Description = "Description", - RedirectUrl = "http://www.mollie.com", - Routings = new List { - routingRequest - } - }; - string expectedRoutingInformation = $"\"routing\":[{{\"amount\":{{\"currency\":\"EUR\",\"value\":\"100.00\"}},\"destination\":{{\"type\":\"organization\",\"organizationId\":\"organization-id\"}},\"releaseDate\":\"2022-01-14\"}}]}}"; - const string expectedJsonResponse = @"{ + [Fact] + public async Task CreatePayment_WithRoutingInformation_RequestIsSerializedInExpectedFormat() { + // Given: We create a payment request with the routing request + PaymentRoutingRequest routingRequest = new PaymentRoutingRequest { + Amount = new Amount("EUR", 100), + Destination = new RoutingDestination { + Type = "organization", + OrganizationId = "organization-id" + }, + ReleaseDate = new DateTime(2022, 1, 14) + }; + PaymentRequest paymentRequest = new PaymentRequest() { + Amount = new Amount(Currency.EUR, "100.00"), + Description = "Description", + RedirectUrl = "http://www.mollie.com", + Routings = new List { + routingRequest + } + }; + string expectedRoutingInformation = $"\"routing\":[{{\"amount\":{{\"currency\":\"EUR\",\"value\":\"100.00\"}},\"destination\":{{\"type\":\"organization\",\"organizationId\":\"organization-id\"}},\"releaseDate\":\"2022-01-14\"}}]}}"; + const string expectedJsonResponse = @"{ ""amount"":{ ""currency"":""EUR"", ""value"":""100.00"" @@ -152,242 +167,1055 @@ public async Task CreatePayment_WithRoutingInformation_RequestIsSerializedInExpe ""releaseDate"": ""2022-01-14"" } ]}"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", expectedJsonResponse, expectedRoutingInformation); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", expectedJsonResponse, expectedRoutingInformation); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - // When: We send the request - PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); + // When: We send the request + PaymentResponse paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest); - // Then - mockHttp.VerifyNoOutstandingExpectation(); - this.AssertPaymentIsEqual(paymentRequest, paymentResponse); - paymentResponse.Method.Should().BeNull(); - } + // Then + mockHttp.VerifyNoOutstandingExpectation(); + this.AssertPaymentIsEqual(paymentRequest, paymentResponse); + paymentResponse.Method.Should().BeNull(); + } - [Fact] - public async Task CreatePaymentAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { - // Given: We make a request to create a payment and include the QR code - PaymentRequest paymentRequest = new PaymentRequest() { - Amount = new Amount(Currency.EUR, "100.00"), - Description = "Description", - RedirectUrl = "http://www.mollie.com", - Method = PaymentMethod.Ideal - }; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments?include=details.qrCode", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - - // When: We send the request - await paymentClient.CreatePaymentAsync(paymentRequest, includeQrCode: true); - - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task CreatePaymentAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { + // Given: We make a request to create a payment and include the QR code + PaymentRequest paymentRequest = new PaymentRequest() { + Amount = new Amount(Currency.EUR, "100.00"), + Description = "Description", + RedirectUrl = "http://www.mollie.com", + Method = PaymentMethod.Ideal + }; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments?include=details.qrCode", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentAsync_NoIncludeParameters_QueryStringIsEmpty() { - // Given: We make a request to retrieve a payment without wanting any extra data - const string paymentId = "abcde"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + await paymentClient.CreatePaymentAsync(paymentRequest, includeQrCode: true); - // When: We send the request - await paymentClient.GetPaymentAsync(paymentId); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentAsync_NoIncludeParameters_RequestIsDeserializedInExpectedFormat() { + // Given: We make a request to retrieve a payment without wanting any extra data + const string paymentId = "tr_WDqYK6vllg"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var payment = await paymentClient.GetPaymentAsync(paymentId); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + payment.Resource.Should().Be("payment"); + payment.Id.Should().Be(paymentId); + payment.Amount.Value.Should().Be("100.00"); + payment.Amount.Currency.Should().Be(Currency.EUR); + payment.Description.Should().Be("Description"); + payment.Method.Should().BeNull(); + payment.Status.Should().Be(PaymentStatus.Open); + payment.IsCancelable.Should().BeFalse(); + payment.Locale.Should().Be("nl_NL"); + payment.ExpiresAt.Value.ToUniversalTime().Should().Be(DateTime.SpecifyKind(20.March(2018).At(13, 28, 37), DateTimeKind.Utc)); + payment.ProfileId.Should().Be("pfl_QkEhN94Ba"); + payment.SequenceType.Should().Be(SequenceType.OneOff); + payment.RedirectUrl.Should().Be("https://webshop.example.org/order/12345/"); + payment.WebhookUrl.Should().Be("https://webshop.example.org/payments/webhook/"); + payment.Links.Should().NotBeNull(); + payment.Links.Self.Href.Should().Be("https://api.mollie.com/v2/payments/tr_WDqYK6vllg"); + payment.Links.Self.Type.Should().Be("application/hal+json"); + payment.Links.Checkout.Href.Should().Be("https://www.mollie.com/payscreen/select-method/WDqYK6vllg"); + payment.Links.Checkout.Type.Should().Be("text/html"); + payment.Links.Dashboard.Href.Should().Be("https://www.mollie.com/dashboard/org_12345678/payments/tr_WDqYK6vllg"); + payment.Links.Dashboard.Type.Should().Be("text/html"); + payment.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/payments-api/get-payment"); + payment.Links.Documentation.Type.Should().Be("text/html"); + } + + [Fact] + public async Task GetPaymentAsync_ForBankTransferPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a bank transfer payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""banktransfer"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""bankName"": ""bank-name"", + ""bankAccount"": ""bank-account"", + ""bankBic"": ""bank-bic"", + ""transferReference"": ""transfer-reference"", + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"", + ""billingEmail"": ""billing-email"", + ""qrCode"":{ + ""height"": 5, + ""width"": 10, + ""src"": ""https://www.mollie.com/qr/12345678.png"" + } + }, + ""_links"": { + ""status"": { + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg"", + ""type"": ""application/hal+json"" + }, + ""payOnline"": { + ""href"": ""https://www.mollie.com/payscreen/select-method/WDqYK6vllg"", + ""type"": ""text/html"" + } + } + }"; - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task GetPaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await paymentClient.GetPaymentAsync(paymentId)); - - // Then - exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); - } + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { - // Given: We make a request to retrieve a payment without wanting any extra data - const string paymentId = "abcde"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?include=details.qrCode", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + var payment = await paymentClient.GetPaymentAsync(paymentId); + + // Then + payment.Should().BeOfType(); + var bankTransferPayment = payment as BankTransferPaymentResponse; + bankTransferPayment.Details.BankName.Should().Be("bank-name"); + bankTransferPayment.Details.BankAccount.Should().Be("bank-account"); + bankTransferPayment.Details.BankBic.Should().Be("bank-bic"); + bankTransferPayment.Details.TransferReference.Should().Be("transfer-reference"); + bankTransferPayment.Details.ConsumerName.Should().Be("consumer-name"); + bankTransferPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + bankTransferPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + bankTransferPayment.Details.BillingEmail.Should().Be("billing-email"); + bankTransferPayment.Details.QrCode.Should().NotBeNull(); + bankTransferPayment.Details.QrCode.Height.Should().Be(5); + bankTransferPayment.Details.QrCode.Width.Should().Be(10); + bankTransferPayment.Details.QrCode.Src.Should().Be("https://www.mollie.com/qr/12345678.png"); + bankTransferPayment.Links.Should().NotBeNull(); + bankTransferPayment.Links.Status.Should().NotBeNull(); + bankTransferPayment.Links.Status.Href.Should().Be("https://api.mollie.com/v2/payments/tr_WDqYK6vllg"); + bankTransferPayment.Links.Status.Type.Should().Be("application/hal+json"); + bankTransferPayment.Links.PayOnline.Should().NotBeNull(); + bankTransferPayment.Links.PayOnline.Href.Should().Be("https://www.mollie.com/payscreen/select-method/WDqYK6vllg"); + bankTransferPayment.Links.PayOnline.Type.Should().Be("text/html"); + } + + [Fact] + public async Task GetPaymentAsync_ForBanContactPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a bancontact payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""bancontact"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""cardNumber"": ""1234567890123456"", + ""cardFingerprint"": ""fingerprint"", + ""qrCode"":{ + ""height"": 5, + ""width"": 10, + ""src"": ""https://www.mollie.com/qr/12345678.png"" + }, + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"", + ""failureReason"": ""failure-reason"" + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); + + // Then + result.Should().BeOfType(); + var banContactPayment = result as BancontactPaymentResponse; + banContactPayment.Details.CardNumber.Should().Be("1234567890123456"); + banContactPayment.Details.CardFingerprint.Should().Be("fingerprint"); + banContactPayment.Details.QrCode.Should().NotBeNull(); + banContactPayment.Details.QrCode.Height.Should().Be(5); + banContactPayment.Details.QrCode.Width.Should().Be(10); + banContactPayment.Details.QrCode.Src.Should().Be("https://www.mollie.com/qr/12345678.png"); + banContactPayment.Details.ConsumerName.Should().Be("consumer-name"); + banContactPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + banContactPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + banContactPayment.Details.FailureReason.Should().Be("failure-reason"); + } + + [Fact] + public async Task CreatePaymentAsync_SepaDirectDebit_RequestAndResponseAreConvertedToExpectedJsonFormat() + { + // Given we create a creditcard specific payment request + var paymentRequest = new SepaDirectDebitRequest() + { + Amount = new Amount() { Currency = Currency.EUR, Value = "100.00" }, + Description = "Description", + Method = PaymentMethod.Ideal, + RedirectUrl = "http://www.mollie.com", + WebhookUrl = "http://www.mollie.com/webhook", + ConsumerName = "consumer-name", + ConsumerAccount = "consumer-account" + }; + const string jsonRequest = @"{ + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""description"": ""Description"", + ""redirectUrl"": ""http://www.mollie.com"", + ""webhookUrl"": ""http://www.mollie.com/webhook"", + ""method"": [ + ""ideal"" + ] +}"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""directdebit"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"", + ""transferReference"": ""transfer-reference"", + ""bankReasonCode"": ""bank-reason-code"", + ""bankReason"": ""bank-reason"", + ""batchReference"": ""batch-reference"", + ""mandateReference"": ""mandate-reference"", + ""creditorIdentifier"": ""creditor-identifier"", + ""dueDate"": ""2018-03-20"", + ""signatureDate"": ""2018-03-20"", + ""endToEndIdentifier"": ""end-to-end-identifier"", + ""batchReference"": ""batch-reference"", + ""fileReference"": ""file-reference"" + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, jsonRequest); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.CreatePaymentAsync(paymentRequest); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + var specificPaymentResponse = result as SepaDirectDebitResponse; + specificPaymentResponse.Should().NotBeNull(); + specificPaymentResponse.Details.ConsumerName.Should().Be("consumer-name"); + specificPaymentResponse.Details.ConsumerAccount.Should().Be("consumer-account"); + specificPaymentResponse.Details.ConsumerBic.Should().Be("consumer-bic"); + specificPaymentResponse.Details.TransferReference.Should().Be("transfer-reference"); + specificPaymentResponse.Details.BankReasonCode.Should().Be("bank-reason-code"); + specificPaymentResponse.Details.BankReason.Should().Be("bank-reason"); + specificPaymentResponse.Details.BatchReference.Should().Be("batch-reference"); + specificPaymentResponse.Details.MandateReference.Should().Be("mandate-reference"); + specificPaymentResponse.Details.CreditorIdentifier.Should().Be("creditor-identifier"); + specificPaymentResponse.Details.DueDate.Should().Be("03/20/2018 00:00:00"); + specificPaymentResponse.Details.SignatureDate.Should().Be("03/20/2018 00:00:00"); + specificPaymentResponse.Details.EndToEndIdentifier.Should().Be("end-to-end-identifier"); + specificPaymentResponse.Details.BatchReference.Should().Be("batch-reference"); + specificPaymentResponse.Details.FileReference.Should().Be("file-reference"); + } + + [Fact] + public async Task GetPaymentAsync_ForPayPalPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a paypal payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""paypal"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""paypalReference"": ""paypal-ref"", + ""paypalPayerId"": ""paypal-payer-id"", + ""sellerProtection"": ""Eligible"", + ""shippingAddress"": { + ""streetAndNumber"": ""street-and-number"", + ""streetAdditional"": ""street-additional"", + ""postalCode"": ""postal-code"", + ""city"": ""city"", + ""region"": ""region"", + ""country"": ""country"" + }, + ""paypalFee"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + } + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, + $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - // When: We send the request - await paymentClient.GetPaymentAsync(paymentId, includeQrCode: true); + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + // Then + result.Should().BeOfType(); + var payPalPayment = result as PayPalPaymentResponse; + payPalPayment.Details.ConsumerName.Should().Be("consumer-name"); + payPalPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + payPalPayment.Details.PayPalReference.Should().Be("paypal-ref"); + payPalPayment.Details.PaypalPayerId.Should().Be("paypal-payer-id"); + payPalPayment.Details.SellerProtection.Should().Be("Eligible"); + payPalPayment.Details.ShippingAddress.Should().NotBeNull(); + payPalPayment.Details.ShippingAddress.StreetAndNumber.Should().Be("street-and-number"); + payPalPayment.Details.ShippingAddress.StreetAdditional.Should().Be("street-additional"); + payPalPayment.Details.ShippingAddress.PostalCode.Should().Be("postal-code"); + payPalPayment.Details.ShippingAddress.City.Should().Be("city"); + payPalPayment.Details.ShippingAddress.Region.Should().Be("region"); + payPalPayment.Details.ShippingAddress.Country.Should().Be("country"); + payPalPayment.Details.PaypalFee.Should().NotBeNull(); + payPalPayment.Details.PaypalFee.Currency.Should().Be("EUR"); + payPalPayment.Details.PaypalFee.Value.Should().Be("100.00"); + } + + [Fact] + public async Task CreatePaymentAsync_CreditcardPayment_RequestAndResponseAreConvertedToExpectedJsonFormat() + { + // Given we create a creditcard specific payment request + var paymentRequest = new CreditCardPaymentRequest() + { + Amount = new Amount() { Currency = Currency.EUR, Value = "100.00" }, + Description = "Description", + Method = PaymentMethod.Ideal, + RedirectUrl = "http://www.mollie.com", + WebhookUrl = "http://www.mollie.com/webhook", + BillingAddress = new AddressObject() + { + City = "Amsterdam", + Country = "NL", + PostalCode = "1000AA", + Region = "Noord-Holland", + StreetAndNumber = "Keizersgracht 313" + }, + ShippingAddress = new AddressObject() + { + City = "Amsterdam", + Country = "NL", + PostalCode = "1000AA", + Region = "Noord-Holland", + StreetAndNumber = "Keizersgracht 313" + }, + CardToken = "card-token" + }; + const string jsonRequest = @"{ + ""billingAddress"": { + ""streetAndNumber"": ""Keizersgracht 313"", + ""postalCode"": ""1000AA"", + ""city"": ""Amsterdam"", + ""region"": ""Noord-Holland"", + ""country"": ""NL"" + }, + ""shippingAddress"": { + ""streetAndNumber"": ""Keizersgracht 313"", + ""postalCode"": ""1000AA"", + ""city"": ""Amsterdam"", + ""region"": ""Noord-Holland"", + ""country"": ""NL"" + }, + ""cardToken"": ""card-token"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""description"": ""Description"", + ""redirectUrl"": ""http://www.mollie.com"", + ""webhookUrl"": ""http://www.mollie.com/webhook"", + ""method"": [ + ""ideal"" + ] +}"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""creditcard"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""cardNumber"": ""1234567890123456"", + ""cardHolder"": ""John Doe"", + ""cardFingerprint"": ""fingerprint"", + ""cardAudience"": ""audience"", + ""cardLabel"": ""American Express"", + ""cardCountryCode"": ""NL"", + ""cardSecurity"": ""security"", + ""feeRegion"": ""american-express"", + ""failureReason"": ""unknown_reason"", + ""failureMessage"": ""faulure-message"", + ""wallet"": ""applepay"" + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, jsonRequest); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.CreatePaymentAsync(paymentRequest); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + var specificPaymentResponse = result as CreditCardPaymentResponse; + specificPaymentResponse.Should().NotBeNull(); + specificPaymentResponse.Details.CardNumber.Should().Be("1234567890123456"); + specificPaymentResponse.Details.CardHolder.Should().Be("John Doe"); + specificPaymentResponse.Details.CardFingerprint.Should().Be("fingerprint"); + specificPaymentResponse.Details.CardAudience.Should().Be("audience"); + specificPaymentResponse.Details.CardLabel.Should().Be("American Express"); + specificPaymentResponse.Details.CardCountryCode.Should().Be("NL"); + specificPaymentResponse.Details.CardSecurity.Should().Be("security"); + specificPaymentResponse.Details.FeeRegion.Should().Be("american-express"); + specificPaymentResponse.Details.FailureReason.Should().Be("unknown_reason"); + specificPaymentResponse.Details.FailureMessage.Should().Be("faulure-message"); + specificPaymentResponse.Details.Wallet.Should().Be("applepay"); + } + + [Fact] + public async Task CreatePaymentAsync_GiftcardPayment_RequestAndResponseAreConvertedToExpectedJsonFormat() + { + // Given we create a giftcard specific payment request + var paymentRequest = new GiftcardPaymentRequest() + { + Amount = new Amount() { Currency = Currency.EUR, Value = "100.00" }, + Description = "Description", + Method = PaymentMethod.Ideal, + RedirectUrl = "http://www.mollie.com", + WebhookUrl = "http://www.mollie.com/webhook", + Issuer = "issuer", + VoucherNumber = "voucher-number", + VoucherPin = "voucher-pin" + }; + const string jsonRequest = @"{ + ""issuer"": ""issuer"", + ""voucherNumber"": ""voucher-number"", + ""voucherPin"": ""voucher-pin"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""description"": ""Description"", + ""redirectUrl"": ""http://www.mollie.com"", + ""webhookUrl"": ""http://www.mollie.com/webhook"", + ""method"": [ + ""ideal"" + ] +}"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""giftcard"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""voucherNumber"": ""voucher-number"", + ""giftcards"": [ + { + ""issuer"": ""issuer"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""voucherNumber"": ""voucher-number"" + } + ], + ""RemainderAmount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""RemainderMethod"": ""ideal"" + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, jsonRequest); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.CreatePaymentAsync(paymentRequest); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + var specificPaymentResponse = result as GiftcardPaymentResponse; + specificPaymentResponse.Should().NotBeNull(); + specificPaymentResponse.Details.VoucherNumber.Should().Be("voucher-number"); + specificPaymentResponse.Details.Giftcards.Should().NotBeNull(); + specificPaymentResponse.Details.Giftcards.Count.Should().Be(1); + specificPaymentResponse.Details.Giftcards[0].Issuer.Should().Be("issuer"); + specificPaymentResponse.Details.Giftcards[0].Amount.Should().NotBeNull(); + specificPaymentResponse.Details.Giftcards[0].Amount.Currency.Should().Be("EUR"); + specificPaymentResponse.Details.Giftcards[0].Amount.Value.Should().Be("100.00"); + specificPaymentResponse.Details.Giftcards[0].VoucherNumber.Should().Be("voucher-number"); + specificPaymentResponse.Details.RemainderAmount.Should().NotBeNull(); + specificPaymentResponse.Details.RemainderAmount.Currency.Should().Be("EUR"); + specificPaymentResponse.Details.RemainderAmount.Value.Should().Be("100.00"); + specificPaymentResponse.Details.RemainderMethod.Should().Be("ideal"); + } - [Fact] - public async Task GetPaymentAsync_IncludeRemainderDetails_QueryStringContainsIncludeRemainderDetailsParameter() { - // Given: We make a request to retrieve a payment without wanting any extra data - const string paymentId = "abcde"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?include=details.remainderDetails", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + [Fact] + public async Task GetPaymentAsync_ForBelfiusPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a belfius payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""belfius"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"" + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); + + // Then + result.Should().BeOfType(); + var belfiusPayment = result as BelfiusPaymentResponse; + belfiusPayment!.Details.ConsumerName.Should().Be("consumer-name"); + belfiusPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + belfiusPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + } - // When: We send the request - await paymentClient.GetPaymentAsync(paymentId, includeRemainderDetails: true); + [Fact] + public async Task GetPaymentAsync_ForIngHomePay_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a ing home pay payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""inghomepay"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"" + } + }"; + + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); + + // Then + result.Should().BeOfType(); + var ingHomePayPayment = result as IngHomePayPaymentResponse; + ingHomePayPayment!.Details.ConsumerName.Should().Be("consumer-name"); + ingHomePayPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + ingHomePayPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + } + + [Fact] + public async Task GetPaymentAsync_ForKbcPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a ing home pay payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""kbc"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"" + } + }"; + + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); + + // Then + result.Should().BeOfType(); + var kbcPayment = result as KbcPaymentResponse; + kbcPayment!.Details.ConsumerName.Should().Be("consumer-name"); + kbcPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + kbcPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task CreatePaymentAsync_IdealPayment_RequestAndResponseAreConvertedToExpectedJsonFormat() + { + // Given we create a ideal specific payment request + var paymentRequest = new IdealPaymentRequest() + { + Amount = new Amount() { Currency = Currency.EUR, Value = "100.00" }, + Description = "Description", + Method = PaymentMethod.Ideal, + RedirectUrl = "http://www.mollie.com", + WebhookUrl = "http://www.mollie.com/webhook", + Issuer = "ideal_INGBNL2A" + }; + const string jsonRequest = @"{ + ""issuer"": ""ideal_INGBNL2A"", + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""100.00"" + }, + ""description"": ""Description"", + ""redirectUrl"": ""http://www.mollie.com"", + ""webhookUrl"": ""http://www.mollie.com/webhook"", + ""method"": [ + ""ideal"" + ] +}"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""ideal"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"", + ""qrCode"": { + ""height"": 5, + ""width"": 10, + ""src"": ""https://www.mollie.com/qr/12345678.png"" + } + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Post, $"{BaseMollieClient.ApiEndPoint}payments", jsonResponse, jsonRequest); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.CreatePaymentAsync(paymentRequest); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + var specificPaymentResponse = result as IdealPaymentResponse; + specificPaymentResponse!.Details.ConsumerName.Should().Be("consumer-name"); + specificPaymentResponse.Details.ConsumerAccount.Should().Be("consumer-account"); + specificPaymentResponse.Details.ConsumerBic.Should().Be("consumer-bic"); + specificPaymentResponse.Details.QrCode.Should().NotBeNull(); + specificPaymentResponse.Details.QrCode.Height.Should().Be(5); + specificPaymentResponse.Details.QrCode.Width.Should().Be(10); + specificPaymentResponse.Details.QrCode.Src.Should().Be("https://www.mollie.com/qr/12345678.png"); + } + + [Fact] + public async Task GetPaymentAsync_ForSofortPayment_DetailsAreDeserialized() + { + // Given: We make a request to retrieve a ing home pay payment + const string paymentId = "tr_WDqYK6vllg"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""sofort"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": { + ""consumerName"": ""consumer-name"", + ""consumerAccount"": ""consumer-account"", + ""consumerBic"": ""consumer-bic"" + } + }"; + + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId); + + // Then + result.Should().BeOfType(); + var sofortPayment = result as SofortPaymentResponse; + sofortPayment!.Details.ConsumerName.Should().Be("consumer-name"); + sofortPayment.Details.ConsumerAccount.Should().Be("consumer-account"); + sofortPayment.Details.ConsumerBic.Should().Be("consumer-bic"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task GetPaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentListAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { - // Given: We make a request to retrieve a payment without wanting any extra data - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?include=details.qrCode", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await paymentClient.GetPaymentAsync(paymentId)); - // When: We send the request - await paymentClient.GetPaymentListAsync(includeQrCode: true); + // Then + exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { + // Given: We make a request to retrieve a payment without wanting any extra data + const string paymentId = "abcde"; + const string jsonResponse = @"{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": ""ideal"", + ""details"": { + ""qrCode"":{ + ""height"": 5, + ""width"": 5, + ""src"": ""https://www.mollie.com/qr/12345678.png"" + } + } + }"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, + $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?include=details.qrCode", + jsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentAsync_EmbedRefunds_QueryStringContainsEmbedRefundsParameter() - { - // Given: We make a request to retrieve a payment with embedded refunds - const string paymentId = "abcde"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?embed=refunds", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + var result = await paymentClient.GetPaymentAsync(paymentId, includeQrCode: true); - // When: We send the request - await paymentClient.GetPaymentAsync(paymentId, embedRefunds: true); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + result.Should().BeOfType(); + var paymentResponse = result as IdealPaymentResponse; + paymentResponse.Details.QrCode.Should().NotBeNull(); + paymentResponse.Details.QrCode.Height.Should().Be(5); + paymentResponse.Details.QrCode.Width.Should().Be(5); + paymentResponse.Details.QrCode.Src.Should().Be("https://www.mollie.com/qr/12345678.png"); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentAsync_IncludeRemainderDetails_QueryStringContainsIncludeRemainderDetailsParameter() { + // Given: We make a request to retrieve a payment without wanting any extra data + const string paymentId = "abcde"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?include=details.remainderDetails", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentListAsync_EmbedRefunds_QueryStringContainsEmbedRefundsParameter() - { - // Given: We make a request to retrieve a payment with embedded refunds - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?embed=refunds", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + await paymentClient.GetPaymentAsync(paymentId, includeRemainderDetails: true); - // When: We send the request - await paymentClient.GetPaymentListAsync(embedRefunds: true); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentListAsync_IncludeQrCode_QueryStringContainsIncludeQrCodeParameter() { + // Given: We make a request to retrieve a payment without wanting any extra data + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?include=details.qrCode", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentAsync_EmbedChargebacks_QueryStringContainsEmbedChargebacksParameter() - { - // Given: We make a request to retrieve a payment with embedded refunds - const string paymentId = "abcde"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?embed=chargebacks", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + await paymentClient.GetPaymentListAsync(includeQrCode: true); - // When: We send the request - await paymentClient.GetPaymentAsync(paymentId, embedChargebacks: true); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentAsync_EmbedRefunds_QueryStringContainsEmbedRefundsParameter() + { + // Given: We make a request to retrieve a payment with embedded refunds + const string paymentId = "abcde"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?embed=refunds", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task GetPaymentListAsync_EmbedChargebacks_QueryStringContainsEmbedChargebacksParameter() - { - // Given: We make a request to retrieve a payment with embedded refunds - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?embed=chargebacks", defaultPaymentJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + await paymentClient.GetPaymentAsync(paymentId, embedRefunds: true); - // When: We send the request - await paymentClient.GetPaymentListAsync(embedChargebacks: true); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentListAsync_EmbedRefunds_QueryStringContainsEmbedRefundsParameter() + { + // Given: We make a request to retrieve a payment with embedded refunds + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?embed=refunds", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - [Fact] - public async Task DeletePaymentAsync_TestmodeIsTrue_RequestContainsTestmodeModel() { - // Given: We make a request to retrieve a payment with embedded refunds - const string paymentId = "payment-id"; - string expectedContent = "\"testmode\":true"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Delete, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", defaultPaymentJsonResponse, expectedContent); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + // When: We send the request + await paymentClient.GetPaymentListAsync(embedRefunds: true); - // When: We send the request - await paymentClient.DeletePaymentAsync(paymentId, true); + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - // Then - mockHttp.VerifyNoOutstandingExpectation(); - } + [Fact] + public async Task GetPaymentAsync_EmbedChargebacks_QueryStringContainsEmbedChargebacksParameter() + { + // Given: We make a request to retrieve a payment with embedded refunds + const string paymentId = "abcde"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}?embed=chargebacks", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + await paymentClient.GetPaymentAsync(paymentId, embedChargebacks: true); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task GetPaymentListAsync_EmbedChargebacks_QueryStringContainsEmbedChargebacksParameter() + { + // Given: We make a request to retrieve a payment with embedded refunds + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}payments?embed=chargebacks", defaultPaymentJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + await paymentClient.GetPaymentListAsync(embedChargebacks: true); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } + + [Fact] + public async Task DeletePaymentAsync_TestmodeIsTrue_RequestContainsTestmodeModel() { + // Given: We make a request to retrieve a payment with embedded refunds + const string paymentId = "payment-id"; + string expectedContent = "\"testmode\":true"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Delete, $"{BaseMollieClient.ApiEndPoint}payments/{paymentId}", defaultPaymentJsonResponse, expectedContent); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + await paymentClient.DeletePaymentAsync(paymentId, true); + + // Then + mockHttp.VerifyNoOutstandingExpectation(); + } - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task DeletePaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await paymentClient.DeletePaymentAsync(paymentId)); - - // Then - exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); - } + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task DeletePaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await paymentClient.DeletePaymentAsync(paymentId)); + + // Then + exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); + } - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task UpdatePaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await paymentClient.UpdatePaymentAsync(paymentId, new PaymentUpdateRequest())); - - // Then - exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); - } + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task UpdatePaymentAsync_NoPaymentIdIsGiven_ArgumentExceptionIsThrown(string paymentId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + PaymentClient paymentClient = new PaymentClient("abcde", httpClient); - private void AssertPaymentIsEqual(PaymentRequest paymentRequest, PaymentResponse paymentResponse) { - paymentResponse.Amount.Value.Should().Be(paymentRequest.Amount.Value); - paymentResponse.Amount.Currency.Should().Be(paymentRequest.Amount.Currency); - paymentResponse.Description.Should().Be(paymentRequest.Description); - if (paymentRequest.Routings != null) { - paymentResponse.Routings.Count.Should().Be(paymentRequest.Routings.Count); - for (int i = 0; i < paymentRequest.Routings.Count; i++) { - var paymentRequestRouting = paymentRequest.Routings[i]; - var paymentResponseRouting = paymentResponse.Routings[i]; - paymentResponseRouting.Amount.Should().Be(paymentRequestRouting.Amount); - paymentResponseRouting.Destination.Type.Should().Be(paymentRequestRouting.Destination.Type); - paymentResponseRouting.Destination.OrganizationId.Should().Be(paymentRequestRouting.Destination.OrganizationId); - paymentResponseRouting.ReleaseDate.Should().Be(paymentRequestRouting.ReleaseDate); - } + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await paymentClient.UpdatePaymentAsync(paymentId, new PaymentUpdateRequest())); + + // Then + exception.Message.Should().Be("Required URL argument 'paymentId' is null or empty"); + } + + private void AssertPaymentIsEqual(PaymentRequest paymentRequest, PaymentResponse paymentResponse) { + paymentResponse.Amount.Value.Should().Be(paymentRequest.Amount.Value); + paymentResponse.Amount.Currency.Should().Be(paymentRequest.Amount.Currency); + paymentResponse.Description.Should().Be(paymentRequest.Description); + if (paymentRequest.Routings != null) { + paymentResponse.Routings.Count.Should().Be(paymentRequest.Routings.Count); + for (int i = 0; i < paymentRequest.Routings.Count; i++) { + var paymentRequestRouting = paymentRequest.Routings[i]; + var paymentResponseRouting = paymentResponse.Routings[i]; + paymentResponseRouting.Amount.Should().Be(paymentRequestRouting.Amount); + paymentResponseRouting.Destination.Type.Should().Be(paymentRequestRouting.Destination.Type); + paymentResponseRouting.Destination.OrganizationId.Should().Be(paymentRequestRouting.Destination.OrganizationId); + paymentResponseRouting.ReleaseDate.Should().Be(paymentRequestRouting.ReleaseDate); } } } -} + + private const string defaultPaymentJsonResponse = @" +{ + ""resource"": ""payment"", + ""id"": ""tr_WDqYK6vllg"", + ""mode"": ""test"", + ""createdAt"": ""2018-03-20T13:13:37+00:00"", + ""amount"":{ + ""currency"":""EUR"", + ""value"":""100.00"" + }, + ""description"":""Description"", + ""method"": null, + ""metadata"": { + ""order_id"": ""12345"" + }, + ""status"": ""open"", + ""isCancelable"": false, + ""locale"": ""nl_NL"", + ""restrictPaymentMethodsToCountry"": ""NL"", + ""expiresAt"": ""2018-03-20T13:28:37+00:00"", + ""details"": null, + ""profileId"": ""pfl_QkEhN94Ba"", + ""sequenceType"": ""oneoff"", + ""redirectUrl"": ""https://webshop.example.org/order/12345/"", + ""webhookUrl"": ""https://webshop.example.org/payments/webhook/"", + ""authorizedAt"": ""2018-03-19T13:28:37+00:00"", + ""paidAt"": ""2018-03-21T13:28:37+00:00"", + ""canceledAt"": ""2018-03-22T13:28:37+00:00"", + ""expiredAt"": ""2018-03-23T13:28:37+00:00"", + ""failedAt"": ""2018-03-24T13:28:37+00:00"", + ""captureBefore"": ""2018-03-25T13:28:37+00:00"", + ""amountRefunded"": { + ""currency"": ""EUR"", + ""value"": ""10.00"" + }, + ""amountRemaining"": { + ""currency"": ""EUR"", + ""value"": ""90.00"" + }, + ""amountChargedBack"": { + ""currency"": ""EUR"", + ""value"": ""10.00"" + }, + ""cancelUrl"": ""https://webshop.example.org/order/12345/cancel"", + ""countryCode"": ""NL"", + ""settlementId"": ""stl_jDk30akdN"", + ""subscriptionId"": ""sub_rVKGtNd6s3"", + ""applicationFee"": { + ""amount"": { + ""currency"": ""EUR"", + ""value"": ""1.00"" + }, + ""description"": ""description"" + }, + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg"", + ""type"": ""application/hal+json"" + }, + ""checkout"": { + ""href"": ""https://www.mollie.com/payscreen/select-method/WDqYK6vllg"", + ""type"": ""text/html"" + }, + ""dashboard"": { + ""href"": ""https://www.mollie.com/dashboard/org_12345678/payments/tr_WDqYK6vllg"", + ""type"": ""text/html"" + }, + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/payments-api/get-payment"", + ""type"": ""text/html"" + } + } +}"; +} \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/PermissionClientTests.cs b/tests/Mollie.Tests.Unit/Client/PermissionClientTests.cs new file mode 100644 index 00000000..32bfc68c --- /dev/null +++ b/tests/Mollie.Tests.Unit/Client/PermissionClientTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Mollie.Api.Client; +using RichardSzalay.MockHttp; +using Xunit; + +namespace Mollie.Tests.Unit.Client; + +public class PermissionClientTests : BaseClientTests +{ + [Fact] + public async Task GetPermissionAsync_WithPermissionId_ResponseIsDeserializedInExpectedFormat() + { + // Arrange + const string permissionId = "payments.read"; + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}permissions/{permissionId}") + .With(request => request.Headers.Contains("Idempotency-Key")) + .Respond("application/json", defaultGetPermissionResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + using var permissionClient = new PermissionsClient("access_abcde", httpClient); + + // Act + var response = await permissionClient.GetPermissionAsync(permissionId); + + // Assert + mockHttp.VerifyNoOutstandingExpectation(); + response.Resource.Should().Be("permission"); + response.Id.Should().Be("payments.read"); + response.Description.Should().Be("View your payments"); + response.Granted.Should().BeTrue(); + response.Links.Should().NotBeNull(); + response.Links.Self.Href.Should().Be("https://api.mollie.com/v2/permissions/payments.read"); + response.Links.Self.Type.Should().Be("application/hal+json"); + response.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/permissions-api/get-permission"); + response.Links.Documentation.Type.Should().Be("text/html"); + } + + [Fact] + public async Task GetPermissionAsync_WithoutPermissionId_ThrowsArgumentException() + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + using var permissionClient = new PermissionsClient("access_abcde", httpClient); + + // Act + var exception = await Assert.ThrowsAsync(() => permissionClient.GetPermissionAsync(string.Empty)); + + // Assert + exception.Message.Should().Be($"Required URL argument 'permissionId' is null or empty"); + } + + [Fact] + public async Task GetPermissionListAsync_ResponseIsDeserializedInExpectedFormat() + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}permissions") + .With(request => request.Headers.Contains("Idempotency-Key")) + .Respond("application/json", defaultListPermissionsResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + using var permissionClient = new PermissionsClient("access_abcde", httpClient); + + // Act + var response = await permissionClient.GetPermissionListAsync(); + + // Assert + mockHttp.VerifyNoOutstandingExpectation(); + response.Count.Should().Be(2); + response.Should().NotBeNull(); + response.Items.Should().HaveCount(2); + response.Items[0].Resource.Should().Be("permission"); + response.Items[0].Id.Should().Be("payments.write"); + response.Items[0].Description.Should().Be("Create new payments"); + response.Items[0].Granted.Should().BeFalse(); + response.Items[0].Links.Should().NotBeNull(); + response.Items[0].Links.Self.Href.Should() + .Be("https://api.mollie.com/v2/permissions/payments.write"); + response.Items[0].Links.Self.Type.Should().Be("application/hal+json"); + response.Items[1].Resource.Should().Be("permission"); + response.Items[1].Id.Should().Be("payments.read"); + response.Items[1].Description.Should().Be("View your payments"); + response.Items[1].Granted.Should().BeTrue(); + response.Items[1].Links.Should().NotBeNull(); + response.Items[1].Links.Self.Href.Should() + .Be("https://api.mollie.com/v2/permissions/payments.read"); + response.Items[1].Links.Self.Type.Should().Be("application/hal+json"); + response.Links.Should().NotBeNull(); + response.Links.Self.Href.Should().Be("https://api.mollie.com/v2/permissions"); + response.Links.Self.Type.Should().Be("application/hal+json"); + response.Links.Documentation.Href.Should() + .Be("https://docs.mollie.com/reference/v2/permissions-api/list-permissions"); + response.Links.Documentation.Type.Should().Be("text/html"); + } + + private const string defaultGetPermissionResponse = @"{ + ""resource"": ""permission"", + ""id"": ""payments.read"", + ""description"": ""View your payments"", + ""granted"": true, + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/permissions/payments.read"", + ""type"": ""application/hal+json"" + }, + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/permissions-api/get-permission"", + ""type"": ""text/html"" + } + } +}"; + + private const string defaultListPermissionsResponse = @"{ + ""_embedded"": { + ""permissions"": [ + { + ""resource"": ""permission"", + ""id"": ""payments.write"", + ""description"": ""Create new payments"", + ""granted"": false, + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/permissions/payments.write"", + ""type"": ""application/hal+json"" + } + } + }, + { + ""resource"": ""permission"", + ""id"": ""payments.read"", + ""description"": ""View your payments"", + ""granted"": true, + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/permissions/payments.read"", + ""type"": ""application/hal+json"" + } + } + } + ] + }, + ""count"": 2, + ""_links"": { + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/permissions-api/list-permissions"", + ""type"": ""text/html"" + }, + ""self"": { + ""href"": ""https://api.mollie.com/v2/permissions"", + ""type"": ""application/hal+json"" + } + } +}"; +} \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/ProfileClientTests.cs b/tests/Mollie.Tests.Unit/Client/ProfileClientTests.cs index 7e2e30ef..ce396b06 100644 --- a/tests/Mollie.Tests.Unit/Client/ProfileClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/ProfileClientTests.cs @@ -81,6 +81,54 @@ public async Task GetCurrentProfileAsync_ResponseIsDeserializedInExpectedFormat( mockHttp.VerifyNoOutstandingRequest(); AssertDefaultProfileResponse(result); } + + [Fact] + public async Task GetProfileListAsync_WithNoParameters_ResponseIsDeserializedInExpectedFormat() + { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Get, $"{BaseMollieClient.ApiEndPoint}profiles") + .With(request => request.Headers.Contains("Idempotency-Key")) + .Respond("application/json", defaultGetProfileListJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + using var profileClient = new ProfileClient("abcde", httpClient); + + // Act + var result = await profileClient.GetProfileListAsync(); + + // Assert + mockHttp.VerifyNoOutstandingRequest(); + result.Count.Should().Be(1); + var profile = result.Items[0]; + profile.Resource.Should().Be("profiles"); + profile.Id.Should().Be("pfl_v9hTwCvYqw"); + profile.Mode.Should().Be(Mode.Live); + profile.Name.Should().Be("My website name"); + profile.Email.Should().Be("info@mywebsite.com"); + profile.Website.Should().Be("https://www.mywebsite.com"); + profile.Phone.Should().Be("+31208202070"); + profile.BusinessCategory.Should().Be("OTHER_MERCHANDISE"); + profile.Status.Should().Be(ProfileStatus.Verified); + profile.Review.Status.Should().Be(ReviewStatus.Pending); + profile.CreatedAt.Should().Be(DateTime.Parse("2018-03-20T09:28:37+00:00")); + profile.Links.Should().NotBeNull(); + profile.Links.Self.Href.Should().Be("https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw"); + profile.Links.Self.Type.Should().Be("application/hal+json"); + profile.Links.Dashboard.Href.Should().Be("https://www.mollie.com/dashboard/org_123456789/settings/profiles/pfl_v9hTwCvYqw"); + profile.Links.Dashboard.Type.Should().Be("text/html"); + profile.Links.Chargebacks.Href.Should().Be("https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw"); + profile.Links.Chargebacks.Type.Should().Be("application/hal+json"); + profile.Links.Methods.Href.Should().Be("https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw"); + profile.Links.Methods.Type.Should().Be("application/hal+json"); + profile.Links.Payments.Href.Should().Be("https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw"); + profile.Links.Payments.Type.Should().Be("application/hal+json"); + profile.Links.Refunds.Href.Should().Be("https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw"); + profile.Links.Refunds.Type.Should().Be("application/hal+json"); + profile.Links.CheckoutPreviewUrl.Href.Should().Be("https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw"); + profile.Links.CheckoutPreviewUrl.Type.Should().Be("text/html"); + profile.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/profiles-api/create-profile"); + profile.Links.Documentation.Type.Should().Be("text/html"); + } [Fact] public async Task UpdateProfileAsync_WithRequiredParameters_ResponseIsDeserializedInExpectedFormat() @@ -410,4 +458,77 @@ private void AssertDefaultProfileResponse(ProfileResponse result) } } }"; + + private const string defaultGetProfileListJsonResponse = @"{ + ""_embedded"": { + ""profiles"": [ + { + ""resource"": ""profiles"", + ""id"": ""pfl_v9hTwCvYqw"", + ""mode"": ""live"", + ""name"": ""My website name"", + ""website"": ""https://www.mywebsite.com"", + ""email"": ""info@mywebsite.com"", + ""phone"": ""+31208202070"", + ""businessCategory"": ""OTHER_MERCHANDISE"", + ""categoryCode"": 5399, + ""status"": ""verified"", + ""review"": { + ""status"": ""pending"" + }, + ""createdAt"": ""2018-03-20T09:28:37+00:00"", + ""_links"": { + ""self"": { + ""href"": ""https://api.mollie.com/v2/profiles/pfl_v9hTwCvYqw"", + ""type"": ""application/hal+json"" + }, + ""dashboard"": { + ""href"": ""https://www.mollie.com/dashboard/org_123456789/settings/profiles/pfl_v9hTwCvYqw"", + ""type"": ""text/html"" + }, + ""chargebacks"": { + ""href"": ""https://api.mollie.com/v2/chargebacks?profileId=pfl_v9hTwCvYqw"", + ""type"": ""application/hal+json"" + }, + ""methods"": { + ""href"": ""https://api.mollie.com/v2/methods?profileId=pfl_v9hTwCvYqw"", + ""type"": ""application/hal+json"" + }, + ""payments"": { + ""href"": ""https://api.mollie.com/v2/payments?profileId=pfl_v9hTwCvYqw"", + ""type"": ""application/hal+json"" + }, + ""refunds"": { + ""href"": ""https://api.mollie.com/v2/refunds?profileId=pfl_v9hTwCvYqw"", + ""type"": ""application/hal+json"" + }, + ""checkoutPreviewUrl"": { + ""href"": ""https://www.mollie.com/payscreen/preview/pfl_v9hTwCvYqw"", + ""type"": ""text/html"" + }, + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/profiles-api/create-profile"", + ""type"": ""text/html"" + } + } + } + ] + }, + ""count"": 1, + ""_links"": { + ""documentation"": { + ""href"": ""https://docs.mollie.com/reference/v2/profiles-api/list-profiles"", + ""type"": ""text/html"" + }, + ""self"": { + ""href"": ""https://api.mollie.com/v2/profiles?limit=5"", + ""type"": ""application/hal+json"" + }, + ""previous"": null, + ""next"": { + ""href"": ""https://api.mollie.com/v2/profiles?from=pfl_3RkSN1zuPE&limit=5"", + ""type"": ""application/hal+json"" + } + } +}"; } \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/SettlementClientTests.cs b/tests/Mollie.Tests.Unit/Client/SettlementClientTests.cs index 02d72e69..3f16c249 100644 --- a/tests/Mollie.Tests.Unit/Client/SettlementClientTests.cs +++ b/tests/Mollie.Tests.Unit/Client/SettlementClientTests.cs @@ -12,6 +12,214 @@ namespace Mollie.Tests.Unit.Client { public class SettlementClientTests : BaseClientTests { + [Fact] + public async Task ListSettlementCaptures_DefaultBehaviour_ResponseIsParsed() { + // Given: We request a list of captures + string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/{defaultSettlementId}/captures"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, defaultCaptureListJsonResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We make the request + ListResponse listCaptureResponse = await settlementsClient.ListSettlementCapturesAsync(defaultSettlementId); + + // Then: Response should be parsed + mockHttp.VerifyNoOutstandingExpectation(); + listCaptureResponse.Should().NotBeNull(); + listCaptureResponse.Count.Should().Be(1); + listCaptureResponse.Links.Self.Href.Should().Be("https://api.mollie.com/v2/settlements/stl_jDk30akdN/captures?limit=50"); + listCaptureResponse.Links.Self.Type.Should().Be("application/hal+json"); + listCaptureResponse.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/settlements-api/list-settlement-captures"); + listCaptureResponse.Links.Documentation.Type.Should().Be("text/html"); + CaptureResponse captureResponse = listCaptureResponse.Items.First(); + captureResponse.PaymentId.Should().Be(defaultPaymentId); + captureResponse.ShipmentId.Should().Be(defaultShipmentId); + captureResponse.SettlementId.Should().Be(defaultSettlementId); + captureResponse.Amount.Value.Should().Be(defaultAmountValue); + captureResponse.Amount.Currency.Should().Be(defaultAmountCurrency); + captureResponse.Links.Should().NotBeNull(); + captureResponse.Links.Self.Href.Should().Be($"https://api.mollie.com/v2/payments/{defaultPaymentId}/captures/cpt_4qqhO89gsT"); + captureResponse.Links.Self.Type.Should().Be("application/hal+json"); + captureResponse.Links.Payment.Href.Should().Be($"https://api.mollie.com/v2/payments/{defaultPaymentId}"); + captureResponse.Links.Payment.Type.Should().Be("application/hal+json"); + captureResponse.Links.Shipment.Href.Should().Be($"https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U"); + captureResponse.Links.Shipment.Type.Should().Be("application/hal+json"); + captureResponse.Links.Settlement.Href.Should().Be($"https://api.mollie.com/v2/settlements/stl_jDk30akdN"); + captureResponse.Links.Settlement.Type.Should().Be("application/hal+json"); + captureResponse.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/captures-api/get-capture"); + captureResponse.Links.Documentation.Type.Should().Be("text/html"); + } + + [Fact] + public async Task GetOpenSettlement_DefaultBehaviour_ResponseIsParsed() { + // Given: We request a list of captures + string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/open"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, defaultGetSettlementResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We make the request + SettlementResponse settlementResponse = await settlementsClient.GetOpenSettlement(); + + // Then: Response should be parsed + mockHttp.VerifyNoOutstandingExpectation(); + settlementResponse.Should().NotBeNull(); + settlementResponse.Amount.Value.Should().Be(defaultAmountValue); + settlementResponse.Amount.Currency.Should().Be(defaultAmountCurrency); + settlementResponse.Periods.Count.Should().Be(1); + settlementResponse.Periods[2018][4].InvoiceId.Should().Be(defaultInvoiceId); + settlementResponse.Periods[2018][4].Revenue.Count.Should().Be(2); + settlementResponse.Periods[2018][4].Revenue[0].Description.Should().Be("iDEAL"); + settlementResponse.Periods[2018][4].Revenue[0].Method.Should().Be("ideal"); + settlementResponse.Periods[2018][4].Revenue[0].Count.Should().Be(6); + settlementResponse.Periods[2018][4].Revenue[0].AmountNet.Value.Should().Be("86.1000"); + settlementResponse.Periods[2018][4].Revenue[0].AmountNet.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Revenue[0].AmountVat.Should().BeNull(); + settlementResponse.Periods[2018][4].Revenue[0].AmountGross.Value.Should().Be("86.1000"); + settlementResponse.Periods[2018][4].Revenue[0].AmountGross.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Revenue[1].Description.Should().Be("Refunds iDEAL"); + settlementResponse.Periods[2018][4].Revenue[1].Method.Should().Be("refund"); + settlementResponse.Periods[2018][4].Revenue[1].Count.Should().Be(2); + settlementResponse.Periods[2018][4].Revenue[1].AmountNet.Value.Should().Be("-43.2000"); + settlementResponse.Periods[2018][4].Revenue[1].AmountNet.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Revenue[1].AmountVat.Should().BeNull(); + settlementResponse.Periods[2018][4].Revenue[1].AmountGross.Value.Should().Be("43.2000"); + settlementResponse.Periods[2018][4].Revenue[1].AmountGross.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs.Count.Should().Be(2); + settlementResponse.Periods[2018][4].Costs[0].Description.Should().Be("iDEAL"); + settlementResponse.Periods[2018][4].Costs[0].Method.Should().Be("ideal"); + settlementResponse.Periods[2018][4].Costs[0].Count.Should().Be(6); + settlementResponse.Periods[2018][4].Costs[0].Rate.Fixed.Value.Should().Be("0.3500"); + settlementResponse.Periods[2018][4].Costs[0].Rate.Fixed.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[0].Rate.Percentage.Should().BeNull(); + settlementResponse.Periods[2018][4].Costs[0].AmountNet.Value.Should().Be("2.1000"); + settlementResponse.Periods[2018][4].Costs[0].AmountNet.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[0].AmountVat.Value.Should().Be("0.4410"); + settlementResponse.Periods[2018][4].Costs[0].AmountVat.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[0].AmountGross.Value.Should().Be("2.5410"); + settlementResponse.Periods[2018][4].Costs[0].AmountGross.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[1].Description.Should().Be("Refunds iDEAL"); + settlementResponse.Periods[2018][4].Costs[1].Method.Should().Be("refund"); + settlementResponse.Periods[2018][4].Costs[1].Count.Should().Be(2); + settlementResponse.Periods[2018][4].Costs[1].Rate.Fixed.Value.Should().Be("0.2500"); + settlementResponse.Periods[2018][4].Costs[1].Rate.Fixed.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[1].Rate.Percentage.Should().BeNull(); + settlementResponse.Periods[2018][4].Costs[1].AmountNet.Value.Should().Be("0.5000"); + settlementResponse.Periods[2018][4].Costs[1].AmountNet.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[1].AmountVat.Value.Should().Be("0.1050"); + settlementResponse.Periods[2018][4].Costs[1].AmountVat.Currency.Should().Be("EUR"); + settlementResponse.Periods[2018][4].Costs[1].AmountGross.Value.Should().Be("0.6050"); + settlementResponse.Periods[2018][4].Costs[1].AmountGross.Currency.Should().Be("EUR"); + settlementResponse.Links.Should().NotBeNull(); + settlementResponse.Links.Self.Href.Should().Be("https://api.mollie.com/v2/settlements/open"); + settlementResponse.Links.Self.Type.Should().Be("application/hal+json"); + settlementResponse.Links.Documentation.Href.Should().Be("https://docs.mollie.com/reference/v2/settlements-api/get-open-settlement"); + settlementResponse.Links.Documentation.Type.Should().Be("text/html"); + } + + [Fact] + public async Task GetOpenSettlement_ResponseWithEmptyPeriods_ResponseIsParsed() { + // Given: We request a list of captures + string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/open"; + var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, emptyPeriodsSettlementResponse); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We make the request + SettlementResponse settlementResponse = await settlementsClient.GetOpenSettlement(); + + // Then: Response should be parsed + mockHttp.VerifyNoOutstandingExpectation(); + settlementResponse.Should().NotBeNull(); + settlementResponse.Periods.Count.Should().Be(0); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task GetSettlementAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementAsync(settlementId)); + + // Then + exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task GetSettlementPaymentsListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementPaymentsListAsync(settlementId)); + + // Then + exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task GetSettlementRefundsListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementRefundsListAsync(settlementId)); + + // Then + exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task GetSettlementChargebacksListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementChargebacksListAsync(settlementId)); + + // Then + exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task ListSettlementCapturesAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { + // Arrange + var mockHttp = new MockHttpMessageHandler(); + HttpClient httpClient = mockHttp.ToHttpClient(); + SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); + + // When: We send the request + var exception = await Assert.ThrowsAsync(async () => await settlementsClient.ListSettlementCapturesAsync(settlementId)); + + // Then + exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); + } + + private const string defaultSettlementId = "tr_Agfg241g"; private const string defaultPaymentId = "tr_WDqYK6vllg"; private const string defaultShipmentId = "shp_3wmsgCJN4U"; @@ -37,11 +245,45 @@ public class SettlementClientTests : BaseClientTests { ""paymentId"": ""{defaultPaymentId}"", ""shipmentId"": ""{defaultShipmentId}"", ""settlementId"": ""{defaultSettlementId}"", - ""createdAt"": ""2018-08-02T09:29:56+00:00"" + ""createdAt"": ""2018-08-02T09:29:56+00:00"", + ""_links"": {{ + ""self"": {{ + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_4qqhO89gsT"", + ""type"": ""application/hal+json"" + }}, + ""payment"": {{ + ""href"": ""https://api.mollie.com/v2/payments/tr_WDqYK6vllg"", + ""type"": ""application/hal+json"" + }}, + ""shipment"": {{ + ""href"": ""https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U"", + ""type"": ""application/hal+json"" + }}, + ""settlement"": {{ + ""href"": ""https://api.mollie.com/v2/settlements/stl_jDk30akdN"", + ""type"": ""application/hal+json"" + }}, + ""documentation"": {{ + ""href"": ""https://docs.mollie.com/reference/v2/captures-api/get-capture"", + ""type"": ""text/html"" + }} + }} }} ] }}, - ""count"": 1 + ""count"": 1, + ""_links"": {{ + ""documentation"": {{ + ""href"": ""https://docs.mollie.com/reference/v2/settlements-api/list-settlement-captures"", + ""type"": ""text/html"" + }}, + ""self"": {{ + ""href"": ""https://api.mollie.com/v2/settlements/stl_jDk30akdN/captures?limit=50"", + ""type"": ""application/hal+json"" + }}, + ""previous"": null, + ""next"": null + }} }}"; private string defaultGetSettlementResponse = $@"{{ @@ -174,150 +416,5 @@ public class SettlementClientTests : BaseClientTests { }} }} }}"; - - [Fact] - public async Task ListSettlementCaptures_DefaultBehaviour_ResponseIsParsed() { - // Given: We request a list of captures - string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/{defaultSettlementId}/captures"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, defaultCaptureListJsonResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We make the request - ListResponse listCaptureResponse = await settlementsClient.ListSettlementCapturesAsync(defaultSettlementId); - - // Then: Response should be parsed - mockHttp.VerifyNoOutstandingExpectation(); - listCaptureResponse.Should().NotBeNull(); - listCaptureResponse.Count.Should().Be(1); - CaptureResponse captureResponse = listCaptureResponse.Items.First(); - captureResponse.PaymentId.Should().Be(defaultPaymentId); - captureResponse.ShipmentId.Should().Be(defaultShipmentId); - captureResponse.SettlementId.Should().Be(defaultSettlementId); - captureResponse.Amount.Value.Should().Be(defaultAmountValue); - captureResponse.Amount.Currency.Should().Be(defaultAmountCurrency); - } - - [Fact] - public async Task GetOpenSettlement_DefaultBehaviour_ResponseIsParsed() { - // Given: We request a list of captures - string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/open"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, defaultGetSettlementResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We make the request - SettlementResponse settlementResponse = await settlementsClient.GetOpenSettlement(); - - // Then: Response should be parsed - mockHttp.VerifyNoOutstandingExpectation(); - settlementResponse.Should().NotBeNull(); - settlementResponse.Amount.Value.Should().Be(defaultAmountValue); - settlementResponse.Amount.Currency.Should().Be(defaultAmountCurrency); - settlementResponse.Periods.Count.Should().Be(1); - settlementResponse.Periods[2018][4].InvoiceId.Should().Be(defaultInvoiceId); - } - - [Fact] - public async Task GetOpenSettlement_ResponseWithEmptyPeriods_ResponseIsParsed() { - // Given: We request a list of captures - string expectedUrl = $"{BaseMollieClient.ApiEndPoint}settlements/open"; - var mockHttp = this.CreateMockHttpMessageHandler(HttpMethod.Get, expectedUrl, emptyPeriodsSettlementResponse); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We make the request - SettlementResponse settlementResponse = await settlementsClient.GetOpenSettlement(); - - // Then: Response should be parsed - mockHttp.VerifyNoOutstandingExpectation(); - settlementResponse.Should().NotBeNull(); - settlementResponse.Periods.Count.Should().Be(0); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task GetSettlementAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementAsync(settlementId)); - - // Then - exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task GetSettlementPaymentsListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementPaymentsListAsync(settlementId)); - - // Then - exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task GetSettlementRefundsListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementRefundsListAsync(settlementId)); - - // Then - exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task GetSettlementChargebacksListAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await settlementsClient.GetSettlementChargebacksListAsync(settlementId)); - - // Then - exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - [InlineData(null)] - public async Task ListSettlementCapturesAsync_NoSettlementIdIsGiven_ArgumentExceptionIsThrown(string settlementId) { - // Arrange - var mockHttp = new MockHttpMessageHandler(); - HttpClient httpClient = mockHttp.ToHttpClient(); - SettlementsClient settlementsClient = new SettlementsClient("api-key", httpClient); - - // When: We send the request - var exception = await Assert.ThrowsAsync(async () => await settlementsClient.ListSettlementCapturesAsync(settlementId)); - - // Then - exception.Message.Should().Be("Required URL argument 'settlementId' is null or empty"); - } } } diff --git a/tests/Mollie.Tests.Unit/Extensions/DictionaryExtensionsTests.cs b/tests/Mollie.Tests.Unit/Extensions/DictionaryExtensionsTests.cs index 7e63ced8..35b3f2db 100644 --- a/tests/Mollie.Tests.Unit/Extensions/DictionaryExtensionsTests.cs +++ b/tests/Mollie.Tests.Unit/Extensions/DictionaryExtensionsTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using FluentAssertions; using Mollie.Api.Extensions; using Xunit; @@ -68,5 +67,33 @@ public void AddValueIfNotNullOrEmpty_ValueIsEmpty_ValueIsNotAdded() // Assert parameters.Should().BeEmpty(); } + + [Fact] + public void AddValueIfTrue_ValueIsTrue_ValueIsAdded() + { + // Arrange + var parameters = new Dictionary(); + var parameterName = "testmode"; + + // Act + parameters.AddValueIfTrue(parameterName, true); + + // Assert + parameters.Should().NotBeEmpty(); + parameters[parameterName].Should().Be(bool.TrueString.ToLower()); + } + + [Fact] + public void AddValueIfTrue_ValueIsFalse_ValueIsNotAdded() + { + // Arrange + var parameters = new Dictionary(); + + // Act + parameters.AddValueIfTrue("testmode", false); + + // Assert + parameters.Should().BeEmpty(); + } } }