Skip to main content

Apple Pay S2S Integration Guide

This guide walks you through integrating Apple Pay payments using the SysPay Wallet Pay S2S API. Your backend orchestrates the full Apple Pay flow through three API calls, while your frontend handles the native Apple Pay session using configuration returned by the API.

Prerequisites

  • Apple Pay activated on your SysPay API feature
  • ROLE_API_PAYMENT permission granted (plus ROLE_PREAUTH for pre-authorizations)
  • Your checkout domain registered for Apple Pay (see Domain Registration below)
  • Your WSSE API credentials (login + passphrase) — see Authentication

Domain Registration

Before you can display the Apple Pay button on your website, the domain serving your checkout page must be registered and verified with Apple. This is a one-time setup handled in coordination with SysPay.

How it works

  1. Request Apple Pay — Contact SysPay to enable the Apple Pay payment method on your contract.
  2. Provide your domain — Tell SysPay the exact domain where Apple Pay will be used (e.g. checkout.mystore.com).
  3. Receive the verification file — SysPay will provide you with the domain verification file: apple-developer-merchantid-domain-association.
  4. Host the verification file — Place the file on your server so that it is accessible at:
    https://<your-domain>/.well-known/apple-developer-merchantid-domain-association

Hosting requirements

The verification file must be publicly reachable. Apple's servers will fetch it to verify domain ownership. Ensure that:

  • The URL is served over HTTPS
  • The server returns HTTP 200
  • There is no authentication required (no Basic Auth, no API key)
  • There are no IP restrictions or geo-blocking
  • There are no redirects (or only clean redirects that Apple can follow)
tip

After uploading, verify it yourself by opening https://<your-domain>/.well-known/apple-developer-merchantid-domain-association in a browser — the file content should be returned directly with a 200 status.

Once SysPay confirms the domain is verified with Apple, you can proceed with the integration.

Payment Flow Overview

The Apple Pay S2S flow consists of three steps:

  1. Init — Create a payment operation and receive Apple Pay configuration
  2. Session — Validate the merchant session with Apple (called from onvalidatemerchant)
  3. Payment — Submit the Apple Pay payment token (called from onpaymentauthorized)
Browser (Safari)                   Your Server                      SysPay API
│ │ │
│ 1. User clicks Apple Pay │ │
│─────────────────────────────> │ │
│ │ 2. POST wallet init │
│ │──────────────────────────────> │
│ │ <── apple_pay_config ──────── │
│ 3. new ApplePaySession() │ │
│ <──── config ──────────────── │ │
│ │ │
│ 4. onvalidatemerchant fires │ │
│─── validationURL ───────────> │ │
│ │ 5. POST session validation │
│ │──────────────────────────────> │
│ │ <── merchantSession ───────── │
│ 6. completeMerchantValidation │ │
│ <──── merchantSession ─────── │ │
│ │ │
│ 7. User authorizes payment │ │
│ (Touch ID / Face ID) │ │
│ │ │
│ 8. onpaymentauthorized fires │ │
│─── paymentData ─────────────> │ │
│ │ 9. POST wallet payment │
│ │──────────────────────────────> │
│ │ <── payment result ────────── │
│ 10. completePayment() │ │
│ <──── SUCCESS / FAILURE ───── │ │

Step 1 — Initialize Payment

Call the Initialize wallet payment endpoint with payment_method.type set to APPLE_PAY.

{
"payment_method": {
"type": "APPLE_PAY"
},
"amount": "1999",
"currency": "EUR",
"reference": "order-20260219-001",
"description": "Order #001",
"preauth": false,
"ems_url": "https://merchant.example.com/notify",
"customer": {
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com",
"ip": "203.0.113.42",
"language": "en",
"billing_address": {
"address1": "123 Main St",
"city": "Paris",
"postal_code": "75001",
"country": "FR"
}
}
}

The response contains:

  • id — Save this. You need it for Steps 2 and 3.
  • apple_pay_config — Pass these fields to your frontend to create the ApplePaySession.
{
"wallet_pay_init": {
"id": 370529,
"status": "OPEN",
"payment_method": "APPLE_PAY",
"apple_pay_config": {
"merchant_id": "merchant.com.syspay.sandbox",
"country_code": "FR",
"label": "My Store",
"supported_networks": ["visa", "masterCard", "amex"],
"merchant_capabilities": ["supports3DS"]
}
}
}

See Initialize wallet payment for the full request and response reference.

Step 2 — Validate Merchant Session

When the onvalidatemerchant event fires on the frontend, forward the validationURL and your domain to the Apple Pay session validation endpoint.

{
"validationURL": "https://apple-pay-gateway.apple.com/paymentservices/startSession",
"domain": "checkout.mystore.com"
}

The response contains a session_data field (base64-encoded). Decode it and pass the resulting JSON to session.completeMerchantValidation():

const merchantSession = JSON.parse(atob(response.session_data));
session.completeMerchantValidation(merchantSession);

See Apple Pay session validation for the full request and response reference.

Step 3 — Submit Payment

When the onpaymentauthorized event fires (after the user authenticates with Touch ID / Face ID), base64-encode the payment token and send it to the Submit wallet payment endpoint.

{
"payment_method": {
"type": "APPLE_PAY",
"token": "eyJkYXRhIjoiYmFzZTY0X2VuY3J5cHRlZF9wYXltZW50X2RhdGEuLi4iLC..."
}
}

The token is built by base64-encoding the paymentData object:

const token = btoa(JSON.stringify(event.payment.token.paymentData));

Based on the returned status, call session.completePayment() with either ApplePaySession.STATUS_SUCCESS or ApplePaySession.STATUS_FAILURE.

Payment Statuses

StatusMeaning
SUCCESSPayment captured successfully
AUTHORIZEDPre-authorization successful (capture later)
FAILEDPayment was declined — see failure_category and failure_message
ERRORProcessing error
TIMED_OUTPayment timed out

See Submit wallet payment for the full request and response reference.

Frontend Integration

Checking Apple Pay Availability

Before showing the Apple Pay button, check that the device supports it:

if (window.ApplePaySession && ApplePaySession.canMakePayments()) {
// Show Apple Pay button
}

Full JavaScript Example

async function startApplePay(orderData) {
// Step 1: Initialize payment via your backend
const initResponse = await fetch('/your-backend/wallet/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});
const { wallet_pay_init } = await initResponse.json();
const operationId = wallet_pay_init.id;
const config = wallet_pay_init.apple_pay_config;

// Create Apple Pay payment request
const paymentRequest = {
countryCode: config.country_code,
currencyCode: orderData.currency,
supportedNetworks: config.supported_networks,
merchantCapabilities: config.merchant_capabilities,
total: {
label: config.label,
amount: (orderData.amount / 100).toFixed(2)
}
};

// Create Apple Pay session
const session = new ApplePaySession(14, paymentRequest);

// Step 2: Merchant validation
session.onvalidatemerchant = async (event) => {
const sessionResponse = await fetch(
`/your-backend/wallet/${operationId}/applepay/session`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
validationURL: event.validationURL,
domain: window.location.hostname
})
}
);
const data = await sessionResponse.json();
const merchantSession = JSON.parse(atob(data.session_data));
session.completeMerchantValidation(merchantSession);
};

// Step 3: Payment authorization
session.onpaymentauthorized = async (event) => {
const token = btoa(JSON.stringify(event.payment.token.paymentData));

const paymentResponse = await fetch(
`/your-backend/wallet/${operationId}/payment`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method: { type: 'APPLE_PAY', token }
})
}
);
const { payment } = await paymentResponse.json();

if (payment.status === 'SUCCESS' || payment.status === 'AUTHORIZED') {
session.completePayment(ApplePaySession.STATUS_SUCCESS);
} else {
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
};

session.oncancel = () => {
// Handle user cancellation
};

session.begin();
}

Backend Integration (PHP)

class ApplePayService
{
private $apiLogin;
private $apiPassphrase;
private $baseUrl;

public function __construct(string $apiLogin, string $apiPassphrase, string $baseUrl)
{
$this->apiLogin = $apiLogin;
$this->apiPassphrase = $apiPassphrase;
$this->baseUrl = $baseUrl;
}

/**
* Step 1: Initialize Apple Pay payment
*/
public function init(array $orderData): array
{
$orderData['payment_method'] = ['type' => 'APPLE_PAY'];

return $this->request('POST', '/merchant/payment/wallet', $orderData);
}

/**
* Step 2: Validate merchant session
*/
public function validateSession(int $operationId, string $validationURL, string $domain): array
{
return $this->request('POST', "/merchant/payment/{$operationId}/wallet/applepay/session", [
'validationURL' => $validationURL,
'domain' => $domain,
]);
}

/**
* Step 3: Submit payment token
*/
public function submitPayment(int $operationId, string $base64Token): array
{
return $this->request('POST', "/merchant/payment/{$operationId}/wallet", [
'payment_method' => [
'type' => 'APPLE_PAY',
'token' => $base64Token,
],
]);
}

private function request(string $method, string $path, array $body): array
{
$ch = curl_init($this->baseUrl . $path);

curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-WSSE: ' . $this->buildWsseHeader(),
],
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($response, true);

if ($httpCode >= 400) {
throw new \RuntimeException(
sprintf('API error %d: %s', $httpCode, $response)
);
}

return $data;
}

private function buildWsseHeader(): string
{
$nonce = random_bytes(16);
$timestamp = time();
$digest = base64_encode(sha1($nonce . $timestamp . $this->apiPassphrase, true));

return sprintf(
'AuthToken MerchantAPILogin="%s", PasswordDigest="%s", Nonce="%s", Created="%d"',
$this->apiLogin,
$digest,
base64_encode($nonce),
$timestamp
);
}
}

Usage

$applePay = new ApplePayService('139112001', 'your-passphrase', 'https://app.syspay.com/api/v2');

// Step 1
$init = $applePay->init([
'amount' => '1999',
'currency' => 'EUR',
'reference' => 'order-001',
'customer' => [
'ip' => $_SERVER['REMOTE_ADDR'],
'email' => 'john@example.com',
],
]);
$operationId = $init['wallet_pay_init']['id'];

// Step 2 (called from your session validation endpoint)
$sessionData = $applePay->validateSession($operationId, $validationURL, $domain);

// Step 3 (called from your payment endpoint)
$result = $applePay->submitPayment($operationId, $base64Token);

Testing Apple Pay

Use the sandbox environment (https://app-sandbox.syspay.com/api/v2) for testing.

To test Apple Pay payments, you need:

  1. An Apple Pay compatible device — iPhone with Face ID/Touch ID, iPad, Mac with Touch ID, or Mac paired with an iPhone.
  2. A sandbox Apple Pay wallet — Sign in to your test device with an Apple sandbox tester account, then add a test card to the Wallet app. See Test Cards for Apps and the Web on Apple's sandbox testing page for available test card numbers and expected behaviors.

Sandbox Setup

  1. Create a sandbox tester account in App Store Connect under Users and Access > Sandbox > Testers.
  2. On your test device, sign out of your personal Apple ID and sign in with the sandbox account (or use a dedicated test device).
  3. Open the Wallet app and add one of Apple's test cards.
  4. Point your integration to the SysPay sandbox environment.
  5. Run through the full payment flow — init, session validation, and payment submission.

What to Verify

  • The Apple Pay payment sheet displays correctly with your merchant name and amount.
  • Merchant session validation succeeds (Step 2 returns a valid session).
  • After authenticating with Face ID/Touch ID, the payment processes and returns SUCCESS or AUTHORIZED.
  • Error scenarios are handled: cancel the payment sheet, use a declined test card, and verify your frontend and backend respond appropriately.
  • If you configured an ems_url, verify that webhook notifications are received.

Further Reading

Checklist

  • Apple Pay is activated on your SysPay API feature
  • Your domain is registered with Apple for Apple Pay (verification file hosted)
  • WSSE authentication is implemented with unique nonces
  • amount is sent in cents as a string
  • customer.ip is the end-user's real IP (not your server's IP)
  • validationURL is forwarded exactly as received from onvalidatemerchant
  • session_data is base64-decoded before passing to completeMerchantValidation()
  • Payment token is base64-encoded before sending to the payment endpoint
  • Payment status is checked and completePayment() is called accordingly
  • Error responses are handled gracefully on both frontend and backend
  • ems_url is set for webhook notifications (recommended)
  • Tested end-to-end in sandbox before going live