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.ApplePaySession must 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 fieldSource in decrypted token
numberapplicationPrimaryAccountNumber (DPAN)
expirationMonthapplicationExpirationDate (first 2 digits: MMYYYY)
expirationYearapplicationExpirationDate (last 2 digits of year)
tavvpaymentData.onlinePaymentCryptogram
ecipaymentData.eciIndicator (optional)

📘

Note

Pass either token or the decoded fields (number, expirationMonth, expirationYear, tavv). Passing both in the same request returns a 400 error.


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

{
  "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

{
  "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

FieldRequiredTypeDescription
typeYesstringMust be "applePay"
numberYes (decoded mode)stringDPAN (applicationPrimaryAccountNumber)
expirationMonthYes (decoded mode)string2-digit expiry month
expirationYearYes (decoded mode)string2-digit expiry year (e.g. "27")
tavvYes (decoded mode)stringOne-time cryptogram (paymentData.onlinePaymentCryptogram), max 32 characters
eciNostringECI 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

SituationError
Both token and decoded fields passed in the same request400 Provide either token or decoded fields, not both
Neither token nor decoded fields passed400 The token or decoded fields are required for applePay

Did this page help you?