> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fintoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Accept a one-click payment with Apple Pay

> Learn how to use the Fintoc Express Checkout to accept one-click Apple Pay payments

Use a single integration to accept payments through one-click payment buttons. The Express Checkout component is a Fintoc SDK feature that lets your customers pay with wallet buttons, without opening the full widget. The currently supported payment method is **Apple Pay**.

Customers see the Apple Pay button depending on their device and browser. If Apple Pay is not available on the device, the button is hidden.

<Warning>
  **Before you begin: your domain must be enabled for Apple Pay.** Apple Pay won't load until Fintoc registers your domain with Apple, so the button stays hidden on an unregistered domain. See [Enabling Apple Pay](#enabling-apple-pay) to start the process before you integrate.
</Warning>

Accepting Apple Pay payments with Express Checkout takes six steps:

1. Set up your server to create a `Checkout Session`
2. Set up the Fintoc SDK on your frontend
3. Create and mount the Express Checkout component
4. Handle the `onPaymentRequest` callback
5. Submit the payment to Fintoc
6. Test the integration

***

# Step 1: Set up your server

> **Server-side**

Express Checkout calls the `onPaymentRequest` callback after the customer authorizes the Apple Pay sheet. In that callback you must create a **Checkout Session** from your backend and return its `session_token` to the browser.

Expose an endpoint on your server that creates a Checkout Session with Fintoc's API:

**Node**

```javascript theme={null}
// server.js (Node example)
app.post("/api/create-checkout", async (req, res) => {
  const response = await fetch("https://api.fintoc.com/v2/checkout_sessions", {
    method: "POST",
    headers: {
      Authorization: process.env.FINTOC_SECRET_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount: 1000,
      currency: "CLP",
      payment_methods: ["card"],
      recipient_account: { id: "acc_a1b2c3d4e5" },
      // ...any other fields your flow requires
    }),
  });

  const session = await response.json();
  res.json({ session_token: session.session_token });
});
```

<Warning>
  Never expose your secret key in the browser. Always keep the Checkout Session creation call on your server.
</Warning>

***

# Step 2: Set up the Fintoc SDK

> **Client-side**

Express Checkout is automatically available as a feature of the Fintoc SDK. Include the Fintoc script on your checkout page by adding it to the `<head>` of your HTML file. Always load the SDK directly from `js.fintoc.com` to receive security updates. Don't include the script in a bundle or host a copy of it yourself.

```html theme={null}
<head>
  <title>Checkout</title>
  <script src="https://js.fintoc.com/v1/"></script>
</head>
```

***

# Step 3: Create and mount the Express Checkout

> **Client-side**

The Express Checkout component renders the wallet button inside an iframe that securely sends the payment information to Fintoc over an HTTPS connection. The checkout page address must also start with `https://`, rather than `http://`, for your integration to work.

First, create an empty DOM node (container) with a unique ID in your payment form:

```html theme={null}
<div id="apple-pay-container">
  <!-- Express Checkout will be inserted here -->
</div>
<div id="error-message">
  <!-- Display an error message to your customers here -->
</div>
```

When the form has loaded, create an instance of the widget and mount Express Checkout to the container DOM node:

```javascript theme={null}
const widget = window.Fintoc.create({
  product: "payments",
  publicKey: "pk_test_...",
  country: "cl",

  expressCheckout: {
    container: "apple-pay-container",
    amount: 1000,
    currency: "CLP",
  },

  onPaymentRequest: async ({ payment_method_type, wallet }) => {
    const res = await fetch("/api/create-checkout", { method: "POST" });
    const { session_token } = await res.json();
    return { sessionToken: session_token };
  },

  onSuccess: (data) => console.log("Payment succeeded", data),
  onExit: (reason) => console.log("Widget closed", reason),
  onEvent: (eventName, metadata) => console.log(eventName, metadata),
});
```

The Apple Pay buttons can have the following styles:

<Frame caption="Button styles: black, white, and white with outline">
  <img src="https://mintcdn.com/fintoc-49b8bee8/YQmOnq8Zegydl6oL/images/561c75e5fcefe7d9d425ebf84aeef3319c52f1faf85c723cf386406727337137-apple-pay_buttons.png?fit=max&auto=format&n=YQmOnq8Zegydl6oL&q=85&s=2cb710e2686837190232688296dbe678" width="1652" height="160" data-path="images/561c75e5fcefe7d9d425ebf84aeef3319c52f1faf85c723cf386406727337137-apple-pay_buttons.png" />
</Frame>

***

# Step 4: Handle the onPaymentRequest callback

> **Client-side**

`onPaymentRequest` runs when the SDK needs a `sessionToken` to continue the flow. For Apple Pay, the SDK calls it after the customer authorizes the Apple Pay sheet. When you call `widget.open()` for bank transfer, the SDK calls the same callback before it opens the widget. In that callback you must call your server, obtain a `session_token`, and return it to the SDK.

The callback receives a single argument with information about the requested method:

| Field                 | Type                | Description                                                                      |
| --------------------- | ------------------- | -------------------------------------------------------------------------------- |
| `payment_method_type` | `string`            | `"card"` for wallet payments. `"bank_transfer"` when `widget.open()` is called.  |
| `wallet`              | `string` (optional) | The specific wallet selected, e.g. `"apple_pay"`. `undefined` for bank transfer. |

Your callback **must** resolve to `{ sessionToken: string }` within **30 seconds**, or the SDK emits `payment_error`.

<Info>
  You can use the `payment_method_type` and `wallet` fields to reuse a single endpoint for wallets and bank transfer, or to route to different endpoints if your backend logic differs.
</Info>

***

# Step 5: Submit the payment to Fintoc

> **Client-side**

Once the customer authorizes the wallet payment, the SDK automatically completes the flow. You don't need to call a `confirmPayment` method yourself.

The sequence is:

1. The SDK opens the Apple Pay sheet and requests a payment token from Apple Pay.
2. Once the customer authorizes, the SDK emits `processing_express_checkout_payment`.
3. The SDK calls your `onPaymentRequest` callback to get a fresh `sessionToken`.
4. The SDK submits both tokens to the Fintoc backend, which creates the Payment Resource and charges the wallet.
5. When the payment reaches `status === 'succeeded'`, the SDK calls `onSuccess(data)` with the resulting resource.

<Info>
  If the payment status is anything other than `succeeded`, or any step in the flow fails, the SDK emits `payment_error` instead of calling `onSuccess`.
</Info>

### Handling the loading state during payment processing

While Express Checkout is processing a payment, you should display a loading state to give clear feedback to your customer and prevent duplicate interactions. Manage it from the `onEvent` and `onSuccess` callbacks you already pass to `Fintoc.create`:

* `processing_express_checkout_payment` fires once the customer authorizes the payment. Use it to show your loading indicator while the SDK gets a fresh `sessionToken` from your server and submits the payment.
* The payment then resolves through `onSuccess` when it succeeds, or through a `payment_error` event when it fails. Use both to clear the loading indicator.

A typical implementation looks like this:

```javascript theme={null}
window.Fintoc.create({
  // ...
  onEvent: (eventName) => {
    if (eventName === "processing_express_checkout_payment") {
      // Show loading state (e.g. spinner, disable buttons)
      setLoading(true);
    }
    if (eventName === "payment_error") {
      // Hide loading state after a failed attempt
      setLoading(false);
    }
  },
  onSuccess: () => {
    // Hide loading state after a successful payment
    setLoading(false);
  },
});
```

Always clear the loading state on both `onSuccess` and `payment_error` so the UI never gets stuck after a payment attempt.

***

# Step 6: Test the integration

Before you go live, test the integration on a device that supports Apple Pay.

**Requirements for testing:**

* Use your test public key and a test recipient account.
* Open your checkout on iOS or macOS.
* Make sure your staging domain is enabled for Apple Pay. If you haven't done this yet, follow [Enabling Apple Pay](#enabling-apple-pay) first.

**Integration checklist:**

* [ ] The Apple Pay button renders on page load on a supported device.
* [ ] `express_checkout_ready` fires with `wallets.applePay === true`.
* [ ] Authorizing the Apple Pay sheet triggers `onPaymentRequest` with `payment_method_type: 'card'`, `wallet: 'apple_pay'`.
* [ ] `processing_express_checkout_payment` fires while the payment is in progress.
* [ ] `onSuccess` fires with the payment resource after a successful authorization.
* [ ] Cancelling the Apple Pay sheet does **not** emit `payment_error`.
* [ ] Calling `widget.open()` (bank transfer fallback) invokes `onPaymentRequest` with `payment_method_type: 'bank_transfer'` and opens the widget.

***

# Optional configurations

## Listen to the express\_checkout\_ready event

After mounting, Express Checkout won't show buttons until the SDK initializes and checks wallet availability. To animate the element when the button appears, listen to the `express_checkout_ready` event. Inspect the `wallets` value to determine which buttons, if any, to display:

```javascript theme={null}
// Optional: hide the element until we know if a button will show
const container = document.getElementById("apple-pay-container");
container.style.visibility = "hidden";

window.Fintoc.create({
  // ...
  onEvent: (eventName, metadata) => {
    if (eventName === "express_checkout_ready") {
      if (metadata.wallets.applePay) {
        container.style.visibility = "initial";
      } else {
        // No buttons will show; fall back to bank transfer only
      }
    }
  },
});
```

## Style the button

You can tune the look of the Apple Pay button through the optional fields of `expressCheckout`.

```javascript theme={null}
expressCheckout: {
  container: "apple-pay-container",
  amount: 1000,
  currency: "CLP",

  // Height in pixels. Defaults to 44. Range [40, 100].
  buttonHeight: 52,
  // Border radius in pixels. Defaults to 4. Range [0, 100].
  buttonBorderRadius: 12,
  // Specify the label shown inside the button.
  // Defaults to "plain" for Apple Pay.
  buttonType: { applePay: "buy" },
  // Specify the color scheme of the button.
  // Defaults to "black".
  buttonStyle: { applePay: "white" },
  // Specify the language for the Apple Pay label.
  // Defaults to "es-ES".
  locale: { applePay: "es-MX" },
}
```

| Option                 | Type                           | Default   | Description                                         |
| ---------------------- | ------------------------------ | --------- | --------------------------------------------------- |
| `buttonHeight`         | `number` (px)                  | `44`      | Height of the button. Clamped to `[40, 100]`.       |
| `buttonBorderRadius`   | `number` (px)                  | `4`       | Border radius of the button. Clamped to `[0, 100]`. |
| `buttonType.applePay`  | `"buy"`, `"pay"`, or `"plain"` | `"plain"` | Label shown inside the Apple Pay button.            |
| `buttonStyle.applePay` | `"black"` or `"white"`         | `"black"` | Color scheme of the Apple Pay button.               |
| `locale.applePay`      | `"es-ES"` or `"es-MX"`         | `"es-ES"` | Language for the Apple Pay button label.            |

<Info>
  Values outside the allowed range are clamped rather than rejected. For example, `buttonHeight: 200` is treated as `100`.
</Info>

<br />

The same `widget` object returned by `Fintoc.create` exposes the usual widget methods. When `expressCheckout` is configured, calling `widget.open()` automatically invokes `onPaymentRequest` with `payment_method_type: 'bank_transfer'` before opening the widget, so you don't need a second initialization.

```javascript theme={null}
document
  .getElementById("bank-transfer-button")
  .addEventListener("click", () => widget.open());
```

***

# Event reference

Express Checkout uses the same `onEvent` callback as the rest of the widget ([widget events reference](/docs/resources/widget/widget-events)), plus the events specific to this component:

| Event                                 | Payload                               | When it fires                                                                                                                                                                                            |
| ------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `express_checkout_ready`              | `{ wallets: { applePay?: boolean } }` | Once the SDK finishes initializing. The `wallets` object lists the wallets that were successfully rendered. If empty, none are available, for example on an unsupported browser or an unverified domain. |
| `processing_express_checkout_payment` | None                                  | After the customer confirms the wallet payment and the SDK is exchanging tokens with your server and the Fintoc backend.                                                                                 |
| `payment_error`                       | None                                  | The wallet payment failed at any step: `onPaymentRequest` rejected or timed out, the wallet provider returned an error, or the resulting payment status is not `succeeded`.                              |
| `express_checkout_error`              | None                                  | An internal error prevented the button from rendering, for example when the SDK fails to load or the container is not found.                                                                             |

***

# Full option reference

## Fintoc.create(options)

| Parameter          | Type                                  | Required | Description                                                                         |
| ------------------ | ------------------------------------- | -------- | ----------------------------------------------------------------------------------- |
| `product`          | `"payments"`                          | Yes      | Literal value `"payments"` for Express Checkout.                                    |
| `publicKey`        | `string`                              | Yes      | Your publishable key.                                                               |
| `country`          | `"cl"` or `"mx"`                      | No       | Destination country. Defaults to `"cl"`.                                            |
| `expressCheckout`  | `ExpressCheckoutConfig`               | Yes      | Configuration for the wallet button.                                                |
| `onPaymentRequest` | `(data) => Promise<{ sessionToken }>` | Yes      | Callback that returns a `sessionToken` when the SDK needs one to continue the flow. |
| `onSuccess`        | `(data) => void`                      | No       | Callback that receives the payment resource once the payment reaches `succeeded`.   |
| `onExit`           | `(reason?: string) => void`           | No       | Callback that runs when the widget closes.                                          |
| `onEvent`          | `(eventName, metadata?) => void`      | No       | Callback that receives every widget event.                                          |

## ExpressCheckoutConfig

| Parameter              | Type                           | Required | Description                                                                                          |
| ---------------------- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------- |
| `container`            | `string`                       | Yes      | `id` of the DOM element where the SDK renders the wallet button.                                     |
| `amount`               | `number`                       | Yes      | Amount to charge, in the currency's minor unit. For example, CLP has no decimals and MXN uses cents. |
| `currency`             | `string`                       | Yes      | Three-letter ISO 4217 currency code, for example `"CLP"` or `"MXN"`.                                 |
| `buttonHeight`         | `number`                       | No       | Button height in px. Range `[40, 100]`. Default `44`.                                                |
| `buttonBorderRadius`   | `number`                       | No       | Button border radius in px. Range `[0, 100]`. Default `4`.                                           |
| `buttonType.applePay`  | `"buy"`, `"pay"`, or `"plain"` | No       | Apple Pay button label. Default `"plain"`.                                                           |
| `buttonStyle.applePay` | `"black"` or `"white"`         | No       | Apple Pay color scheme. Default `"black"`.                                                           |
| `locale.applePay`      | `"es-ES"` or `"es-MX"`         | No       | Apple Pay button language. Default `"es-ES"`.                                                        |

***

# Enabling Apple Pay

Apple Pay won't load on a domain until Fintoc has registered it with Apple. Registration is a prerequisite for both testing and going live. Complete the registration for every domain that will show the button, in both staging and production. Each registered domain must use HTTPS.

The process works like this:

1. **Tell Fintoc you want to use Apple Pay.** Contact your account manager, customer success contact, or sales contact, and share the domains you plan to integrate.
2. **Host the verification file Fintoc sends you.** Fintoc sends you a domain association file. Host it on each domain under the path `/.well-known/apple-developer-merchantid-domain-association`, so it's reachable at `https://your-domain.com/.well-known/apple-developer-merchantid-domain-association`. The file must return `200 OK` and be downloadable.
3. **Confirm the file is live.** Let Fintoc know once the file is hosted on every domain. Fintoc then registers each domain with Apple.
4. **Test your integration.** Once your domains are registered, the Apple Pay button can load, and you can follow the [testing steps](#step-6-test-the-integration) on a supported device.

<Warning>
  If a domain isn't registered, the Apple Pay button stays hidden and `express_checkout_ready` reports no available Apple Pay wallet (`wallets.applePay` is not `true`). Confirm the domain is enabled before debugging your integration.
</Warning>

***

# See also

* [Accept a payment](/docs/payments/accept-a-payment): standard Checkout Session integration guide.
* [Web Integration](/docs/resources/widget/web-integration): full widget integration guide.
* [Listen to Widget events](/docs/resources/widget/widget-events): complete event catalog.
* [WebView Integration](/docs/resources/widget/webview): integrating the widget inside a native app.
