Apple Pay on the Web with MVC .NET

01.03.17   Byron Calisto

Apple Pay is a convenient, secure and fast way to make payments. It is mostly used in retail store point of sales terminals, where the customer uses his/her iPhone or Apple Watch to perform payments instead of swiping a conventional plastic card. But it is also used on apps that sell goods or services, such as Uber, Starbucks, Target, and others. In these apps the user, instead of entering debit/credit card details when buying something, selects one of the cards stored in the Wallet app, and performs the payment by just choosing the "Apple Pay" method and confirming the payment with his/her fingerprint.

Apple now has extended Apple Pay's functionality for regular e-commerce websites, by introducing its new Apple Pay JS API. This API allows merchants to integrate Apple Pay into their e-commerce platforms, and offer this payment option to customers. Unfortunately it only works with the Safari web browser in Apple's platforms (macOS 10.12+ and iOS 10+)

Although the API is client-side JavaScript, you still need to implement server-side functionality. Most examples on the web show server-side implementations in PHP, there aren't many that explain how to do it in a regular MVC .NET web application. Here I will guide you on how to do it for MVC .NET, and the potential problems you might find (especially with certificates).

The following is a checklist of the things you'll have to do when implementing Apple Pay JS on a .NET application:

  • If you don't already have one, open a developer account with Apple in https://developer.apple.com
  • Make sure your server can be accessed from the outside Internet with a valid domain. Obtain a valid website certificate so you can set up the IIS web app with HTTPS.
  • Follow the steps in https://developer.apple.com/reference/applepayjs to create a new merchant ID and register and validate your domain on the certificates, IDs and profiles section of the Apple Developer Portal.
  • On the same Merchant IDs option in the Certificates, IDs and profiles section of the Apple Pay Developer Portal, select the merchant ID you just created, and generate a Merchant Identity Certificate.
  • On the same section, generate a payment processing certificate by following the instructions provided in that same option.
  • Import the generated Merchant ID Certificate and private key into your server.
  • Implement the merchant validation logic in your MVC .NET application
  • Implement the client-side resources and logic using the Apple Pay JS API.
  • Deploy the web app to IIS in the server and test (you won't be able to test locally).

Merchant ID Certificate Generation and Import

Important note: You can also create the CSR and private key directly in your Windows Server. This reference is only when you create it in a Mac computer and need to move the generated certificate and private key to a Windows Server.

The "Easy" Way

Follow the instructions in the Apple Developer Portal Merchant Certificate generation page.

Submit the CSR file to the Developer Portal, and download the generated certificate file (called "merchant_id.cer"). Go to the folder where the file was download it, and double-click it to import it into your keychain. Once this is done, open the Keychain Access app and open the "login" keychain. Open the "Certificates" category, and locate the certificate. It should be called something like "Apple Pay Merchant Identity:merchant.xxxxx", where xxxxx will be the merchant ID you chose when creating it. Click on the pull-down arrow on the left of the the certificate name to see the private key (if you don't see the arrow, then you have done something wrong during the certificate creation).

Hold the Command key and click on both, the certificate and the private key. Right click on the selected items, and click on "Export 2 items". Choose a name (such as "merchant_id.p12") and save the file. You will be asked for a password. You can leave it blank, or if you enter it, make sure you take note of it since you will need it when importing it into your Windows Server machine. Copy the generated .p12 file to your Windows Server machine.

The "Hard" Way

Do this if you don't want to import the certificate into your Mac, and want to mess around with the openssl command. Follow the instructions in the Apple Developer Portal Merchant Certificate generation page. When creating the CSR (certificate signing request) as per the instructions in the Apple Developer Portal, you'll be able to enter any Common Name. Take note of this name, since you will need it to locate the private key and be able to export it. For this example, I chose the common name "ApplePay Test".

Submit the CSR file to the Developer Portal, and download the generated certificate file (called "merchant_id.cer"). Do NOT double-click it to import it to your Mac. Open the Keychain Access app, and open the "login" keychain. Under category, select "Keys". Look for the entries named "ApplePay Test" (or the name you have chosen as the common name). You will see 2 entries, one for the public key and the other for the private key (you can see this under the "Kind" column). Right click on the private key entry, and choose the export option. Enter a (preferably) short name for the exported .p12 file (for this example I named it "apple-pay-test-key.p12") and leave the password blank. Make sure to place the .p12 file on the same folder as the merchant_id.cer file.

Now we need to generate a .p12 file that will contain both, the private key and the merchant certificate. To do this, open a terminal window, change to the folder where the key and certificate files are located, and enter the following commands:

openssl x509 -inform der -inmerchant_id.cer -outmerchant_id.pem
openssl pkcs12 -nocerts -inapple-pay-test-key.p12 -outapple-pay-test-key.pem

After running the second command, when it asks for a PEM passphrase, enter 1234. Now enter the following command, and when asked for the passphrase, enter 1234 (this will create a "pure" key file without encryption):

openssl rsa -inapple-pay-test-key.pem -outapple-pay-test.key

Now, we can create the .p12 file that will include both the merchant certificate and the private key (when asked for a password, you can leave it blank, but if you specify one, take note of it):

openssl pkcs12 -export -inmerchant_id.pem -inkey apple-pay-test.key -outmerchant_id.p12

Close the terminal app, and copy the generated "merchant_id.p12" file to your Windows server.

Import certificate into Windows Server Machine

On the Windows server machine, go to the folder where you placed the "merchant_id.p12" file. Double-click it and import it to the Local Machine. On one of the wizard pages, it will ask for the password. If you specified a password during the file creation, enter it, otherwise leave it blank. On the Certificate Store wizard page, select "Place certificates in the following store", click on Browse and select the "Personal" folder. Finish the wizard, and the certificate import should be successful.

You will need to give the IIS_IUSRS user access to the certificate private key. Open a management console (mmc) and add a certificates snap-in for the computer account (Local computer). Open the Personal → Certificates node, and right click on the "Apple Pay Merchant identity:merchant.xxxx" certificate (where "xxxx" will appear the name of the merchant ID you specified when creating it). On the popup menu, open All Tasks, and select Manage Private Keys. You will get a permissions window similar to the one for regular files. Click on Add, look for the IIS_IUSRS user and click OK. Click on Apply, and click OK to close the permissions window. Close the management console. The certificate is ready for use.

Creating the Server-Side Validation Service

For this specific demo implementation, I created a simple MVC .NET web application using the standard template provided by Visual Studio. In your code, create a manager class that will contain some utility methods. For this example I named it "ApplePayManager". The first method will handle the certificate loading:

publicstaticX509Certificate2 GetMerchantCertificate()
{
X509Certificate2 certificate =null;
 
var certStore =newX509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
 
try
{
certificate = certStore.Certificates.Find(X509FindType.FindByThumbprint,"[ENTER HERE CERT THUMBPRINT]", validOnly:false)[0];
}
catch(Exception ex)
{
thrownewException("An error occurred when loading the merchant certificate.", ex);
}
finally
{
certStore.Close();
}
 
returncertificate;
}

Replace the "Enter Here Cert Thumbprint" placeholder with the merchant ID cert thumbprint. You can also have this in your Web.config file's appSettings section, and use ConfigurationManager.AppSettings to insert it here. Make sure to enter the thumbprint hex value with no spaces, and all caps.

In this ApplePayManager class we also create a small method to create a secure HTTP Client:

publicstaticHttpClient GetSecureHttpClient(X509Certificate2 certificate)
{
var handler =newWebRequestHandler();
handler.ClientCertificates.Add(certificate);
 
var httpClient =newHttpClient(handler,true);
 
//Set security protocol to TLS 1.2 only (REQUIRED by Apple Pay)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
 
returnhttpClient;
}

As you can see in the comment, Apple Pay REQUIRES TLS 1.2 for connecting to its web services. That line of code is required if you are using .NET Framework 4.5. If you are using .NET 4.6 or later, TLS 1.2 is enabled by default and you won't need that line of code (although it is recommended to keep it just in case you need to deploy to an environment with an earlier .NET version)

Before creating the main controller, we need to create a very small model for the Apple Pay gateway that will be sent for model validation:

publicclassMerchantValidationModel
{
[DataType(DataType.Url)]
[Required]
publicstringValidationUrl {get;set; }
}

Finally, create a regular MVC controller. For this example I will name it "ApplePayController". Also, create a view, and add some simple control to enter a value for payment (I won't go into detail on how to create this. But try using Bootstrap and any other front-end framework to make it easier). In that same controller, we will create the action that will act as a web service for starting a payment session:

publicclassApplePayController : Controller
{
// GET: ApplePay
publicActionResult Index()
{
//... Any logic to pass to your testing View
 
returnView();
}
 
[HttpPost]
publicasync Task<ActionResult> StartPaySession(MerchantValidationModel validateMerchant)
{
Uri requestUri;
 
//Validation URL for the Apple Pay gateway must be correct, otherwise a Bad Request will be returned.
if(!ModelState.IsValid ||
string.IsNullOrWhiteSpace(validateMerchant?.ValidationUrl) ||
!Uri.TryCreate(validateMerchant.ValidationUrl, UriKind.Absolute,outrequestUri))
{
returnnewHttpStatusCodeResult(HttpStatusCode.BadRequest,"Bad Request");
}
 
 
//Load the Merchant certificate
var certificate = ApplePayManager.GetMerchantCertificate();
var merchantId ="[ENTER MERCHANT ID HERE]";
 
//Set up the data object to be sent for merchant validation
var validationData =new
{
merchantIdentifier = merchantId,
domainName = Request.Url.Host,
displayName ="[ENTER DISPLAY NAME HERE]"
};
 
using(var httpClient = ApplePayManager.GetSecureHttpClient(certificate))
{
var jsonData = JsonConvert.SerializeObject(validationData);
 
using(var content =newStringContent(jsonData, Encoding.UTF8,"application/json"))
{
try
{
//Post the validation data object to the Apple Pay web service through secure channel
using(var response = await httpClient.PostAsync(requestUri, content))
{
response.EnsureSuccessStatusCode();
 
//Build merchant session object from web service response
var merchantSessionJson = await response.Content.ReadAsStringAsync();
var merchantSession = JObject.Parse(merchantSessionJson);
 
returnContent(merchantSession.ToString(Formatting.None),"application/json");
}
}
catch(Exception ex)
{
returnnewHttpStatusCodeResult(HttpStatusCode.BadGateway,"Apple Pay Gateway error: "+ ex.Message);
}
}
}
}
}

Replace the placeholders for Merchant ID and Display Name with your values, or values from configuration.

Integration with Apple Pay JS API

Apple and other sources have good examples on how to integrate the Apple Pay JS API into your website client-side code. You can follow the client-side part of the EmporiumWeb example (https://developer.apple.com/library/content/samplecode/EmporiumWeb/Introduction/Intro.html). Also refer to the Apple Pay JS API documentation for more details on the objects, methods and events on the API (https://developer.apple.com/reference/applepayjs/applepaysession).

Although I won't go into detail about the client-side implementation, this is the basic Apple Pay logic flow:

  1. Determine that the browser supports Apple Pay, and show the "Buy with Apple Pay" button if supported.
  2. Intercept the button click event, and begin a new ApplePaySession.
  3. Validate the merchant (using the ApplePaySession onvalidatemerchant event). Abort pay session if any errors occur.
  4. Intercept the ApplePaySession onpaymentauthorized event, submit payment to your back-end gateway, and end pay session.

For the merchant validation, you have to call to the StartPaySession action in the back end. When setting up the ApplePaySession object in your JavaScript, do something similar to this:

//... set up your payment data object
 
varpaySession =newApplePaySession(2, paymentData);
 
paySession.onvalidatemerchant =function(event) {
varvalidationData = { ValidationUrl: event.validationURL };
 
$.ajax({
url:"/ApplePay/StartPaySession",//This is the endpoint to the .NET action to start the payment session with merchant validation
method:"POST",
contentType:"application/json; charset=utf-8",
data: JSON.stringify(validationData)
}).then(function(merchantSession) {
//Validation successful, complete the merchant validation
console.log(merchantSession);
paySession.completeMerchantValidation(merchantSession);
},function(error) {
//Validation not successful or error occurred
console.log(error);
paySession.abort();
//... display the error to the user
});
};
 
//... set up other events such as onpaymentauthorized:
paySession.onpaymentauthorized =function(event) {
 
//... send the payment token (event.payment.token) to a back-end service that connects to a payment processor
 
//... if payment successful, display something to the user, and complete the payment:
paySession.completePayment(ApplePaySession.STATUS_SUCCESS);
};
 
//Trigger the payment session
paySession.begin();

Set up your view so you can enter any value in a text box. Follow Apple's recommendations on how to implement, style and show (and hide) the "Buy with Apple Pay" button.

Testing

For testing you need the following:

  • A Mac computer with macOS 10.12+, or an iPhone with iOS 10+
  • For pure testing, set up in iTunesConnect a Sandbox Account, and use it to login to iCloud in your iPhone. With a sandbox account, you can set up "fake" cards in the Wallet and use them for testing. Refer to https://developer.apple.com/support/apple-pay-sandbox/ for details on the "fake" card numbers and other data.
  • In case you haven't set up the actual payment through a payment processor (in the onpaymentauthorized event), you can use a regular debit/credit card added to the Wallet (you won't be charged because the payment token is not sent to a payment processor).

Follow these steps to test:

  1. Open Safari.
  2. Access your test page.
  3. If everything is set up correctly, you will see the "Buy with Apple Pay" button.
  4. Enter a value in the textbox, and click/tap on the "Buy with Apple Pay" button. A window similar to this should appear if merchant validation is successful:
  5. Authorize the payment through Touch ID on an iPhone (fingerprint), or by double clicking your Apple Watch side button.