# Apple Pay

> How Apple Pay differs across Checkout, Web SDK, and Direct API, domain verification and account setup, the Direct API session flow and token submission, wallet subscriptions, and common issues.

Apple Pay lets customers pay with a card already added to Wallet, using Face ID or Touch ID. With Checkout and the Web SDK, Onerway renders the Apple Pay button and handles the token, and you only need to complete domain registration. With the Direct API, you render the button, drive `ApplePaySession`, and submit the encrypted payment token returned by Apple to Onerway.

## Choose an integration method

| Integration method | Button and session | Token handling | What you do |
| --- | --- | --- | --- |
| [Checkout](/payments/online-payments/checkout) | Rendered and driven by the checkout page | Never reaches your system | Submit `productType=ALL`, or `lpmsInfo.lpmsType=ApplePay` to lock to Apple Pay |
| [Web SDK](/payments/online-payments/sdk) | Rendered and driven by the SDK on your page; appearance in [Configure wallet buttons](/payments/online-payments/sdk#configure-wallet-buttons) | Never reaches your system | Verify your domain; receive the result through `payment_result`, see [SDK-owned buttons](/payments/online-payments/sdk#sdk-owned-apple-pay-and-google-pay-buttons) |
| [Direct API](/payments/online-payments/api) | Your own button and `ApplePaySession` | Decrypted by Onerway or by you | Verify your domain; every step under "Direct API integration" on this page |

The checkout page is hosted by Onerway and its domain is already registered with Apple, so no domain verification is needed. The Web SDK and the Direct API show the Apple Pay button on your own domain, which Apple requires to be verified first; see "Setup".

All three methods require HTTPS across your site with TLS 1.2 or later and Apple Pay enabled on your Onerway merchant account. Sandbox prerequisites and test cards are listed in [Apple Pay sandbox testing](/payments/get-started/testing#apple-pay-sandbox-testing).

### Browser and device support

Apple Pay on the Web is no longer limited to Safari:

- Safari, and third-party browsers on iOS / iPadOS (all WebKit-based): the payment completes on the device.
- Compatible third-party browsers on Mac, Windows, and other devices: the page shows a QR code that the customer scans with an iPhone or iPad running iOS 18 / iPadOS 18 or later. Direct API merchants must use the `<apple-pay-button>` element from Apple Pay JS SDK 1.2.0 or later; CSS-rendered buttons do not work in non-Safari browsers.
- China mainland: Safari on iPhone and iPad only; third-party browsers are not available.

Checkout and the Web SDK handle these differences for you; Direct API merchants get the same coverage by using the official SDK and button element as shown on this page. See the [Apple support article](https://support.apple.com/en-us/120364) for the authoritative list.

## Setup

Apple requires every merchant domain that shows the Apple Pay button to pass domain verification, which is bound to a Merchant ID in an Apple Developer account. Depending on who holds the Merchant ID, setup falls into three tiers:

| Tier | Domain and merchant validation | Token decryption | Suitable for |
| --- | --- | --- | --- |
| **Default** | Your own Apple Developer account: create the Merchant ID, verify the domains yourself, and request the merchant session from Apple with your own Merchant Identity certificate | Onerway decryption: Onerway generates the CSR for the payment processing certificate, and you upload it to your Merchant ID | Most merchants |
| **Merchant decryption** | Same as Default | You hold the payment processing certificate and submit the decrypted result in `cardInfo`; PCI DSS required | Merchants with PCI DSS and certificate management capability |
| **Onerway proxy** | Domains are registered under the Onerway Apple Developer account, and you call [Validate Apple Pay merchant](/payments/api-reference/endpoints/validate-apple-pay-merchant) to obtain the merchant session | Onerway decryption | Merchants without an Apple Developer account, or that do not want to maintain one |

The Web SDK only involves domain verification, so complete either the Default or the Onerway proxy tier. Do not mix your own Merchant ID with Onerway proxy validation.

In both domain verification approaches, your own account and the Onerway proxy, the verification file is deployed under the `.well-known` path of your site (follow the exact path given by Apple Developer or by Onerway). The address must not sit behind a proxy or redirect, and must be reachable by Apple's verification servers:

```text
https://your-store.com/.well-known/apple-developer-merchantid-domain-association
```

### Your own account

1. **Create a Merchant ID**: sign in to [Apple Developer](https://developer.apple.com/account/), open [Merchant IDs](https://developer.apple.com/account/resources/identifiers/list/merchant) under Certificates, Identifiers & Profiles, and register one with a Description and an Identifier such as `merchant.com.yourcompany.appname`. Skip this step if you already have one.
2. **Configure the payment processing certificate**: for the Default tier, first give the Merchant ID to Onerway technical support and obtain the CSR that Onerway generates. Create the certificate under Apple Pay Payment Processing Certificate for that Merchant ID, answer No to "Will payments be processed exclusively in China mainland?", and upload that CSR. Once the certificate is created, download the `.cer` file and send it back to Onerway: Onerway holds the matching private key and can decrypt Apple Pay tokens only after receiving the certificate. For the Merchant decryption tier, generate the CSR yourself and keep the private key.
3. **Configure the Merchant Identity certificate** (Direct API only): create and download it under Apple Pay Merchant Identity Certificate following [Apple's CSR guide](https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request), store it securely on your merchant validation server, and use it to [request an Apple Pay payment session](https://developer.apple.com/documentation/ApplePayontheWeb/requesting-an-apple-pay-payment-session).
4. **Verify the domain**: add the domain under Merchant Domains, download the verification file, deploy it to the path above, confirm it is reachable over HTTPS, and click Verify in Apple Developer. Domain verification expires together with your site's SSL certificate: Apple re-checks 30, 15, and 7 days before expiry, so renewing the SSL certificate in advance keeps the verification valid; if you replace the certificate only after it expires, verify the domain again. The payment processing and Merchant Identity certificates each expire after 25 months; the Merchant ID does not expire.

### Onerway proxy

1. Give Onerway technical support every domain that shows the Apple Pay button, including subdomains and both sandbox and production domains, in the form `https://your-store.com`.
2. Onerway registers the domains and returns the verification file; deploy it to the path above and confirm it is reachable:

```bash
curl -I https://your-store.com/.well-known/apple-developer-merchantid-domain-association
```

1. Onerway completes domain verification with Apple and notifies you. The payment processing and Merchant Identity certificates are held and renewed by Onerway; you do not manage them.

In every tier, keep certificates in a controlled environment with least-privilege access, and monitor the expiry of both your SSL certificate and the Apple certificates.

## Direct API integration

You render the button and drive `ApplePaySession`; the encrypted payment token returned by Apple is submitted to Onerway according to the decryption mode. Choose one mode; never submit `tokenInfo` and `cardInfo` together.

**Onerway decryption** (recommended; Default and Onerway proxy tiers): serialize the whole `event.payment.token` to a string and put it in `tokenInfo.tokenId` unchanged, without taking apart `paymentData`, `paymentMethod`, and `transactionIdentifier`.

```json
"tokenInfo": "{\"provider\":\"ApplePay\",\"tokenId\":\"<event.payment.token serialized as a string>\"}"
```

**Merchant decryption** (this tier; PCI DSS required): omit `tokenInfo` and put the decrypted result in [`cardInfo`](/payments/api-reference/endpoints/direct-create-transaction#request-cardInfo) as shown below.

| Decrypted Apple field | `cardInfo` field |
| --- | --- |
| `applicationPrimaryAccountNumber` | `cardNumber` |
| `applicationExpirationDate` (`YYMMDD`) | `month` from digits 3 and 4; `year` from the first two digits expanded to four, for example `30` becomes `2030` |
| `onlinePaymentCryptogram` | `cryptogram` |
| `eciIndicator` | `eci` |
| `paymentDataType` | `wallet.applePay.paymentDataType`, `3DSecure` or `EMV`, whichever your decryption returns |

Complete requests are shown in the "Onerway-decrypted Apple Pay payment" and "Merchant-decrypted Apple Pay payment" examples of Create direct transaction.

Load the Apple Pay JS SDK on the page first: the auto-updating `1.latest` address is recommended; cross-browser support requires 1.2.0 or later; if you pin a version, use the `v1.x.y` path and add `integrity`, which `1.latest` does not support. The SDK injects `ApplePaySession` in non-Safari browsers.

<steps level="3">

### Check availability and show the button

After loading the Apple Pay JS SDK, show the button only when `ApplePaySession` exists and `canMakePayments()` returns true. To check whether the customer already has a usable card, use `applePayCapabilities()`; it returns only `paymentCredentialStatusUnknown` in non-Safari browsers, in which case you should still show the button. `canMakePaymentsWithActiveCard()` is deprecated. Use the `<apple-pay-button>` element provided by the SDK; style attributes are documented in [Apple Pay button](https://developer.apple.com/documentation/applepayontheweb/applepaybutton) and branding in the [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/apple-pay).

```html
<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<apple-pay-button buttonstyle="black" type="buy" locale="en-US"></apple-pay-button>
```

### Fetch the Onerway configuration

On your server, call [List available payment methods](/payments/api-reference/endpoints/list-available-payment-methods), take the `paymentMethod=ApplePay` record, and return `countryCode` and `subCardTypes` to the page as the payment request `countryCode` and `supportedNetworks`; both are already in the format Apple expects, so pass them through unchanged. `applePayCapabilities()` needs a merchant identifier: your own Merchant ID in the own-account tiers, or the `merchantId` returned by List available payment methods in the Onerway proxy tier.

### Create the payment session

Use `supportsVersion()` to find the highest version the browser supports, from newest to oldest, then create the `ApplePaySession` and call `begin()`. The payment request needs at least `countryCode`, `currencyCode`, `supportedNetworks`, `merchantCapabilities` including `supports3DS`, and `total` with `label`, `amount`, and `type: 'final'`, where `amount` is a string.

### Check the validationURL and obtain the merchant session

In the `onvalidatemerchant` event, take `validationURL`, confirm that its host is an Apple validation gateway (`apple-pay-gateway.apple.com`, `cn-apple-pay-gateway.apple.com` for China mainland, with the matching `-cert` hosts in the sandbox) and call `abort()` for anything else, then hand it to your server. Your server must re-check the host before forwarding it, never rely on the page check alone, and never hard-code the validation address. In the own-account tiers, your server makes an mTLS request to Apple with the Merchant Identity certificate, sending `merchantIdentifier`, `displayName` (a stable store name, not localized and without order numbers), `initiative` as `web`, and `initiativeContext` as the full domain. In the Onerway proxy tier, call [Validate Apple Pay merchant](/payments/api-reference/endpoints/validate-apple-pay-merchant) and parse the `data` in the response into an object.

### Complete merchant validation

Call `completeMerchantValidation()` as soon as you have the session. A merchant session can be used once and expires five minutes after creation: request it on the server at that moment, never from the client directly against Apple.

### Authorize the payment

In the `onpaymentauthorized` event, send `event.payment.token` to your server, which calls [Create direct transaction](/payments/api-reference/endpoints/direct-create-transaction) with `productType=CARD`, `subProductType=DIRECT`, and `txnType=SALE`, submitting `tokenInfo` or `cardInfo` according to the decryption mode. Based on the server result, the page calls `completePayment()` with the success or failure status, exactly once; otherwise the Apple Pay sheet stays open.

### Confirm the final result

In the synchronous response, `status=S` means success and `P` means processing; the final state is determined by the [payment result webhook](/payments/api-reference/webhooks/payment-result), which carries `walletTypeName=ApplePay` for wallet transactions. Signature verification, acknowledgement, and deduplication are covered in [Webhooks](/payments/get-started/webhooks).

</steps>

## Front-end example

The three internal server endpoints are yours to implement; they correspond to fetching the configuration, validating the merchant, and authorizing the payment.

```html
<apple-pay-button id="applePayButton" buttonstyle="black" type="buy" locale="en-US" style="display:none;"></apple-pay-button>
<script src="https://applepay.cdn-apple.com/jsapi/1.latest/apple-pay-sdk.js"></script>
<script>
  const button = document.getElementById('applePayButton')

  // Server calls List available payment methods and returns { countryCode, subCardTypes }
  const fetchConfig = () => fetch('/api/apple-pay/config').then(r => r.json())
  // Server obtains the merchant session for your tier: own account via the Merchant Identity certificate, Onerway proxy via Validate Apple Pay merchant
  const validateMerchant = (validationURL, website) =>
    fetch('/api/apple-pay/validate-merchant', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ validationURL, website })
    }).then(r => r.json())
  // Server calls Create direct transaction (tokenInfo.provider=ApplePay) and returns { success: boolean }
  const processPayment = (paymentToken) =>
    fetch('/api/apple-pay/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ paymentToken })
    }).then(r => r.json())

  // Accept only Apple's validation gateway hosts, including the China mainland and sandbox domains
  const APPLE_PAY_GATEWAY = /^(cn-)?apple-pay-gateway(-[a-z0-9-]+)?\.apple\.com$/

  const highestSupportedVersion = () => {
    for (let version = 14; version >= 3; version -= 1) {
      if (ApplePaySession.supportsVersion(version)) return version
    }
    return 3
  }

  function startSession(config) {
    const paymentRequest = {
      countryCode: config.countryCode,
      currencyCode: 'USD',
      supportedNetworks: config.subCardTypes,
      merchantCapabilities: ['supports3DS'],
      total: { label: 'Example Store', amount: '99.99', type: 'final' }
    }
    const session = new ApplePaySession(highestSupportedVersion(), paymentRequest)

    session.onvalidatemerchant = async (event) => {
      // Your server must validate the host again; do not rely on this check alone
      if (!APPLE_PAY_GATEWAY.test(new URL(event.validationURL).hostname)) {
        session.abort()
        return
      }
      try {
        const merchantSession = await validateMerchant(event.validationURL, window.location.hostname)
        session.completeMerchantValidation(merchantSession)
      } catch {
        session.abort()
      }
    }

    session.onpaymentauthorized = async (event) => {
      try {
        const result = await processPayment(event.payment.token)
        session.completePayment(result.success ? ApplePaySession.STATUS_SUCCESS : ApplePaySession.STATUS_FAILURE)
      } catch {
        session.completePayment(ApplePaySession.STATUS_FAILURE)
      }
    }

    session.oncancel = () => {
      // The customer closed the Apple Pay sheet; restore the page
    }

    session.begin()
  }

  async function init() {
    if (!window.ApplePaySession || !ApplePaySession.canMakePayments()) return
    const config = await fetchConfig()
    button.style.display = 'block'
    button.addEventListener('click', () => startSession(config))
  }
  init()
</script>
```

Apple's [interactive demo](https://applepaydemo.apple.com/) walks through the complete flow.

## Wallet subscriptions

Apple Pay can be used for subscriptions: submit `subProductType=SUBSCRIBE` with `subscription` on the initial subscription; both `selfExecute=1` (managed by Onerway) and `selfExecute=2` (you initiate each billing) are supported. On Checkout, lock to Apple Pay with `lpmsInfo.lpmsType=ApplePay`; on the Direct API, submit the encrypted wallet token in `tokenInfo` to create the initial subscription. After the initial subscription succeeds, the [subscription payment webhook](/payments/api-reference/webhooks/subscription-payment) returns `contractId` and the subscription `tokenId`; renewals and plan changes then work the same as card subscriptions, see [Subscription payments](/payments/online-payments/scenarios/subscriptions).

Differences from card subscriptions: the encrypted wallet token is single-use and cannot be saved for reuse, so the only billing credential during the subscription is the subscription token; wallet subscriptions produce no saved payment method result webhook and return no card token.

## Common issues

| Symptom | Common causes | What to do |
| --- | --- | --- |
| The button does not appear | Apple Pay JS SDK not loaded or a CSS button used, not HTTPS, unsupported device or browser, or Apple Pay not available in the country or region | Use the SDK `<apple-pay-button>`; show it only when `canMakePayments()` is true; an unknown status from `applePayCapabilities()` in non-Safari browsers is normal; offer other payment methods |
| The sheet flashes and closes after tapping | Merchant validation failed: `validationURL` rejected, the merchant session older than five minutes or reused, the domain verification file missing or unreachable, domain verification lapsed with an expired SSL certificate, or `initiativeContext` not matching the verified domain | Confirm `completeMerchantValidation` is called; confirm the verification file returns 200 with `curl -I`; check the domains match; contact Onerway to regenerate certificates; do not mix sandbox and production merchant identifiers |
| The page reports the payment incomplete after confirmation | `completePayment()` not called or called more than once; `total.amount` not a string or with wrong precision; payment processing certificate in an abnormal state | Check the page logic; contact Onerway support if it still fails |
| Sandbox test cards are declined | No sandbox tester account, device region not matching the test card network, or the card not added to Wallet | Complete the prerequisites in [Apple Pay sandbox testing](/payments/get-started/testing#apple-pay-sandbox-testing) |

When contacting Onerway technical support, provide your merchant number and setup tier, the time and environment (sandbox or production), the device, OS, and browser version, complete console and network logs, and reproduction steps.

## Go-live checklist

- Every sandbox and production domain is verified, the verification file is reachable, and SSL certificate renewal is monitored.
- Merchant sessions are requested only on the server, and `validationURL` is checked on both the page and the server.
- `completePayment()` is called exactly once on both success and failure.
- Apple Pay payment tokens are never logged or stored.
- Webhook signature verification and deduplication are in place.
