# Apple Pay

> For the complete documentation index, see [llms.txt](https://docs.banxa.com/llms.txt). Append `.md` to any page URL for its markdown version.


Apple Pay lets users pay with a card already saved to their device — no card entry, no redirect. The payment sheet is native, authenticated by Face ID or Touch ID, and dismisses back into your app once the transaction is confirmed.

Identity creation, KYC, eligibility, and OTP must be completed before reaching the payment step. See the [Integration Guide](/products/native-api/docs/guides/foundations) if you haven't done this yet. This guide picks up from `paymentReady: true`.

Apple Pay can be tested in sandbox
With the React Native, iOS, and Flutter SDKs, you can test Apple Pay end to end in sandbox before going live. See [Test Apple Pay in sandbox](#test-apple-pay-in-sandbox).

## Choose your approach

| Approach | Best for |
|  --- | --- |
| [React Native SDK](#react-native-sdk) | React Native mobile apps |
| [iOS SDK](#ios-sdk) | Native iOS apps (Swift) |
| [Flutter SDK](#flutter-sdk) | Flutter apps (Dart) |
| [Embedded Payment Button](#embedded-payment-button) | Web apps (Vanilla JS, React, Vue) |
| [JS Native Payments SDK](#js-native-payments-sdk) | Web apps (native payment sheet, including cards) |


The three mobile SDKs share the same Apple setup, testing, troubleshooting, and go-live steps, covered once in [iOS platform setup](#ios-platform-setup) and the sections after the SDKs. The two web options register your domain with Apple instead.

## Who does what

**Banxa handles** the payment provider account that processes Apple Pay, so you don't need one of your own. Banxa issues a Certificate Signing Request (CSR) for each environment, installs the certificate you create from it, and, for web integrations, registers your domain with Apple.

**You handle** your Apple Developer account: a merchant identifier for each environment, the payment processing certificate created from Banxa's CSR, and the merchant identifier in Xcode and in your SDK configuration.

## iOS platform setup

Complete these steps before writing any payment code. They apply to the React Native, iOS, and Flutter SDKs.

**You will need:**

- An active Apple Developer Program membership, with the Account Holder or Admin role to create certificates
- Access to [Apple's Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources)
- Xcode
- Your Banxa integration contact, who issues the CSR in Step 2


### Sandbox and production

Sandbox and production are two separate Apple Pay setups. Banxa's sandbox and production environments each hold their own payment processing certificate. Use a separate merchant identifier for each environment, and complete Steps 1 to 4 once for each.

|  | Sandbox | Production |
|  --- | --- | --- |
| Merchant identifier | For example `merchant.com.yourcompany.yourapp.sandbox` | For example `merchant.com.yourcompany.yourapp` |
| Payment processing certificate | Created from Banxa's **sandbox** CSR | Created from Banxa's **production** CSR |
| Apple Pay capability in Xcode | On the build you test with | On your release build |
| SDK environment and credentials | Sandbox | Production |


If your staging and production apps have different bundle identifiers, each app carries its own merchant identifier. If you have already completed the setup for one environment, you only need to repeat it for the other.

#### Step 1 — Create a merchant identifier

A merchant identifier uniquely identifies your app to Apple Pay. It lives under your Apple Developer account, not Banxa's. Create one for each environment.

1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources)
2. Click **Identifiers** in the sidebar, then **+**
3. Select **Merchant IDs**, then click **Continue**
4. Enter a description and an identifier — it must start with `merchant.` (e.g. `merchant.com.yourcompany.yourapp`)
5. Click **Continue**, review, then **Register**


If a merchant identifier you already have is used with another payment provider, create a new one for Banxa.

#### Step 2 — Create a payment processing certificate

The payment processing certificate encrypts Apple Pay payment tokens so that only Banxa's payment provider can read them. It must be created from a CSR that Banxa provides, and Banxa issues a separate CSR for each environment. A certificate created from any other CSR, including one you generate yourself, will not work.

Plan this step as a short live session with your Banxa integration contact: they send you the CSR, you create the certificate and send the `.cer` file straight back, and they install it.

Once you have the CSR file from your Banxa integration contact:

1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources)
2. Click **Identifiers**, filter by **Merchant IDs**, and select the merchant identifier for this environment
3. Under **Apple Pay Payment Processing Certificate**, click **Create Certificate**
4. Upload the `.certSigningRequest` file Banxa provided
5. Click **Continue**, then **Download** to save the `.cer` file
6. Send the `.cer` file back to your Banxa integration contact, and tell them which environment and merchant identifier it is for


Banxa installs the certificate and confirms when it is active. Wait for that confirmation before you test.

Certificate expiry
Payment processing certificates expire every 25 months. When yours approaches expiry, repeat this step — Banxa will provide a fresh CSR.

#### Step 3 — Configure Xcode

1. Open your project in Xcode and select your app target
2. Go to the **Signing & Capabilities** tab
3. Click **+ Capability** and select **Apple Pay**
4. Under the Apple Pay capability, click **+** and add the merchant identifier from Step 1


Each build must carry the merchant identifier for the environment it connects to. If your sandbox and production builds are separate apps or targets, add the sandbox merchant identifier to the one you test with, and the production merchant identifier to your release build.

After adding the capability or changing a merchant identifier, regenerate your provisioning profiles. With automatic signing, Xcode does this for you. With manual signing, enable **Apple Pay Payment Processing** on the App ID in Certificates, Identifiers & Profiles, assign the merchant identifier, and regenerate the profiles. A profile created before the change does not include the Apple Pay entitlement.

The merchant identifier you add here must exactly match the one registered with Banxa. A mismatch is the most common cause of Apple Pay failures at runtime.

#### Step 4 — Set the merchant identifier in the SDK

The SDK passes the merchant identifier to Apple when it presents the payment sheet. Use the same identifier as the Xcode capability for that build, matching the environment the SDK connects to.

| SDK | Setting |
|  --- | --- |
| React Native | `applePayMerchantIdentifier` on the `Banxa` client. See [Create the client](#create-the-client). |
| iOS | `merchantIdentifier` in `PrimerApplePayOptions`. See [Configure](#configure). |
| Flutter | `applePayMerchantIdentifier` on `BanxaConfig`. See [Flutter SDK](#flutter-sdk). |


Select the SDK environment and the merchant identifier from the same build setting, so a build can never pair one environment with the other's merchant identifier.

A missing merchant identifier fails quietly
In React Native, if `applePayMerchantIdentifier` is not set, the SDK uses a placeholder (`merchant.com.yourdomain`) and Apple Pay fails. In Flutter, leaving `applePayMerchantIdentifier` unset disables Apple Pay, and orders fall back to hosted checkout.

## React Native SDK

Complete [iOS platform setup](#ios-platform-setup) first, including Step 4.

### Create the client

Set the environment and the Apple Pay merchant identifier together, from your build configuration:

```typescript
import { Banxa } from '@banxa-official/react-native-sdk';

const isProduction = APP_ENV === 'production'; // your build configuration

const banxa = new Banxa({
  apiKey: 'YOUR_API_KEY',
  partner: 'your-partner-id',
  environment: isProduction ? 'production' : 'sandbox',
  applePayMerchantIdentifier: isProduction
    ? 'merchant.com.yourcompany.yourapp'
    : 'merchant.com.yourcompany.yourapp.sandbox',
});
```

The React Native SDK defaults to `'production'` when `environment` is omitted, so always set it explicitly. If you also pass `primerSettings.paymentMethodOptions.applePayOptions.merchantIdentifier`, that value takes precedence.

### Execute payment

After your backend confirms `paymentReady: true` from eligibility, call `createOrderAndShowPrimerCheckout` with `paymentMethod: 'applePay'`:

```typescript
import {
  isPrimerCheckoutWebViewResult,
} from '@banxa-official/react-native-sdk';

const result = await banxa.buy.createOrderAndShowPrimerCheckout(orderRequest, {
  paymentMethod: 'applePay',
  callbacks: {
    onCheckoutComplete: () => {
      // Apple Pay confirmed — transaction is in progress
    },
    onError: (error) => {
      // Apple Pay unavailable or failed — see error handling below
    },
    onDismiss: () => {
      // User dismissed the payment sheet without completing
    },
  },
});

if (isPrimerCheckoutWebViewResult(result)) {
  // Apple Pay unavailable for this session — SDK fell back to hosted WebView
  render(<CheckoutWebView {...result.webViewProps} />);
}
```

The `orderRequest` must include `externalCustomerId` — pass the same `identityReference` you used for eligibility.

### Error handling

`onError` is called when Apple Pay is unavailable for the current session. Common causes:

- User hasn't set up Apple Pay on their device
- No cards added to Wallet
- None of the user's cards match supported networks


Do not display an Apple Pay button until you've confirmed Apple Pay is available for the session.

For testing, troubleshooting, and going live, see [Test Apple Pay in sandbox](#test-apple-pay-in-sandbox), [Troubleshooting](#troubleshooting), and [Going live](#going-live).

## iOS SDK

The [iOS SDK](/products/native-api/docs/sdk/ios-sdk-reference) presents Apple Pay in a native payment sheet from a native Swift app. It is headless: you start a payment with one call and receive the outcome on a delegate.

Complete [iOS platform setup](#ios-platform-setup) first, including Step 4.

### Install

```swift
dependencies: [
    .package(url: "https://github.com/BanxaOfficial/ios-payment-sdk", from: "1.0.0")
]
```

### Configure

Configure once at app launch and set your delegate.

```swift
import BanxaPaymentSDK

init() {
    // Must match the Apple Pay capability in Xcode for this build, and the
    // identifier registered with Banxa for this environment.
    let applePayOptions = PrimerApplePayOptions(
        merchantIdentifier: "merchant.com.yourcompany.yourapp.sandbox",
        merchantName: nil,
        isCaptureBillingAddressEnabled: true,
        showApplePayForUnsupportedDevice: true,
        checkProvidedNetworks: true
    )

    let settings = PrimerSettings(
        paymentMethodOptions: PrimerPaymentMethodOptions(
            applePayOptions: applePayOptions
        )
    )

    BanxaPaymentSDK.shared.configure(
        config: BanxaConfig(
            apiKey: "YOUR_API_KEY",
            partnerID: "YOUR_PARTNER_ID",
            environment: .sandbox,
            primerSettings: settings
        )
    )
    BanxaPaymentSDK.shared.delegate = self
}
```

| Option | Effect |
|  --- | --- |
| `merchantIdentifier` | Your Apple Pay merchant identifier for this environment. Required. |
| `merchantName` | Name shown on the payment sheet. `nil` uses the one on the merchant identifier. |
| `isCaptureBillingAddressEnabled` | Returns the billing address with the payment token. |
| `showApplePayForUnsupportedDevice` | Shows the Apple Pay option on devices that cannot pay, so the customer sees why. |
| `checkProvidedNetworks` | Validates the card networks against those the device can present. |


Note: Use `.sandbox` with a sandbox `apiKey` and your sandbox merchant identifier while testing, and switch all three to production for release.

### Execute payment

After your backend confirms `paymentReady: true` from eligibility, call `startPayment` with `paymentMethodID` set to `apple-pay`.

```swift
let request = CreateOrderRequest(
    crypto: "BTC",
    fiat: "AUD",
    fiatAmount: "500.00",
    walletAddress: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
    email: "user@example.com",
    redirectURL: "your-app-scheme://banxa-return",
    paymentMethodID: "apple-pay",
    externalCustomerID: "user-123"
)

BanxaPaymentSDK.shared.startPayment(request: request, controller: self)
```

Pass the same `identityReference` you used for eligibility as `externalCustomerID`. `startPayment` creates the order and presents the payment sheet in one call, so call it at the moment the customer confirms.

### Handle the outcome

```swift
extension CheckoutViewController: BanxaPaymentSDKDelegate {

    func banxaDidCompleteCheckout(_ result: BanxaCheckoutResult) {
        // Apple Pay confirmed, the transaction is in progress.
    }

    func banxaDidFail(error: Error) {
        // Apple Pay unavailable or the payment failed.
    }

    func banxaDidDismiss() {
        // The customer dismissed the payment sheet without completing.
    }
}
```

Callbacks are delivered on the main actor. Treat `banxaDidCompleteCheckout` as a UI signal only, and confirm the final state from [webhooks](/products/native-api/docs/transaction-lifecycle/webhooks) before crediting a customer.

The SDK does not tell you why Apple Pay was unavailable
`banxaDidFail` fires when Apple Pay cannot be presented, but the iOS SDK does not expose the eligibility result or the reason. Do not render an Apple Pay button on the strength of a failed attempt. Gate on `POST /eapi/v0/eligibility` from your backend, and treat `banxaDidFail` as a terminal state for that attempt.

For testing, troubleshooting, and going live, see [Test Apple Pay in sandbox](#test-apple-pay-in-sandbox), [Troubleshooting](#troubleshooting), and [Going live](#going-live).

## Flutter SDK

The [Flutter SDK](/products/native-api/docs/sdk/flutter-sdk-reference) presents Apple Pay in a native payment sheet from a Flutter app. `startPayment` creates the order and presents the sheet in one call, and outcomes arrive on a stream.

Complete [iOS platform setup](#ios-platform-setup) first, including Step 4. The merchant identifier you pass to `BanxaConfig` must match both the Xcode capability for that build and the one registered with Banxa for that environment.

### Install

```yaml
dependencies:
  banxa_payments_flutter: 0.1.0
```

Set the iOS deployment target to 15.0, then build once with Flutter before opening the iOS project in Xcode:

```sh
flutter build ios --config-only
```

### Configure

Configure once at app launch. Apple Pay is disabled unless `applePayMerchantIdentifier` is set.

```dart
import 'package:banxa_payments_flutter/banxa_payments_flutter.dart';

await BanxaPayments.configure(
  const BanxaConfig(
    apiKey: 'YOUR_API_KEY',
    partnerId: 'your-partner-id',
    environment: BanxaEnvironment.sandbox,
    applePayMerchantIdentifier: 'merchant.com.yourcompany.yourapp.sandbox',
    applePayMerchantName: 'Your Store',
  ),
);
```

### Execute payment

After your backend confirms `paymentReady: true` from eligibility, call `startPayment` with `paymentMethodId` set to `apple-pay`.

```dart
final launch = await BanxaPayments.startPayment(
  const CreateOrderRequest(
    orderType: OrderType.buy,
    crypto: 'BTC',
    fiat: 'AUD',
    fiatAmount: '500.00',
    walletAddress: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
    email: 'user@example.com',
    redirectUrl: 'https://app.yourcompany.com/complete',
    paymentMethodId: 'apple-pay',
    externalCustomerId: 'user-123',
  ),
);
```

Pass the same `identityReference` you used for eligibility as `externalCustomerId`. `startPayment` creates the order and presents the payment sheet in one call, so call it at the moment the customer confirms.

### Handle the outcome

Subscribe to `checkoutEvents` before starting the payment, and switch on the launch result.

```dart
BanxaPayments.checkoutEvents.listen((event) {
  switch (event) {
    case BanxaCheckoutCompleted():
      // Apple Pay confirmed, the transaction is in progress.
    case BanxaCheckoutFailed(:final message):
      // The payment failed.
    case BanxaCheckoutDismissed():
      // The customer dismissed the payment sheet without completing.
  }
});

switch (launch) {
  case BanxaPrimerCheckoutLaunched():
    // The Apple Pay sheet is on screen.
  case BanxaHostedCheckoutRequired():
    // Apple Pay was not available. Render BanxaHostedCheckoutView.
}
```

Treat `BanxaCheckoutCompleted` as a UI signal only, and confirm the final state from [webhooks](/products/native-api/docs/transaction-lifecycle/webhooks) before crediting a customer.

An unavailable Apple Pay falls back silently
When Apple Pay cannot be presented, `startPayment` returns `BanxaHostedCheckoutRequired` rather than throwing. The SDK does not tell you why. Do not render an Apple Pay button on the strength of a previous attempt: gate on `POST /eapi/v0/eligibility` from your backend, and decide up front whether to offer Apple Pay at all.

For testing, troubleshooting, and going live, see [Test Apple Pay in sandbox](#test-apple-pay-in-sandbox), [Troubleshooting](#troubleshooting), and [Going live](#going-live).

## Test Apple Pay in sandbox

Apple Pay can be tested end to end in sandbox, from the payment sheet to a successful test payment on Banxa's side. These steps apply to the React Native, iOS, and Flutter SDKs. For the JS Native Payments SDK on the web, have your sandbox domain registered as described in [Apple Pay domain setup](#apple-pay-domain-setup), then follow Steps 5 and 6.

Sandbox is a separate Apple Pay setup from production, with its own merchant identifier and its own payment processing certificate. Banxa's sandbox and production environments each hold their own certificate.

**You will need:**

- An Apple Developer account with the Account Holder or Admin role
- Sandbox API credentials from Banxa
- A real iOS device. Apple Pay cannot be tested on the iOS simulator.
- Your Banxa integration contact, for Step 2


### Step 1 — Create a sandbox merchant identifier

Create a merchant identifier for sandbox only, for example `merchant.com.yourcompany.yourapp.sandbox`. Keep your production merchant identifier for production. The Apple Developer steps are in [Create a merchant identifier](#step-1--create-a-merchant-identifier).

### Step 2 — Exchange the sandbox certificate with Banxa

Ask your Banxa integration contact for the **sandbox** CSR. Plan this as a short live session: they send you the CSR, you create the certificate on your sandbox merchant identifier and send the `.cer` file straight back, and they install it. The Apple Developer steps are in [Create a payment processing certificate](#step-2--create-a-payment-processing-certificate).

Wait for Banxa to confirm the sandbox certificate is active before you test.

### Step 3 — Add the sandbox merchant identifier in Xcode

On the target or build configuration you test with, add the sandbox merchant identifier to the Apple Pay capability under **Signing & Capabilities**, then regenerate the provisioning profile. See [Configure Xcode](#step-3--configure-xcode).

The step most often missed
If the test payment does not go through, check this step first. The build you test with must carry the sandbox merchant identifier in its Apple Pay capability.

### Step 4 — Point the SDK at sandbox

Set the SDK to the sandbox environment with your sandbox credentials, and pass the same sandbox merchant identifier as in Xcode.

```typescript React Native
const banxa = new Banxa({
  apiKey: 'YOUR_SANDBOX_API_KEY',
  partner: 'your-partner-id',
  environment: 'sandbox', // the React Native SDK defaults to 'production'
  applePayMerchantIdentifier: 'merchant.com.yourcompany.yourapp.sandbox',
});
```

```swift iOS
// Pass these options in your PrimerSettings and set
// environment to .sandbox, as shown in Configure.
let applePayOptions = PrimerApplePayOptions(
    merchantIdentifier: "merchant.com.yourcompany.yourapp.sandbox",
    merchantName: nil,
    isCaptureBillingAddressEnabled: true,
    showApplePayForUnsupportedDevice: true,
    checkProvidedNetworks: true
)
```

```dart Flutter
await BanxaPayments.configure(
  const BanxaConfig(
    apiKey: 'YOUR_SANDBOX_API_KEY',
    partnerId: 'your-partner-id',
    environment: BanxaEnvironment.sandbox,
    applePayMerchantIdentifier: 'merchant.com.yourcompany.yourapp.sandbox',
  ),
);
```

### Step 5 — Set up the test device

1. In [App Store Connect](https://appstoreconnect.apple.com/), create a sandbox tester under **Users and Access**, **Sandbox**, **Testers**.
2. On the test device, sign out of your Apple Account and sign in with the sandbox tester account.
3. Set the device region to a country or region where Apple Pay is available.
4. In Wallet, add one of [Apple's test cards](https://developer.apple.com/apple-pay/sandbox-testing/) by manual entry.


### Step 6 — Make a test payment

1. From your backend, run [eligibility](/products/native-api/openapi/eligibility/checkeligibility) for a sandbox test customer and confirm `paymentReady: true`.
2. Start an Apple Pay payment: `paymentMethod: 'applePay'` in React Native, or `apple-pay` as the payment method ID in the iOS and Flutter SDKs.
3. Authenticate on the payment sheet.
4. Your completion callback fires. To confirm the payment on Banxa's side, send your Banxa integration contact the order ID.


### If the test payment does not go through

| What you see | What to check |
|  --- | --- |
| The test payment does not go through | Step 3: the Apple Pay capability for this build includes the sandbox merchant identifier, and the provisioning profile was regenerated. Step 4: the SDK uses the same identifier. |
| `[server-error] 404: No Matching ProcessingCertificate` | Step 2: Banxa has confirmed the sandbox certificate, and it was created on the sandbox merchant identifier from Banxa's sandbox CSR. |
| Apple Pay never appears, or the Flutter SDK falls back to hosted checkout | Step 4: the merchant identifier is set. Step 5: you are on a real device with a test card in Wallet. |
| "Apple Pay Is Not Available in [App Name]" | The merchant identifier in Xcode and in the SDK must be identical, and match the one registered with Banxa for sandbox. |


For other errors, see [Troubleshooting](#troubleshooting).

### Moving to production

Production is set up the same way with production values: a production merchant identifier, a certificate from Banxa's **production** CSR, the production merchant identifier on your release build, and the SDK set to production with production credentials. See [Going live](#going-live).

## Troubleshooting

The failure modes below are the same for the React Native, iOS, and Flutter SDKs, because all three present the same payment sheet. The error codes are Primer errors surfaced through the SDK.

**Apple Pay does not go through in sandbox**

The build is not fully set up for the sandbox merchant identifier. Check that:

- The Apple Pay capability in Xcode includes the sandbox merchant identifier, and the provisioning profile was regenerated after you added it (Step 3)
- The SDK is configured with the same sandbox merchant identifier (Step 4)
- Banxa has confirmed the sandbox certificate is active (Step 2)


**Apple Pay works in sandbox but not in production, or the other way round**

Each environment needs its own merchant identifier and certificate. Check that your release build carries the production merchant identifier in both Xcode and the SDK, that the SDK uses production credentials, and that Banxa has confirmed the production certificate is active.

**Apple Pay button does not appear / `[unable-to-present-payment-method]`**

Apple Pay is not available for the current user or session. Common reasons:

- User has not set up Apple Pay on their device
- No cards added to Wallet
- None of the user's cards match supported networks
- Device doesn't support Apple Pay


The SDK filters Apple Pay from available payment methods in these cases. Only show an Apple Pay option when the SDK confirms it's available.

**`[unable-to-make-payments-on-provided-networks]`**

- Apple Pay capability not enabled in Xcode — check **Signing & Capabilities**
- No valid card in Wallet
- Card network not supported
- Device restrictions (e.g. parental controls) enabled


Try on a different real device with a different card.

**"Apple Pay Is Not Available in [App Name]" system error**

The merchant identifier in Xcode does not match the one associated with the payment processing certificate. Verify:

- The merchant identifier in your Xcode Apple Pay capability exactly matches the one registered with Banxa for this environment
- Your Banxa integration contact has confirmed the certificate is active


**`[payment-cancelled]` or payment sheet not presenting**

The merchant identifier is incorrect or does not match what is configured in Xcode. Verify both are identical.

**`[server-error] 404: No Matching ProcessingCertificate`**

Entitlements are correct, but Banxa has no active certificate for this merchant identifier in this environment. The certificate may have been installed for the other environment, or created from a different CSR. Contact your Banxa integration contact to verify the certificate.

**Flutter: payments always land in hosted checkout**

Check `applePayMerchantIdentifier` first: leaving it unset disables Apple Pay entirely. The same happens on the iOS simulator and on a device with no usable card in Wallet.

**The customer authenticates, then the payment is declined**

Your setup is working: the payment sheet appeared and the payment reached the customer's bank, which declined it. See [Declined payments](#declined-payments).

## Going live

Before releasing to the App Store:

1. Create your production merchant identifier (Step 1).
2. Create the production certificate from Banxa's **production** CSR, and wait for Banxa to confirm it is active (Step 2).
3. Add the production merchant identifier to your release build in Xcode, and regenerate the provisioning profiles (Step 3).
4. Set the SDK to production, with production credentials and the production merchant identifier (Step 4).
5. Make a first live payment with a card from a bank with a high approval rate. See [Authorisation Rates](/reference/authorisation-rates). If it is declined after the customer authenticates, see [Declined payments](#declined-payments) before changing your setup.
6. Let your Banxa integration contact know your launch date.


## Embedded Payment Button

The Embedded Payment Button renders Apple Pay inside a web component — no SDK install required. It is a web integration for Vanilla JS, React, and Vue apps.

**Before you start:** Contact your Banxa integration contact to get provisioned. Two things must be set up on Banxa's side: the button-only setting on your environment, and Apple Pay domain registration (covered below). Neither can be self-served. Two certificates are required per merchant for Apple Pay — Banxa creates these as part of provisioning.

Apple Pay is Apple devices only
Apple Pay only works on Apple devices. Do not show the Apple Pay button on Android or other devices — if a user on a non-Apple device triggers the payment sheet, the payment will fail. Detect the device and render only the appropriate payment method.

Environment-level setting
The button-only configuration applies to your entire environment. If it is enabled, the standard full checkout widget will no longer render for that environment. If you need both — for example, the button on one surface and full checkout on another — you will need a separate environment provisioned by Banxa.

### Apple Pay domain setup

Apple Pay on web requires your domain to be registered with Apple. Banxa handles the Apple Developer console steps — your part is to provide your domain and host a verification file.

**Step 1 — Share your domain with Banxa**

Send your integration contact the domain that will host the iframe, for example `app.yourcompany.com`. Banxa will register it in Apple's system. If the button will be served from a subdomain, provide that specific subdomain — domain registration applies at the exact domain level and subdomains must be registered separately.

One Apple Merchant ID per domain
A domain can only be registered with one Apple Merchant ID. If your domain is already registered with another Apple Pay provider (for example, Coinbase Onramp), it cannot also be registered with Banxa. Partners using multiple Apple Pay providers must use separate domains or subdomains for each.

**Step 2 — Host the domain association file**

Once Banxa has registered your domain, they will send you an `apple-developer-merchantid-domain-association` file. Host it at both paths — some configurations require the `.txt` extension:

```
https://app.yourcompany.com/.well-known/apple-developer-merchantid-domain-association
https://app.yourcompany.com/.well-known/apple-developer-merchantid-domain-association.txt
```

The file must be publicly accessible before Banxa can verify the domain.

**Step 3 — Confirmation**

Once the file is live, let your Banxa integration contact know. They will verify the domain and confirm when Apple Pay is active for your environment.

Do not re-download the file from Apple
If the domain association file is downloaded again from Apple's developer console, the file contents change and your hosted copy becomes invalid. If this happens, Banxa will send you an updated file to re-host.

### Install the web component

```bash
npm install @banxa-official/embedded-checkout-web-component
```

Import the package once to register the custom element:

```js
import '@banxa-official/embedded-checkout-web-component';
```

### Create an order

Once your backend confirms `paymentReady: true` from eligibility, create an order. This returns a `checkoutUrl` which you pass to the web component.

```http
POST /{partner}/v2/buy
Content-Type: application/json
x-api-key: YOUR_API_KEY

{
  "externalCustomerId": "user-123",
  "fiat": "AUD",
  "crypto": "BTC",
  "walletAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "fiatAmount": "500.00",
  "redirectUrl": "https://app.yourcompany.com/complete"
}
```

### Embed the component

Pass the `checkoutUrl` as the `source` attribute:

```html
<banxa-checkout source="https://checkout.banxa.com/..."></banxa-checkout>
```

The component stays hidden until the checkout page signals it is ready — at that point it resizes to fit the payment button and becomes visible.

**Lifecycle callbacks**

Assign functions to these properties on the element instance:

| Property | When called |
|  --- | --- |
| `onLoading` | Checkout is loading |
| `onReady` | Component is ready and visible |
| `onPaymentPending` | Payment initiated, awaiting confirmation |
| `onComplete` | Payment completed successfully |
| `onError` | An error occurred |


Each callback receives a `detail` object, not a DOM event.

### Framework examples

```html Vanilla JS
<script type="module">
  import '@banxa-official/embedded-checkout-web-component';
</script>

<banxa-checkout id="checkout" source="https://checkout.banxa.com/..."></banxa-checkout>

<script>
  const el = document.getElementById('checkout');
  el.onLoading = (detail) => console.log('loading', detail);
  el.onReady = (detail) => console.log('ready', detail);
  el.onPaymentPending = (detail) => console.log('payment pending', detail);
  el.onComplete = (detail) => console.log('complete', detail);
  el.onError = (detail) => console.log('error', detail);
</script>
```

```jsx React
import { useEffect, useRef } from 'react';
import '@banxa-official/embedded-checkout-web-component';

export function Checkout() {
  const checkoutRef = useRef(null);

  useEffect(() => {
    const el = checkoutRef.current;
    if (!el) return;

    el.onLoading = (detail) => console.log('loading', detail);
    el.onReady = (detail) => console.log('ready', detail);
    el.onPaymentPending = (detail) => console.log('payment pending', detail);
    el.onComplete = (detail) => console.log('complete', detail);
    el.onError = (detail) => console.log('error', detail);
  }, []);

  return <banxa-checkout ref={checkoutRef} source="https://checkout.banxa.com/..." />;
}
```

```vue Vue
<template>
  <banxa-checkout
    source="https://checkout.banxa.com/..."
    @loading="onLoading"
    @ready="onReady"
    @paymentPending="onPaymentPending"
    @complete="onComplete"
    @error="onError"
  />
</template>

<script setup>
import '@banxa-official/embedded-checkout-web-component';

const onLoading = (event) => console.log('loading', event.detail);
const onReady = (event) => console.log('ready', event.detail);
const onPaymentPending = (event) => console.log('payment pending', event.detail);
const onComplete = (event) => console.log('complete', event.detail);
const onError = (event) => console.log('error', event.detail);
</script>
```

### Going live

Before releasing to production:

- Confirm with your Banxa integration contact that your environment is provisioned for the Embedded Payment Button
- Confirm Apple Pay domain verification is complete for your production domain
- Switch to production credentials for order creation


## JS Native Payments SDK

The [JS Native Payments SDK](/products/native-api/docs/sdk/javascript-native-payments-sdk-reference) renders Apple Pay in the native payment sheet directly on your page through the `<banxa-primer-checkout>` web component. Unlike the Embedded Payment Button, it is not limited to a button: the same component can present cards and Apple Pay together. It is a web integration for Vanilla JS, React, and Vue apps.

Apple Pay is Apple devices only
Apple Pay only works on Apple devices and Safari. Detect the platform and offer Apple Pay only where it is available.

### Install

```bash
npm install @banxa-official/javascript-native-payments-sdk @primer-io/primer-js
```

`@primer-io/primer-js` is the peer dependency that powers the native payment sheet.

### Apple Pay domain registration

Apple Pay on web requires your domain to be registered with Apple, exactly as for the Embedded Payment Button. Banxa handles the Apple Developer console steps; your part is to provide your domain and host the verification file. Complete the steps under [Embedded Payment Button, Apple Pay domain setup](#apple-pay-domain-setup) before going live.

### Create an order

Once your backend confirms `paymentReady: true` from eligibility, create the order and return the `nativeToken` (the Primer client token) to your page. Keep your API key server-side.

```typescript
import { BanxaApiClient } from '@banxa-official/javascript-native-payments-sdk';

const client = new BanxaApiClient({ apiKey, partner, environment: 'sandbox' });

const order = await client.createOrder({
  externalCustomerId: 'user-123', // the same identityReference used for eligibility
  fiat: 'AUD',
  crypto: 'BTC',
  fiatAmount: '500.00',
  walletAddress: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq',
  redirectUrl: 'https://app.yourcompany.com/complete',
});

// return order.nativeToken to the browser
```

### Render the payment sheet

Register the component, include `APPLE_PAY` in `payment-methods`, and set the token returned by your backend.

```html
<script type="module">
  import { registerBanxaPrimerCheckout } from '@banxa-official/javascript-native-payments-sdk/web';
  registerBanxaPrimerCheckout();
</script>

<banxa-primer-checkout id="checkout" payment-methods="APPLE_PAY" locale="en"></banxa-primer-checkout>

<script type="module">
  const { nativeToken } = await (await fetch('/api/create-order', { method: 'POST' })).json();

  const checkout = document.getElementById('checkout');
  checkout.clientToken = nativeToken;

  checkout.addEventListener('banxa:payment-success', () => { /* Apple Pay confirmed */ });
  checkout.addEventListener('banxa:payment-failure', () => { /* payment failed */ });
</script>
```

To offer Apple Pay alongside cards in one sheet, set `payment-methods="PAYMENT_CARD,APPLE_PAY"`.

### Going live

- Confirm Apple Pay domain verification is complete for your production domain
- Install the `@primer-io/primer-js` peer dependency in your production build
- Switch to production credentials for order creation


For the full component API (attributes, events, layout modes), see the [JS Native Payments SDK Reference](/products/native-api/docs/sdk/javascript-native-payments-sdk-reference).

## Declined payments

Card, Apple Pay, and Google Pay payments are all paid with the customer's card. Once the customer authenticates, the payment goes to the bank that issued the card, and that bank approves or declines it. Some banks decline most crypto purchases. A decline at this point means your integration worked: the payment sheet appeared, the payment was processed, and it reached the bank.

| What you see | What it means |
|  --- | --- |
| The payment sheet does not appear, or fails before the customer authenticates | A setup problem. See [Troubleshooting](#troubleshooting). |
| The customer authenticates, then the payment is declined | The customer's bank declined it. |


When the bank declines:

- **Ask Banxa to confirm.** Send your Banxa integration contact the order ID. They can see whether the payment reached the bank and was declined there.
- **Testing in production?** Retry with a card from a different bank. [Authorisation Rates](https://docs.banxa.com/reference/authorisation-rates) shows how often the main banks in each region approve card payments.
- **For your customers,** Banxa cannot override a bank's decision. The customer can ask their bank to allow the payment. Offer another payment method after a decline: in Australia, [PayID bank transfer](https://docs.banxa.com/products/native-api/docs/guides/bank-transfer) does not depend on card approval.