Apple Pay on the Web Debugging Guide

This thread has been locked by a moderator; it no longer accepts new replies.

This content has moved to TN3103 https://developer.apple.com/documentation/dts-technotes/tn3103-apple-pay-on-the-web-troubleshooting-guide

This document is intended to be used as a general guide to debugging common problems with Apple Pay on the Web. Note that this document does not cover 3rd party tooling or integrations. If you are experiencing issues with 3rd party code, libraries, or server side configurations, it’s best that you talk to these vendors directly. This document assumes that you are building Apple Pay on the Web from your own website and then integrating with a 3rd party Payment Service Provider.

Creating your Merchant Assets

This section is primarily focused on Merchants who have their own Developer Account. If you are working with a Payment Service Provider who can give you access to Apple Pay without needing your own Apple Developer account, this process will be different and you should contact the the Payment Service Provider for additional help on Merchant Asset Configuration.

Using your own Apple Developer Account to create Merchant Assets, the first thing you will need to log into the Developer Portal and create a new Merchant Identifier. This is the identifier that ties together your Merchant Domain, your Merchant payments, your Merchant Identity Certificate, and your Payment Processing Certificate. A Merchant Identifier will look something like: merchant.com.testbed.applepay.

After you have created your Merchant Identifier you will need to create a Payment Processing Certificate. On the Keychain in your macOS machine, create a CSR, or a certificate signing request. Note that for the Payment Processing Certificate will need to be created using a ECC 256 bit key pair. Use this CSR to upload to the Developer Portal under the Merchant Identifier that you just created. This should produce a Payment Processing Certificate. Download this certificate and install it onto the Keychain of the machine that you created the CSR on. Downloading this certificate to the Keychain that you created the CSR on will create a Payment Processing Identity (p12 / key and cert). Keep a reference to this identity because at some point you will need to provide this to your Payment Processor. Note that the process described here details a situation where you, as the Developer, are setting up the Merchant Assets. It could also be the case that your Payment Service Provider creates the CSR and gives it to you, the Merchant, to upload to the Developer Portal to generate a Payment Certificate. The benefit of this workflow is that you would not need to export your Payment Processing Identity from your Keychain to provide it to the Payment Processor, they would already have the required private key from the CSR generation.

Next, in the Developer Portal under your Merchant Identifier, you will need to create a Merchant Identity Certificate in the same place you created your Payment Processing Certificate. Note that for a Merchant Identity Certificate you will need to create a CSR with a RSA 2048 bit key pair. If you are creating this asset from the Keychain you can follow the same process that you used for creating your Payment Processing Certificate and download your Merchant Identity Certificate into your Keychain. Just note the difference between the ECC and RSA key pairs. Your Merchant Identity Certificate is very important because this is the cryptographic asset that you will use to authenticate with the Apple Pay Servers to obtain a payment session during the Merchant Validation process. 

NOTE: a CSR does not have to be created in the Keychain of a macOS machine. A CSR can technically be created from an external source such as a server, another workstation, or from your Payment Service Provider, with a tool like OpenSSL. The important thing to remember is that when a CSR is created, so is a private key. And when you are issued a certificate from the Apple Developer Portal you need to store the certificate and the private key in a secure location so that a PKCS#12 (or p12) can be created from both the private key and certificate. The Keychain on macOS does this for you and so you do not have to worry about it, but if you are using an external tool you will need to do this by hand. The creation of a p12 from a cryptographic private key and certificate is what makes up a digital identity, and this is what is used to authenticate your team as a Merchant to the Apple Pay servers. Your Payment Processing Identity will also be used to decrypt your payment tokens by your Payment Processor. For example, the creation of a Merchant Identity in the form of a p12, or a stacked PEM, contains your private key and Merchant Certificate. These assets that are tied to your Merchant account are then used to perform client authentication when requesting a payment session from the Apple Pay servers. It is important that the digital identity in this context be correctly formatted and stored in a secure location if created outside of macOS. If you do end up creating this identity outside macOS, you will be responsible for converting Certificate and Private Key into a format that can be used for Merchant Authentication.

Lastly, you will need to add a Merchant domain to your Merchant Identifier so that you can process payments on this domain. You will need to register each domain that you plan to process payments from. For example, if you plan to process payments on test.mydomain.com and mydomain.com you will need to register and verify each domain. Note, that if you plan to process payments for mydomain.com, and you will actually be using www.mydomain.com, make sure to include the fully qualified domain name (FQDN) in the registration process.

Domain Verification

Once your domain is configured in the Developer Portal for mydomain.com, you will be asked to verify your domain with the apple-developer-merchantid-domain-association file. Download and place this file on your server at this location:

https://mydomain.com/.well-known/apple-developer-merchantid-domain-association.txt

In some cases, due to the server side environment, the domain verification file will not be able to be placed in the root level of the server, like what is described above. In these cases your will need to work with your server side team to figure out a way to expose this file at this specific location otherwise the domain verification process will not work.

NOTE: the domain verification file expires after 7 days, so if you are not able to verify your domain after 7 days you will need to regenerate this file and replace it on your server.

To perform domain verification, click the Verify button in the Developer Portal to verify your domain. If all goes well, your domain should be verified and show up as “Verified” in the Developer Portal. If you experience issues, you will need to debug this further to figure out what is happening. Here are some common reasons why domain verification fails:

TLS Handshake cannot be established.

There are many reasons why a TLS handshake can fail during the domain verification process. For example:

  • Make sure your server supports TLS 1.2 or above and supports these cipher suites described here. To debug what cipher suites are being negotiated between the Apple’s domain verification client and your server, take a packet trace and take a look at the the cipher suites being offered by Apple’s domain verification client in the client hello packet. If all goes well, you will be able to see one of those offered cipher suites being chosen in the Server Hello packet being sent from your server. If there is a break down in the cipher suite negotiation then there will typically be a failure after the client hello packet.
  • Make sure that you are using a valid TLS certificate. For example, make sure that this certificate is setup with proper chain of trust and adhere’s to Apple's Certificate Transparency policy for Safari. You can also double check this with a packet trace again and make sure the leaf certificate being used on your domain follows a correct chain of trust through the intermediate certificates to the root certificate.

The Apple Developer Portal cannot reach your Server.

One common problem is that the domain verification request seems to error out, but there is no apparent reason why this is happening. To debug this, try reviewing your server’s access logs, or try setting your server application logs to be as verbose as possible. From here, make sure that the domain verification request is reaching your server. If it is not then you know there is an issue with Apple’s domain verification client reaching your server. If the request is reaching your server, and it is not a TLS issue, then your server side logs should provide more insight here on where the break down is.

Your Domain sits behind a Proxy or in a Private Network.

One common issue is that your domain sits behind a network proxy or a load balancer that is not correctly routing traffic. In the case of the proxy, this is problematic because Apple’s domain verification client is trying to validate your domain verification file, but also read client specific information about your TLS certificate. When this request flows through a proxy it will pickup incorrect information about your domain’s certificate because of the proxy.

Domain verification issues for a server sitting behind a load balancer are not common but can happen when the load balancer is not routing traffic incorrectly. If you think this is the case for network, double check the routing algorithm on your load balancer to ensure that traffic always makes it to your server.

Lastly, if you domain sits inside of a private network and needs to be verified, make sure that Apple’s domain verification client IP addresses are allowed in your network’s firewall. See more on these IP Ranges here.

Building your Client Application

Building your client application is done with either ApplePay JS or the Payment Request API. In the context of the following document, the examples will use ApplePay JS. There are many paths you can take here, but as a prerequisites, you need to first make sure that the Browser supports Apple Pay. For more details on this see the documentation here for Checking for Apple Pay Availability.  

After your application has verified that the Browser supports Apple Pay, then it is time to build out the payment workflow. For a complete step by step example of how this works, see the Apple Pay on the Web Interactive Demo. This demo includes sample code that outlines each step in the payment process, and can come in handy when determining how to build your payment workflow.

When building out your payment workflow you should consider how you will build out your payment request. There is a sample of this payment request here, and I have pasted it below, but essentially you should decide how much information is needed to gather in shopping cart flow to process the transaction. The recommendation is to only gather the needed information to process the transaction.

{
    "countryCode": "US",
    "currencyCode": "USD",
    "merchantCapabilities": [
        "supports3DS"
    ],
    "supportedNetworks": [
        "visa",
        "masterCard",
        "amex",
       "discover"
    ],
    "total": {
        "label": "Demo (Card is not charged)",
        "type": "final",
        "amount": "1.99"
    }
}

For example, if you are charging one flat fee for shipping, or are not charging for shipping at all then the following payment request would be sufficient. However, if you need to calculate total amounts based on location or address then you have two options; the first is to gather this shipping information as part of your payment workflow before the payment request is created. That way you have this information up front before the payment workflow. The second option is to pass in information for requiredShippingContactFields into your payment request:

"requiredShippingContactFields": [
    "postalAddress",
    "name",
    "phone",
    "email"
]

Using the requiredShippingContactFields field tells the Apple Pay SDK that you would like to receive redacted shipping and contact information to calculate shipping and further fees on the transaction. These fields will show up in the onshippingcontactselected with just enough information to calculate shipping fees.

onshippingcontactselected:
{
    "administrativeArea": "CA",
    "country": "United States",
    "countryCode": "US",
    "locality": "San Francisco",
    "postalCode": "94114"
}
// Note that you need to pass in a populated object here to `completeShippingContactSelection`
session.onshippingcontactselected = event => {
     // You will receive redacted shipping and contact info here.
     const update = {};
     // Construct update object to pass in below, do now pass in blank object.
    // For example, `required ApplePayLineItem newTotal` is required in this case.
    session.completeShippingContactSelection(update);
};

After the transaction is authorized you will receive the complete shipping information.

Merchant Validation

After the payment request is created your application will need to go through the Merchant Validation process to authenticate itself as a valid Merchant to process a transaction. Upon successful authentication your server will receive a payment session to submit with your payment request to the Apple Pay SDK. This process is where most Apple Pay on the Web issues take place because there are a lot of moving parts. Here is an overview of that process as well as suggestions on how to resolve common errors in this workflow:

  1. Call your server with the URL that your client side application received in validationURL. In this step your JavaScript client application with send a HTTPS request to your server side application passing it the validationURL.
  2. Your server side application will receive the HTTPS request from step one and use the validationURL to create a POST request to obtain a payment session. This is where your application uses the Merchant Identity Certificate to identify itself as a valid Merchant to the Apple Pay Servers. DO NOT MAKE this request from your client side JavaScript application as it will expose your private key. Make sure this request utilizes the correct format of your p12/PEM to perform client authentication with the Apple Pay servers. If you receive a TLS or SSL failure while making this request then examine the format of the p12/PEM in this payment session request. 
  3. Once your server has a valid payment session it should respond to your server with this session and your client application should pass the session into completeMerchantValidation(paymentSession);

In step two above is where a lot of issues take place. To validate that your server side APIs are not causing an issue you can use CURL to test this process. Assuming that your Merchant Identity Certificate is in your Keychain, export that as a p12 and extract the Certificate/Key in PEM format.

$ openssl pkcs12 -in Certificates.p12 -out apple-pay-cert.pem -nodes -clcerts

Now, on your server side APIs, make a request with your payload information to the Apple Pay servers. DO NOT MAKE this request from your client side JavaScript application as it will expose your private key. 

$ curl -gv --data '{"merchantIdentifier":"merchant.com.testbed.applepay", "initiativeContext":"mydomain.com", "initiative":"web", "displayName":"Apple Pay Testbed"}' --cert /path/to/pem/apple-pay-cert.pem https://apple-pay-gateway.apple.com/paymentservices/paymentSession
{
    "merchantIdentifier":"merchant.com.testbed.applepay",
    "initiativeContext":"mydomain.com",
    "initiative":"web",
    "displayName":"Apple Pay Testbed"
}

Payment Services Exception

In a situation where you believe that everything is setup correctly and you send a request to the Apple Pay servers to get a Payment Session and come back with a 400 server response error, the first thing you will want to do is check that your request was formatted correctly. A 400 server response means “Bad Request.” If the request looks fine, then you will want to double check the Merchant ID and the Merchant Identity Certificate you are using for this request. Consider this error:

{
"statusMessage": "Payment Services Exception merchantId=bdb3ed0005c72b1e8c2a536bb65a0ec22de030578c08b45dde6cdc13cd925f03 unauthorized to process transactions on behalf of merchantId=12953b72a8db15cf4a079b49a87ddd5eed1b0ba2eb249c7907265a7b04cff6ce reason=\"12953b72a8db15cf4a079b49a87ddd5eed1b0ba2eb249c7907265a7b04cff6ce never authorized mass enablement transactions to occur via bdb3ed0005c72b1e8c2a536bb65a0ec22de030578c08b45dde6cdc13cd925f03\"",
"statusCode": "400"
}

Now, this looks hard to read, but to debug this you need to check the merchantId= value against a sha256 hash of the Merchant ID to see if they match. 

echo -n "merchant.com.testbed.applepay" | shasum -a 256
12953b72a8db15cf4a079b49a87ddd5eed1b0ba2eb249c7907265a7b04cff6ce 

It looks like something is wrong with the Merchant ID being used here. Taking a look at the request I noticed that I mis-typed the Merchant ID: merchant.com.testbed.applepayd. This now matches because looking at the sha256 hash of the wrong Merchant ID I see:

echo -n "merchant.com.testbed.applepayd" | shasum -a 256
bdb3ed0005c72b1e8c2a536bb65a0ec22de030578c08b45dde6cdc13cd925f03

Fixing these mis-placed Merchant IDs should now resolve the issue.

completeMerchantValidation Fails

If you pass a valid payment session into completeMerchantValidation and receive an error, there are a few things you can try:

  1. Make sure that to isolate a device that is being used for Sandbox testing. For example, make sure you only have a Sandbox test user signed into this device and that you are only using a production card or Sandbox test card. Do not mix cards and accounts. Create a Sandbox Tester Account.
  2. Make sure the your test device is properly authenticating the transaction with Touch ID.
  3. If you hit a wall here, and can reproduce this issue on macOS, try logging out what passd is giving when you reproduce this error on macOS with Safari. For example, on macOS, open two terminals and run each of these commands in a separate Terminal:
$ log stream --level debug --predicate 'process == "passd"'
$ log stream --level debug --predicate 'subsystem == "com.apple.passkit”'

Then reproduce your issue in Safari on macOS. Typically you will see logs that end up like this from these daemons:

com.apple.PassKit.PaymentAuthorizationUIExtension: (PassKitCore) [com.apple.passkit:Payment] 
Evaluating merchant session using PROD trust policy.

com.apple.PassKit.PaymentAuthorizationUIExtension: (PassKitCore) [com.apple.passkit:Payment] 
Application failed to provide a valid merchant session. We can't proceed to authorize the transaction.

There are a few different reasons these errors can happen:

  • You are requesting a payment session for a domain other than what you are testing on. For example, you are testing on www.mydomain.com and you request a payment session for staging.mydomain.com.
  • If your Merchant Session is considered to have a bad signature because of issues with your Merchant Identity Certificate or Merchant Domain.
  • The Merchant Domain that this payment is being processed for does not match the one in the Developer Portal. For example, make sure you are using the fully qualified domain name, i.e., www.mydomain.com if that is what you registered.
  • You have not authorized your payment with watchOS, macOS, or iOS:

https://support.apple.com/guide/safari/pay-with-apple-pay-ibrw8e207504/mac

Decrypting the Payment Token

Once you have completed the payment workflow you will need to pass the payment token to your Payment Processor. This is where your Payment Processing Identity comes into play. Your Payment Processor will need your Payment Processing Identity to decrypt the payment token that was encrypted with the public key of your Payment Processing Certificate.

If you attempt to decrypt a payment token and receive something other than a parseable JSON output then the most common reason for this is that you are trying to decrypt the payment token with a private key that does not match the public key that the token was encrypted with. For example, when the payment token is encrypted, it is encrypted with Payment Processing Certificate's public key. This public key should be embedded in the private key as well. If you are able to extract the public key from the private key and compare a sha 256 hash of each key, they should match. If the public key hashes do not match then the Payment Processor has the wrong private key. To extract the public key from the private key you should be able to use a tool like OpenSSL to do so.

For a complete overview of how to decrypt the Apple Pay Payment token and how the payment token is structured, please see the Payment Token Format documentation here.

Boost
Apple Pay on the Web Debugging Guide
 
 
Q