> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paymentkit.com/guides/integration/sdk-reference/payment-kit-js/apple-pay/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server.
# Apple Pay
Apple Pay lets customers pay using cards saved to their Apple Wallet with a single tap or glance.
# Prerequisites
PaymentKit.js prepares Apple Pay **automatically** when it initializes — the recommended flow has no manual prepare step. You reveal your button once the SDK reports Apple Pay is ready (see [Show the express button](#show-the-express-button)), and the customer's tap submits the payment directly.
> **Info**
>
> Auto-prepare only succeeds when the checkout session's account has an **express-checkout processor** configured with Apple Pay enabled. If none is configured, the SDK stays in the not-ready state and your `onApplePayReady` callback reports `false` — so your button simply never appears.
Add the browser script for your processor:
#### Stripe processor
Add Stripe.js to your page:
```html
```
#### Airwallex processor
No additional scripts required. PaymentKit uses the native `ApplePaySession` API directly for Airwallex.
> **Warning**
>
> **Airwallex processors must also have Apple Pay activated in your Airwallex dashboard.** Enabling Apple Pay in your PaymentKit dashboard alone is **not** enough — Apple Pay must be enabled as a payment method on your Airwallex account (including any required domain registration) before it can be tested or charged. If it isn't activated on the Airwallex side, the Apple Pay sheet will fail to complete even when your PaymentKit configuration is correct.
# Setup
#### CDN
```html
```
#### NPM/ES Modules
```typescript
import PaymentKit from '@payment-kit-js/vanilla';
import ApplePayPaymentMethod from '@payment-kit-js/vanilla/payment-methods/apple-pay';
const paymentKit = PaymentKit({
environment: 'production',
secureToken: 'your_secure_token',
paymentMethods: [ApplePayPaymentMethod]
});
```
## Choose the wallet processor
By default the SDK auto-selects your account's **express-checkout processor** for each wallet. If you have **more than one account** (Stripe or Airwallex) and want a specific one to handle a wallet, pass `processorIds` when you construct PaymentKit — a map of payment method to processor id. The SDK then mints **and** charges that wallet on the exact account you name (Stripe PaymentMethods are account-scoped, so mint and charge must be the same account — this keeps them aligned).
Pass **ids only**; the processor family (Stripe/Airwallex) is resolved from the account. Apple Pay and Google Pay are set **independently**:
```typescript
const paymentKit = PaymentKit({
environment: 'production',
secureToken: 'your_secure_token',
paymentMethods: [ApplePayPaymentMethod],
processorIds: {
applePay: 'proc_prod_1e769fc8bf77493c', // EU account
},
});
```
`processorIds` is read once, when PaymentKit is constructed, and applies for that instance's lifetime. To steer wallets per checkout — e.g. the EU account for EU shoppers, the US account for US shoppers — compute the id and pass it when you initialize PaymentKit **for that session**. A single instance reused across checkouts keeps its original value until you re-init.
```typescript
const paymentKit = PaymentKit({
environment: 'production',
secureToken: 'your_secure_token',
paymentMethods: [ApplePayPaymentMethod],
processorIds: {
applePay: isEuShopper
? 'proc_prod_1e769fc8bf77493c' // EU account
: 'proc_prod_53cd0b7dbdc4449e', // US account
},
});
```
> **Info**
>
> Leave a wallet's entry out of `processorIds` (or omit `processorIds` entirely) to keep the automatic express-checkout selection. The processor **family** (Stripe or Airwallex) is resolved from your account — the SDK looks the pinned id up in the account's wallet processors — so pinning an **Airwallex** processor routes through the Airwallex flow automatically, with no type field to set. If the pinned id isn't one of the account's wallet-eligible processors for the session, that wallet simply stays unavailable (its button never appears) rather than failing mid-payment.
# Show the express button
The SDK prepares Apple Pay **automatically** as soon as it initializes — you no longer need to call `prepareApplePay` yourself. Use `onApplePayReady` to show or hide your express button based on whether Apple Pay is available, and `notifyAmountChanged` to re-prepare whenever the amount changes.
## React to readiness
Register a callback with `onApplePayReady`. It fires immediately with the current state, then again on every change (for example when a re-prepare starts or finishes). Register as many callbacks as you need — none are overwritten.
```typescript
paymentKit.apple_pay.onApplePayReady((isReady) => {
applePayButton.hidden = !isReady;
});
```
While the SDK is re-preparing (see below), it reports `isReady: false`, then `true` again once the new session is ready — so the same callback naturally disables the button during the gap.
## Re-prepare after the amount changes
Apple Pay sessions are pinned to a specific amount. Whenever the cart total changes — a coupon is applied, quantity is updated, shipping is added — call `notifyAmountChanged()` so the SDK clears the stale session and prepares a fresh one. It returns a promise that resolves once the new session is ready.
```typescript
await paymentKit.apple_pay.notifyAmountChanged();
```
Your `onApplePayReady` callback fires across the re-prepare (`false` while in flight, then `true`), so the button hides and reappears on its own. Calling it again while a prepare is already running coalesces the calls — the promise resolves once the latest prepare completes.
> **Info**
>
> `onApplePayReady` and `notifyAmountChanged` are methods on `paymentKit.apple_pay`. They are the recommended way to drive the express button — the manual `prepareApplePay` flow below is retained for backward compatibility.
# Prepare Apple Pay (legacy)
> **Warning**
>
> `prepareApplePay` is **deprecated**. The SDK now prepares automatically on init — observe readiness with `onApplePayReady` and re-prepare with `notifyAmountChanged` instead (see above). This section documents the older manual flow.
If you opt into the legacy manual flow, call `prepareApplePay` before showing the Apple Pay button to check availability and pre-initialize the session — this must happen before the user clicks. With auto-prepare (the recommended flow above), you don't call this at all.
```typescript
import { prepareApplePay, isApplePayPrepared, clearPreparedApplePay } from '@payment-kit-js/vanilla/payment-methods/apple-pay';
const result = await prepareApplePay(
'https://app.paymentkit.com', // API base URL
'your_secure_token',
{
processorId: 'proc_abc123',
processorType: 'airwallex', // Required for Airwallex processors
customerInfo: {
first_name: 'Jane',
last_name: 'Smith'
},
country: 'US'
},
'production' // environment
);
if (result.success) {
// Show Apple Pay button
}
```
> **Warning**
>
> **Airwallex processors**: You must pass `processorType: 'airwallex'` in both `prepareApplePay` and `submit` options. Without this, the SDK will route the payment through the Stripe flow instead of Airwallex, causing a backend error. Stripe processors work without `processorType`.
# Submit payment
Attach `submit` to the same button you reveal with `onApplePayReady`. In the auto-prepare flow the SDK fills `processorId`, `processorType`, and `country` from the prepared session, and the Apple Pay sheet supplies the final payer details — so you don't pass them.
```typescript
applePayButton.addEventListener('click', () => {
paymentKit.submit({
fields: {},
paymentMethod: 'apple_pay',
options: {
customerInfo: {
first_name: 'Jane',
last_name: 'Smith'
}
},
onSuccess: (result) => {
console.log('Transaction ID:', result.transaction_id);
console.log('Checkout Session:', result.checkout_session_id);
window.location.href = '/success';
},
onError: (errors) => {
if (errors.apple_pay) {
// Surface the error or fall back to the card form
}
}
});
});
```
> **Info**
>
> If you use the legacy manual flow, pass `processorId`, `processorType` (`"airwallex"` for Airwallex), and `country` in `options` to match what you passed to `prepareApplePay`.
> **Info**
>
> Need to run your own validation after the shopper authorizes but before the charge? Pass `beforeConfirm` in `options` — see [Pre-charge validation](/guides/integration/sdk-reference/payment-kit-js/pre-charge-validation).
> **Warning**
>
> **Which Stripe account handles the wallet.** For Stripe, the Apple Pay PaymentMethod is minted in the browser on the processor used to **prepare** the sheet, and Stripe PaymentMethods are account-scoped — so the payment is always **charged on that same processor**. Choose the account with the [`processorIds` init option](#choose-the-wallet-processor) (or the account's express-checkout processor when unset). A different `processorId` passed at `submit` is ignored for an already-prepared sheet — the SDK keeps mint and charge on one account and logs a warning.
# Cleanup
In the auto-prepare flow the SDK manages prepared state per instance, so no manual cleanup call is required. If you use the legacy `prepareApplePay` flow, clear the prepared state when unmounting or navigating away:
```typescript
clearPreparedApplePay();
```
# Options
## Submit options
In the auto-prepare flow, `processorId`, `processorType`, and `country` are filled from the prepared session — you only pass them if you use the legacy manual flow.
| Option | Type | Description |
| ------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `processorId` | `string` | The processor that handles the whole wallet payment — the sheet mints its PaymentMethod on this account and the charge is made on the same one (Stripe PMs are account-scoped). Fixed at **prepare** time (auto-filled from the prepared session); a different value passed here is ignored for an already-prepared sheet. Required only for the legacy flow. |
| `processorType` | `"stripe" \| "airwallex"` | Auto-filled from the session. For the legacy flow, set to `"airwallex"` for Airwallex processors. |
| `customerInfo.first_name` | `string` | Customer's first name. The Apple Pay sheet supplies the final payer name. |
| `customerInfo.last_name` | `string` | Customer's last name. |
| `country` | `string` | Two-letter country code (e.g., `"US"`). Auto-filled from the session in the express flow. |
| `amount` | `number` | Amount in atomic units (cents) for the Apple Pay sheet display |
| `currency` | `string` | Currency code (e.g., `"usd"`) |
| `merchantName` | `string` | Merchant display name shown on the Apple Pay sheet |
| `mockScenario` | `ApplePayMockScenario` | Testing only. Use `"success"` or `"cancelled"` |
## `prepareApplePay` options (legacy)
These apply only to the deprecated manual `prepareApplePay` flow.
| Option | Type | Description |
| ------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `processorId` | `string` | **Required.** The processor that handles the whole wallet payment — the sheet mints on it and the charge is made on it. Pass the same value at `submit`. |
| `processorType` | `"stripe" \| "airwallex"` | **Required for Airwallex.** Set to `"airwallex"` when using an Airwallex processor |
| `customerInfo.first_name` | `string` | **Required.** Customer's first name |
| `customerInfo.last_name` | `string` | **Required.** Customer's last name |
| `country` | `string` | **Required.** Two-letter country code (e.g., `"US"`) |
| `amount` | `number` | Amount in atomic units (cents) for the Apple Pay sheet display |
| `currency` | `string` | Currency code (e.g., `"usd"`) |
| `merchantName` | `string` | Merchant display name shown on the Apple Pay sheet |
| `mockScenario` | `ApplePayMockScenario` | Testing only. Use `"success"` or `"cancelled"` to simulate Apple Pay flows without a real device |
# Browser support
Apple Pay works on:
* Safari on macOS (with Touch ID or a paired iPhone/Apple Watch)
* Safari on iOS and iPadOS (with Face ID, Touch ID, or passcode)
> **Info**
>
> Apple Pay requires Safari. For Stripe processors, PaymentKit.js uses Stripe's Payment Request API, which also only surfaces Apple Pay in Safari. For Airwallex processors, the native `ApplePaySession` API is used directly, which is Safari-only. Always provide card payments as a fallback.
# Error handling
In the auto-prepare flow, availability problems surface through `onApplePayReady(false)` — the button never appears — rather than as submit errors. `Processor ID is required` means the checkout session has no express-checkout processor configured.
| Error | Cause |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `Processor ID is required` | No processor available — the session has no express-checkout processor configured (or none passed in the legacy flow) |
| `Apple Pay not available on this device (requires Safari)` | Airwallex: device or browser does not support Apple Pay (non-Safari) |
| `Apple Pay not available on this device` | Airwallex: `canMakePayment()` returned false — no cards in wallet or hardware restriction |
| `Apple Pay is not available on this device or Stripe account` | Stripe: `prepareApplePay` could not confirm Apple Pay availability via Payment Request API |
| `Stripe.js not loaded. Add to your page.` | Stripe.js script not present when initializing the Stripe adapter |
| `Apple Pay cancelled by user` | Customer dismissed the Apple Pay sheet |
| `Failed to start Apple Pay` | API call to the `/apple-pay/start` endpoint failed |
| `Apple Pay failed` | Airwallex payment sheet returned a non-cancelled failure |
| `3DS authentication failed` | Airwallex 3DS challenge completed without success |
| `Too many authentication attempts. Please try again.` | Airwallex 3DS loop exceeded the maximum retry limit |
| `Payment failed` | Confirm or verify endpoint returned a non-success charge status |
| `Apple Pay error: {message}` | Unexpected exception during the payment flow |
> Accept Apple Pay for fast, secure checkout on Safari and iOS devices.