diff --git a/.travis.yml b/.travis.yml
index 9a531df0..c40737b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,7 @@
sudo: required
dist: xenial
+language: csharp
+mono: none
os:
- linux
@@ -22,10 +24,11 @@ addons:
- libicu-dev
- libssl-dev
- libunwind8
+ chrome: stable
install:
- - npm install -g bower
- - npm install -g npm
+ - npm install --global bower
+ - npm install --global npm
script:
- ./build.sh
diff --git a/.vscode/launch.json b/.vscode/launch.json
index d6519312..a1fa166a 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -6,7 +6,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
- "program": "${workspaceRoot}/src/ApplePayJS/bin/Debug/netcoreapp2.2/JustEat.ApplePayJS.dll",
+ "program": "${workspaceRoot}/src/ApplePayJS/bin/Debug/netcoreapp3.0/JustEat.ApplePayJS.dll",
"args": [],
"cwd": "${workspaceRoot}/src/ApplePayJS",
"stopAtEntry": false,
diff --git a/ApplePayJS.sln b/ApplePayJS.sln
index df0e8291..69363c86 100644
--- a/ApplePayJS.sln
+++ b/ApplePayJS.sln
@@ -14,6 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Build.ps1 = Build.ps1
build.sh = build.sh
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
+ Directory.Build.props = Directory.Build.props
global.json = global.json
LICENSE = LICENSE
NuGet.config = NuGet.config
@@ -29,6 +30,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A4EF
.github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md
EndProjectSection
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".vscode", ".vscode", "{F99C01C5-8B5D-4BDD-B40B-35DE14D08D57}"
+ ProjectSection(SolutionItems) = preProject
+ .vscode\launch.json = .vscode\launch.json
+ .vscode\tasks.json = .vscode\tasks.json
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3F1B1211-EEF9-482B-93CD-6FF250907EB9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApplePayJS.Tests", "tests\ApplePayJS.Tests\ApplePayJS.Tests.csproj", "{20EE5C1E-2059-4291-93F6-AA0F76C08EBE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +50,10 @@ Global
{4CFD067B-FD1A-4303-9322-3138E9104B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CFD067B-FD1A-4303-9322-3138E9104B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CFD067B-FD1A-4303-9322-3138E9104B1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20EE5C1E-2059-4291-93F6-AA0F76C08EBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20EE5C1E-2059-4291-93F6-AA0F76C08EBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20EE5C1E-2059-4291-93F6-AA0F76C08EBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20EE5C1E-2059-4291-93F6-AA0F76C08EBE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -46,6 +61,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{4CFD067B-FD1A-4303-9322-3138E9104B1F} = {ABF2B260-1BAE-45CD-9348-C163CA02ECA3}
{A4EFB4F4-BC05-4A67-89AC-4D7D21B71D4E} = {C697BCC9-C4F9-4AD8-8336-E90A239865DE}
+ {F99C01C5-8B5D-4BDD-B40B-35DE14D08D57} = {C697BCC9-C4F9-4AD8-8336-E90A239865DE}
+ {20EE5C1E-2059-4291-93F6-AA0F76C08EBE} = {3F1B1211-EEF9-482B-93CD-6FF250907EB9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8797D09A-407D-424A-A8F0-22FB26F0650C}
diff --git a/Build.ps1 b/Build.ps1
index 3b981dbf..16539a86 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -58,6 +58,9 @@ else {
Write-Host "Publishing solution..." -ForegroundColor Green
& $dotnet publish $solutionFile --output $OutputPath --configuration $Configuration
+Write-Host "Running tests..." -ForegroundColor Green
+& $dotnet test $solutionFile --output $OutputPath --configuration $Configuration
+
if ($LASTEXITCODE -ne 0) {
throw "dotnet publish failed with exit code $LASTEXITCODE"
}
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..50bc9752
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,20 @@
+
+
+ Martin Costello
+ Just Eat
+ Just Eat (c) 2016-$([System.DateTime]::Now.ToString(yyyy))
+ latest
+ en-US
+ enable
+ Apache-2.0
+ https://github.com/justeat/ApplePayJSSample
+ $(PackageProjectUrl)/releases
+ false
+ applepay
+ git
+ $(PackageProjectUrl).git
+ latest
+ 3.0.0
+
+
+
diff --git a/README.md b/README.md
index 64f6746b..db113828 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
|:-:|:-:|:-:|
| **Build Status** | [![Build status](https://img.shields.io/travis/justeat/ApplePayJSSample/master.svg)](https://travis-ci.org/justeat/ApplePayJSSample) | [![Build status](https://img.shields.io/appveyor/ci/justeattech/applepayjssample/master.svg)](https://ci.appveyor.com/project/justeattech/applepayjssample) |
-This repository contains a sample implementation of [Apple Pay JS](https://developer.apple.com/reference/applepayjs/) using ASP.NET Core 2.2 written in C# and JavaScript.
+This repository contains a sample implementation of [Apple Pay JS](https://developer.apple.com/reference/applepayjs/) using ASP.NET Core 3.0 written in C# and JavaScript.
## Overview
@@ -24,7 +24,7 @@ The key components to look at for the implementation are:
To setup the repository to run the sample, perform the steps below:
- 1. Install the [.NET Core 2.2.402 SDK](https://www.microsoft.com/net/download/core), Visual Studio 2019 or Visual Studio Code.
+ 1. Install the [.NET Core 3.0.100 SDK](https://www.microsoft.com/net/download/core), Visual Studio 2019 or Visual Studio Code.
1. Fork this repository.
1. Clone the repository from your fork to your local machine: ```git clone https://github.com/{username}/ApplePayJSSample.git```
1. Restore the Bower, npm and NuGet packages.
diff --git a/appveyor.yml b/appveyor.yml
index fa7a4535..d78288a6 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-os: Visual Studio 2017
+os: Visual Studio 2019
version: 3.0.{build}
environment:
@@ -10,8 +10,9 @@ branches:
- master
install:
- - ps: npm install -g bower --loglevel=error
- - ps: npm install -g npm
+ - ps: npm install --global bower --loglevel=error
+ - ps: npm install --global npm
+ - ps: choco upgrade googlechrome --confirm --ignore-checksums --no-progress
build_script:
- ps: .\Build.ps1
diff --git a/build.sh b/build.sh
index a73cb7de..32508e27 100755
--- a/build.sh
+++ b/build.sh
@@ -31,7 +31,9 @@ export PATH="$DOTNET_INSTALL_DIR:$PATH"
dotnet_version=$(dotnet --version)
if [ "$dotnet_version" != "$CLI_VERSION" ]; then
+ curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version "2.2.402" --install-dir "$DOTNET_INSTALL_DIR"
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version "$CLI_VERSION" --install-dir "$DOTNET_INSTALL_DIR"
fi
dotnet publish ./ApplePayJS.sln --output $artifacts/publish --configuration $configuration || exit 1
+dotnet test ./ApplePayJS.sln --output $artifacts --configuration $configuration || exit 1
diff --git a/global.json b/global.json
index 89b3b0f4..79422f0c 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "2.2.402"
+ "version": "3.0.100"
}
}
diff --git a/src/ApplePayJS/ApplePayJS.csproj b/src/ApplePayJS/ApplePayJS.csproj
index c4e74d3f..f3c79a45 100644
--- a/src/ApplePayJS/ApplePayJS.csproj
+++ b/src/ApplePayJS/ApplePayJS.csproj
@@ -1,14 +1,8 @@
- inprocess
- Martin Costello
+ InProcess
JustEat.ApplePayJS
- Just Eat
- Just Eat (c) 2016-$([System.DateTime]::Now.ToString(yyyy))
- latest
- en-US
Exe
- https://avatars3.githubusercontent.com/u/1516790?v=3&s=100
JustEat.ApplePayJS
Apache-2.0
https://github.com/justeat/ApplePayJSSample
@@ -16,21 +10,14 @@
false
applepay
true
- git
- $(PackageProjectUrl).git
JustEat.ApplePayJS
- netcoreapp2.2
+ netcoreapp3.0
latest
JustEat.ApplePayJS
- 3.0.0
-
-
-
-
true
@@ -50,6 +37,6 @@
-
+
diff --git a/src/ApplePayJS/Clients/ApplePayClient.cs b/src/ApplePayJS/Clients/ApplePayClient.cs
index 29587ba9..aaa3528b 100644
--- a/src/ApplePayJS/Clients/ApplePayClient.cs
+++ b/src/ApplePayJS/Clients/ApplePayClient.cs
@@ -1,8 +1,13 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
using System;
using System.Net.Http;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
namespace JustEat.ApplePayJS.Clients
{
@@ -15,19 +20,24 @@ public ApplePayClient(HttpClient httpClient)
_httpClient = httpClient;
}
- public async Task GetMerchantSessionAsync(
+ public async Task GetMerchantSessionAsync(
Uri requestUri,
MerchantSessionRequest request,
CancellationToken cancellationToken = default)
{
// POST the data to create a valid Apple Pay merchant session.
- using (var response = await _httpClient.PostAsJsonAsync(requestUri, request, cancellationToken))
- {
- response.EnsureSuccessStatusCode();
+ string json = JsonSerializer.Serialize(request);
+
+ using var content = new StringContent(json, Encoding.UTF8, MediaTypeNames.Application.Json);
+
+ using var response = await _httpClient.PostAsync(requestUri, content, cancellationToken);
+
+ response.EnsureSuccessStatusCode();
+
+ // Read the opaque merchant session JSON from the response body.
+ using var stream = await response.Content.ReadAsStreamAsync();
- // Read the opaque merchant session JSON from the response body.
- return await response.Content.ReadAsAsync(cancellationToken);
- }
+ return await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
}
}
}
diff --git a/src/ApplePayJS/Clients/MerchantCertificate.cs b/src/ApplePayJS/Clients/MerchantCertificate.cs
index 351068d3..4e4ac73a 100644
--- a/src/ApplePayJS/Clients/MerchantCertificate.cs
+++ b/src/ApplePayJS/Clients/MerchantCertificate.cs
@@ -1,3 +1,6 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@@ -32,7 +35,7 @@ public string GetMerchantIdentifier()
{
try
{
- var merchantCertificate = GetCertificate();
+ using var merchantCertificate = GetCertificate();
return GetMerchantIdentifier(merchantCertificate);
}
catch (InvalidOperationException)
@@ -76,23 +79,21 @@ private X509Certificate2 LoadCertificateFromStore()
// your application, but it is also required to be able to use an X.509
// certificate with a private key if the user profile is not available,
// such as when using IIS hosting in an environment such as Microsoft Azure.
- using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
- {
- store.Open(OpenFlags.ReadOnly);
-
- var certificates = store.Certificates.Find(
- X509FindType.FindByThumbprint,
- _options.MerchantCertificateThumbprint?.Trim(),
- validOnly: false);
+ using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadOnly);
- if (certificates.Count < 1)
- {
- throw new InvalidOperationException(
- $"Could not find Apple Pay merchant certificate with thumbprint '{_options.MerchantCertificateThumbprint}' from store '{store.Name}' in location '{store.Location}'.");
- }
+ var certificates = store.Certificates.Find(
+ X509FindType.FindByThumbprint,
+ _options.MerchantCertificateThumbprint?.Trim(),
+ validOnly: false);
- return certificates[0];
+ if (certificates.Count < 1)
+ {
+ throw new InvalidOperationException(
+ $"Could not find Apple Pay merchant certificate with thumbprint '{_options.MerchantCertificateThumbprint}' from store '{store.Name}' in location '{store.Location}'.");
}
+
+ return certificates[0];
}
}
}
diff --git a/src/ApplePayJS/Clients/MerchantSessionRequest.cs b/src/ApplePayJS/Clients/MerchantSessionRequest.cs
index 07eb9289..73dab2ae 100644
--- a/src/ApplePayJS/Clients/MerchantSessionRequest.cs
+++ b/src/ApplePayJS/Clients/MerchantSessionRequest.cs
@@ -1,19 +1,22 @@
-using Newtonsoft.Json;
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System.Text.Json.Serialization;
namespace JustEat.ApplePayJS.Clients
{
public class MerchantSessionRequest
{
- [JsonProperty("merchantIdentifier")]
- public string MerchantIdentifier { get; set; }
+ [JsonPropertyName("merchantIdentifier")]
+ public string? MerchantIdentifier { get; set; }
- [JsonProperty("displayName")]
- public string DisplayName { get; set; }
+ [JsonPropertyName("displayName")]
+ public string? DisplayName { get; set; }
- [JsonProperty("initiative")]
- public string Initiative { get; set; }
+ [JsonPropertyName("initiative")]
+ public string? Initiative { get; set; }
- [JsonProperty("initiativeContext")]
- public string InitiativeContext { get; set; }
+ [JsonPropertyName("initiativeContext")]
+ public string? InitiativeContext { get; set; }
}
}
diff --git a/src/ApplePayJS/Controllers/HomeController.cs b/src/ApplePayJS/Controllers/HomeController.cs
index 06a6ab09..1c94e1c6 100644
--- a/src/ApplePayJS/Controllers/HomeController.cs
+++ b/src/ApplePayJS/Controllers/HomeController.cs
@@ -4,6 +4,8 @@
namespace JustEat.ApplePayJS.Controllers
{
using System;
+ using System.Net.Mime;
+ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using JustEat.ApplePayJS.Clients;
@@ -11,7 +13,6 @@ namespace JustEat.ApplePayJS.Controllers
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Models;
- using Newtonsoft.Json.Linq;
public class HomeController : Controller
{
@@ -42,7 +43,7 @@ public IActionResult Index()
}
[HttpPost]
- [Produces("application/json")]
+ [Produces(MediaTypeNames.Application.Json)]
[Route("applepay/validate", Name = "MerchantValidation")]
public async Task Validate([FromBody] ValidateMerchantSessionModel model, CancellationToken cancellationToken = default)
{
@@ -51,7 +52,7 @@ public async Task Validate([FromBody] ValidateMerchantSessionMode
// these servers are available here: https://developer.apple.com/documentation/applepayjs/setting_up_server_requirements
if (!ModelState.IsValid ||
string.IsNullOrWhiteSpace(model?.ValidationUrl) ||
- !Uri.TryCreate(model.ValidationUrl, UriKind.Absolute, out Uri requestUri))
+ !Uri.TryCreate(model.ValidationUrl, UriKind.Absolute, out Uri? requestUri))
{
return BadRequest();
}
@@ -65,10 +66,10 @@ public async Task Validate([FromBody] ValidateMerchantSessionMode
MerchantIdentifier = _certificate.GetMerchantIdentifier(),
};
- JObject merchantSession = await _client.GetMerchantSessionAsync(requestUri, request, cancellationToken);
+ JsonDocument merchantSession = await _client.GetMerchantSessionAsync(requestUri, request, cancellationToken);
// Return the merchant session as-is to the JavaScript as JSON.
- return Json(merchantSession);
+ return Json(merchantSession.RootElement);
}
public IActionResult Error() => View();
diff --git a/src/ApplePayJS/Models/ApplePayOptions.cs b/src/ApplePayJS/Models/ApplePayOptions.cs
index 9a549a17..3749dd7f 100644
--- a/src/ApplePayJS/Models/ApplePayOptions.cs
+++ b/src/ApplePayJS/Models/ApplePayOptions.cs
@@ -5,14 +5,14 @@ namespace JustEat.ApplePayJS.Models
{
public class ApplePayOptions
{
- public string StoreName { get; set; }
+ public string? StoreName { get; set; }
public bool UseCertificateStore { get; set; }
- public string MerchantCertificateFileName { get; set; }
+ public string? MerchantCertificateFileName { get; set; }
- public string MerchantCertificatePassword { get; set; }
+ public string? MerchantCertificatePassword { get; set; }
- public string MerchantCertificateThumbprint { get; set; }
+ public string? MerchantCertificateThumbprint { get; set; }
}
}
diff --git a/src/ApplePayJS/Models/HomeModel.cs b/src/ApplePayJS/Models/HomeModel.cs
index af07e74e..c84a82dc 100644
--- a/src/ApplePayJS/Models/HomeModel.cs
+++ b/src/ApplePayJS/Models/HomeModel.cs
@@ -1,9 +1,12 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
namespace JustEat.ApplePayJS.Models
{
public class HomeModel
{
- public string MerchantId { get; set; }
+ public string? MerchantId { get; set; }
- public string StoreName { get; set; }
+ public string? StoreName { get; set; }
}
}
diff --git a/src/ApplePayJS/Models/ValidateMerchantSessionModel.cs b/src/ApplePayJS/Models/ValidateMerchantSessionModel.cs
index 21edf06e..e6d2a9de 100644
--- a/src/ApplePayJS/Models/ValidateMerchantSessionModel.cs
+++ b/src/ApplePayJS/Models/ValidateMerchantSessionModel.cs
@@ -9,6 +9,6 @@ public class ValidateMerchantSessionModel
{
[DataType(DataType.Url)]
[Required]
- public string ValidationUrl { get; set; }
+ public string? ValidationUrl { get; set; }
}
}
diff --git a/src/ApplePayJS/Program.cs b/src/ApplePayJS/Program.cs
index 99b45ed2..ecd07550 100644
--- a/src/ApplePayJS/Program.cs
+++ b/src/ApplePayJS/Program.cs
@@ -3,18 +3,18 @@
namespace JustEat.ApplePayJS
{
- using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.Hosting;
public static class Program
{
public static void Main(string[] args)
{
- CreateWebHostBuilder(args).Build().Run();
+ CreateHostBuilder(args).Build().Run();
}
- public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseStartup();
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults((builder) => builder.UseStartup());
}
}
diff --git a/src/ApplePayJS/Startup.cs b/src/ApplePayJS/Startup.cs
index aacf62d3..f7c58ddf 100644
--- a/src/ApplePayJS/Startup.cs
+++ b/src/ApplePayJS/Startup.cs
@@ -11,26 +11,27 @@ namespace JustEat.ApplePayJS
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Hosting;
using Models;
public class Startup
{
- public Startup(IHostingEnvironment env, IConfiguration configuration)
+ public Startup(IHostEnvironment environment, IConfiguration configuration)
{
Configuration = configuration;
- Environment = env;
+ Environment = environment;
}
public IConfiguration Configuration { get; }
- public IHostingEnvironment Environment { get; }
+ public IHostEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure(Configuration.GetSection("ApplePay"));
- services.AddAntiforgery(options =>
+ services.AddAntiforgery((options) =>
{
options.Cookie.Name = "antiforgerytoken";
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
@@ -38,7 +39,7 @@ public void ConfigureServices(IServiceCollection services)
});
services.AddMvc(
- options =>
+ (options) =>
{
// Apple Pay JS requires pages to be served over HTTPS
if (Environment.IsProduction())
@@ -47,7 +48,7 @@ public void ConfigureServices(IServiceCollection services)
options.Filters.Add(new RequireHttpsAttribute());
}
})
- .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
+ .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
// Register class for managing the application's use of the Apple Pay merchant certificate
services.AddSingleton();
@@ -73,9 +74,9 @@ public void ConfigureServices(IServiceCollection services)
});
}
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ public void Configure(IApplicationBuilder app)
{
- if (env.IsDevelopment())
+ if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
@@ -94,7 +95,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
ServeUnknownFileTypes = true, // Required to serve the files in the .well-known folder
});
- app.UseMvcWithDefaultRoute();
+ app.UseRouting();
+ app.UseEndpoints((endpoints) => endpoints.MapDefaultControllerRoute());
}
}
}
diff --git a/src/ApplePayJS/Views/Home/_Polyfill.cshtml b/src/ApplePayJS/Views/Home/_Polyfill.cshtml
index b1de3032..c6520eb0 100644
--- a/src/ApplePayJS/Views/Home/_Polyfill.cshtml
+++ b/src/ApplePayJS/Views/Home/_Polyfill.cshtml
@@ -1,3 +1,8 @@
+@*
+ Copyright (c) Just Eat, 2016. All rights reserved.
+ Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+*@
+
@model string
@*
Use this polyfill during development to test your Apple Pay JS implementation
diff --git a/tests/ApplePayJS.Tests/ApplePayJS.Tests.csproj b/tests/ApplePayJS.Tests/ApplePayJS.Tests.csproj
new file mode 100644
index 00000000..c619c322
--- /dev/null
+++ b/tests/ApplePayJS.Tests/ApplePayJS.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+ netcoreapp3.0
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/ApplePayJS.Tests/IntegrationTests.cs b/tests/ApplePayJS.Tests/IntegrationTests.cs
new file mode 100644
index 00000000..1eb33b4b
--- /dev/null
+++ b/tests/ApplePayJS.Tests/IntegrationTests.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using JustEat.HttpClientInterception;
+using OpenQA.Selenium;
+using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Support.UI;
+using Shouldly;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace ApplePayJS.Tests
+{
+ public class IntegrationTests : IAsyncLifetime
+ {
+ public IntegrationTests(ITestOutputHelper outputHelper)
+ {
+ Fixture = new TestFixture()
+ {
+ OutputHelper = outputHelper,
+ };
+ }
+
+ private TestFixture Fixture { get; }
+
+ public async Task InitializeAsync()
+ {
+ await Fixture.StartServerAsync();
+ }
+
+ public Task DisposeAsync()
+ {
+ if (Fixture != null)
+ {
+ Fixture.Dispose();
+ }
+
+ return Task.CompletedTask;
+ }
+
+ [Fact]
+ public void Can_Pay_With_Apple_Pay()
+ {
+ // Arrange
+ var builder = new HttpRequestInterceptionBuilder()
+ .Requests()
+ .ForPost()
+ .ForUrl("https://apple-pay-gateway-cert.apple.com/paymentservices/startSession")
+ .Responds()
+ .WithJsonContent(new { })
+ .RegisterWith(Fixture.Interceptor);
+
+ using var driver = CreateWebDriver();
+ driver.Navigate().GoToUrl(Fixture.ServerAddress);
+
+ // Act
+ driver.FindElement(By.Id("amount")).Clear();
+ driver.FindElement(By.Id("amount")).SendKeys("1.23");
+ driver.FindElement(By.Id("apple-pay-button")).Click();
+
+ // Assert
+ var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
+ wait.Until((p) => p.FindElement(By.ClassName("card-name")).Displayed);
+
+ driver.FindElement(By.ClassName("card-name")).Text.ShouldBe("American Express");
+ driver.FindElement(By.Id("billing-contact")).FindElement(By.ClassName("contact-name")).Text.ShouldBe("John Smith");
+ driver.FindElement(By.Id("shipping-contact")).FindElement(By.ClassName("contact-name")).Text.ShouldBe("John Smith");
+ }
+
+ private static IWebDriver CreateWebDriver()
+ {
+ string chromeDriverDirectory = Path.GetDirectoryName(typeof(IntegrationTests).Assembly.Location) ?? ".";
+
+ var options = new ChromeOptions()
+ {
+ AcceptInsecureCertificates = true,
+ };
+
+ if (!System.Diagnostics.Debugger.IsAttached)
+ {
+ options.AddArgument("--headless");
+ }
+
+ return new ChromeDriver(chromeDriverDirectory, options);
+ }
+ }
+}
diff --git a/tests/ApplePayJS.Tests/MerchantCertificateGenerator.cs b/tests/ApplePayJS.Tests/MerchantCertificateGenerator.cs
new file mode 100644
index 00000000..4a47a145
--- /dev/null
+++ b/tests/ApplePayJS.Tests/MerchantCertificateGenerator.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Net;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace ApplePayJS.Tests
+{
+ public static class MerchantCertificateGenerator
+ {
+ [Fact(Skip = "Enable this test to generate a new dummy Apple Pay merchant certificate to use for the tests.")]
+ public static async Task Generate_Fake_Apple_Pay_Merchant_Certificate()
+ {
+ // Arrange
+ string certificateName = "applepay.local";
+ string certificatePassword = "Pa55w0rd!";
+ string certificateFileName = "CHANGE_ME";
+
+ var builder = new SubjectAlternativeNameBuilder();
+ builder.AddIpAddress(IPAddress.Loopback);
+ builder.AddIpAddress(IPAddress.IPv6Loopback);
+ builder.AddDnsName("localhost");
+
+ var distinguishedName = new X500DistinguishedName($"CN={certificateName}");
+
+ using var key = RSA.Create(2048);
+ var request = new CertificateRequest(distinguishedName, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+
+ request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyAgreement | X509KeyUsageFlags.KeyEncipherment, false));
+ request.CertificateExtensions.Add(new X509Extension("1.2.840.113635.100.6.32", Guid.NewGuid().ToByteArray(), false));
+ request.CertificateExtensions.Add(builder.Build());
+
+ var utcNow = DateTimeOffset.UtcNow;
+
+ X509Certificate2 certificate = request.CreateSelfSigned(utcNow.AddDays(-1), utcNow.AddDays(3650));
+ certificate.FriendlyName = certificateName;
+
+ byte[] pfxBytes = certificate.Export(X509ContentType.Pfx, certificatePassword);
+
+ File.Delete(certificateFileName);
+ await File.WriteAllBytesAsync(certificateFileName, pfxBytes);
+ }
+ }
+}
diff --git a/tests/ApplePayJS.Tests/TestFixture.cs b/tests/ApplePayJS.Tests/TestFixture.cs
new file mode 100644
index 00000000..19247fb2
--- /dev/null
+++ b/tests/ApplePayJS.Tests/TestFixture.cs
@@ -0,0 +1,164 @@
+// Copyright (c) Just Eat, 2016. All rights reserved.
+// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using JustEat.ApplePayJS;
+using JustEat.HttpClientInterception;
+using MartinCostello.Logging.XUnit;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Http;
+using Microsoft.Extensions.Logging;
+using Xunit.Abstractions;
+
+namespace ApplePayJS.Tests
+{
+ public class TestFixture : WebApplicationFactory, ITestOutputHelperAccessor
+ {
+ private IHost? _host;
+ private bool _disposed;
+
+ public TestFixture()
+ : base()
+ {
+ ClientOptions.AllowAutoRedirect = false;
+ ClientOptions.BaseAddress = new Uri("https://localhost");
+ Interceptor = new HttpClientInterceptorOptions().ThrowsOnMissingRegistration();
+ }
+
+ public HttpClientInterceptorOptions Interceptor { get; }
+
+ public ITestOutputHelper? OutputHelper { get; set; }
+
+ public Uri ServerAddress => ClientOptions.BaseAddress;
+
+ public async Task StartServerAsync()
+ {
+ if (_host == null)
+ {
+ await CreateHttpServer();
+ }
+ }
+
+ protected override void ConfigureWebHost(IWebHostBuilder builder)
+ {
+ builder.ConfigureServices(
+ (services) => services.AddSingleton(
+ (_) => new HttpRequestInterceptionFilter(Interceptor)));
+
+ builder.ConfigureAppConfiguration(ConfigureTests)
+ .ConfigureLogging((loggingBuilder) => loggingBuilder.ClearProviders().AddXUnit(this).AddDebug())
+ .UseContentRoot(GetContentRootPath());
+
+ builder.ConfigureKestrel(
+ (kestrelOptions) => kestrelOptions.ConfigureHttpsDefaults(
+ (connectionOptions) => connectionOptions.ServerCertificate = new X509Certificate2("localhost-dev.pfx", "Pa55w0rd!")));
+
+ builder.UseUrls(ServerAddress.ToString());
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _host?.Dispose();
+ }
+
+ _disposed = true;
+ }
+ }
+
+ private static void ConfigureTests(IConfigurationBuilder builder)
+ {
+ string? directory = Path.GetDirectoryName(typeof(TestFixture).Assembly.Location);
+ string fullPath = Path.Combine(directory ?? ".", "testsettings.json");
+
+ builder.AddJsonFile(fullPath);
+ }
+
+ private static Uri FindFreeServerAddress()
+ {
+ int port = GetFreePortNumber();
+
+ return new UriBuilder()
+ {
+ Scheme = "https",
+ Host = "localhost",
+ Port = port,
+ }.Uri;
+ }
+
+ private static int GetFreePortNumber()
+ {
+ var listener = new TcpListener(IPAddress.Loopback, 0);
+ listener.Start();
+
+ try
+ {
+ return ((IPEndPoint)listener.LocalEndpoint).Port;
+ }
+ finally
+ {
+ listener.Stop();
+ }
+ }
+
+ private async Task CreateHttpServer()
+ {
+ // Configure the server address for the server to listen on for HTTP requests
+ ClientOptions.BaseAddress = FindFreeServerAddress();
+
+ var builder = CreateHostBuilder().ConfigureWebHost(ConfigureWebHost);
+
+ _host = builder.Build();
+
+ // Force creation of the Kestrel server and start it
+ var hostedService = _host.Services.GetService();
+ await hostedService.StartAsync(default);
+ }
+
+ private string GetContentRootPath()
+ {
+ var attribute = GetTestAssemblies()
+ .SelectMany((p) => p.GetCustomAttributes())
+ .Where((p) => string.Equals(p.Key, "JustEat.ApplePayJS", StringComparison.OrdinalIgnoreCase))
+ .OrderBy((p) => p.Priority)
+ .First();
+
+ return attribute.ContentRootPath;
+ }
+
+ private sealed class HttpRequestInterceptionFilter : IHttpMessageHandlerBuilderFilter
+ {
+ internal HttpRequestInterceptionFilter(HttpClientInterceptorOptions options)
+ {
+ Options = options;
+ }
+
+ private HttpClientInterceptorOptions Options { get; }
+
+ public Action Configure(Action next)
+ {
+ return (builder) =>
+ {
+ next(builder);
+ builder.AdditionalHandlers.Add(Options.CreateHttpMessageHandler());
+ };
+ }
+ }
+ }
+}
diff --git a/tests/ApplePayJS.Tests/applepay-dev.pfx b/tests/ApplePayJS.Tests/applepay-dev.pfx
new file mode 100644
index 00000000..dad6b947
Binary files /dev/null and b/tests/ApplePayJS.Tests/applepay-dev.pfx differ
diff --git a/tests/ApplePayJS.Tests/localhost-dev.pfx b/tests/ApplePayJS.Tests/localhost-dev.pfx
new file mode 100644
index 00000000..c30b0ab5
Binary files /dev/null and b/tests/ApplePayJS.Tests/localhost-dev.pfx differ
diff --git a/tests/ApplePayJS.Tests/testsettings.json b/tests/ApplePayJS.Tests/testsettings.json
new file mode 100644
index 00000000..c4ee5e9b
--- /dev/null
+++ b/tests/ApplePayJS.Tests/testsettings.json
@@ -0,0 +1,17 @@
+{
+ "ApplePay": {
+ "DefaultLanguage": "en-GB",
+ "StoreName": "Test Store",
+ "UseCertificateStore": false,
+ "MerchantCertificateFileName": "applepay-dev.pfx",
+ "MerchantCertificatePassword": "Pa55w0rd!",
+ "UsePolyfill": true
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft": "Warning",
+ "System": "Warning"
+ }
+ }
+}
diff --git a/tests/ApplePayJS.Tests/xunit.runner.json b/tests/ApplePayJS.Tests/xunit.runner.json
new file mode 100644
index 00000000..1d280220
--- /dev/null
+++ b/tests/ApplePayJS.Tests/xunit.runner.json
@@ -0,0 +1,3 @@
+{
+ "methodDisplay": "method"
+}