Apple Pay
Apple Pay integration lets customers using Apple devices (iPhone, iPad, or a Mac with Touch ID/Face ID) pay quickly with cards stored in Apple Wallet.
How it works
/api/v1_0/cards/payment/{transactionId}/pay/apple-payFull contract: APPLE_PAY_INIT and APPLE_PAY, deviceInfo, and error codes.Full contract in the API Reference
Step 1: Register the payment
Register the transaction as usual:
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.example/success",
"url_fail": "https://myshop.example/failure",
"url_ipn": "https://myshop.example/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. At this point, your server must call dpay.pl with xPayType: APPLE_PAY_INIT to obtain a signed merchant session. The Apple Pay session is created entirely by dpay based on the transaction reference—you do not pass the browser's validationURL:
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",
"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",
"geoLocalization": "52.2297,21.0122",
"deviceID": "device-unique-id",
"applicationName": "Netscape"
}
}
Required fields: xPayType and deviceInfo (all fields shown in the example, including systemFamily, geoLocalization, deviceID, and applicationName; omitting any of them returns HTTP 422). The channelId field is optional and only applies to APPLE_PAY_INIT—it is the card channel identifier provided by dpay support.
Response
dpay.pl returns the merchant session Base64-encoded in the redirectText field. Decoding it produces a ready-to-use ApplePayMerchantSession object, which you pass 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.example",
"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—complete integration
// Check whether 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 {
// /api/pay/apple-pay/session is an endpoint on YOUR backend (example name)
// that calls dpay with xPayType: APPLE_PAY_INIT. Do not call dpay directly
// from the browser because channelId is stored in the server configuration.
// event.validationURL is not needed—dpay creates the session itself.
const response = await fetch('/api/pay/apple-pay/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId }),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.message);
}
// dpay returns the session Base64-encoded in redirectText
const merchantSession = JSON.parse(atob(result.message.redirectText));
session.completeMerchantValidation(merchantSession);
} catch (error) {
session.abort();
console.error('Session validation error:', error);
}
};
// Payment authorization handling
session.onpaymentauthorized = async (event) => {
try {
// Serialize the entire token object (event.payment.token) to JSON
// and Base64-encode it. The backend adds channelId and forwards it to dpay.
const xPayToken = btoa(JSON.stringify(event.payment.token));
const email = event.payment.billingContact?.emailAddress
|| event.payment.shippingContact?.emailAddress;
// /api/pay/apple-pay/process is an endpoint on YOUR backend (example name).
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' or an error.
// Authorization, including 3DS, takes place in the customer's Apple Pay wallet.
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('The customer canceled the Apple Pay payment');
};
// Start the session
session.begin();
});
Server—endpoint handling
The backend exposes two of its own endpoints (/api/pay/apple-pay/session and /api/pay/apple-pay/process in the example above; you may choose other names). Both call the same dpay endpoint, /cards/payment/{transactionId}/pay/apple-pay. They differ only in the xPayType value (APPLE_PAY_INIT versus APPLE_PAY).
Session initialization (APPLE_PAY_INIT)
app.post('/api/pay/apple-pay/session', async (req, res) => {
const { transactionId } = 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',
deviceInfo: buildDeviceInfo(req),
}
);
res.json(response.data);
});
Process the payment (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) {
// All fields are required—omitting any of them returns HTTP 422.
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',
geoLocalization: req.body.geoLocalization || 'unknown',
deviceID: req.ip || 'unknown',
applicationName: 'Netscape',
};
}
API responses
Every call (both INIT and the payment call) returns a {success, status, message} structure. On success, message is a {redirectText, redirectType} object; on error, it is a text message.
With Apple Pay, authorization (including a possible 3DS challenge) happens in the customer's Apple Pay wallet before the token is sent to dpay. For a successful transaction, the payment endpoint (xPayType: APPLE_PAY) always returns redirectType: "SUCCESS"; otherwise, it returns an error. It never returns a FORM with a 3DS challenge. The FORM type appears only in the response to APPLE_PAY_INIT, where it contains the Base64-encoded merchant session.
Successful payment
{
"success": true,
"status": "success",
"message": {
"redirectText": "",
"redirectType": "SUCCESS"
}
}
Error
{
"success": false,
"status": "error",
"message": "Error during payment creation"
}
Testing in sandbox mode
In test mode, you do not need to open a real Apple Pay sheet. Instead of a wallet token, pass one of the special test values in the xPayToken field:
| Token | Scenario |
|---|---|
TEST_SUCCESS | Payment completed successfully |
TEST_SUCCESS_DELAYED | Success after a longer wait |
TEST_DECLINE | Transaction declined |
TEST_INSUFFICIENT | Insufficient funds |
TEST_TIMEOUT | Timeout |
TEST_ERROR | System error |
Example test request (step 3, without steps 1–2):
curl -X POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/pay/apple-pay \
-H "Content-Type: application/json" \
-d '{
"channelId": 31,
"xPayType": "APPLE_PAY",
"xPayToken": "TEST_SUCCESS",
"deviceInfo": {
"browserAcceptHeader": "application/json",
"browserJavaEnabled": "false",
"browserLanguage": "pl-PL",
"browserColorDepth": 24,
"browserScreenHeight": 1080,
"browserScreenWidth": 1920,
"browserTZ": 0,
"browserUserAgent": "Mozilla/5.0",
"systemFamily": "Darwin",
"geoLocalization": "52.2297,21.0122",
"deviceID": "test-device",
"applicationName": "Netscape"
}
}'
The complete list of test tokens is available on the Test environment page.
Requirements
- Your domain must be verified with Apple—dpay.pl handles domain validation as part of the
APPLE_PAY_INITprocess - Your website must be available over HTTPS
- Apple Pay works only on Apple devices with Safari (or Chrome on iOS 16+)
- Apple Pay cannot be tested on Android or Windows devices