diff --git a/README.md b/README.md index c11f3a66..73c17aae 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Have you spotted a bug or want to add a missing feature? All pull requests are w [15. Balances Api](#15-balances-api) [16. Terminal Api](#16-terminal-api) [17. Client Link Api](#17-client-link-api) +[18. Wallet Api](#18-wallet-api) ## 1. Getting started @@ -76,6 +77,7 @@ This library currently supports the following API's: - Balances API - Terminal API - ClientLink API +- Wallet API ### Supported .NET versions This library is built using .NET standard 2.0. This means that the package supports the following .NET implementations: @@ -1037,3 +1039,14 @@ var clientLinkUrl = response.Links.ClientLink; string result = clientLinkClient.GenerateClientLinkWithParameters(clientLinkUrl, {yourState}, {yourScope}, {forceApprovalPrompt}); ``` +## 18. Wallet Api +## Request Apple Pay Payment Session +For integrating Apple Pay in your own checkout on the web, you need to provide merchant validation. For more information on this topic, read the [Mollie documentation on Apple Pay Payment sessions](https://docs.mollie.com/reference/v2/wallets-api/request-apple-pay-payment-session). +```C# +var request = new ApplePayPaymentSessionRequest() { + Domain = "pay.mywebshop.com", + ValidationUrl = "https://apple-pay-gateway-cert.apple.com/paymentservices/paymentSession" +}; +using var walletClient = new WalletClient({yourApiClient}); +ApplePayPaymentSessionResponse response = await walletClient.RequestApplePayPaymentSessionAsync(request); +``` diff --git a/src/Mollie.Api/Client/Abstract/IWalletClient.cs b/src/Mollie.Api/Client/Abstract/IWalletClient.cs new file mode 100644 index 00000000..27d31216 --- /dev/null +++ b/src/Mollie.Api/Client/Abstract/IWalletClient.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Mollie.Api.Models.Wallet.Request; +using Mollie.Api.Models.Wallet.Response; + +namespace Mollie.Api.Client.Abstract { + public interface IWalletClient { + Task RequestApplePayPaymentSessionAsync(ApplePayPaymentSessionRequest request); + } +} \ No newline at end of file diff --git a/src/Mollie.Api/Client/WalletClient.cs b/src/Mollie.Api/Client/WalletClient.cs new file mode 100644 index 00000000..54ec9ca4 --- /dev/null +++ b/src/Mollie.Api/Client/WalletClient.cs @@ -0,0 +1,16 @@ +using System.Net.Http; +using System.Threading.Tasks; +using Mollie.Api.Client.Abstract; +using Mollie.Api.Models.Wallet.Request; +using Mollie.Api.Models.Wallet.Response; + +namespace Mollie.Api.Client { + public class WalletClient : BaseMollieClient, IWalletClient { + public WalletClient(string apiKey, HttpClient httpClient = null) : base(apiKey, httpClient) { + } + + public async Task RequestApplePayPaymentSessionAsync(ApplePayPaymentSessionRequest request) { + return await this.PostAsync("wallets/applepay/sessions", request).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Mollie.Api/DependencyInjection.cs b/src/Mollie.Api/DependencyInjection.cs index 15a3ad30..e7b4afd7 100644 --- a/src/Mollie.Api/DependencyInjection.cs +++ b/src/Mollie.Api/DependencyInjection.cs @@ -58,6 +58,8 @@ public static IServiceCollection AddMollieApi( new TerminalClient(mollieOptions.ApiKey, httpClient), retryPolicy); RegisterMollieApiClient(services, httpClient => new ClientLinkClient(mollieOptions.ClientId, mollieOptions.ApiKey, httpClient), retryPolicy); + RegisterMollieApiClient(services, httpClient => + new WalletClient(mollieOptions.ApiKey, httpClient), retryPolicy); return services; } diff --git a/src/Mollie.Api/JsonConverters/MicrosecondEpochConverter.cs b/src/Mollie.Api/JsonConverters/MicrosecondEpochConverter.cs new file mode 100644 index 00000000..7d4771f9 --- /dev/null +++ b/src/Mollie.Api/JsonConverters/MicrosecondEpochConverter.cs @@ -0,0 +1,18 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Mollie.Api.JsonConverters { + public class MicrosecondEpochConverter : DateTimeConverterBase + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + var longValue = long.Parse(reader.Value.ToString()); + return DateTimeOffset.FromUnixTimeMilliseconds(longValue).UtcDateTime; + } + } +} \ No newline at end of file diff --git a/src/Mollie.Api/Models/Wallet/Request/ApplePayPaymentSessionRequest.cs b/src/Mollie.Api/Models/Wallet/Request/ApplePayPaymentSessionRequest.cs new file mode 100644 index 00000000..69135b9e --- /dev/null +++ b/src/Mollie.Api/Models/Wallet/Request/ApplePayPaymentSessionRequest.cs @@ -0,0 +1,15 @@ +namespace Mollie.Api.Models.Wallet.Request { + public class ApplePayPaymentSessionRequest { + /// + /// The validationUrl you got from the ApplePayValidateMerchant event. + /// A list of all valid host names for merchant validation is available. You should white list these in your + /// application and reject any validationUrl that have a host name not in the list. + /// + public string ValidationUrl { get; set; } + + /// + /// The domain of your web shop, that is visible in the browser’s location bar. For example pay.myshop.com. + /// + public string Domain { get; set; } + } +} \ No newline at end of file diff --git a/src/Mollie.Api/Models/Wallet/Response/ApplePayPaymentSessionResponse.cs b/src/Mollie.Api/Models/Wallet/Response/ApplePayPaymentSessionResponse.cs new file mode 100644 index 00000000..060a0acc --- /dev/null +++ b/src/Mollie.Api/Models/Wallet/Response/ApplePayPaymentSessionResponse.cs @@ -0,0 +1,18 @@ +using System; +using Mollie.Api.JsonConverters; +using Newtonsoft.Json; + +namespace Mollie.Api.Models.Wallet.Response { + public class ApplePayPaymentSessionResponse { + [JsonConverter(typeof(MicrosecondEpochConverter))] + public DateTime EpochTimestamp { get; set; } + [JsonConverter(typeof(MicrosecondEpochConverter))] + public DateTime ExpiresAt { get; set; } + public string MerchantSessionIdentifier { get; set; } + public string Nonce { get; set; } + public string MerchantIdentifier { get; set; } + public string DomainName { get; set; } + public string DisplayName { get; set; } + public string Signature { get; set; } + } +} \ No newline at end of file diff --git a/src/Mollie.Api/Mollie.Api.csproj b/src/Mollie.Api/Mollie.Api.csproj index 0f684e55..523a0b60 100644 --- a/src/Mollie.Api/Mollie.Api.csproj +++ b/src/Mollie.Api/Mollie.Api.csproj @@ -1,7 +1,7 @@  - 3.2.0.0 + 3.3.0.0 True Vincent Kok This is a wrapper for the Mollie REST webservice. All payment methods and webservice calls are supported. @@ -9,9 +9,9 @@ Mollie Payment API https://github.com/Viincenttt/MollieApi Mollie - 3.2.0.0 - 3.2.0.0 - 3.2.0.0 + 3.3.0.0 + 3.3.0.0 + 3.3.0.0 netstandard2.0 README.md LICENSE @@ -25,8 +25,8 @@ - - + + \ No newline at end of file diff --git a/tests/Mollie.Tests.Unit/Client/WalletClientTest.cs b/tests/Mollie.Tests.Unit/Client/WalletClientTest.cs new file mode 100644 index 00000000..ba771f06 --- /dev/null +++ b/tests/Mollie.Tests.Unit/Client/WalletClientTest.cs @@ -0,0 +1,49 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Mollie.Api.Client; +using Mollie.Api.Models.Wallet.Request; +using Xunit; + +namespace Mollie.Tests.Unit.Client; + +public class WalletClientTest : BaseClientTests { + private const string defaultApplePayPaymentSessionResponse = @"{ + ""epochTimestamp"": 1555507053169, + ""expiresAt"": 1555510653169, + ""merchantSessionIdentifier"": ""SSH2EAF8AFAEAA94DEEA898162A5DAFD36E_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24"", + ""nonce"": ""0206b8db"", + ""merchantIdentifier"": ""BD62FEB196874511C22DB28A9E14A89E3534C93194F73EA417EC566368D391EB"", + ""domainName"": ""pay.example.org"", + ""displayName"": ""Chuck Norris's Store"", + ""signature"": ""308006092a864886f7...8cc030ad3000000000000"" + }"; + + [Fact] + public async Task RequestApplePayPaymentSessionAsync_ResponseIsDeserializedInExpectedFormat() { + // Arrange + var request = new ApplePayPaymentSessionRequest() { + Domain = "pay.mywebshop.com", + ValidationUrl = "https://apple-pay-gateway-cert.apple.com/paymentservices/paymentSession" + }; + var mockHttp = this.CreateMockHttpMessageHandler( + HttpMethod.Post, + $"{BaseMollieClient.ApiEndPoint}wallets/applepay/sessions", + defaultApplePayPaymentSessionResponse); + using var walletClient = new WalletClient("abcde", mockHttp.ToHttpClient()); + + // Act + var response = await walletClient.RequestApplePayPaymentSessionAsync(request); + + // Assert + response.EpochTimestamp.Should().Be(DateTimeOffset.FromUnixTimeMilliseconds(1555507053169).UtcDateTime); + response.ExpiresAt.Should().Be(DateTimeOffset.FromUnixTimeMilliseconds(1555510653169).UtcDateTime); + response.MerchantSessionIdentifier.Should().Be("SSH2EAF8AFAEAA94DEEA898162A5DAFD36E_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24"); + response.Nonce.Should().Be("0206b8db"); + response.MerchantIdentifier.Should().Be("BD62FEB196874511C22DB28A9E14A89E3534C93194F73EA417EC566368D391EB"); + response.DomainName.Should().Be("pay.example.org"); + response.DisplayName.Should().Be("Chuck Norris's Store"); + response.Signature.Should().Be("308006092a864886f7...8cc030ad3000000000000"); + } +} \ No newline at end of file