Apple Pay Web Integration
Apple Pay on the Web allows customers to use their Apple Wallet to pay for purchases during checkout in a retail website.
You can integrate Apple Pay Web in your webstore's checkout process by following the process described below.
Prerequisites for Apple Pay Integration
- The merchant/retailer must have an Apple Developer Account.
- All webstore pages served to the customer must use HTTPS
- The domain and all subdomains must have valid SSL certificates and be registered with Apple. (Please see the Merchant Validation section below).
- The merchant server must support the Transport Layer Security (TLS) 1.2 protocol and one of the cipher suites listed by Apple.
Testing the integration is supported on iOS 10 (and above) and mac OS 10.12 (and above).
Apple Pay Certificate Setup
Before Radial can accept and process Apple Pay payments for your webstore, you must set up a certificate file.
Certificate Setup for Test Environments
For test environments, the Apple Pay certificate setup process is performed in Radial's Payments & Fraud Sandbox (https://dev.radial.com). For detailed instructions, see Apple Pay Configuration in the Sandbox online help.
If you don't have a Sandbox account, you can create one. For details, see Set Up a Sandbox Account in the Sandbox online help.
Certificate Setup for Production Environments
For production environments, the Apple Pay certificate setup process is performed in Radial's Payments & Fraud Portal (https://portal.radial.com). For detailed instructions, see Apple Pay Configuration in the Portal online help.
If you don't have a Portal account, ask your Radial team to create an account for you.
Apple Pay JS API
The Apple Pay JavaScript API lets you accept Apple Pay payments on the web. The Apple Pay JavaScript API is supported on the following platforms:
-
iOS 10. Apple Pay JavaScript is supported on all iOS devices with a Secure Element. It is supported both in Safari and in SFSafariViewController objects.
-
MacOS 10.12. Apple Pay JavaScript is supported in Safari. The user must have an iPhone, Apple Watch, or a MacBook Pro with Touch ID that can authorize the payment.
The following steps are necessary for integrating with Apple Pay JS API:
-
Show Apple Pay web button
-
Merchant validation
-
Display payment sheet
-
Authorization with encrypted blob
For details of these steps, see the following sections.
Enabling Apple Pay Web Button on the Checkout Flow Payments Page
Step 1: Show Apple Pay Web Button
- Include a <div> tag with the class apple-pay-button and an onclick event associated with the div.
- When the DOMContentLoaded Event is fired (this typically happens when the web page is loaded), check whether ApplePaySession.canMakePayments is true.
- After the value is determined to true, enable the button class.
The code snippets below show an example of how this can be done.
HTML Snippet
<div class="apple-pay">
<h2> Buy with Apple Pay </h2>
<p>
Compatible browsers will display an Apple Pay button below.
</p>
<div class="apple-pay-button" onclick="applePayButtonClicked()"></div>
</div>
JavaScript Snippet
document.addEventListener('DOMContentLoaded', () => {
if (window.ApplePaySession) {
if (ApplePaySession.canMakePayments) {
showApplePayButton();
}
}
});
function showApplePayButton() {
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
const buttons = document.getElementsByClassName("apple-pay-button");
for (let button of buttons) {
button.className += " visible";
}
}
CSS Snippet
.apple-pay {
max-width: 600px;
margin-left: auto;
margin-right: auto;
text-align: center;
}
.apple-pay-button {
-webkit-appearance: -apple-pay-button;
-apple-pay-button-type: buy;
visibility: hidden;
display: inline-block;
width: 200px;
min-height: 30px;
border: 1px solid black;
background-image: -webkit-named-image(apple-pay-logo-black);
background-size: 100% calc(60% + 2px);
background-repeat: no-repeat;
background-color: white;
background-position: 50% 50%;
border-radius: 5px;
padding: 0px;
margin: 5px auto;
transition: background-color .15s;
}
.apple-pay-button.visible {
visibility: visible;
}
.apple-pay-button:active {
background-color: rgb(152, 152, 152);
}
After all the above code snippets are put together, the Apple Pay web button displays. An example is shown below.
Step 2: Merchant Validation
After the user clicks the Buy with Apple Pay button, you must create an Apple Pay session with the necessary order data.
After the session is initiated, define the session.onvalidatemerchant method from Apple JS API. In this method, do a server-to-server call on a validationURL (this will be generated from Safari browser) with a payload (an example is below).
After the success message comes back, use this response on session.completeMerchantValidation(response). To see how this works, please go through the code snippet provided below.
The value of validationURL can be chosen from the values listed below, based on the environment. For example, for sandbox testing, configure the iCloud account to the sandbox tester section. Based on this setup, the Safari browser will give us apple-pay-gateway-cert.apple.com as the validationURL.
validationURL values provided by Apple
For production environment: 17.171.78.7 apple-pay-gateway-nc-pod1.apple.com 17.171.78.71 apple-pay-gateway-nc-pod2.apple.com 17.171.78.135 apple-pay-gateway-nc-pod3.apple.com 17.171.78.199 apple-pay-gateway-nc-pod4.apple.com 17.171.79.12 apple-pay-gateway-nc-pod5.apple.com 17.141.128.7 apple-pay-gateway-pr-pod1.apple.com 17.141.128.71 apple-pay-gateway-pr-pod2.apple.com 17.141.128.135 apple-pay-gateway-pr-pod3.apple.com 17.141.128.199 apple-pay-gateway-pr-pod4.apple.com 17.141.129.12 apple-pay-gateway-pr-pod5.apple.com 17.171.78.9 apple-pay-gateway-nc-pod1-dr.apple.com 17.171.78.73 apple-pay-gateway-nc-pod2-dr.apple.com 17.171.78.137 apple-pay-gateway-nc-pod3-dr.apple.com 17.171.78.201 apple-pay-gateway-nc-pod4-dr.apple.com 17.171.79.13 apple-pay-gateway-nc-pod5-dr.apple.com 17.141.128.9 apple-pay-gateway-pr-pod1-dr.apple.com 17.141.128.73 apple-pay-gateway-pr-pod2-dr.apple.com 17.141.128.137 apple-pay-gateway-pr-pod3-dr.apple.com 17.141.128.201 apple-pay-gateway-pr-pod4-dr.apple.com 17.141.129.13 apple-pay-gateway-pr-pod5-dr.apple.com For sandbox testing only: 17.171.85.7 apple-pay-gateway-cert.apple.com
Payload for merchant validation
{
"merchantIdentifier": "B7...D8",
"domainName:"www.xxx",
"displayName":"merchant.com.xxxxxxxxx"
}
JavaScript snippet for merchant validation
/**
* Apple Pay Logic
* Our entry point for Apple Pay interactions.
* Triggered when the Apple Pay button is pressed
*/
function applePayButtonClicked() {
const paymentRequest = {
countryCode: 'US',
currencyCode: 'USD',
shippingMethods: [
{
label: 'Standard Shipping',
amount: '5.00',
identifier: 'free',
detail: 'Delivers in five business days',
},
{
label: 'Priority Shipping(3-5 Days)',
amount: '10.00',
identifier: 'priority',
detail: 'Delivers in three business days',
},
{
label: 'Express Shipping',
amount: '20.00',
identifier: 'express',
detail: 'Delivers in two business days',
},
],
lineItems: [
{
label: ‘This is a line item label’,
amount: ‘0.00’,
}
],
total: {
label: 'This is the total Amount',
amount: ‘0.99’,
},
supportedNetworks:[ 'amex', 'discover', 'masterCard', 'visa'],
merchantCapabilities: [ 'supports3DS'],
};
const session = new ApplePaySession(1, paymentRequest);
/**
* Merchant Validation
* We call our merchant session endpoint, passing the URL to use
*/
session.onvalidatemerchant = (event) => {
const validationURL = event. ValidationURL;
getApplePaySession(validationURL).then(function(response) {
session.completeMerchantValidation(response);
});
};
}
/**
* Server to Server call to apple for response used to validate *merchant
*/
function getApplePaySession(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/session/create');
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(JSON.parse(xhr.response));
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({validationUrl: url}));
});
}
Java code snippet for server-to-server call to Apple for response used to validate merchant
@RequestMapping(value = "api/session/create", method = RequestMethod.POST)
public String generateSession(String validationURL) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, JSONException {
String keyStoreFile = "tls//DEMOUS-STORE-MERCHANT-PRIVATE-KEY.p12";
String keyStorePassword = "password";
String uri = validationURL;
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
KeyStore clientStore = KeyStore.getInstance("PKCS12");
clientStore.load(new FileInputStream(classLoader.getResource(keyStoreFile).getFile()), keyStorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, keyStorePassword.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
SSLContext sslContext = null;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, null, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
URL url = new URL(uri);
HttpsURLConnection urlConn = (HttpsURLConnection) url.openConnection();
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
urlConn.setRequestProperty("Content-Type", "application/json");
urlConn.setRequestProperty("Accept", "application/json");
urlConn.setRequestMethod("POST");
JSONObject cred = new JSONObject();
cred.put("merchantIdentifier","B7...D8");
cred.put("domainName", "www.xxx");
cred.put("displayName", "xxx");
OutputStreamWriter wr = new OutputStreamWriter
(urlConn.getOutputStream());
wr.write(cred.toString());
wr.flush();
StringBuilder sb = new StringBuilder();
int HttpResult = urlConn.getResponseCode();
if (HttpResult == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(
new InputStreamReader(urlConn.getInputStream(), "utf-8"));
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
return sb.toString();
} else {
return urlConn.getResponseMessage());
}
}
The response will be something like the following example.
{
"epochTimestamp": 1502127006708,
"expiresAt": 1502134206708,
"merchantSessionIdentifier": "90BBDED6D37D450198ABEB1E6175F9DA_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24",
"nonce": "4ca739fe",
"merchantIdentifier": "85B3D63087D5C2F993C7D16C1BF2FF4E878B668A556378E4408202C70F88CB97",
"domainName": "www.demo-webstore.radial.com",
"displayName": "DEMOUS",
"signature": "308006092a864886f70d010702a0803080020101310f300d06096086480165030402010500308006092a864886f70d0107010000a0803…..
"
}
Step 3: Display Payment Sheet on Browser and Phone
After the merchant validation is completed in step 2, the Apple payment sheet will be shown on the Macbook and the connected iPhone. Note that the Apple Macbook and iPhone should be logged into the same iCloud account. Below are screenshots of examples.
Macbook payment sheet
IPhone payment sheet waiting for touch impression
IPhone payment sheet after touch impression and processing
IPhone payment sheet after successful processing
Step 4: Authorization with the Encrypted Blob
After the merchant is validated and touchID is validated, the next step is to capture the payment object (the encrypted token, as in the example below) from the Apple Pay event and pass it to the payment processor to process the authorization.
session.onpaymentauthorized = (event) => {
// Send payment for processing...
const payment = event.payment;
authorize(payment.token).then(function(response) {
if(response.match(/status=.error./)) {
console.log("an error occured!");
console.log(response);
return session.abort();
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);
$('#complete_checkout_apple_pay').submit();
});
};
function authorize(token) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/authorize');
var orderData = orderPayload(); // custom method to fetch order data
token.epsOrderData = orderData;
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(token));
});
}session.onpaymentauthorized = (event) => {
// Send payment for processing...
const payment = event.payment;
authorize(payment.token).then(function(response) {
if(response.match(/status=.error./)) {
console.log("an error occured!");
console.log(response);
return session.abort();
}
session.completePayment(ApplePaySession.STATUS_SUCCESS);
$('#complete_checkout_apple_pay').submit();
});
};
function authorize(token) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/authorize');
var orderData = orderPayload(); // custom method to fetch order data
token.epsOrderData = orderData;
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(token));
});
}
Sample Blob
Apple sends a lot of transaction data, but for payment processing purposes, only the paymentData information is relevant. Extract the data and send it to Radial. Radial will decrypt the information and use it to do an Authorization call to the payment gateway. A sample Blob is shown below.
"paymentData": {
"version": "EC_v1",
"data": "cVSiVUsQHFWiJ5jUBKOozrSRw1s1jRnKhjfC5/Lq7zsr7nXCVqYRIdi3RJoOBGz4kNCOoCXs8xWXrLpCppFxw1HOZBdIxlWoJ0BJLlj+jgs775f6MFs6AE7i64R0oRuw03PJxTJ9wOqux39a/JCywvjFiDsRaOK+lY9L76KXUeZ0f7wrTIVJZTrFhz6Iob4vYMFXk+o4ynfLnZyHqO5TRhjGP06DNPVRmRZOGWkSnVRGWYEqqteY2SL3PEqfEkguy4pClTHmqq367qBhhescFHpiDqtFjxeUV3h4WLIKNwWzSmjSoyFAyWR5LLo9cKaaAExABJF+quO9GWqwiyvqPtmrFTYamEtnS/dZPHxeRWC97v2ktIy2jIEGhu4vDy13wYvz/VgKU8pAkHNbtWsattMDG+XGG3EeeZRvFWl1",
"signature": "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",
"header": {
"ephemeralPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKHACe0d4cHonHQJwjrZB6q/GDpv574RoKkc/xYbgpSP5p/4GqT8k4/y6rHUy75e+qW9yUVPo7kOWr9YZ6wB2sw==",
"publicKeyHash": "sNALPkRCH7b7dM63odMKQhMx8oCriXbTNxkOdvuqnAA=",
"transactionId": "d280ae5d54a9c13ab73abcf55a12c24aac32ea240e3e74cc7a15a5d9cc82fda6"
}
},
"paymentMethod": {
"displayName": "Visa 0492",
"network": "Visa",
"type": "debit"
},
"transactionIdentifier": "D280AE5D54A9C13AB73ABCF55A12C24AAC32EA240E3E74CC7A15A5D9CC82FDA6"
}
Radial Payment API
The merchant must call the Radial payment API when the payment is authorized by the customer.
Use the mapping below to pass the encrypted data in Radial's CreditCardAuthRequest API:
Blob to Radial CreditCard API mapping
Apple Pay Token Attribute |
Description |
Radial CreditCardAuth Xpath mapping |
---|---|---|
Version |
Version information about the payment token. The token uses EC_v1 for ECC-encrypted data, and RSA_v1 for RSA-encrypted data. |
CreditCardAuthRequest/WalletPaymentInformation/Version |
data |
Encrypted payment data from Apple Pay |
CreditCardAuthRequest/WalletPaymentInformation/Data |
Signature |
Signature of the payment and header data. The signature includes the signing certificate, its intermediate CA certificate, and information about the signing algorithm. |
CreditCardAuthRequest/WalletPaymentInformation/Signature |
Header/ephemeralPublicKey |
Ephemeral public key bytes. |
CreditCardAuthRequest/WalletPaymentInformation/EphemeralPublicKey |
Header/transactionId |
Transaction Identifier generated on the device |
CreditCardAuthRequest/WalletPaymentInformation/ApplePayTransactionId |
Header/publicKeyHash |
Hash of the X.509 encoded public key bytes of the merchant’s certificate. |
CreditCardAuthRequest/WalletPaymentInformationPublicKeyHash |
For more information, please refer to the Apple Pay JS API, at https://developer.apple.com/documentation/applepayjs/applepaysession
Test Card/Mock Cards
For information on Apple Pay testing, see Accounts for API Testing.