Set up Apple Pay (decoded mode)
In decoded mode, you decrypt the Apple Pay payment token on your backend and pass the resulting card fields directly to the Centrobill POST /payment endpoint. Centrobill does not need to validate an Apple Pay session. You handle decryption entirely on your side.
For the standard token mode, see Set up Apple Pay.
Requirements
The requirements are the same as for token mode:
- Your website must use HTTPS
- Your domain must be registered with your Apple Merchant ID
- You have created the Apple Pay CSRs. See Get started with Apple Pay integration
- The customer must use Safari (iOS/macOS)
window.ApplePaySessionmust be available in the browser
Step 1: Display the Apple Pay button (frontend)
<script src="https://applepay.cdn-apple.com/jsapi/v1.3.2/apple-pay-sdk.js" integrity="sha384-DZRWMZLyVXr+7shJfal8pIG2v4KisLoSWFjZQMUv0+GWaCwoa82qeHsWrbBIUDPU" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', async function () {
if (!window.ApplePaySession || !ApplePaySession.supportsVersion(14)) return;
const merchantIdentifier = 'merchant.some.domain';
try {
const applePayCapabilities = await ApplePaySession.applePayCapabilities(merchantIdentifier);
const paymentCredentialStatus = applePayCapabilities?.paymentCredentialStatus;
if (paymentCredentialStatus === 'paymentCredentialsAvailable' || paymentCredentialStatus === 'paymentCredentialStatusUnknown') {
showApplePayButton();
}
} catch {
const can = await ApplePaySession.canMakePayments();
if (can) showApplePayButton();
}
function showApplePayButton() {
const btn = document.getElementById('apple-pay-button');
if (!btn) return;
btn.style.display = 'inline-block';
btn.addEventListener('click', async () => {
const paymentRequest = {
countryCode: 'US',
currencyCode: 'USD',
supportedNetworks: ['visa', 'masterCard', 'amex'],
merchantCapabilities: ['supports3DS'],
total: { label: 'Your Store', amount: '10.00' }
};
const session = new ApplePaySession(14, paymentRequest);
// STEP 1: Ask your own backend to retrieve an Apple Pay session from CentroBill
session.onvalidatemerchant = async (event) => {
try {
const res = await fetch('/api/applepay/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ validationUrl: event.validationURL })
});
if (!res.ok) throw new Error('Merchant validation failed');
const merchantSession = await res.json();
session.completeMerchantValidation(merchantSession);
} catch (e) {
console.error(e);
session.abort();
}
};
// STEP 2: Handle authorized payment and forward token to your backend
session.onpaymentauthorized = async (event) => {
try {
const paymentResponse = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paymentSource: {
type: 'applepay',
token: event.payment.token
},
amount: '10.00',
currency: 'USD',
orderId: 'ORD-12345'
})
});
const result = await paymentResponse.json();
session.completePayment(
result.success ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE
);
} catch (e) {
console.error(e);
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
};
session.oncancel = () => {
console.warn('Apple Pay cancelled by user');
};
session.begin();
});
}
});
</script>
<a id="apple-pay-button" style="display: none;" href="#">
<img decoding="async" loading="lazy" alt="Apple Pay button" srcset="https://docs-assets.developer.apple.com/published/61bec328eef83e2a656d8f82768c219e/ap-button%402x.png" src="https://docs-assets.developer.apple.com/published/61bec328eef83e2a656d8f82768c219e/ap-button%402x.png" data-orientation="landscape" width="244" height="auto">
</a>Step 2: Decrypt the Apple Pay token (your backend)
When the customer authorises the payment, Apple returns an encrypted payment.token object to the browser. Forward it to your backend for decryption.
Decrypt the token using your Payment Processing Certificate and private key. After decryption, extract the following fields from the token payload:
| API field | Source in decrypted token |
|---|---|
number | applicationPrimaryAccountNumber (DPAN) |
expirationMonth | applicationExpirationDate (first 2 digits: MMYYYY) |
expirationYear | applicationExpirationDate (last 2 digits of year) |
tavv | paymentData.onlinePaymentCryptogram |
eci | paymentData.eciIndicator (optional) |
Note
Pass either
tokenor the decoded fields (number,expirationMonth,expirationYear,tavv). Passing both in the same request returns a400error.
Step 3: Submit the payment (your backend → Gateway)
Send a POST /payment request with paymentSource.type = "applePay" and the decoded card fields. Do not include paymentSource.token.
Decoded mode without eci
eci{
"paymentSource": {
"type": "applePay",
"number": "4111111111111111",
"expirationMonth": "12",
"expirationYear": "27",
"tavv": "AQAAA...your_cryptogram..."
},
"sku": {
"name": "your-sku-name"
},
"consumer": {
"email": "[email protected]",
"ip": "203.0.113.45"
},
"url": {
"ipnUrl": "https://your-server.example.com/webhooks/centrobill",
"redirectUrl": "https://your-server.example.com/payment/success"
}
}Decoded mode with eci
eci{
"paymentSource": {
"type": "applePay",
"number": "4111111111111111",
"expirationMonth": "12",
"expirationYear": "27",
"tavv": "AQAAA...your_cryptogram...",
"eci": "05"
},
"sku": {
"name": "your-sku-name"
},
"consumer": {
"email": "[email protected]",
"ip": "203.0.113.45"
},
"url": {
"ipnUrl": "https://your-server.example.com/webhooks/centrobill",
"redirectUrl": "https://your-server.example.com/payment/success"
}
}paymentSource fields
paymentSource fields| Field | Required | Type | Description |
|---|---|---|---|
type | Yes | string | Must be "applePay" |
number | Yes (decoded mode) | string | DPAN (applicationPrimaryAccountNumber) |
expirationMonth | Yes (decoded mode) | string | 2-digit expiry month |
expirationYear | Yes (decoded mode) | string | 2-digit expiry year (e.g. "27") |
tavv | Yes (decoded mode) | string | One-time cryptogram (paymentData.onlinePaymentCryptogram), max 32 characters |
eci | No | string | ECI indicator (paymentData.eciIndicator), max 2 characters. If omitted, may be determined during processing |
Step 4: Handle the response and IPN
The response and IPN handling are identical to token mode. A successful charge returns payment.action: "charge" and payment.status: "success". The final result is delivered to your ipnUrl.
See IPN reference for the full callback structure.
Error handling
| Situation | Error |
|---|---|
Both token and decoded fields passed in the same request | 400 Provide either token or decoded fields, not both |
Neither token nor decoded fields passed | 400 The token or decoded fields are required for applePay |
Updated 8 days ago
