Apple Pay Integration Guide
How to Integrate Apple Pay with : Full Technical Walkthrough
Integrating Apple Pay on the merchant side requires more than just enabling a payment method — it involves hosting the Apple Pay button, handling H2H (host-to-host) API flows, and managing token decryption. The setup defines where the button is rendered, how validation and payment requests are exchanged, and whether decryption is performed by the or directly on the merchantʼs side. This guide provides a step-by-step overview of Apple Pay connectivity and security requirements for handling payment tokens.
1. General Information
The platform allows the use of the Apple Pay payment method, given that it is also supported by the Payment Provider.
- Hosted payment page with Apple Pay Button
- Apple Pay button can be hosted on end (Redirect flow)
- H2H flow with Decryption on side
- H2H flow with Decryption on Merchant side
Who can host Apple Pay button in case of the H2H flow ?
The platform supports two options:
- Apple Pay button can be hosted on end (Hosted Payment Page / Redirect Button)
- Apple Pay button can be hosted on Merchantʼs end
Please note, if the Apple Pay button is hosted on Merchantʼs end, the API integration to our platform should be implemented.2. H2H flow with Decryption on side. Apple Pay button on Merchants side
H2H Apple Pay allows the Merchant to place the Apple Pay Payment Button on their side (technically), without embedding the platform's page into the user's payment flow. Token decryption in this case (described below) is done by .
Pre-requisites
- Merchant should host domain verification file received during registration with Apple Pay
- Merchant should have merchant validation certificate received during registration with Apple Pay
- platform should have private key generated while registering with Apple Pay
2.1 Interaction Key Participants
| User | The user making the payment. |
| Merchant Website (Frontend) | The frontend of the merchant's web application. |
| Apple Pay Widget | The user interface of Apple Pay. |
| Merchant Website (Backend) |
|
| system | Decrypt tokens, and interacts with the final acquirer. |
| Apple Pay Validation Server | A server that validates the merchant and the host site where the payment (Apple Pay button placement) takes place. |
2.2 High-Level Interaction Flow
Actions within the Merchantʼs system may vary in presence, sequence, etc.
- User – initiates an "order."
- Merchant Website (Backend) – creates a transaction and opens the Checkout page (where payment will takes place).
- User – clicks the Apple Pay button. Merchant Website (Frontend) – initiates interaction with the Apple Pay Widget. Apple Pay Widget returns a URL for merchant and host validation.
- Merchant Website (Backend) – initiates Merchant and Host Validation. and performs the validation via Apple Pay Validation Server.
- User – confirms the payment with the selected card. Apple Pay Widget provides the Encrypted token.
- Merchant Website (Backend) – initiates the payment with the received encrypted token.
- Platform – decrypt the encrypted token and executes the payment through the Acquirer (Bank).
- Merchant Website (Backend) – opens the Payment Status page.
3. H2H flow with Decryption on Merchant's side. Apple Pay button on Merchants side
3.1 General info
H2H Apple Pay allows the Merchant to place the Apple Pay Payment Button on their side (technically), without embedding the platform's page into the user's payment flow. Token decryption in this case (described below) is done by the Merchant.
3.2 Apple Pay Button Integration, Merchant Validation and Decryption
Apple Pay Button:
Example Code for Displaying the Button:
<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<apple-pay-button buttonstyle="black" type="plain" locale="en-US"></apple-pay-button>
Example Code for Styling the Button:
apple-pay-button {
display: inline-block;
width: 100%;
max-width: 320px;
--apple-pay-button-width: 100%;
--apple-pay-button-height: 44px;
--apple-pay-button-border-radius: 6px;
--apple-pay-button-padding: 0px 0px;
--apple-pay-button-box-sizing: border-box;
}
For more information, please visit below documentation
Displaying Apple Pay Buttons Using JavaScript | Apple Developer Documentation
Sample code for JavaScript
var applePayInProgress = false;
function onApplePayLoaded() {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", onApplePayLoaded);
return;
}
var buttonHolder = document.getElementById("applePayButton");
var unsupported = document.getElementById("applePayUnsupported");
var payButton = document.getElementById("apple-pay-button");
if (!buttonHolder || !unsupported || !payButton) {
return;
}
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
buttonHolder.style.display = "block";
unsupported.style.display = "none";
payButton.addEventListener("click", startApplePay);
} else {
buttonHolder.style.display = "none";
unsupported.style.display = "block";
}
}
function showApplePayLoader() {
var loader = document.getElementById("apayProcessingLoader");
var applePayButton = document.getElementById("applePayButton");
applePayInProgress = true;
if (loader) {
loader.style.display = "block";
}
if (applePayButton) {
applePayButton.style.display = "none";
}
}
function hideApplePayLoader() {
var loader = document.getElementById("apayProcessingLoader");
var applePayButton = document.getElementById("applePayButton");
applePayInProgress = false;
if (loader) {
loader.style.display = "none";
}
if (applePayButton) {
applePayButton.style.display = "block";
}
}
function buildApplePayDetails(payment) {
var token = payment.token || {};
var pm = token.paymentMethod || {};
var billing = payment.billingContact || {};
var contact = payment.shippingContact || {};
var lines = billing.addressLines || [];
return {
apay_card_network: pm.network || "",
apay_card_type: pm.type || "",
apay_card_display_name: pm.displayName || "",
apay_billing_first_name: billing.givenName || "",
apay_billing_last_name: billing.familyName || "",
apay_billing_address1: lines[0] || "",
apay_billing_address2: lines[1] || "",
apay_billing_address3: lines.slice(2).join(", "),
apay_billing_sublocality: billing.subLocality || "",
apay_billing_city: billing.locality || "",
apay_billing_state: billing.administrativeArea || "",
apay_billing_zip: billing.postalCode || "",
apay_billing_country: billing.countryCode || "",
apay_email: contact.emailAddress || "",
apay_phone: contact.phoneNumber || ""
};
}
function startApplePay() {
if (applePayInProgress) {
return;
}
showApplePayLoader();
const paymentRequest = {
countryCode: "<%=country%>",
currencyCode: "<%=currency%>",
supportedNetworks: ["AMEX", "DISCOVER", "JCB", "MASTERCARD", "VISA"],
merchantCapabilities: ["supports3DS"],
requiredBillingContactFields: ["postalAddress", "name"],
requiredShippingContactFields: ["email", "phone"],
total: {
label: "<%=apay_merchantName%>",
amount: "<%=amount%>"
}
};
let session;
try {
session = new ApplePaySession(3, paymentRequest);
} catch (e) {
hideApplePayLoader();
alert("Unable to start Apple Pay: " + e.message);
return;
}
session.onvalidatemerchant = function(event) {
fetch("/transaction/APPMerchantValidationServlet", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
accountId:"<%=accid%>",
host:"<%=merchantDetailsVO.getHostUrl()%>",
validationURL: event.validationURL
})
})
.then(function(response) {
return response.json();
})
.then(function(merchantSession) {
session.completeMerchantValidation(
merchantSession
);
})
.catch(function(error) {
session.abort();
hideApplePayLoader();
alert("Merchant validation failed");
});
};
session.onpaymentauthorized = function(event) {
var payment = event.payment;
var details = buildApplePayDetails(payment);
var payload = JSON.parse(JSON.stringify(payment));
Object.assign(payload, details, {
trackingId: "<%=trackingId%>",
accid: "<%=accid%>",
fromRedirect: "<%=fromRedirect%>"
});
fetch("/transaction/AppPaymentProcessServlet", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(function(response) {
return response.text();
})
.then(function(result) {
var jsonResult = JSON.parse(result);
if (jsonResult.status === "SUCCESS") {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
alert("Payment Fail::"+jsonResult.remark);
}
setTimeout(function() {
var form = document.createElement("form");
form.method = "POST";
form.action = "/transaction/ConfirmationCheckoutServlet";
var input = document.createElement("input");
input.type = "hidden";
input.name = "ctoken";
input.value = jsonResult.ctoken;
form.appendChild(input);
var input1 = document.createElement("input");
input1.type = "hidden";
input1.name = "trackingid";
input1.value = jsonResult.tid;
form.appendChild(input1);
var input2 = document.createElement("input");
input2.type = "hidden";
input2.name = "status";
input2.value = (jsonResult.status === "SUCCESS") ? "success" : jsonResult.status;
form.appendChild(input2);
var input3 = document.createElement("input");
input3.type = "hidden";
input3.name = "mid";
input3.value = jsonResult.mid;
form.appendChild(input3);
document.body.appendChild(form);
form.submit();
}, 500);
})
.catch(function(error) {
session.completePayment(ApplePaySession.STATUS_FAILURE);
hideApplePayLoader();
alert("Payment Processing Error:::");
});
};
session.oncancel = function(event) {
hideApplePayLoader();
};
session.begin();
}
Merchant Validation :
Merchant has to verify his identity with Apple Pay.
Sample java code for merchant validation
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream fis = new FileInputStream(validationPath); //applepay_merchant_identity.p12 file path
keyStore.load(fis, validationPass.toCharArray());
Enumeration <String> aliases1 = keyStore.aliases();
while (aliases1.hasMoreElements()) {
String alias = aliases1.nextElement();
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, validationPass.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509KeyManager km = (X509KeyManager) kmf.getKeyManagers()[0];
String[] aliases = km.getClientAliases("RSA", null);
if (aliases != null) {
for (String a : aliases)
}
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(
kmf.getKeyManagers(),
tmf.getTrustManagers(),
new SecureRandom()
);
JSONObject payload = new JSONObject();
payload.put("merchantIdentifier",merchantIdentifier);
payload.put("displayName",displayName);
payload.put("initiative","web");
payload.put("initiativeContext",host);
transactionlogger.error("payload.toString() : " + payload.toString());
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(
sslContext,
new String[]{"TLSv1.2"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
client = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.setConnectionReuseStrategy(
NoConnectionReuseStrategy.INSTANCE)
.disableConnectionState()
.disableCookieManagement()
.build();
X509Certificate cert = (X509Certificate) keyStore.getCertificate("1");
HttpPost post = new HttpPost(validationURL);
post.setHeader("Content-Type","application/json");
post.setHeader("Connection", "close");
post.setEntity(new StringEntity(payload.toString(),ContentType.APPLICATION_JSON));
appleResponse = client.execute(post);
int statusCode = appleResponse.getStatusLine().getStatusCode();
transactionlogger.error("Apple Status Code : "+ statusCode);
String responseBody = EntityUtils.toString(appleResponse.getEntity(),"UTF-8");
For more information, please visit below documentation
Requesting an Apple Pay payment session | Apple Developer Documentation
Payment token decryption :
Merchant has to decrypt the token on merchant side and pass the decrypted data in the request
Sample java code for token decryption
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public static String decrypt(String base64Data,
String base64EphemeralKey, String pKeyPath, String mid)
throws Exception {
Security.addProvider(
new org.bouncycastle.jce.provider.BouncyCastleProvider());
// ----------------------------------------------------
// 1. Decode Apple Pay values
// ----------------------------------------------------
// Decode inputs
byte[] encryptedData = Base64.getDecoder().decode(base64Data);
byte[] ephemeralKeyBytes = Base64.getDecoder().decode(base64EphemeralKey);
transactionLogger.error(
"Encrypted data length = " +
encryptedData.length
);
transactionLogger.error(
"Ephemeral key length = " +
ephemeralKeyBytes.length
);
// ----------------------------------------------------
// 2. Apple ephemeral public key
// ----------------------------------------------------
// Build ephemeral public key
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey ephemeralPublicKey =
kf.generatePublic(
new X509EncodedKeySpec(ephemeralKeyBytes));
transactionLogger.error(
"Ephemeral public key loaded"
);
// ----------------------------------------------------
// 3. Load merchant private key
// ----------------------------------------------------
// Load payment processing private key
PrivateKey privateKey = loadECPrivateKey(pKeyPath);
//PrivateKey privateKey = loadECPrivateKey2(pKeyPath);
transactionLogger.error(
"Private key loaded. Algorithm = " +
privateKey.getAlgorithm()
);
// ----------------------------------------------------
// 4. ECDH
// ----------------------------------------------------
// ECDH
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
ka.init(privateKey);
ka.doPhase(ephemeralPublicKey, true);
byte[] sharedSecret = ka.generateSecret();
transactionLogger.error("Shared secret length : "+ sharedSecret.length);
// ----------------------------------------------------
// 5. Merchant ID Hash
// ----------------------------------------------------
String merchantId = mid;
byte[] merchantIdHash =
MessageDigest.getInstance("SHA-256")
.digest(
merchantId.getBytes(
StandardCharsets.UTF_8
)
);
transactionLogger.error(
"Merchant ID = " +
merchantId);
// ----------------------------------------------------
// 6. Apple Pay KDF
// ----------------------------------------------------
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Counter = 00000001
baos.write(new byte[] {
0x00, 0x00, 0x00, 0x01
});
// Z = ECDH shared secret
baos.write(sharedSecret);
// Algorithm ID length = 13
baos.write(0x0D);
// Algorithm ID
baos.write("id-aes256-GCM".getBytes(StandardCharsets.US_ASCII));
// Party U Info
baos.write("Apple".getBytes(StandardCharsets.US_ASCII));
// Party V Info
// SHA-256(merchant ID)
baos.write(merchantIdHash);
byte[] kdfInput = baos.toByteArray();
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] aesKey = sha256.digest(kdfInput);
transactionLogger.error(
"AES Key Length = " + aesKey.length
);
transactionLogger.error(
"Merchant ID = [" + merchantId + "]"
);
transactionLogger.error(
"KDF Input Length = " + kdfInput.length
);
// ----------------------------------------------------
// 7. AES-256-GCM
// ----------------------------------------------------
// AES-GCM
SecretKeySpec key = new SecretKeySpec(aesKey,"AES");
byte[] iv = new byte[16];
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding","BC");
GCMParameterSpec spec = new GCMParameterSpec(128,iv);
cipher.init(Cipher.DECRYPT_MODE,key,spec);
byte[] decrypted = cipher.doFinal(encryptedData);
String json = new String(decrypted,StandardCharsets.UTF_8);
transactionLogger.error(
"Decrypted JSON : "
+ json);
return json;
}
public static PrivateKey loadECPrivateKey(String keyPath)
{
String key = null;
PrivateKey privateKey = null;
try
{
key = new String(
Files.readAllBytes(Paths.get(keyPath))
);
key = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] decoded = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance("EC");
privateKey = kf.generatePrivate(spec);
}
catch (IOException e)
{
transactionLogger.error("IOException---",e);
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
transactionLogger.error("NoSuchAlgorithmException---",e);
e.printStackTrace();
}
catch (InvalidKeySpecException e)
{
transactionLogger.error("InvalidKeySpecException---",e);
e.printStackTrace();
}
return privateKey;
}
For more information, please visit below documentation
https://developer.apple.com/documentation/passkit/payment-token-format-reference
3.3 API Integration
Merchant needs to pass decrypted token details to ’s Rest Api.
Refer below link for API sample request and response
For Auth token, refer
Sample code to pass encrypted/token details to
paymentMode=APAY
paymentBrand=APPLEPAY
tokenType=applepayToken
Sample Request
{
"authentication": {
"memberId": "11344",
"checksum": "efac101d0b30bc51771567f9957cec58",
"terminalId": "1106"
},
"merchantTransactionId": "01234",
"paymentBrand": "APPLEPAY",
"paymentMode": "APAY",
"tokenType": "applepayToken",
"paymentType": "DB",
"amount": "50.00",
"currency": "USD",
"orderDescriptor": "Test Transaction",
"shipping": {
"country": "UK",
"city": "Aston",
"state": "NA",
"postcode": "CH5 3LJ",
"street1": "19 Scrimshire Lane"
},
"customer": {
"telnocc": "+44",
"phone": "07730432996",
"email": "john.doe@xyz.com",
"givenName": "John",
"surname": "Doe",
"ip": "192.168.0.1",
"birthDate": "19890202",
"customerId": "12345"
},
"applepayTokenPayload": {
"tokenPaymentData": {
"applicationPrimaryAccountNumber": "4111111111111111",
"applicationExpirationDate": "2812",
"currencyCode": "USD",
"transactionAmount": "100.00",
"deviceManufacturerIdentifier": "000100010000",
"paymentDataType": {
"onlinePaymentCryptogram": "MOLynj+8bwD66fFGi5AoABFIA=",
"eci": "05"
}
}
},
"merchantRedirectUrl": "https://www.merchantRedirectUrl.com",
"notificationUrl": "www.merchantNotificationUrl.com",
"tmpl_amount": "50.00",
"tmpl_currency": "USD",
"merchant_website_url": "https://www.abc.com"
}
4. Merchant Website Apple Pay Button Integration (Redirect)
This section describes the flow where Apple Pay payment button will be hosted on ’s side which can be opened on merchant frontend in an iframe.
In this case tokenType=applepayRedirect
In order to get it enabled, kindly contact our Support Team.
4.1 API Integration
Refer below link for API sample request and response
Sample code to pass encrypted/token details to
paymentMode=APAY
paymentBrand=APPLEPAY
tokenType=applepayRedirect
Sample Request
{
"authentication": {
"memberId": "11344",
"checksum": "efac101d0b30bc51771567f9957cec58",
"terminalId": "1106"
},
"merchantTransactionId": "01234",
"paymentBrand": "APPLEPAY",
"paymentMode": "APAY",
"tokenType": "applepayRedirect",
"paymentType": "DB",
"amount": "50.00",
"currency": "USD",
"orderDescriptor": "Test Transaction",
"shipping": {
"country": "UK",
"city": "Aston",
"state": "NA",
"postcode": "CH5 3LJ",
"street1": "19 Scrimshire Lane"
},
"customer": {
"telnocc": "+44",
"phone": "07730432996",
"email": "john.doe@xyz.com",
"givenName": "John",
"surname": "Doe",
"ip": "192.168.0.1",
"birthDate": "19890202",
"customerId": "12345"
},
"merchantRedirectUrl": "https://www.merchantRedirectUrl.com",
"notificationUrl": "www.merchantNotificationUrl.com",
"tmpl_amount": "50.00",
"tmpl_currency": "USD",
"merchant_website_url": "https://www.abc.com"
}
Sample Response
{
"paymentId": "54279",
"paymentType": "DB",
"paymentBrand": "APPLEPAY",
"paymentMode": "APAY",
"amount": "50.00",
"currency": "USD",
"result": {
"code": "10004",
"description": "Transaction is pending"
},
"timestamp": "2026-09-05 16:03:53",
"transactionStatus": "P",
"redirect": {
"url": "https://document.twpayz.com/transaction/ApplePayRedirect.jsp?trackingId=54279"
}
}
5. Hosted Payment Page with Apple Pay Button
5.1 General info
In this integration flow, the merchant integrates the Hosted Payment Page into their payment process. When a customer chooses to pay using Apple Pay, the customer is redirected to the -hosted payment page, where the Apple Pay button is displayed.
The customer completes the payment using Apple Pay directly on the -hosted payment page. handles the Apple Pay payment flow, including the interaction with Apple Pay, processing of the encrypted Apple Pay payment token, and forwarding the payment for further processing.