Apple Pay
Apple Pay integration allows customers using Apple devices (iPhone, iPad, Mac with Touch ID/Face ID) to pay quickly using cards stored in Apple Wallet.
Flow diagram
Step 1: Register the payment
Register the transaction using the standard method:
curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "79.99",
"url_success": "https://myshop.com/success",
"url_fail": "https://myshop.com/error",
"url_ipn": "https://myshop.com/api/ipn",
"checksum": "..."
}'
Save the transactionId from the response.
Step 2: Initialize the Apple Pay session
When the customer clicks the Apple Pay button, the browser invokes the onvalidatemerchant handler with Apple's validationURL. At that moment your server must call dpay.pl with xPayType: APPLE_PAY_INIT to obtain a signed merchant session:
POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/pay/apple-pay
Content-Type: application/json
Session initialization request
{
"channelId": 31,
"xPayType": "APPLE_PAY_INIT",
"validationUrl": "https://apple-pay-gateway.apple.com/paymentservices/paymentSession",
"deviceInfo": {
"browserAcceptHeader": "application/json,text/plain,*/*",
"browserJavaEnabled": "false",
"browserLanguage": "pl-PL",
"browserColorDepth": 24,
"browserScreenHeight": 1080,
"browserScreenWidth": 1920,
"browserTZ": -60,
"browserUserAgent": "Mozilla/5.0 ...",
"systemFamily": "Win32",
"deviceID": "device-unique-id",
"applicationName": "Netscape"
}
}
Required fields: channelId, xPayType, deviceInfo. The channelId is the card channel identifier provided by dpay support.
Response
dpay.pl returns the merchant session Base64-encoded in the redirectText field. After decoding you receive a ready-to-use ApplePayMerchantSession object that must be passed to session.completeMerchantValidation().
{
"success": true,
"status": "success",
"message": {
"redirectText": "<Base64(JSON ApplePayMerchantSession)>",
"redirectType": "FORM"
}
}
The decoded redirectText contains:
{
"epochTimestamp": 1700000000,
"expiresAt": 1700003600,
"merchantSessionIdentifier": "SSH...",
"nonce": "abc123",
"merchantIdentifier": "merchant.pl.dpay",
"domainName": "myshop.com",
"displayName": "dpay.pl",
"signature": "..."
}
Step 3: Configure the Apple Pay button
<!-- Check Apple Pay availability -->
<div id="apple-pay-button" style="display: none;"></div>
<style>
#apple-pay-button {
-webkit-appearance: -apple-pay-button;
-apple-pay-button-type: pay;
-apple-pay-button-style: black;
width: 100%;
height: 48px;
cursor: pointer;
}
</style>
JavaScript - full integration
// Check if Apple Pay is available
if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
document.getElementById('apple-pay-button').style.display = 'block';
}
document.getElementById('apple-pay-button').addEventListener('click', async () => {
const transactionId = '...'; // From Step 1
// Payment request configuration
const paymentRequest = {
countryCode: 'PL',
currencyCode: 'PLN',
supportedNetworks: ['visa', 'masterCard'],
merchantCapabilities: ['supports3DS'],
total: {
label: 'My Shop',
amount: '79.99',
},
};
const session = new ApplePaySession(3, paymentRequest);
// Session validation - required by Apple
session.onvalidatemerchant = async (event) => {
try {
const response = await fetch('/api/pay/apple-pay/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
transactionId,
validationUrl: event.validationURL,
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message);
}
// dpay returns the session in redirectText, Base64-encoded
const merchantSession = JSON.parse(atob(result.message.redirectText));
session.completeMerchantValidation(merchantSession);
} catch (error) {
session.abort();
console.error('Session validation error:', error);
}
};
// Handle payment authorization
session.onpaymentauthorized = async (event) => {
try {
// The whole token object (event.payment.token) is serialized to JSON
// and Base64-encoded before sending to dpay.pl
const xPayToken = btoa(JSON.stringify(event.payment.token));
const email = event.payment.billingContact?.emailAddress
|| event.payment.shippingContact?.emailAddress;
const response = await fetch('/api/pay/apple-pay/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
transactionId,
email,
xPayToken,
}),
});
const result = await response.json();
if (result.success) {
// For Apple Pay the response is always redirectType: 'SUCCESS' on success, or an error.
// Authorization (including 3DS) happens inside the Apple Pay wallet on the client side.
session.completePayment(ApplePaySession.STATUS_SUCCESS);
window.location.href = '/success';
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
} catch (error) {
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
};
session.oncancel = () => {
console.log('Customer cancelled Apple Pay payment');
};
// Start the session
session.begin();
});
Server - endpoint handling
Both calls hit the same dpay endpoint (/pay/apple-pay); they differ only in the xPayType value.
Session initialization (APPLE_PAY_INIT)
app.post('/api/pay/apple-pay/session', async (req, res) => {
const { transactionId, validationUrl } = req.body;
const response = await axios.post(
`https://api-payments.dpay.pl/api/v1_0/cards/payment/${transactionId}/pay/apple-pay`,
{
channelId: process.env.DPAY_CARD_CHANNEL_ID,
xPayType: 'APPLE_PAY_INIT',
validationUrl,
deviceInfo: buildDeviceInfo(req),
}
);
res.json(response.data);
});
Payment processing (APPLE_PAY)
app.post('/api/pay/apple-pay/process', async (req, res) => {
const { transactionId, email, xPayToken } = req.body;
const response = await axios.post(
`https://api-payments.dpay.pl/api/v1_0/cards/payment/${transactionId}/pay/apple-pay`,
{
email,
channelId: process.env.DPAY_CARD_CHANNEL_ID,
xPayType: 'APPLE_PAY',
xPayToken, // Base64(JSON.stringify(event.payment.token))
deviceInfo: buildDeviceInfo(req),
}
);
res.json(response.data);
});
function buildDeviceInfo(req) {
return {
browserAcceptHeader: req.headers['accept'] || 'application/json',
browserJavaEnabled: 'false',
browserLanguage: req.headers['accept-language']?.split(',')[0] || 'pl-PL',
browserColorDepth: 24,
browserScreenHeight: 1080,
browserScreenWidth: 1920,
browserTZ: 0,
browserUserAgent: req.headers['user-agent'] || 'Unknown',
systemFamily: 'Unknown',
deviceID: req.ip || 'unknown',
applicationName: 'Netscape',
};
}
API responses
Every response (both INIT and payment) returns a {success, status, message} structure. For success, message is an object {redirectText, redirectType}; for errors it is a string.
For Apple Pay, authorization (including any 3DS challenge) happens inside the Apple Pay wallet on the client side, before the token is sent to dpay. The payment endpoint (xPayType: APPLE_PAY) always returns redirectType: "SUCCESS" on a successful transaction, or an error - it never returns FORM with a 3DS challenge. FORM only occurs in response to APPLE_PAY_INIT and then contains a Base64-encoded merchant session.
Payment success
{
"success": true,
"status": "success",
"message": {
"redirectText": "",
"redirectType": "SUCCESS"
}
}
Error
{
"success": false,
"status": "error",
"message": "Error during payment creation"
}
Requirements
- Your domain must be verified with Apple - dpay.pl handles domain validation as part of the
APPLE_PAY_INITprocess - Your website must be accessible via HTTPS
- Apple Pay works exclusively on Apple devices with Safari (or Chrome on iOS 16+)
- It is not possible to test Apple Pay on Android or Windows devices