Skip to main content

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.

3D Secure

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

Apple Pay configuration
  • Your domain must be verified with Apple - dpay.pl handles domain validation as part of the APPLE_PAY_INIT process
  • 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