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

# Headless Checkout

export const ApiEndpoint = ({method, path}) => {
  const getMethodColor = method => {
    switch (method?.toUpperCase()) {
      case "GET":
        return "#0285c7";
      case "POST":
        return "#008d71";
      case "PUT":
        return "#16a34a";
      case "PATCH":
        return "#808080";
      case "DELETE":
        return "#dc2626";
      default:
        return "#6b7280";
    }
  };
  const getEndpointUrl = (method, path) => {
    const endpoints = {
      "POST /client-session": "/api-reference/v2.4/api-reference/client-session-api/create-a-client-session",
      "PATCH /client-session": "/api-reference/v2.4/api-reference/client-session-api/update-client-session",
      "POST /payments": "/api-reference/v2.4/api-reference/payments-api/create-a-payment",
      "POST /payments/<YOUR-PAYMENT-ID>/resume": "/api-reference/v2.4/api-reference/payments-api/resume-payment",
      "POST /payments/<YOUR-PAYMENT-ID>/adjust-authorization": "/api-reference/v2.4/api-reference/payments-api/adjust-authorized-amount",
      "POST /payments/<PAYMENT_ID>/cancel": "/api-reference/v2.4/api-reference/payments-api/cancel-payment",
      "POST /payments/<PAYMENT_ID>/refund": "/api-reference/v2.4/api-reference/payments-api/refund-payment",
      "GET /payments/<PAYMENT_ID>": "/api-reference/v2.4/api-reference/payments-api/get-a-payment",
      "GET /payment-instruments": "/api-reference/v2.4/api-reference/payment-methods-api/list-saved-payment-methods",
      "DELETE /payment-instruments/<PAYMENT_METHOD_TOKEN>": "/api-reference/v2.4/api-reference/payment-methods-api/delete-payment-method-payment-methods-token-delete"
    };
    const key = `${method?.toUpperCase()} ${path}`;
    return endpoints[key] || "#";
  };
  const getPrettyPath = (method, path) => {
    const pathMapping = {
      "POST /payments/<YOUR-PAYMENT-ID>/resume": "/payments/{paymentId}/resume",
      "POST /payments/<YOUR-PAYMENT-ID>/adjust-authorization": "/payments/{paymentId}/adjust-authorization",
      "POST /payments/<PAYMENT_ID>/cancel": "/payments/{paymentId}/cancel",
      "POST /payments/<PAYMENT_ID>/refund": "/payments/{paymentId}/refund",
      "GET /payments/<PAYMENT_ID>": "/payments/{paymentId}",
      "DELETE /payment-instruments/<PAYMENT_METHOD_TOKEN>": "/payment-instruments/{paymentMethodToken}"
    };
    const key = `${method?.toUpperCase()} ${path}`;
    return pathMapping[key] || path;
  };
  const methodColor = getMethodColor(method);
  const url = getEndpointUrl(method, path);
  const displayPath = getPrettyPath(method, path);
  return <div style={{
    display: "inline-flex",
    alignItems: "center",
    cursor: "pointer",
    margin: "2px 0",
    padding: "4px",
    paddingRight: "6px",
    background: "rgb(245, 245, 245)",
    borderRadius: "6px",
    whiteSpace: "nowrap",
    textDecoration: "none"
  }}>
      <a href={url} target="_blank" rel="noopener noreferrer" style={{
    textDecoration: "none",
    border: "none"
  }}>
        <span style={{
    display: "inline-block",
    whiteSpace: "nowrap",
    padding: "0 8px",
    fontSize: "13px",
    lineHeight: "23px",
    borderRadius: "6px",
    fontWeight: 400,
    backgroundColor: methodColor,
    color: "white",
    border: "none"
  }}>
          {method.toUpperCase()}
        </span>
        <code style={{
    padding: "0",
    marginLeft: "4px",
    fontFamily: "monospace",
    color: methodColor,
    border: "none",
    textDecorationColor: methodColor
  }}>
          {displayPath}
        </code>
      </a>
    </div>;
};

<Note>
  We've introduced a new way to integrate our Checkout for Web\
  Learn how to build a fully customizable checkout using modular components through [our documentation here](/checkout/overview)
</Note>

Where there is a need for more customization and control over the checkout experience, a headless version of Primer’s Universal Checkout is available.

You can use Headless Universal Checkout with your UI, giving you more flexibility and allowing you to move faster when making design changes, while still having Universal Checkout capture sensitive PCI card data or other form data.

<Warning>
  Not all payment methods are currently compatible with Headless Checkout.

  Please refer to [this table](/connections/payment-methods/available-payment-methods) to learn more about the payment methods available for Headless Checkout.
</Warning>

## Before you start

Before you start, make sure:

* you are ready to [process a payment](/get-started/connect-a-processor)
* Universal Checkout is properly [configured in the dashboard](/checkout/checkout-builder)

## Create a client session

A **client session** is the starting point for integrating payments at Primer. You can attach any data associated with the `order` to your client session.

Creating a client session provides you with a **client token**, a temporary key used to initialize the Universal Checkout.

The information you include in the client session is used in the Dashboard:

* to conditionally route payments with Workflows
* to activate payment methods and other features in Universal Checkout

So pass as much information as you can!

### Generate an API key

Requests to our API are authenticated using an API key in the `X-Api-Key` header. Create an API key by visiting the [developer page](https://dashboard.primer.io/developers/apiKeys) of the Primer Dashboard.

Make sure to set the following scopes for your API Key:

* `client_tokens:write`
* `transactions:authorize`

### Make a client session request

On your server, create a client session with <ApiEndpoint method="POST" path="/client-session" />
.

Make sure to pass at least the following data:

| Field                                                                                                                    | Description                                                                                                                                                                                                             |
| :----------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [orderId](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-order-id)                    | Your reference for the payment. <br /><br /> *`Make sure to keep track of orderId - you will later receive updates to the payment via Webhooks. The payment will contain the orderId specified in the client session.`* |
| [currencyCode](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-currency-code)          | The three-letter currency code in [ISO 4217 format](https://en.wikipedia.org/wiki/ISO_4217#Active_codes). <br /> e.g. use `USD` for US dollars.                                                                         |
| [order  ↳ lineItems](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-order-line-items) | The details of the line items of the order.                                                                                                                                                                             |

<Note>
  The body of a successful response contains a [clientToken](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#response-client-token) that you will use to initialize the Universal Checkout.
</Note>

Here is how the client session request to the Primer API should look like:

```bash BASH theme={"dark"}
curl --location --request \
 POST 'https://api.sandbox.primer.io/client-session' \
 --header 'X-Api-Key: <YOUR_API_KEY>' \
 --header 'X-Api-Version: 2.2' \
 --header 'Content-Type: application/json' \
 --data '{
    "orderId": "<YOUR_ORDER_ID>",
    "currencyCode": "GBP",
    "amount": 5000,
    "order": {
      "lineItems": [{
        "itemId": "shoes-123",
        "amount": 2500,
        "quantity": 2
      }],
      "countryCode": "GB"
    }
 }'

# Here is a (heavily truncated) example response

{
  "clientToken": "THE_CHECKOUT_SESSION_TOKEN",
  "clientExpirationDate": "2022-03-08T14:00:00Z",
  "orderId": "<YOUR_ORDER_ID>",
  "currencyCode": "GBP",
  "amount": 5000,
	"order": {
      "lineItems": [{
        "itemId": "shoes-123",
        "amount": 2500,
        "quantity": 2
      }],
      "countryCode": "GB",
    }
}
```

## Get Started

Primer Headless Universal Checkout works in a simple way:

1. Get a `clientToken` from your server
2. Start Primer Headless Universal Checkout with the client token
3. Primer Headless Universal Checkout will then return the `available payment methods` for the session initiated. Those payment methods that have been configured in the Dashboard and whose conditions match the current client session will be returned.
4. You show the user the list of available payment methods.
5. When the user selects a payment method, show its UI to enable the user to enter their credentials. Depending on the payment method, you will have to either ask the SDK to render it, or build the UI yourself.
6. Primer's Headless Universal Checkout will then create a payment for you and manage its lifecycle. You will receive a confirmation of payment with a callback to indicate the checkout flow has completed.

<Tabs>
  <Tab title="Web">
    <Note>
      This documentation is only relevant for v2.21.0 and upward.
    </Note>

    ## Step 1. Install the SDK

    <Note>
      Please review the [Content Security Policy (CSP)](/checkout/advanced/content-security-policy/) recommendations before installing.
    </Note>

    ### With npm

    Our Web SDK is available on npm under the name [`@primer-io/checkout-web`](https://www.npmjs.com/package/@primer-io/checkout-web).

    This package includes TypeScript definitions.

    ```bash BASH theme={"dark"}
    # With yarn
    yarn add @primer-io/checkout-web

    # With npm
    npm install --save @primer-io/checkout-web
    ```

    ```typescript Typescript theme={"dark"}
    import { Primer } from '@primer-io/checkout-web'

    Primer.createHeadless(clientToken, options)
    ```

    <Warning>
      * The npm package only works if used alongside a bundler such as Webpack or Parcel. If you're directly writing JavaScript using `script` tag, please use our [CDN](#with-our-cdn) instead.
      * As of today, the npm package does not work in a server environment. If you are using Next.js, Gatsby, or a similar framework, make sure the `Primer` functions are called on the client side, or use our [CDN](#with-our-cdn) instead.
    </Warning>

    ### With our CDN

    Include the `Primer.min.js` script and the `Checkout.css` stylesheet on the page where you want to render the Checkout. Make sure to pass the proper version in the URL.

    ```html HTML theme={"dark"}
    <link rel="stylesheet" href="https://sdk.primer.io/web/v2.64.4/Checkout.css" />

    <script
      src="https://sdk.primer.io/web/v2.64.4/Primer.min.js"
      integrity="sha384-OCseHAfsbYg4/Fgg3Wd01/j/CRqQheXwuHdQGyuvJrYkqXSlapEL4OrLs+qqxOQR"
      crossorigin="anonymous"
    ></script>
    ```

    The `Primer.min.js` will add the `Primer` object to the global scope:

    ```typescript Typescript theme={"dark"}
    const { Primer } = window

    Primer.createHeadless(clientToken, options)
    ```

    ## Step 2: Initialize Primer’s Headless Universal Checkout

    ### Generate a client token

    Request a client token from your backend by creating a client session.

    <Tip>
      Check our guide on how to create a client session [here](/checkout/client-session).
    </Tip>

    <Note>
      Remember that based on your client token different payment methods will be available for display.
    </Note>

    ### Configure Headless Universal Checkout

    Once you have a client token, initialize Primer’s headless checkout with `Primer.createHeadless(clientToken, options)`.

    ```typescript Typescript theme={"dark"}
    const headless = await Primer.createHeadless(clientToken, options)
    ```

    Make sure to implement at least the following callbacks:

    * `onAvailablePaymentMethodsLoad(paymentMethodTypes)` \
      returns the available payment methods for the client session. Use it to render a list of payment methods.
    * `onCheckoutComplete(data)` \
      is called when the payment has been successfully completed. It returns a reference to the payment.
    * `onCheckoutFail(error, data, handler)` \
      is called if the payment fails to be created or processed.

    <Note>
      Payment methods are added and configured through your Primer Dashboard. `onAvailablePaymentMethodsLoad` will return the payment methods whose conditions match the current client session.
    </Note>

    Finally, call `headless.start()` to retrieve the list of payment methods, and start the checkout flow.

    ```typescript Typescript theme={"dark"}
    await headless.start()
    ```

    Here is a full example to configure and start Headless Universal Checkout:

    ```typescript Typescript theme={"dark"}
    window.addEventListener('load', onLoaded)

    async function onLoaded() {
    	// Create a client session via your backend
    	const clientSession = await fetch('/client-session', {
    		method: 'post',
    		headers: { 'Content-Type': 'application/json' },
    	}).then(data => data.json())

    	const { clientToken } = clientSession
    	const { Primer } = window

    	// Create an instance of the headless checkout
    	const headless = await Primer.createHeadless(clientToken, {
    		onAvailablePaymentMethodsLoad(paymentMethods) {
    			// Called when the available payment methods are retrieved

    			for (const paymentMethod of paymentMethods) {
    				// `type` is a unique ID representing the payment method
    				const { type, managerType } = paymentMethod

    				switch (managerType) {
    					case 'CARD': {
    						// Configure your card form (see Step 4.a)
    						// await configureCardForm(paymentMethod);
    						break
    					}
    					case 'NATIVE': {
    						// Render the native payment method button (see Step 4.b)
    						// Relevant for PayPal, Apple Pay and Google Pay
    						// configureNativeButton(paymentMethod);
    						break
    					}
    					case 'REDIRECT': {
    						// Handle redirect payment methods (see Step 4.c)
    						// configureRedirectPaymentMethod(paymentMethod);
    						break
    					}
    					case 'KLARNA': {
    						// Handle Klarna payment methods (see Step 4.d)
    						// configureklarnaPaymentMethod(paymentMethod);
    						break
    					}
    					case 'ACH': {
    						// Handle ACH payment method
    						// Only `STRIPE_ACH` is supported (see Step 4.e)
    						// configureStripeAchPaymentMethod(paymentMethod);
    						break
    					}

    					// More payment methods to follow
    				}
    			}
    		},

    		onCheckoutComplete({ payment }) {
    			// Notifies you that a payment was created
    			// Move on to next step in your checkout flow:
    			// e.g. Show a success message, ...
    			console.log('onCheckoutComplete', payment)
    		},

    		onCheckoutFail(error, { payment }, handler) {
    			// Notifies you that the checkout flow has failed and a payment could not be created
    			// This callback can also be used to display an error state within your own UI.

    			// ⚠️ `handler` is undefined if the SDK does not expect anything from you
    			if (!handler) {
    				return
    			}

    			// ⚠️ If `handler` exists, you MUST call one of the functions of the handler

    			// Show a default error message
    			return handler.showErrorMessage()
    		},
    	})

    	// Start the headless checkout
    	await headless.start()

    	console.log('Headless Universal Checkout is loaded!')
    }
    ```

    <Note>
      See more options and events in the [SDK API Reference](https://www.npmjs.com/package/@primer-io/checkout-web)
    </Note>

    ## Step 3: Show available payment methods

    When the checkout is done initializing, the callback `onAvailablePaymentMethodsLoad` is invoked. Use this event to show the list of payment methods to the user:

    * Some payment methods such as Google Pay, Apple Pay, and PayPal require [Primer to manage their payment method button](/checkout/headless#step-4-b%3A-handle-payment-methods-with-native-buttons).
    * For the others, you have full control over how the payment method button should be presented. To assist you, Primer exposes the `AssetsManager` that enables you to retrieve the logo and main colors attached to each payment method.

    ```typescript Typescript theme={"dark"}
    // getAssetsManager() is only available once Headless Checkout has been fully initialized
    const assetsManager = headless.getAssetsManager()

    const { iconUrl, paymentMethodName, backgroundColor } = await assetsManager.getPaymentMethodAsset('ADYEN_IDEAL') // ADYEN_IDEAL comes from `paymentMethod.type`
    ```

    <Note>
      The assets manager is only available once `onAvailablePaymentMethodsLoad` has been called.
    </Note>

    ## Step 4: Handle payment method selection

    Headless Universal Checkout enables you to create any UI that suits your needs, using the components and data we provide.

    ### Step 4.a: Handle cards

    When `PAYMENT_CARD` is available as a payment method and provided via `onAvailablePaymentMethodsLoad`, build your card form using Primer input elements.

    Get started by creating a payment method manager for cards.

    ```typescript Typescript theme={"dark"}
    const cardManager = await headless.createPaymentMethodManager('PAYMENT_CARD')
    ```

    #### Show card fields

    <Note>
      Headless Universal Checkout securely captures payment method data while fully embedded in your app. By communicating directly with Primer's PCI-L1 tokenization service, Universal Checkout transforms sensitive customer data into a secure uniform string called a payment method token.
    </Note>

    First, prepare containers in the DOM for the Primer hosted inputs. You would need three containers for the card number, the expiry date, and the CVV.

    ```typescript Typescript theme={"dark"}
    const container = document.getElementById('my-container')

    const cardNumberInputId = 'checkout-card-number-input'
    const cardNumberInputEl = document.createElement('div')
    cardNumberInputEl.setAttribute('id', cardNumberInputId)

    const cardExpiryInputId = 'checkout-card-expiry-input'
    const cardExpiryInputEl = document.createElement('div')
    cardExpiryInputEl.setAttribute('id', cardExpiryInputId)

    const cardCvvInputId = 'checkout-card-cvv-input'
    const cardCvvInputEl = document.createElement('div')
    cardCvvInputEl.setAttribute('id', cardCvvInputId)

    container.append(cardNumberInputEl, cardExpiryInputEl, cardCvvInputEl)
    ```

    Then, create the hosted card inputs:

    ```typescript Typescript theme={"dark"}
    const { cardNumberInput, expiryInput, cvvInput } = cardManager.createHostedInputs()
    ```

    Finally, render your inputs into the relevant containers:

    ```typescript Typescript theme={"dark"}
    await Promise.all([
    	cardNumberInput.render(cardNumberInputId, {
    		placeholder: '1234 1234 1234 1234',
    		ariaLabel: 'Card number',
    	}),
    	expiryInput.render(cardExpiryInputId, {
    		placeholder: 'MM/YY',
    		ariaLabel: 'Expiry date',
    	}),
    	cvvInput.render(cardCvvInputId, {
    		placeholder: '123',
    		ariaLabel: 'CVV',
    	}),
    ])
    ```

    ##### Customize card fields

    <Warning>
      Card fields are rendered with individual iframes in order to remain PCI-L1 compliant. One key consequence is that the CSS of your page will not be propagated to the card fields. This includes **color**, and **font**.
    </Warning>

    Pass a `style` object to the `render` function to configure colors and font options.

    ```typescript Typescript theme={"dark"}
    const style = {
    	input: {
    		base: {
    			height: 'auto',
    			border: '1px solid rgb(0 0 0 / 10%)',
    			borderRadius: '2px',
    			padding: '12px',
    			boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)',
    		},
    	},
    }

    cardNumberInput.render(cardNumberInputId, {
    	placeholder: '1234 1234 1234 1234',
    	ariaLabel: 'Card number',
    	style,
    })
    ```

    <Note>
      Check the ["Styling Inputs" section of the Customize Universal Checkout guide](/checkout/drop-in/customization#styling-inputs) to learn how to adapt the style to your requirements.
    </Note>

    ##### Reset card form

    If needed, the card form can be cleared by calling `reset()`:

    ```typescript Typescript theme={"dark"}
    cardManager.reset()
    ```

    ##### Remove card elements

    Call `removeHostedInputs()` to remove the hosted card input fields from the DOM:

    ```typescript Typescript theme={"dark"}
    cardManager.removeHostedInputs()
    ```

    #### Detect card type

    When the user enters the card credentials, Headless Universal Checkout automatically detects the card type and retrieves BIN data including issuer information.

    Listen to the callback `onBinDataAvailable` on the card manager to receive card type and BIN data.

    ```typescript Typescript theme={"dark"}
    const cardManager = await headless.createPaymentMethodManager('PAYMENT_CARD', {
    	onBinDataAvailable(event) {
    		// event.preferred - the recommended network
    		// event.alternatives - all detected networks, excluding preferred
    		// event.status - 'complete' or 'partial'
    	},
    })
    ```

    <Note>
      Learn more about `onBinDataAvailable` in the [API reference](/sdk/web/v2.x.x/primer-headless-checkout/methods/createPaymentMethodManager#card.parameters.options-properties.onBinDataAvailable).
    </Note>

    #### Capture cardholder name

    You are free to render the cardholder name input however you want.

    As the user enters their cardholder name, call `setCardholderName(cardholderName)` to pass the cardholder name to the `cardManager`:

    ```typescript Typescript theme={"dark"}
    cardholderNameInput.addEventListener('change', e => {
    	cardManager.setCardholderName(e.target.value)
    })
    ```

    If the cardholder name is required, its content will be validated by Headless Universal Checkout.

    You can specify whether the cardholder name is required by setting an option when initializing Headless Universal Checkout:

    ```typescript Typescript theme={"dark"}
    const headless = await Primer.createHeadless(clientToken, {
    	card: {
    		cardholderName: {
    			required: true,
    		},
    	},
    })
    ```

    #### Handle input errors

    The event `change`, available on each input, reacts to input changes. This returns if the input is valid or not, and the error type.

    ```typescript Typescript theme={"dark"}
    cardNumberInput.addEventListener('change', (...args) => {
    	console.log('cardNumberInput changed', ...args)
    })
    ```

    #### Validate and Submit

    When the user submits the card information, follow the following flow:

    * First, validate all the inputs using the `validate()` function that does basic validations on the hosted inputs.
    * Then, submit the validated data using the `submit()` function. This triggers the payment creation.

    For example, if the user clicks submit, you can handle it as follows:

    ```typescript Typescript theme={"dark"}
    submitButton.addEventListener('click', async () => {
    	// Validate your card input data
    	const { valid } = await cardManager.validate()
    	if (valid) {
    		// Submit the card input data to Primer for tokenization
    		await cardManager.submit()
      	}	}
    })
    ```

    Calling `submit()` triggers the creation and handling of the payment.

    * If `onCheckoutComplete` is called, show a success message and reset the inputs.
    * If `onCheckoutFail` is called, show a failure message and allow the customer to try again with the same details.

    #### Prepare 3DS

    When the user pays by card, the [Workflow](/get-started/create-workflow) will decide whether a 3DS challenge is required or not. If so, Headless Universal Checkout will automatically render the 3DS challenge in context.

    To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:

    * `customer.emailAddress`
    * `customer.billingAddress`

    #### Integration example snippet

    Below is an example code snippet of how it all fits together.

    ```typescript Typescript theme={"dark"}
    const container = document.getElementById('my-container')

    // Create containers for your hosted inputs
    const cardNumberInputId = 'checkout-card-number-input'
    const cardNumberInputEl = document.createElement('div')
    cardNumberInputEl.setAttribute('id', cardNumberInputId)

    const cardExpiryInputId = 'checkout-card-expiry-input'
    const cardExpiryInputEl = document.createElement('div')
    cardExpiryInputEl.setAttribute('id', cardExpiryInputId)

    const cardCvvInputId = 'checkout-card-cvv-input'
    const cardCvvInputEl = document.createElement('div')
    cardCvvInputEl.setAttribute('id', cardCvvInputId)

    const cardHolderInputId = 'checkout-card-holder-input'
    const cardHolderInputEl = document.createElement('input')
    cardHolderInputEl.setAttribute('id', cardHolderInputId)
    cardHolderInputEl.setAttribute('placeholder', 'Cardholder Name')

    const submitButton = document.createElement('input')
    const buttonId = 'submit-button'
    submitButton.setAttribute('type', 'button')
    submitButton.setAttribute('id', buttonId)
    submitButton.value = 'Submit'

    // Add them to your container
    container.append(cardNumberInputEl, cardExpiryInputEl, cardCvvInputEl, cardHolderInputEl, submitButton)
    async function configureCardForm() {
    	const baseStyles = {
    		height: 'auto',
    		border: '1px solid rgb(0 0 0 / 10%)',
    		borderRadius: '2px',
    		padding: '12px',
    		boxShadow: '0 4px 8px 0 rgba(0,0,0,0.2)',
    	}

    	// Create the payment method manager
    	const cardManager = await headless.createPaymentMethodManager('PAYMENT_CARD')

    	// Create the hosted inputs
    	const { cardNumberInput, expiryInput, cvvInput } = cardManager.createHostedInputs()

    	await Promise.all([
    		cardNumberInput.render(cardNumberInputId, {
    			placeholder: '1234 1234 1234 1234',
    			ariaLabel: 'Card number',
    			style: baseStyles,
    		}),
    		expiryInput.render(cardExpiryInputId, {
    			placeholder: 'MM/YY',
    			ariaLabel: 'Expiry date',
    			style: baseStyles,
    		}),
    		cvvInput.render(cardCvvInputId, {
    			placeholder: '123',
    			ariaLabel: 'CVV',
    			style: baseStyles,
    		}),
    	])

    	// Set the cardholder name if it changes
    	document.getElementById(cardHolderInputId).addEventListener('change', e => {
    		cardManager.setCardholderName(e.target.value)
    	})

    	// Configure event listeners for supported events
    	cardNumberInput.addEventListener('change', (...args) => {
    		console.log('cardNumberInput changed', ...args)
    	})

    	cardNumberInput.focus()

    	submitButton.addEventListener('click', async () => {
    		// Validate your card input data
    		const { valid } = await cardManager.validate()
    		if (valid) {
    			// Submit the card input data to Primer for tokenization
    			await cardManager.submit()
    		}
    	})
    }
    ```

    <Note>
      See more options and events in the [SDK API Reference](https://www.npmjs.com/package/@primer-io/checkout-web#user-content--headless-options)
    </Note>

    ### Step 4.b: Handle payment methods with native buttons

    <Note>
      This applies to PayPal, Apple Pay and Google Pay.
    </Note>

    Some payment methods require Primer to manage the payment method’s button and implementation, and only require you to display the button to your customer.

    Follow this approach when `PAYPAL`, `APPLE_PAY` or `GOOGLE_PAY` is available as a payment method and provided via `onAvailablePaymentMethodsLoad`.

    #### Render the button

    Get started by creating the payment method manager:

    ```typescript Typescript theme={"dark"}
    const paymentMethodManager = await headless.createPaymentMethodManager('PAYPAL') // or APPLE_PAY / GOOGLE_PAY
    ```

    Then, create an instance of a payment method button and render it:

    ```typescript Typescript theme={"dark"}
    // Create the button container
    const payPalButton = document.createElement('div')
    const payPalButtonId = 'paypal-button'
    payPalButton.setAttribute('type', 'button')
    payPalButton.setAttribute('id', payPalButtonId)

    // Create and render the button
    const button = paymentMethodManager.createButton()
    button.render(payPalButtonId, {
    	style: {
    		buttonColor: 'silver',
    	},
    })
    ```

    <Note>
      See additional style options in the [SDK API Reference](https://www.npmjs.com/package/@primer-io/checkout-web#user-content-style-options).
    </Note>

    #### Handle button clicks (optional)

    When the payment method button is clicked, Headless Universal Checkout automatically handles the rendering of the payment method screen and the payment. Based on the result of the payment, you should handle different callbacks.

    * If `onCheckoutComplete` is called, show a success message and hide the button.
    * If `onCheckoutFail` is called, show a failure message and allow the customer to try again.

    You can also listen to the click event for logging or analytics purposes:

    ```typescript Typescript theme={"dark"}
    button.addEventListener('click', () => {
    	// React to click
    	// E.g. send off analytics
    })
    ```

    #### Other button methods

    The button object also supports other methods:

    ```typescript Typescript theme={"dark"}
    // Hide the button
    button.clean()

    // Set the button to disabled
    button.setDisabled(true | false)

    // Focus the button (not supported for PayPal)
    button.focus()

    // Unfocus the button (not supported for PayPal)
    button.blur()
    ```

    #### Integration example snippet

    Below is an example code snippet of how it all fits together.

    ```typescript Typescript theme={"dark"}
    // Create your button container
    const payPalButton = document.createElement('div')
    const payPalButtonId = 'paypal-button'
    payPalButton.setAttribute('type', 'button')
    payPalButton.setAttribute('id', payPalButtonId)

    function configurePayPalButton() {
    	// Create the payment method manager
    	const button = paymentMethodManager.createButton()

    	// Render the button
    	button.render(paypalButtonId, {
    		style: {
    			buttonColor: 'silver',
    		},
    	})
    }
    ```

    ### Step 4.c: Handle payment methods with redirect

    Some payment methods require redirecting to another web page in order to capture payment details.

    Headless checkout automatically renders that web page in a popup window in order to maintain the current context.

    Get started by creating a payment method manager:

    ```typescript Typescript theme={"dark"}
    const manager = await headless.createPaymentMethodManager(paymentMethod.type)
    ```

    When the user has selected the payment method, call the `start` function. This function automatically:

    * opens a popup to present a loading indicator, then the payment method web page
    * shows an overlay on top of your page that prompts the user to focus on the popup

    ```typescript Typescript theme={"dark"}
    myButton.addEventListener('click', () => {
    	manager.start()
    })
    ```

    <Warning>
      **`Make sure start is called synchronously after a click`**

      `start` opens a popup. However, most browsers have strict popup-blocking rules to protect the user. One such rule is to only allow popups that result from a direct user action.

      Therefore, to ensure that the popup is properly rendered, make sure to call this function immediately after a user click.
    </Warning>

    <Note>
      **If the browser cannot open popup windows, the current window will be redirected instead.**

      To ensure maximum compatibility, read the guide on [how to handle redirects](/sdk/web/v2.x.x/handle-redirects-&-deeplinks).
    </Note>

    ### Step 4.d: Handle Klarna

    The Klarna payment method requires selecting a payment category before redirecting to capture payment details and finalize the payment.

    You can create the interface for selecting a payment category before the redirect. Using an api function exposed by the payment method manager you can `start` the checkout, after the payment category has been selected and confirmed, and it will automatically render the payment web page in a popup window.

    Get started by creating a payment method manager. You need to provide a callback function, `onPaymentMethodCategoriesChange`, in the options parameter of the `createPaymentMethodManager` function:

    ```typescript Typescript theme={"dark"}
    const manager = await headless.createPaymentMethodManager(paymentMethod.type, {
    	onPaymentMethodCategoriesChange,
    })
    ```

    Use the data sent through the `onPaymentMethodCategoriesChange` callback and use it to render a custom user interface for the user to be able to select payment categories. More details about the callback can be found [here](/sdk/web/v2.x.x/primer-headless-checkout/methods/createPaymentMethodManager#klarna.parameters.options-properties.onPaymentMethodCategoriesChange)

    ```typescript Typescript theme={"dark"}
    function onPaymentMethodCategoriesChange(paymentMethodCategories) {
    	// Use the paymentMethodCategories sent through the onPaymentMethodCategoriesChange function and render a custom user interface to the user
    	// This example assumes that you implement displayCategory that will render a custom user interface that will display the DOM structure for payment categories for Klarna
    	for (let paymentMethodCategory in paymentMethodCategories) {
    		displayCategory(paymentMethodCategory, handlePaymentCategoryClick)
    	}
    }
    ```

    Klarna will need to render more details about the payment method inside a container. You need to provide a `containerId`, that belongs to a container in the DOM, which will later be used to render payment details about the selected payment method category. When a payment method category has been selected call the `renderCategory` function from the payment method manager. The `renderCategory` function uses the id of the selected payment method category, a `containerId` and an `onHeightChange` callback in order to render more details about the category in the selected container.

    The `onHeightChange` callback will return a new height based on the selected payment method category. Use the new height to change the height of the container in which Klarna will render the payment category details. More details about the callback can be found [here](/sdk/web/v2.x.x/primer-headless-checkout/methods/createPaymentMethodManager#klarna.returns.PromiselessIKlarnaPaymentMethodManager-or-nullgreater-iklarnapaymentmethodmanager.renderCategory)

    ```typescript Typescript theme={"dark"}
    // This example assumes that you implement the handleHeightChange function that will update
    // the height of the container in which Klarna will display the payment category details
    function handlePaymentCategoryClick(paymentMethodCategoryId) {
    	manager.renderCategory({
    		paymentMethodCategoryId,
    		containerId,
    		onHeightChange: newHeight => {
    			handleHeightChange(paymentMethodCategoryId, newHeight)
    		},
    	})
    }
    ```

    When the user has selected a payment category and has confirmed the start of the payment, call the `start` function. This function automatically:

    * opens a popup to present a loading indicator, then the payment method web page
    * shows an overlay on top of your page that prompts the user to focus on the popup

    ```typescript Typescript theme={"dark"}
    function handleCategorySelected(selectedCategory) {
    	manager.start({
    		paymentMethodCategoryId: selectedCategory.id,
    	})
    }
    ```

    ### Step 4.e: Handle Stripe ACH

    The Stripe ACH payment method requires filling in a form with the customer's first name, last name, and email, and then going through the Stripe interface to collect the bank account details. Once the bank details are collected, the mandate should be displayed together with a button so the customer can click to confirm it.

    Optionally, if it's an existing customer, the existing customer details can be used to pre-populate the input fields.

    You can grab the details from the [`onClientSessionUpdate callback`](/sdk/web/v2.x.x/primer/methods/createHeadless#parameters.options-client-session-lifecycle-callbacks.onClientSessionUpdate) and store them on a variable, example:

    ```typescript Typescript theme={"dark"}
    let initialCustomerDetails = { firstName: '', lastName: '', emailAddress: '' };

    await Primer.createHeadless(clientToken, {
    	onClientSessionUpdate(clientSession) {
    		initialCustomerDetails.firstName = clientSession.customer?.firstName ?? '';
    		initialCustomerDetails.lastName = clientSession.customer?.lastName ?? '';
    		initialCustomerDetails.emailAddress = clientSession.customer?.emailAddress ?? '';
    	}
    })
    ```

    <Warning>
      **`The customer client session property can be undefined.`**
    </Warning>

    The `onClientSessionUpdate` is always called once before the `onAvailablePaymentMethodsLoad`, so the payment method manager can be created as usual.

    When creating the ACH payment method manager, the `stripePublishableKey` needs to be provided, and optionally, the `onCollectBankAccountDetailsComplete` callback.

    ```typescript Typescript theme={"dark"}
    const manager = await headless.createPaymentMethodManager(paymentMethod.type, {
    	stripePublishableKey: 'pk_...',
    	onCollectBankAccountDetailsComplete,
    })

    // Display your own form with the first name, last name and email input fields
    ```

    After creating the manager and displaying the form, the data can be submitted.

    In case something is wrong, a validation result will be returned, and if there's no validation error, the `collectBankAccountDetails` method can be called to start the Stripe flow.

    ```typescript Typescript theme={"dark"}
    const validationError = await manager.start({ firstName, lastName, emailAddress })

    if (validationError) {
    	// Handle the validation errors updating the form accordingly
    } else {
    	await manager.collectBankAccountDetails()

    	// Display the mandate text and the Confirm button
    }
    ```

    Alternatively, instead of awaiting for the `collectBankAccountDetails` promise to be resolved, the `onCollectBankAccountDetailsComplete` callback can be set up to display the mandate screen.

    ```typescript Typescript theme={"dark"}
    function onCollectBankAccountDetailsComplete() {
    	// Display the mandate text and the Confirm button
    }
    ```

    Once the customer clicks the `confirm` button, the `confirmMandate` method can be called.

    ```typescript Typescript theme={"dark"}
    await manager.confirmMandate()

    // Display a success screen
    ```
  </Tab>

  <Tab title="iOS">
    <Note>
      This documentation is only relevant for **v2.17.0** and upward.

      The **migration guide** to update from v2.16.1 is available [here](/changelogs/migration-guides/).

      The documentation for versions v2.5.0 up to v2.16.1 is available [here](#i-os).
    </Note>

    <Note>
      Backend API V2.4 enhances reliability and minimizes synchronization issues with external processors by significantly extending payment timeouts. This longer timeout accommodates external processor limits and internal operations, ensuring more reliable transaction handling. These improvements are fully supported starting with Primer iOS SDK 2.34.0, so upgrading your app to use the newest SDK version is highly recommended when creating client sessions with API V2.4.
    </Note>

    ## Install the SDK

    ### With CocoaPods

    Add the following in your `Podfile`:

    ```ruby RUBY theme={"dark"}
    use_frameworks!

    target 'MyApp' do

      # 👇 Add this line
      pod 'PrimerSDK'

      # ...

    end
    ```

    Then run `pod install` to install `PrimerSDK` on your workspace.

    <Note>
      In case you encounter an error that the bundle needs signing on Xcode 14, add the following post-install script in your podfile.

      ```ruby theme={"dark"}
      post_install do |installer|
        installer.pods_project.targets.each do |target|
          if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
            target.build_configurations.each do |config|
                config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
            end
          end
        end
      end
      ```
    </Note>

    ### With Swift Package Manager

    The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into Xcode. In order to add PrimerSDK with Swift Package Manager:

    1. Select your project, and then navigate to `Package Dependencies`
    2. Click on the **+** button at the bottom-left of the `Packages` section
    3. Paste [https://github.com/primer-io/primer-sdk-ios.git](https://github.com/primer-io/primer-sdk-ios.git) into the **Search Bar,** or search for `primer-sdk-ios` in the **Search Bar**.
    4. Press **Add Package**
    5. Let Xcode download the package and set everything up

    <Note>
      For more details about SDK versions, please see our [changelog](/changelogs/ios-sdk).
    </Note>

    ## Step 1: Prepare Headless Universal Checkout

    First, import the `PrimerSDK`. Then call `PrimerHeadlessUniversalCheckout.current.start` to start the checkout session with your client token and a delegate to receive important events.

    The completion handler will return the available payment methods for the current session. We will use them later to present a list of payment methods.

    Each available payment method contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method (e.g. `ADYEN_IDEAL`)
    * `paymentMethodName` \
      a user friendly English localized string identifier for the payment method (e.g. `Apple Pay`)
    * `supportedPrimerSessionIntents` \
      an array of `PrimerSessionIntent` which defines what intents can be used with this payment method (i.e. `.checkout` or `.vault`).
    * `paymentMethodManagerCategories` \
      an array which defines the payment method managers that can be used with this payment method (i.e. `.nativeUI` or `.rawData`).
    * \[Optional] `requiredInputDataClass` \
      the class type that should be used with the raw data manager.

    Implement the `primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData(_:)` which will notify you when the checkout is complete with a payment.

    ```swift SWIFT theme={"dark"}
    import UIKit
    // 👇 Import the SDK
    import PrimerSDK

    class ViewController: UIViewController {

        // ...

        var availablePaymentMethods: [PrimerHeadlessUniversalCheckout.PaymentMethod]?

        override func viewDidLoad() {
            super.viewDidLoad()

            // ...

            // 👇 Start Headless Universal Checkout with your session's client token
            PrimerHeadlessUniversalCheckout.current.start(withClientToken: clientToken, delegate: self) { availablePaymentMethods, err in
                if let err = err {
                    // Handle error
                } else if let availablePaymentMethods = availablePaymentMethods {
                    // Payment methods that are available for this session
                    self.availablePaymentMethods = availablePaymentMethods
                }
            }
        }
    }

    extension ViewController: PrimerHeadlessUniversalCheckoutDelegate {

        // 👇 [Required] This function will return the checkout data once the payment is finished
        func primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData(_ data: PrimerCheckoutData) {
            // ...
        }
    }
    ```

    <Warning>
      **Only render payment methods from `availablePaymentMethods`**

      The `availablePaymentMethods` returned by the SDK is the **only source of truth** for which payment methods are available for this session. Do not hardcode or assume payment method availability.

      Payment methods may be unavailable for various reasons:

      * **Apple Pay**: User hasn't set up Apple Pay or has no cards in their Wallet
      * **Device limitations**: Payment method not supported on the current device
      * **Configuration**: Payment method not enabled for this client session
      * **Region restrictions**: Payment method not available in user's region

      Attempting to present a payment method that is not in `availablePaymentMethods` will result in an `unable-to-present-payment-method` error.
    </Warning>

    ## Step 2: Render available payment methods UI

    You should now have the available payment methods for the client session.

    You can render your own UI for each payment method, or take advantage of the `PrimerHeadlessUniversalCheckout.AssetsManager` helper to get access to payment methods' assets.

    **Always check availability before rendering a payment button:**

    ```swift SWIFT theme={"dark"}
    // ✅ Correct: Only show payment methods that are available
    func renderPaymentMethods() {
        guard let availablePaymentMethods = self.availablePaymentMethods else { return }

        // Check if Apple Pay is available before showing the button
        let isApplePayAvailable = availablePaymentMethods.contains { $0.paymentMethodType == "APPLE_PAY" }
        applePayButton.isHidden = !isApplePayAvailable

        // Render other payment methods similarly
        for paymentMethod in availablePaymentMethods {
            // Build UI only for available payment methods
        }
    }
    ```

    ```swift SWIFT theme={"dark"}
    do {
        let paymentMethodAsset = try PrimerHeadlessUniversalCheckout.AssetsManager.getPaymentMethodAsset(for: paymentMethod.paymentMethodType)
    } catch {
        // Handle error
    }
    ```

    The payment method asset contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method
    * `paymentMethodName`\
      a user friendly English localized string identifier for the payment method (e.g. `Apple Pay`)
    * `paymentMethodLogo`\
      an instance of the `PrimerPaymentMethodLogo`
    * `paymentMethodBackgroundColor` \
      an instance of the `PrimerPaymentMethodBackgroundColor`

    The `PrimerPaymentMethodLogo` holds `UIImage` objects for different scenarios

    * \[Optional] `colored` \
      a `UIImage` to be used anywhere
    * \[Optional] `dark` \
      a `UIImage` to be used in dark mode
    * \[Optional] `light` \
      a `UIImage` to be used in light mode

    The `PrimerPaymentMethodBackgroundColor` holds `UIColor` objects for different scenarios

    * \[Optional] `colored` \
      a `UIColor` to be used anywhere
    * \[Optional] `dark` \
      a `UIColor` to be used in dark mode
    * \[Optional] `light` \
      a `UIColor` to be used in light mode

    <Tip>
      `colored`, `dark` and `light` variables are all optional, but it is guaranteed that the objects will contain at least one of them.
    </Tip>

    With the above images and colors you can build your own payment methods UI 💪

    ## Step 3: Implement a payment method manager

    Now that your UI presents the available payment methods, let's use the payment method managers to allow users to go through the payment method flow.

    A payment method manager is a class abstracting the state and UI of a given payment method. Currently, 3 payment methods managers are supported, each of them responsible for a different payment method category.

    * `PrimerHeadlessUniversalCheckout.NativeUIManager` \
      The native UI manager is responsible for payment methods that need to present their own UI (e.g. Apple Pay).
    * `PrimerHeadlessUniversalCheckout.RawDataManager` \
      The raw data manager is responsible for payment methods that need to receive data from the user (e.g. card form).

    <Tip>
      Bear in mind that a payment method could be implemented by more than one payment method manager. Check which managers are supported for each payment method in the `paymentMethodManagerCategories` of the payment method objects received in [Step 1](/checkout/headless#step-1%3A-prepare-headless-universal-checkout).
    </Tip>

    <Note>
      A manager can only be used after Headless Checkout has been initialized. We consider Headless Checkout to be initialized after the `primerHeadlessUniversalCheckoutDidLoadAvailablePaymentMethods` is triggered and payment methods for the provided `clientToken` are returned.

      If that’s not the case Headless Checkout throws a `uninitialized-sdk-session` error.

      If a payment method manager gets initialized with an unsupported payment method type (i.e. the payment method's object doesn't include the manager's category), Headless Checkout throws an `unsupported-payment-method-type` error.
    </Note>

    ### Native UI manager

    This manager can be used for the payment methods that contain `.nativeUI` in the `paymentMethodManagerCategories` array.

    The native UI manager is typically used for the payment methods that need to present their own UI, such as Apple Pay.

    ```swift SWIFT theme={"dark"}
    import UIKit
    import PrimerSDK

    class ViewController: UIViewController {

        // ...

        @IBAction func paymentMethodButtonTapped(_ sender: UIButton) {
            do {
                // 👇 Create the payment method manager
                let nativeUIPaymentMethodManager = try PrimerHeadlessUniversalCheckout.NativeUIManager(paymentMethodType: paymentMethod.paymentMethodType)

                // 👇 Show the payment method
                try nativeUIPaymentMethodManager.showPaymentMethod(intent: .checkout)

            } catch {
                // Handle error
            }
        }
    }
    ```

    <Note>
      Make sure that the intent passed in the `showPaymentMethod` function (`.checkout` for making a payment, `.vault` for vaulting the payment method) is supported by the payment method. Check the `supportedPrimerSessionIntents` array of the payment method.
    </Note>

    That's it! The SDK will present the native UI to the user, and then attempt to create a payment.

    The payment's data will be returned on `primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData` configured in [Step 1](/checkout/headless#step-1%3A-prepare-headless-universal-checkout).

    ### Raw data manager

    This manager can be used for any payment method that contains `.rawData` in the `paymentMethodManagerCategories` array.

    The raw data manager can be used for payment methods that allow you to capture customer data yourself. This gives you full control over the UI while still taking advantage of all the features that make Primer great.

    At any point, you can validate your data by calling `setRawData()` and by listening to `primerRawDataManager(_: dataIsValid: errors:)` - the SDK will return errors specific to each required input.

    Once the data is valid, send it to Headless Checkout for further processing by calling `submit()`.

    #### Configure the `PrimerHeadlessUniversalCheckout.RawDataManager` and conform to its delegate

    Assuming that the payment method contains `.rawData` in the `paymentMethodManagerCategories`, create your raw data manager, and set its delegate.

    ```swift SWIFT theme={"dark"}

    import UIKit
    import PrimerSDK

    class ViewController: UIViewController {

        // ...

        var rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager!

        override func viewDidLoad() {
            super.viewDidLoad()

            // ...

            do {
                // 👇 Initialize the raw data manager
                self.rawDataManager = try PrimerHeadlessUniversalCheckout.RawDataManager(paymentMethodType: paymentMethod.paymentMethodType, delegate: self)

            } catch {
                // Handle error
            }
        }
    }
    ```

    #### Build a form

    Build your own form to let the users enter their data. You can use the `requiredInputElementTypes` of the `PrimerHeadlessUniversalCheckout.RawDataManager` object to get informed about what input fields you have to render.

    The `requiredInputElementTypes` is an array that can contain any of the following:

    * `.cardNumber`
    * `.expiryDate`
    * `.cvv`
    * `.cardholderName`
    * `.phoneNumber`
    * `.retailer`

    ```swift SWIFT theme={"dark"}
    func renderForm() {
        let inputElementTypes = primerRawDataManager.requiredInputElementTypes

        for inputElementType in inputElementTypes {
            switch inputElementType {
                case .cardNumber:
                    // Create a card number input element and add it to your view
                    break

                case .expiryDate:
                    // Create a expiry date input element and add it to your view
                    break

                case .cvv:
                    // Create a CVV input element and add it to your view
                    break

                case .cardholderName:
                    // Create a cardholder name input element and add it to your view
                    break

                case .phoneNumber:
                    // Create a phone number input element and add it to your view
                    break

                case .retailer:
                    // Create a retailer input element and add it to your view
                    break
                }
            }
        }
    }
    ```

    #### Build and validate your raw data

    Use the class type that has been provided in the `requiredInputDataClass` of the payment method to build the captured data, and pass it to the manager. Here is an example for card payments:

    ```swift SWIFT theme={"dark"}
    import UIKit
    import PrimerSDK

    class ViewController: UIViewController {

        // ...

        func dataDidChange() {
            // 👇 Create your raw data
            let rawCardData = PrimerCardData(
                cardNumber: cardnumberTextField.text,
                expiryDate: expiryDateTextField.text, // 👈 ex. 03/2030
                cvv: cvvTextField.text,
                cardholderName: cardholderNameTextField.text,
            )

            // 👇 Set your raw data on the manager
            self.rawDataManager.rawData = rawCardData
        }
    }
    ```

    When you set the data on the raw data manager, the manager validates it and notifies you via the delegate functions. You can then change the state of your submit button based on the validation changes.

    ```swift SWIFT theme={"dark"}
    extension ViewController: PrimerHeadlessUniversalCheckoutRawDataManagerDelegate {

        // 👇 Implement the validation function to receive updates on the raw data validation changes
        func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, dataIsValid isValid: Bool, errors: [Error]?) {
            // Enable your submit button when `isValid == true`
        }
    }
    ```

    <Note>
      The delegate also provides a function that will notify you of metadata updates, e.g. that the card type was detected.
    </Note>

    #### Submit the form's data

    Once the data has been validated, call the manager's `submit()` function.

    ```swift SWIFT theme={"dark"}
    import UIKit
    import PrimerSDK

    class ViewController: UIViewController {

    		// ...

    		@IBAction func payButtonTapped(_ sender: UIButton) {
                // 👇 Submit the data that have been set in prior steps
                self.rawDataManager.submit()
            }
    }
    ```

    When the function `submit()` is called, the SDK will attempt to create a payment. The payment's data will be returned on `primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData` configured in [Step 1](/checkout/headless#step-1%3A-prepare-headless-universal-checkout).

    ## Step 4: Handle errors

    Some functions can throw and they will fail immediately. All other errors will end up in the SDK’s optional delegate function `func primerHeadlessUniversalCheckoutDidFail(withError:checkoutData:)`, where you can handle them accordingly.

    ```swift SWIFT theme={"dark"}
    extension ViewController: PrimerHeadlessUniversalCheckoutDelegate {

        func primerHeadlessUniversalCheckoutDidFail(withError err: Error, checkoutData: PrimerCheckoutData?) {
            // Handle the error
        }
    }
    ```

    ## Advanced Configuration

    For more customization on your needs, you can listen to all events posted by Headless Universal Checkout and react to them. Visit the [API reference](https://www.notion.so/primerio/Headless-API-Reference-e162338560a7477ebf0dc71bf10558dc) for a full list of the delegate functions.

    ## Prepare 3DS

    * In order to make the SDK lightweight, 3DS requires the addition of the Primer 3DS library. Check out our [guide](/payment-services/3d-secure/get-started#ios) on how to add the Primer 3DS library to your iOS app.
    * When the user pays by card, the Workflow will decide whether or not a 3DS challenge is required. If so, Headless Universal Checkout will automatically render the 3DS challenge.
    * To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:

    | Field                                                                                                                                                                                                                                        | Description                    |
    | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------- |
    | [customer](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer) <br /> ↳ [emailAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-email-address)     | The customer's email address   |
    | [customer](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer) <br /> ↳ [billingAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-billing-address) | The customer's billing address |

    Learn more about [how to configure 3DS](/payment-services/3d-secure/overview)!

    ## Did It Work?

    If everything went well, you should be able to see the payment you just created on your [Dashboard](https://dashboard.primer.io) under the Payments menu.

    <Frame caption="Payment list">
      ![](https://goat-assets.production.core.primer.io/marketing/checkout/external-docs/headless/payment-list.png)
    </Frame>
  </Tab>

  <Tab title="Android">
    <Note>
      This documentation is only relevant for versions **v2.17.0** and upward.

      The **migration guide** to update from v2.16.1 is available [here](/changelogs/migration-guides/).

      The documentation for versions v2.5.0 up to v2.16.1 is available [here](#android).
    </Note>

    <Note>
      Backend API V2.4 enhances reliability and minimizes synchronization issues with external processors by significantly extending payment timeouts. This longer timeout accommodates external processor limits and internal operations, ensuring more reliable transaction handling. These improvements are fully supported starting with Primer Android SDK 2.35.0, so upgrading your app to use the newest SDK version is highly recommended when creating client sessions with API V2.4.
    </Note>

    ## Install the SDK

    Add the following to your `app/build.gradle` file:

    ```kotlin Kotlin theme={"dark"}
    repositories {
      mavenCentral()
    }

    dependencies {
      implementation 'io.primer:android:latest.version'
    }
    ```

    <Note>
      For more details about SDK versions, please see our [changelog](https://www.notion.so/primerio/Android-SDK-8b4bd28444eb4af283678c9f2b5f46fe).
    </Note>

    It is highly recommended adding the following settings to your `app/build.gradle` file:

    ```kotlin Kotlin theme={"dark"}
    android {
        kotlinOptions {
            freeCompilerArgs += '-Xjvm-default=all'
        }
    }
    ```

    ## Step 1: Prepare the Headless Universal Checkout Listener

    Prepare the [PrimerHeadlessUniversalCheckoutListener](/sdk/android/v2.x.x/primer-headless-checkout/listeners/PrimerHeadlessUniversalCheckoutListener) that will handle callbacks that happen during the lifecycle of the payment methods and the payment.

    Import the [PrimerHeadlessUniversalCheckout](/sdk/android/v2.x.x/primer-headless-checkout/) SDK and set its listener as shown in the example below.

    ```kotlin Kotlin theme={"dark"}
    class CheckoutActivity : AppCompatActivity() {

      private val listener = object : PrimerHeadlessUniversalCheckoutListener {

        // 👇 [Required] This function will return the list of payment methods
        override fun onAvailablePaymentMethodsLoaded(paymentMethods: List<PrimerHeadlessUniversalCheckoutPaymentMethod>) {
          // Use it to enable users to select a payment method
        }

        // 👇 [Required] This function is called when the checkout has been completed
        override fun onCheckoutCompleted(checkoutData: PrimerCheckoutData) {
          // Show an order confirmation screen...
          // checkoutData contains the payment that has been created
        }

        // 👇 [Required] This function is called if something goes wrong
        override fun onFailed(error: PrimerError, checkoutData: PrimerCheckoutData?) {
          // your custom method to show an error view
        }
      }
    }
    ```

    `onAvailablePaymentMethodsLoaded` is invoked when Headless Universal Checkout is started with your provided client token. It returns a list of payment methods to render.

    Each returned payment method contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method (e.g. `ADYEN_IDEAL`)
    * `paymentMethodManagerCategories` \
      a list which defines the payment method managers that can be used with this payment method (i.e. `NATIVE_UI` or `RAW_DATA`).
    * `supportedPrimerSessionIntents` \
      a list of `PrimerSessionIntent` which defines what intents can be used with this payment method (i.e. `CHECKOUT` or `VAULT`).
    * \[Optional] `requiredInputDataClass` \
      the class type that should be used with the raw data manager.

    ## Step 2: Start Headless Universal Checkout

    Once you have a client token, you can initialize Primer’s Headless Checkout by calling [PrimerHeadlessUniversalCheckout.current.start(...)](/sdk/android/v2.x.x/primer-headless-checkout/)

    ```kotlin Kotlin theme={"dark"}
    class CheckoutActivity : AppCompatActivity() {

      // other code goes here
      private val listener = object : PrimerHeadlessUniversalCheckoutListener {
        // ...
      }

      private fun setupObservers() {
        viewModel.clientToken.observe(this) { clientToken ->
          // Start the headless checkout session upon reception of the client token
          startHeadlessUniversalCheckout(clientToken)
        }
      }

      private fun startHeadlessUniversalCheckout(clientToken: String) {
        // 👇 Start Headless Universal Checkout with your session's client token
        PrimerHeadlessUniversalCheckout.current.start(
          this,
          clientToken,
          PrimerSettings(),
          listener,
        )
      }
    }
    ```

    Once Primer’s Headless Universal Checkout is configured, its listener functions will notify you about Headless Universal Checkout events.

    <Note>
      On the `PrimerHeadlessUniversalCheckout.current.start` you can optionally pass custom settings and/or add the `PrimerHeadlessUniversalCheckoutUiListener` to listen to UI events.

      Additionally, you can set the listener by calling `PrimerHeadlessUniversalCheckout.current.setListener(listener)`
    </Note>

    <Note>
      Check the SDK reference [here](/sdk/android/v2.x.x/common-objects/) to customize your SDK settings.
    </Note>

    ## Step 3: Render available payment methods UI

    When the checkout is done initializing, the `onAvailablePaymentMethodsLoaded` method will be invoked.

    Use this event to show the list of payment methods to the user.

    ```kotlin Kotlin theme={"dark"}
    private val listener = object : PrimerHeadlessUniversalCheckoutListener {

      override fun onAvailablePaymentMethodsLoaded(
        paymentMethods: List<PrimerHeadlessUniversalCheckoutPaymentMethod>
      ) {
        paymentMethods.forEach { setupPaymentMethod(it.paymentMethodType) }
      }
    }

    // your custom method to render list of payment method buttons UI.
    private fun setupPaymentMethod(paymentMethodType: String) {
      // implement payment method UI
    }
    ```

    You can render your own UI for each payment method, or take advantage of the `PrimerHeadlessUniversalCheckout.AssetsManager` helper to get access to payment methods' resources.

    ```kotlin Kotlin theme={"dark"}

    private val listener = object : PrimerHeadlessUniversalCheckoutListener {

      override fun onAvailablePaymentMethodsLoaded(
        paymentMethods: List<PrimerHeadlessUniversalCheckoutPaymentMethod>
      ) {
        paymentMethods.forEach { setupPaymentMethod(it.paymentMethodType) }
      }
    }

    // your custom method to render the list of payment method buttons
    private fun setupPaymentMethod(paymentMethodType: String) {
      val paymentMethodResource = try {
        // 👇 Retrieve the assets of a specific payment method
        PrimerHeadlessUniversalCheckoutAssetsManager.getPaymentMethodResource(
          this,
          paymentMethodType
        )
      } catch (e: SdkUninitializedException) {
        null
      }

      when (paymentMethodResource) {
        is PrimerPaymentMethodAsset -> {
          // 👇 Example of setting background color for your button
          paymentMethodResource.paymentMethodBackgroundColor.colored?.let { color ->
            setBackgroundColor(color)
          }

          // 👇 Example of setting drawable for your button
          paymentMethodResource.paymentMethodLogo.colored?.let { drawable ->
            setImageDrawable(drawable)
          }
        }

        is PrimerPaymentMethodNativeView -> {
          val paymentMethodView = paymentMethodResource.createView(
            requireContext()
          )
        }

        null -> {
          // this should never happen in case SDK is initialized
        }
      }
    }
    ```

    ### PrimerPaymentMethodAsset

    The payment method asset contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method
    * `paymentMethodName` \
      a user friendly English localized string identifier for the payment method (e.g. `Google Pay`)
    * `paymentMethodLogo` \
      an instance of the `PrimerPaymentMethodLogo`
    * `paymentMethodBackgroundColor` \
      an instance of the `PrimerPaymentMethodBackgroundColor`

    The `PrimerPaymentMethodLogo` holds `Drawable` objects for different scenarios:

    * \[Optional] `colored` \
      a `Drawable` to be used anywhere
    * \[Optional] `dark` \
      a `Drawable` to be used on dark mode
    * \[Optional] `light` \
      a `Drawable` to be used on light mode

    The `PrimerPaymentMethodBackgroundColor` holds `@ColorInt` values for different scenarios:

    * \[Optional] `colored` \
      a `@ColorInt` to be used anywhere
    * \[Optional] `dark` \
      a `@ColorInt` to be used on dark mode
    * \[Optional] `light` \
      a `@ColorInt` to be used on light mode

    <Note>
      `colored`, `dark` and `light` variables are all optional, but it is guaranteed that the objects will contain at least on of them.
    </Note>

    ### PrimerPaymentMethodNativeView

    Using the provided payment method views is required for certain payment methods, such as Google Pay. The Primer SDK ensures these views are seamlessly integrated and configured based on the [PrimerPaymentMethodOptions](/sdk/android/v2.x.x/common-objects/) and your client session details, where applicable.

    With the above images and colors you can build your own payment methods UI 💪

    ## Step 4: Implement a payment method manager

    Now that your UI presents the available payment methods, use the payment method managers to allow users to go through the payment method flow.

    A payment method manager is a class abstracting the state and UI of a given payment method. Currently there are 3 payment methods managers responsible for a different payment method category:

    * `PrimerHeadlessUniversalCheckoutRawDataManager` \
      The raw data manager is responsible for payment methods that need to receive data from the user (e.g. card form).
    * `PrimerHeadlessUniversalCheckoutNativeUiManager` \
      The native UI manager is responsible for payment methods that need to present their own UI (e.g. Google Pay).

    <Tip>
      Bear in mind that a payment method might be able to be implemented by more than one payment method manager. You can check which managers are supported for each payment method in the `paymentMethodManagerCategories` of the payment method objects received in [Step 1](#step-1-prepare-the-headless-universal-checkout-listener).
    </Tip>

    <Note>
      A manager can only be used after Headless Checkout has been initialized. We consider Headless Checkout to be initialized after the `onAvailablePaymentMethodsLoaded` is triggered and payment methods for the provided `clientToken` are returned.

      If that’s not the case SDK will throw `SdkUninitializedException`.

      If a payment method manager gets initialized with an unsupported payment method type (i.e. the payment method's object doesn't include the manager's category), the SDK will throw an `UnsupportedPaymentMethodException` error.
    </Note>

    ### Native UI manager

    This manager can be used for any payment method that contains `NATIVE_UI` in the `paymentMethodManagerCategories` array.

    The native UI manager can be used for any payment method that needs to present its own UI, like Google Pay.

    ```kotlin Kotlin theme={"dark"}
    class CheckoutActivity : AppCompatActivity() {
        // other code goes here
        private fun onPaymentMethodSelected(
          paymentMethodType: String,
          sessionIntent: PrimerSessionIntent
        ) {
          try {
            // 👇 Create the payment method manager
            val nativeUiManager =
              PrimerHeadlessUniversalCheckoutNativeUiManager.newInstance(paymentMethodType)

            // 👇 Show the payment method
            nativeUiManager.showPaymentMethod(this, sessionIntent)
          } catch (e: SdkUninitializedException) {
            // handle exception
          } catch (e: UnsupportedPaymentIntentException) {
            // handle exception
          } catch (e: UnsupportedPaymentMethodException) {
            // handle exception
          }
        }
    }
    ```

    <Note>
      Make sure that the `sessionIntent` in the `showPaymentMethod` function is valid and contained in the `supportedPrimerSessionIntents` array. If that’s not the case SDK will throw `UnsupportedPaymentIntentException`.
    </Note>

    That’s it! The SDK will present the native UI to the user, and then attempt to create a payment.

    The payment’s data will be returned on `onCheckoutCompleted` configured in [Step 1](#step-1-prepare-the-headless-universal-checkout-listener).

    ### Raw Data Manager

    This manager can be used for any payment method that contains `RAW_DATA` in the `paymentMethodManagerCategories` array.

    The raw data manager can be used for payment methods that allow you to pass the data in the SDK (an example would be card data).

    With the raw manager, you can use your fully customized UI and still use all of the features that make Primer great.

    At any point, you can validate your data by calling `setRawData()` and by listening to `onValidationChanged`. Primer will return errors specific to each required input.

    **Only** when the data is valid, you can send it to Primer for further processing by calling `submit`.

    #### Configure the `PrimerHeadlessUniversalCheckoutRawDataManager` and subscribe to callbacks

    Assuming that the payment method contains `RAW_DATA` in the `paymentMethodManagerCategories`, create your raw data manager, and optionally set the `onValidationChanged` and the `onMetadataChanged` callbacks.

    ```kotlin Kotlin theme={"dark"}
    class CheckoutActivity : AppCompatActivity() {

        // val submitButton = findViewById<Button>(R.id.pay_with_card)

        private lateinit var rawDataManager: PrimerHeadlessUniversalCheckoutRawDataManagerInterface

        private val rawDataManagerListener: PrimerHeadlessUniversalCheckoutRawDataManagerListener =
            object : PrimerHeadlessUniversalCheckoutRawDataManagerListener {
                override fun onValidationChanged(
                    isValid: Boolean,
                    errors: List<PrimerInputValidationError>
                ) {
                }
            }

        private fun setupManager(paymentMethodType: String) {
            try {
                // 👇 Initialize the raw data manager
                rawDataManager =
                    PrimerHeadlessUniversalCheckoutRawDataManager.newInstance(paymentMethodType)

                // 👇 Set the manager listener
                rawDataManager.setListener(rawDataManagerListener)
            } catch (e: SdkUninitializedException) {
                // handle exception
            } catch (e: UnsupportedPaymentMethodException) {
                // handle exception
            }
        }
    }
    ```

    Listen to the card data validation state and enable your form’s submit button accordingly.

    #### Build a form

    Build your own form to let the users enter their data. You can use the `getRequiredInputElementTypes` of the `PrimerHeadlessUniversalCheckoutRawDataManager` object to get informed about what input fields you have to render. The `getRequiredInputElementTypes` is a List that can contain any of the following:

    * `CARD_NUMBER`
    * `EXPIRY_DATE`
    * `CVV`
    * `CARDHOLDER_NAME`
    * `PHONE_NUMBER`
    * `RETAIL_OUTLET`

    You can render the form input elements like this:

    ```kotlin Kotlin theme={"dark"}
    class CheckoutActivity : AppCompatActivity() {

      private fun createForm(paymentMethodType: String) {
        // 👇 Get the list of input fields to render
        val requiredInputElementTypes = rawDataManager.getRequiredInputElementTypes()
        // simple example of creating inputs based on type
        val inputElements = requiredInputElementTypes.map { type ->
          TextInputLayout(this).apply {
            tag = type
            hint = type.name
            addView(TextInputEditText(context).apply {
              id = View.generateViewId()
              doAfterTextChanged {
                rawDataManager.setRawData(getRawData(paymentMethodType))
              }
              layoutParams = LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT,
              )
            })
          }
        }

        val formContainer = findViewById<ViewGroup>(R.id.formContainer)

        inputElements.forEach { formContainer.addView(it) }
      }

      private fun getInputTypeValue(inputElementType: PrimerInputElementType) =
          findViewById<ViewGroup>(R.id.formContainer).findViewWithTag<TextInputLayout>(
              inputElementType
          ).editText?.text?.trim().toString()
    }
    ```

    #### Build and validate your raw data

    Use the class type that has been provided in the `requiredInputDataClass` of the payment method to build the capture data, and pass it to the manager. Here is an example for card payments:

    ```kotlin Kotlin theme={"dark"}
    data class PrimerCardData(
        val cardNumber: String, // e.g. 4242424242424242
        val expiryDate: String, // e.g. 01/2024, 1/2024, 12/2024...
        val cvv: String, // e.g. 244
        val cardholderName: String? = null,
    )

    // 👇 Set your raw data on the manager
    rawDataManager.setRawData(getRawData(paymentMethodType))
    ```

    When you set the data on the raw data manager, the manager validates it and notifies you via the listener functions. You can then change the state of your submit button based on the state of validation.

    ```kotlin Kotlin theme={"dark"}
        private val rawDataManagerListener: PrimerHeadlessUniversalCheckoutRawDataManagerListener =
      object : PrimerHeadlessUniversalCheckoutRawDataManagerListener {
        // 👇 Implement the validation function to receive updates on the raw data validation changes
        override fun onValidationChanged(
          isValid: Boolean,
          errors: List<PrimerInputValidationError>
        ) {
          // Enable your submit button when `isValid == true`
          submitButton.isEnabled = isValid
        }
      }
    ```

    <Note>
      The listener also provides a function `onMetadataChanged` that will notify you on metadata updates, e.g. that the card type was detected.
    </Note>

    #### Submit the form's data

    Finally, configure the "on click" listener of the submit button:

    ```kotlin Kotlin theme={"dark"}
    // 👇 Submit the data that have been set in prior steps
    submitButton.setOnClickListener { rawDataManager.submit() }
    ```

    When the function `submit` is called, the SDK will attempt to create a payment.

    The payment’s data will be returned on `onCheckoutCompleted(checkoutData)` configured in [Step 1](#step-1-prepare-the-headless-universal-checkout-listener).

    #### Perform cleanup

    In order to remove the provided listeners and stop any API calls performed by the manager, make sure to call the `cleanup` function:

    ```kotlin Kotlin theme={"dark"}
    rawDataManager.cleanup()
    ```

    ## Step 5: Handle Listener Callbacks (Optional)

    ### Handle `onPreparationStarted` Callback (Optional)

    This function will notify you that the tokenization preparation has started. At this point, the SDK has not yet started to communicate with Primer's backend. It may be useful in case you want to show a loader or notify user about progress in any other way.

    ```kotlin Kotlin theme={"dark"}
    private val listener = object : PrimerHeadlessUniversalCheckoutUiListener {
      override fun onPreparationStarted(paymentMethodType: String) {
        // e.g. show a loading screen.
      }
    }
    ```

    ### Handle `onTokenizationStarted` Callback (Optional)

    This function will notify you that the tokenization API call has been fired. It may be useful in case you want to show a loader or notify user about progress in any other way.

    ```kotlin Kotlin theme={"dark"}
    private val listener = object : PrimerHeadlessUniversalCheckoutListener {
        override fun onTokenizationStarted(paymentMethodType: String) {
            // e.g. hide a loading screen.
        }
    }
    ```

    ### Handle `onPaymentMethodShowed` Callback (Optional)

    This function will notify you that the payment method you requested to show has been presented.

    ```kotlin Kotlin theme={"dark"}
    private val listener = object : PrimerHeadlessUniversalCheckoutUiListener {
        override fun onPaymentMethodShowed(paymentMethodType: String) {
            // e.g. hide a loading screen.
        }
    }
    ```

    ## Handle errors

    Use the `onFailed` method to handle any errors emitted by the SDK during the checkout flow.

    ```kotlin Kotlin theme={"dark"}
    private val listener = object : PrimerHeadlessUniversalCheckoutListener {
        override fun onFailed(error: PrimerError, checkoutData: PrimerCheckoutData?) {
            // your custom method to show an error view
            showErrorView(error)
        }
    }
    ```

    ## Advanced Configuration

    For more customization on your needs, you can listen to all events posted by Headless Universal Checkout and react to them. Visit the [SDK reference](/sdk/android/v2.x.x/primer-headless-checkout/listeners/PrimerHeadlessUniversalCheckoutListener) for a full list of the listener functions.

    ## Prepare 3DS

    * In order to make the SDK lightweight, 3DS requires the addition of the Primer 3DS library. Check out our [guide](/payment-services/3d-secure/get-started#android) on how to add the Primer 3DS library to your Android app.
    * When the user pays by card, the Workflow will decide whether or not a 3DS challenge is required. If so, Headless Universal Checkout will automatically render the 3DS challenge.
    * To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:

    | Field                                                                                                                                 | Description                    |
    | :------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------- |
    | [customer.emailAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-email-address)     | The customer's email address   |
    | [customer.billingAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-billing-address) | The customer's billing address |

    Learn more about [how to configure 3DS](/payment-services/3d-secure/overview)!

    ## Did It Work?

    If everything went well, you should be able to see the payment you just created on your [Dashboard](https://dashboard.primer.io) under the Payments menu.

    <Frame caption="Payment list">
      ![](https://goat-assets.production.core.primer.io/marketing/checkout/external-docs/headless/payment-list.png)
    </Frame>
  </Tab>

  <Tab title="React Native">
    <Note>
      This documentation is only relevant for **v2.17.0** and upward.

      The **migration guide** to update from v2.16.1 is available [here](/changelogs/migration-guides/).

      The documentation for versions v2.5.0 up to v2.16.1 is available [here](#android).
    </Note>

    <Note>
      Backend API V2.4 enhances reliability and minimizes synchronization issues with external processors by significantly extending payment timeouts. This longer timeout accommodates external processor limits and internal operations, ensuring more reliable transaction handling. These improvements are fully supported starting with Primer React Native SDK 2.35.0, so upgrading your app to use the newest SDK version is highly recommended when creating client sessions with API V2.4.
    </Note>

    ## Install the SDK

    Add the SDK package

    ```bash BASH theme={"dark"}
    # With yarn
    yarn add @primer-io/react-native

    # With npm
    npm i @primer-io/react-native --save
    ```

    **Requirement for iOS**

    Once you are done, navigate to the `/ios` folder and run `pod install`.

    <Note>
      For more details about SDK versions, please see our [changelog](https://www.notion.so/primerio/Android-SDK-8b4bd28444eb4af283678c9f2b5f46fe).
    </Note>

    ## Step 1. Prepare Headless Universal Checkout

    Firstly, import `HeadlessUniversalCheckout` from `@primer-io/react-native`. Then set Headless Universal Checkout callbacks in the `headlessUniversalCheckoutCallbacks` property of your settings. These callbacks are handled during the checkout’s lifecycle.

    Once done, start `HeadlessUniversalCheckout` with your session’s client token. This will return a list of available payment methods for you to handle.

    ```tsx Typescript theme={"dark"}
    import { useEffect } from 'react'
    import { HeadlessUniversalCheckout, PrimerSettings, PrimerCheckoutData } from '@primer-io/react-native'

    export const CheckoutScreen = () => {
    	const [paymentMethods, setPaymentMethods] = useState(null)

    	useEffect(() => {
    		const initHeadlessUniversalCheckout = async () => {
    			const settings: PrimerSettings = {
    				//...
    				headlessUniversalCheckoutCallbacks: {
    					onCheckoutComplete = (checkoutData: PrimerCheckoutData) => {
    						// Here you will receive the checkout data of the payment.
    					},
    					onAvailablePaymentMethodsLoad = (paymentMethodTypes: string[]) => {
    						// paymentMethodTypes contains the payment methods identifiers available
    						// for the client session.
    					},
    				},
    			}

    			// 👇 Call this function to start Headless Universal Checkout and retrieve the list of available payment methods
    			const availablePaymentMethods = await HeadlessUniversalCheckout.startWithClientToken(clientToken, settings)
    			setPaymentMethods(availablePaymentMethods)
    		}

    		initHeadlessUniversalCheckout()
    	}, [clientToken])
    }
    ```

    Each payment method returned contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method (e.g. `ADYEN_IDEAL`)
    * `paymentMethodName` \
      a user friendly English localized string identifier for the payment method (e.g. `Apple Pay`)
    * `supportedPrimerSessionIntents` \
      an array of `SessionIntent` which defines what intents can be used with this payment method (i.e. `CHECKOUT` or `VAULT`).
    * `paymentMethodManagerCategories` \
      an array which defines the payment method managers that can be used with this payment method (i.e. `NATIVE_UI` or `RAW_DATA`).
    * \[Optional] `requiredInputDataClass` \
      the `String` representing class type that should be used with the raw data manager.

    ## Step 2: Render available payment methods UI

    You should now have the available payment methods for the client session. You can render your own UI for each payment method, or take advantage of the `AssetsManager` helper to get access to payment methods' resources.

    ```tsx Typescript theme={"dark"}
    import { AssetsManager, Asset } from '@primer-io/react-native'

    const getPaymentMethodResources = async () => {
    	const assetsManager = new AssetsManager()
    	const resources: Resource[] = await assetsManager.getPaymentMethodResources()
    	return resources
    }


    const resources = getPaymentMethodResources();
    const isNativeView = typeof paymentMethodResource.nativeViewName === "string";
    if (isNativeView) {
    	// paymentMethodResource is of type NativeViewResource
    	<NativeResourceView
    		onPress={() => { /* Handle press */ }}
    		nativeViewName={paymentMethodResource.nativeViewName!}
    	/>
    } else {
    	// paymentMethodResource is of type AssetResource
    	// Render your own UI using colors from the asset object
    }

    ```

    ### AssetResource

    The payment method asset contains the following:

    * `paymentMethodType` \
      a unique string identifier for the payment method.
    * `paymentMethodName`\
      a user friendly English localized string identifier for the payment method (e.g. `Apple Pay`)
    * `paymentMethodLogo` \
      contains the logos’ local URLs
      * `colored?: string`: Local URL for the colored value of the logo
      * `dark?: string`: Local URL for the dark value of the logo
      * `light?: string`: Local URL for the light value of the logo
    * `paymentMethodBackgroundColor` \
      contains the background colors hex values
      * `colored?: string`: Color hex value for the colored background color
      * `dark?: string`: Color hex value for the dark mode background color
      * `light?: string`: Color hex value for the light mode background color

    <Note>
      `colored`, `dark` and `light` variables are all optional, but it is guaranteed that the objects will contain at least one of them.
    </Note>

    ### NativeViewResource

    * `paymentMethodType` \
      a unique string identifier for the payment method.
    * `paymentMethodName`\
      a user friendly English localized string identifier for the payment method (e.g. `Apple Pay`)
    * `nativeViewName`\
      a string representing the name of the native view. Pass this when rendering `NativeResourceView`.

    The payment method native view resource contains the following:

    Using the provided payment method views is required for certain payment methods, such as Google Pay. The Primer SDK ensures these views are seamlessly integrated and configured based on the [PrimerSettings](/sdk/react-native/v2.x.x/common-objects/PrimerSettings) and your client session details, where applicable.

    With the above URLs and colors you can build your own payment method UI 💪.

    ## Step 3: Implement a payment method manager

    Now that your UI presents the available payment methods, let's use the payment method managers to allow users to go through the payment method flow.

    A payment method manager is a class abstracting the state and UI of a given payment method. Currently, there are 3 payment methods managers, each of them responsible for a different payment method category.

    * `PrimerHeadlessUniversalCheckout.NativeUIManager` \
      The native UI manager is responsible for all payment methods that need to present their own UI (e.g. Apple Pay or Google Pay).
    * `PrimerHeadlessUniversalCheckout.RawDataManager` \
      The raw data manager is responsible for each payment method that you need to pass some raw data (e.g. card form).

    <Tip>
      Bear in mind that a payment method might be able to be implemented by more than one payment method manager. You can check which managers are supported for each payment method in the `paymentMethodManagerCategories` of the payment method objects received in [Step 1](#step-1-prepare-headless-universal-checkout).
    </Tip>

    <Note>
      A manager can only be used after Headless Checkout has been initialized. We consider Headless Checkout to be initialized after the `onAvailablePaymentMethodsLoad` is triggered and payment methods for the provided `clientToken` are returned.

      If that’s not the case SDK will throw a `uninitialized-sdk-session` error.

      If a payment method manager gets initialized with an unsupported payment method type (i.e. the payment method's object doesn't include the manager's category), it will throw an `unsupported-payment-method-type` error.
    </Note>

    ### Native UI manager

    This manager can be used for any payment method that contains `NATIVE_UI` in the `paymentMethodManagerCategories` array.

    The native UI manager can be used for any payment method that needs to present its own UI, like Apple Pay.

    <Note>
      Make sure that the `intent` in the `showPaymentMethod` function is valid and contained in the `supportedPrimerSessionIntents` array.
    </Note>

    ```tsx Typescript theme={"dark"}
    import { NativeUIManager } from '@primer-io/react-native'

    const payWithPaymentMethod = async (paymentMethodType: string) => {
    	const nativeUIManager = new NativeUIManager()
    	await nativeUIManager.configure({ paymentMethodType: paymentMethod.paymentMethodType })
    	await nativeUIManager.showPaymentMethod(SessionIntent.CHECKOUT)
    }
    ```

    That’s it! The SDK will present the native UI to the user, and then attempt to create a payment.

    The payment’s data will be returned on the `onCheckoutComplete` that we configured in Step 1.

    ### Raw Data Manager

    This manager can be used for any payment method that contains `RAW_DATA` in the `paymentMethodManagerCategories` array.

    The raw data manager can be used for payment methods that allow you to pass the data in the SDK (an example would be card data).

    With the raw manager, you can use your fully customized UI and still use all of the features that make Primer great.

    At any point, you can validate your data by calling `setRawData()` and by listening to `onValidation`. Primer will return errors specific to each required input.

    When the data is valid, you can send it to Primer for further processing by calling `submit()`.

    #### Configure the `RawDataManager`

    Assuming that the payment method contains `RAW_DATA` in the `paymentMethodManagerCategories`, create your raw data manager, and optionally set its delegate.

    ```tsx Typescript theme={"dark"}
    const initialize = async (paymentMethod: PaymentMethod) => {
    	// 👇 Initialize the raw data manager
    	await rawDataManager.configure({
    		paymentMethodType: paymentMethod.paymentMethodType,
    		onMetadataChange: data => {
    			// For example card number detected to be Visa
    		},
    		onValidation: (isValid, errors) => {
    			// Show on your UI if the card data aren't valid
    		},
    	})
    }
    ```

    #### Build a form

    Build your own form to let the users enter their data. You can use the `getRequiredInputElementTypes()` object to get informed about what input fields you have to render. It returns an array that can contain any of the following:

    * "CARD\_NUMBER",
    * "EXPIRY\_DATE",
    * "CVV",
    * "CARDHOLDER\_NAME",
    * "PHONE\_NUMBER"

    ```tsx Typescript theme={"dark"}
    const initialize = async () => {
    	// ...
    	// Once done, get the required input elements types.

    	// 👇 Get the required input element types, which will enable you to build your form
    	const requiredInputElementTypes = await rawDataManager.getRequiredInputElementTypes()
    }
    ```

    #### Build and validate your `rawData`

    Then use the class type that has been provided in the `requiredInputDataClass` in the payment method to build your raw data. Let’s say that you’re building card data, then you would do the following:

    ```tsx Typescript theme={"dark"}
    // 👇 Create your raw data
    const rawCardData: CardData = {
    	cardNumber: cardNumber,
    	expiryDate: expiryDate, // 👈 ex. 03/2030
    	cvv: cvv,
    	cardholderName: cardholderName,
    }

    // 👇 Set your raw data on the manager
    await rawDataManager.setRawData(rawCardData)
    ```

    Each time you set the data on the raw data manager, the manager will validate them and notify you via the callback `onValidation`. You can then change the state of your submit button based on the validation changes.

    <Note>
      You can also listen to the `onMetadataChange` that will notify you of metadata updates, e.g. that the card type was detected.
    </Note>

    #### Submit the form's data

    Once the data has been validated, call the manager's `submit()` function.

    ```tsx Typescript theme={"dark"}
    const handleSubmit = async () => {
    	// 👇 Submit the data that have been set in prior steps
    	await rawDataManager.submit()
    }
    ```

    That’s it! When the function `submit()` is called, the SDK will attempt to create a payment.

    The payment’s data will be returned on the `onCheckoutComplete` configured in Step 1.

    ## Handle errors

    Use the `onError` callback of the `HeadlessUniversalCheckout` options to handle any errors emitted by the SDK during the checkout flow.

    ```typescript Typescript theme={"dark"}
    const onError = (
    	error: PrimerError,
    	checkoutData: PrimerCheckoutData | null,
    	handler: PrimerErrorHandler | undefined,
    ) => {
    	// Handle the error
    }
    ```

    ## Advanced Configuration

    For more customization on your needs, you can listen to all events posted by Headless Universal Checkout and react to them. Visit the [API reference](https://www.notion.so/primerio/Headless-API-Reference-de2b7e29621746f1b75dcc61b971035b) for a full list of the listener functions.

    ## Prepare 3DS

    * In order to make the SDK lightweight, 3DS requires the addition of the Primer 3DS library. Check out our [guide](/payment-services/3d-secure/get-started#react-native) on how to add the Primer 3DS library to your iOS app.
    * When the user pays by card, the Workflow will decide whether or not a 3DS challenge is required. If so, Headless Universal Checkout will automatically render the 3DS challenge.
    * To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:

    | Field                                                                                                                                 | Description                    |
    | :------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------- |
    | [customer.emailAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-email-address)     | The customer's email address   |
    | [customer.billingAddress](/api-reference/v2.4/api-reference/client-session-api/create-a-client-session#body-customer-billing-address) | The customer's billing address |

    Learn more about [how to configure 3DS](/payment-services/3d-secure/overview)!

    ## Did It Work?

    If everything went well, you should be able to see the payment you just created on your [Dashboard](https://dashboard.primer.io) under the Payments menu.

    <Frame caption="Payment list">
      ![](https://goat-assets.production.core.primer.io/marketing/checkout/external-docs/headless/payment-list.png)
    </Frame>
  </Tab>
</Tabs>
