> ## 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.

# Drop-in Checkout: Get started

> [!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](https://primer.io/docs/checkout/overview.md)

The simplest way to integrate Universal Checkout is with our drop-in UI. With just a few lines of code, you can display a fully in-context checkout UI with all your payment methods.

> [!WARNING]
>
> Not all payment methods are currently compatible with Drop-in Checkout.
>
> Please refer to [this table](https://primer.io/docs/connections/payment-methods/available-payment-methods.md) to learn more about the payment methods available for Drop-in Checkout.

## Before you start

Before you start, make sure:

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

## 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 POST
.

Make sure to pass at least the following data:

| Field | Description |
| :- | :- |
| [orderId](https://primer.io/docs/api-reference/v2.4/api-reference/client-session-api/create-a-client-session.md#body-order-id) | Your reference for the payment.   *`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](https://primer.io/docs/api-reference/v2.4/api-reference/client-session-api/create-a-client-session.md#body-currency-code) | The three-letter currency code in [ISO 4217 format](https://en.wikipedia.org/wiki/ISO_4217#Active_codes).  e.g. use `USD` for US dollars. |
| [order.lineItems](https://primer.io/docs/api-reference/v2.4/api-reference/client-session-api/create-a-client-session.md#body-order-line-items) | The details of the line items of the order. |

> [!NOTE]
>
> The body of a successful response contains a [clientToken](https://primer.io/docs/api-reference/v2.4/api-reference/client-session-api/create-a-client-session.md#response-client-token) that you will use to initialize the Universal Checkout.

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.4' \
 --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",
    }
}
```

> [tab] Web
>
> ## Install the SDK
>
> > [!NOTE]
> >
> > Please review the [Content Security Policy (CSP)](https://primer.io/docs/checkout/advanced/content-security-policy.md) recommendations before installing!
>
> ### 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.showUniversalCheckout(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.
>
> ### 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.66.1/Checkout.css" />
>
> <script
>   src="https://sdk.primer.io/web/v2.66.1/Primer.min.js"
>   integrity="sha384-u7VslNrVZDblkrGkcrMwHyMHKAgu+ndoiwn0FWlAH9pmGkaMkclLGpdHN5wAzgnc"
>   crossorigin="anonymous"
> ></script>
> ```
>
> The `Primer.min.js` will add the `Primer` object to the global scope:
>
> ```typescript Typescript theme={"dark"}
> const { Primer } = window;
>
> Primer.showUniversalCheckout(clientToken, {
>   /* Options */
> });
> ```
>
> ## Initialize and Show Universal Checkout
>
> Use the generated client token on the client-side to show Universal Checkout by calling `Primer.showUniversalCheckout(clientToken, options)`. Make sure to pass a selector to your container in the options.
>
> ```javascript Javascript theme={"dark"}
> const universalCheckout = await Primer.showUniversalCheckout(clientToken, {
>   // Specify the selector of the container element
>   container: "#checkout-container",
>
>   /**
>    * When the checkout flow has been completed, you'll receive
>    * the successful payment via `onCheckoutComplete`.
>    * Implement this callback to redirect the user to an order confirmation page.
>    */
>   onCheckoutComplete({ payment }) {
>     console.log("Checkout Complete!", payment);
>   },
>
>   /**
>    * When the checkout flow has been fail, you'll receive
>    * the error via `onCheckoutFail`
>    */
>   onCheckoutFail(error, { payment }, handler) {
>     console.log("Checkout Fail!", error, payment);
>   },
> });
> ```
>
> When the user interacts with the checkout:
>
> * Update the client session
> * Create a payment (by default)
>
> ## Handle successful payments
>
> ### Listen to callback
>
> On the client-side, listen to the `onCheckoutComplete` callback to be notified when the payment has been successfully completed. Use it to show an order confirmation screen.
>
> ### Handle webhooks
>
> To receive updates about the status of your payments you’ll need to listen to webhooks. This is particularly useful for updating an order or any other data stored server-side.
>
> Head to the [Developers section](https://dashboard.primer.io/developers) of the Primer Dashboard to setup and test a webhook for `PAYMENT.STATUS` event.
>
> If you are not yet ready to receive webhooks, you can use [https://webhook.site](https://webhook.site/) to test your implementation.
>
> ### Handle redirects
>
> Some payment methods require redirecting to another web page in order to capture payment details.
>
> Drop-in checkout automatically renders that web page in a popup window in order to maintain the current context.
>
> > [!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](https://primer.io/docs/sdk/web/v2.x.x/handle-redirects-and-deeplinks.md).
>
> ## Handle failed payments
>
> Any errors, cancelled payment interactions or failed payments will trigger the `onCheckoutFail` callback with a [PrimerClientError](https://primer.io/docs/sdk/web/v2.x.x/common-objects/PrimerClientError.md)
>
> ```javascript Javascript theme={"dark"}
> 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();
>
>   // Show a custom error message
>   return handler.showErrorMessage('This is my custom error message');
> }
> ```
>
> > [!WARNING]
> >
> > [Not all failures are retryable](https://primer.io/docs/sdk/web/v2.x.x/common-objects/PrimerClientError.md#not-all-failures-are-retryable). A client-side error doesn't always mean the payment failed, and decline reasons are intentionally generic. Verify the payment status server-side before asking the user to pay again.

> [tab] iOS
>
> > [!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.
>
> ## 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
> > ```
>
> ### 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](https://primer.io/docs/changelogs/android-sdk.md).
>
> ## Initialize Universal Checkout
>
> Import the `PrimerSDK` and set its delegate as shown in the following example:
>
> ```swift SWIFT theme={"dark"}
> import PrimerSDK
>
> class MyViewController: UIViewController, PrimerDelegate {
>
>     override func viewDidLoad() {
>         super.viewDidLoad()
>
>         // Initialize the SDK with the default settings.
>         Primer.shared.configure(delegate: self)
>     }
>
>     func primerDidCompleteCheckoutWithData(_ data: PrimerCheckoutData) {
>         // Primer checkout completed with data
>         // do something...
>     }
> }
> ```
>
> > [!NOTE]
> >
> > See all the configurations in the [SDK Reference](https://primer.io/docs/sdk/ios/v2.x.x/installation.md).
>
> ## Show Universal Checkout
>
> At this step you should have a client token available - see [Manage Client Sessions](https://primer.io/docs/checkout/client-session.md) for more.
>
> Call the `showUniversalCheckout(clientToken:)` function (as shown below) to present Universal Checkout.
>
> ```swift SWIFT theme={"dark"}
> class MyViewController: UIViewController, PrimerDelegate {
>
>     func startUniversalCheckout() {
>         Primer.shared.showUniversalCheckout(clientToken: self.clientToken)
>     }
> }
> ```
>
> > [!NOTE]
> >
> > You should now be able to see Universal Checkout! The user can now interact with Universal Checkout, and the SDK will create the payment. The payment’s data will be returned on `primerDidCompleteCheckoutWithData(_:)` configured in the previous step.
>
> When the user interacts with the checkout and clicks to pay, the Universal Checkout:
>
> * Shows the payment method screen if applicable
> * Tokenizes the payment method for payment
> * Creates the payment
> * Handles any other interactions (e.g. 3DS)
>
> ## Handle successful payments
>
> ### Listen to delegate function
>
> Listen to the `primerDidCompleteCheckoutWithData(_:)` delegate function
>
> This function will notify you of a successful payment.
>
> ```swift SWIFT theme={"dark"}
> class MyViewController: UIViewController {
>
> 		// ...
>
>     func primerDidCompleteCheckoutWithData(_ data: PrimerCheckoutData) {
>         // Primer checkout completed with data
>         // Do something with the data, e.g. show the payment id
>     }
> }
> ```
>
> ### Handle webhooks in your dashboard
>
> To receive updates about the status of your payments you’ll need to listen to webhooks. This is particularly useful for updating an order or any other data stored server-side.
>
> Head to the [Developers section](https://dashboard.primer.io/developers) of the Primer Dashboard to setup and test a webhook for `PAYMENT.STATUS` event.
>
> If you are not yet ready to receive webhooks, you can use [https://webhook.site](https://webhook.site/) to test your implementation.
>
> ## Handle failed payments
>
> Any errors, cancelled payment interactions or failed payments will trigger the `primerDidFailWithError(_:data:decisionHandler:)` delegate function.
>
> ```swift SWIFT theme={"dark"}
> class MyViewController: UIViewController, PrimerDelegate {
>
> 		// ...
>
>     func primerDidFailWithError(_ error: Error, data: PrimerCheckoutData?, decisionHandler: @escaping ((PrimerErrorDecision) -> Void)) {
>         // 👇 Call the decision handler to show a failure message
>         decisionHandler(.fail(withErrorMessage: message))
>     }
> }
> ```
>
> > [!WARNING]
> >
> > Not all failures are retryable. A client-side error doesn't always mean the payment failed, and decline reasons are intentionally generic. Verify the payment status server-side — through [payment webhooks](https://primer.io/docs/get-started/payment-updates.md) or the Payments API — before asking the user to pay again.

> [tab] Android
>
> > [!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.
>
> ## Install the SDK
>
> Add the Primer BOM and the SDK to your app module's `build.gradle.kts` or `build.gradle` file, replacing `<version>` with the latest version from the [Android SDK changelog](https://primer.io/docs/changelogs/android-sdk.md). The BOM keeps the SDK and any Primer payment method libraries on the same version:
>
> ```kotlin Kotlin theme={"dark"}
> repositories {
>     mavenCentral()
> }
>
> dependencies {
>     implementation(platform("io.primer:bom:<version>"))
>     implementation("io.primer:android")
> }
> ```
>
> ```groovy Groovy theme={"dark"}
> repositories {
>     mavenCentral()
> }
>
> dependencies {
>     implementation platform('io.primer:bom:<version>')
>     implementation 'io.primer:android'
> }
> ```
>
> > [!NOTE]
> >
> > Some payment methods and features, such as Klarna and 3D Secure, need a separate Primer library. The integration guide for each one shows the library to add, and [Version alignment](https://primer.io/docs/sdk/android/v2.x.x/installation.md#version-alignment) explains how its version is set.
>
> It is highly recommended adding the following settings to your `app/build.gradle` file:
>
> ```kotlin Kotlin theme={"dark"}
> android {
>     kotlinOptions {
>         freeCompilerArgs += '-Xjvm-default=all'
>     }
> }
> ```
>
> ## Initialize Universal Checkout
>
> Prepare the `PrimerCheckoutListener` that will handle the callbacks that happen during the lifecycle. Import the Primer SDK and set its listener as shown in the following example:
>
> ```kotlin Kotlin theme={"dark"}
> class CheckoutActivity : AppCompatActivity() {
>
>   private val listener = object : PrimerCheckoutListener {
>
>     // 👇 [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
>     }
>   }
>
>   override fun onCreate(savedInstanceState: Bundle?) {
>     super.onCreate(savedInstanceState)
>     setContentView(R.layout.activity_main)
>
>     configureCheckout()
>   }
>
>   private fun configureCheckout() {
>     // 👇 Initialize the SDK with the default settings.
>     Primer.instance.configure(settings = PrimerSettings(), listener = listener)
>   }
> }
> ```
>
> > [!NOTE]
> >
> > See all the configurations in the [SDK Reference](https://primer.io/docs/sdk/android/v2.x.x/common-objects/PrimerSettings.md).
>
> ## Show Universal Checkout
>
> At this step you should have a client token available - see [Manage Client Sessions](https://primer.io/docs/checkout/client-session.md) for more.
>
> When the client token is retrieved, show Universal Checkout by calling `showUniversalCheckout` method:
>
> ```kotlin Kotlin theme={"dark"}
> class CheckoutActivity : AppCompatActivity() {
>
>   // other code goes here
>   private lateinit var viewModel: CheckoutViewModel
>
>   override fun onCreate(savedInstanceState: Bundle?) {
>     super.onCreate(savedInstanceState)
>     setContentView(R.layout.activity_main)
>
>     setupViewModel()
>     setupObservers()
>     fetchClientToken()
>   }
>
>   private fun setupViewModel() {
>     viewModel = ViewModelProvider(this).get(CheckoutViewModel::class.java)
>   }
>
>   private fun fetchClientToken() = viewModel.fetchClientToken()
>
>   private fun setupObservers() {
>     viewModel.clientToken.observe(this) { clientToken ->
>         showUniversalCheckout(clientToken)
>      }
>  }
>
>  private fun showUniversalCheckout(clientToken: String) {
>     Primer.instance.showUniversalCheckout(this, clientToken)
>  }
> }
> ```
>
> > [!NOTE]
> >
> > You should now be able to see Universal Checkout! The user can now interact with Universal Checkout, and the SDK will create the payment. The payment’s data will be returned on `onCheckoutCompleted()` configured in the previous step.
>
> When the user interacts with the checkout and clicks to pay, the Universal Checkout:
>
> * Shows the payment method screen if applicable
> * Tokenizes the payment method for payment
> * Creates the payment
> * Handles any other interactions (e.g. 3DS)
>
> ## Handle successful payments
>
> ### Listen to callback
>
> Listen to `onCheckoutCompleted` callback
>
> This function will notify you of a successful payment
>
> ```kotlin Kotlin theme={"dark"}
> private val listener = object : PrimerCheckoutListener {
>
>   override fun onCheckoutCompleted(checkoutData: PrimerCheckoutData) {
>     // Show an order confirmation screen...
>     // checkoutData contains the payment that has been created
>   }
> }
> ```
>
> ### Handle webhooks in your dashboard
>
> To receive updates about the status of your payments you’ll need to listen to webhooks. This is particularly useful for updating an order or any other data stored server-side.
>
> Head to the [Developers section](https://dashboard.primer.io/developers) of the Primer Dashboard to setup and test a webhook for `PAYMENT.STATUS` event.
>
> If you are not yet ready to receive webhooks, you can use [https://webhook.site](https://webhook.site/) to test your implementation.
>
> ## Handle failed payments
>
> Any errors, cancelled payment interactions or failed payments will trigger the `onFailed` callback
>
> ```kotlin Kotlin theme={"dark"}
>  private val listener = object : PrimerCheckoutListener {
>
>   override fun onFailed(
>     error: PrimerError,
>     checkoutData: PrimerCheckoutData?,
>     errorHandler: PrimerErrorDecisionHandler?
>   ) {
>     // 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.
>
>     // Show a default error message
>     errorHandler?.showErrorMessage(null)
>
>     // Show a custom error message
>     errorHandler?.showErrorMessage("This is my custom error message")
>   }
> }
> ```
>
> > [!WARNING]
> >
> > Not all failures are retryable. A client-side error doesn't always mean the payment failed, and decline reasons are intentionally generic. Verify the payment status server-side — through [payment webhooks](https://primer.io/docs/get-started/payment-updates.md) or the Payments API — before asking the user to pay again.

> [tab] React Native
>
> > [!NOTE]
> >
> > This SDK requires React Native [v0.63](https://reactnative.dev/versions) or higher.
>
> > [!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.
>
> ## 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://primer.io/docs/sdk/react-native/v2.x.x/installation.md).
>
> ## Initialize Universal Checkout
>
> Import the Primer SDK, construct your settings and call the SDK’s `configure` function.
>
> ```tsx Typescript theme={"dark"}
> import {
>   Primer,
>   PrimerSettings,
>   PrimerCheckoutData,
> } from "@primer-io/react-native";
>
> const CheckoutScreen = (props: any) => {
>   const onCheckoutComplete = (checkoutData: PrimerCheckoutData) => {
>     // Perform an action based on the payment creation response
>     // ex. show success screen, redirect to order confirmation view, etc.
>   };
>
>   const onUniversalCheckoutButtonTapped = async () => {
>     try {
>       const settings: PrimerSettings = {
>         onCheckoutComplete,
>       };
>
>       await Primer.configure(settings);
>     } catch (err) {
>       // Handle error
>     }
>   };
> };
> ```
>
> > [!NOTE]
> >
> > See all the configurations in the [SDK Reference](https://primer.io/docs/sdk/react-native/v2.x.x/installation.md).
>
> ## Show Universal Checkout
>
> First, make an API call to your backend to fetch a client token. Here is a simple example of how it can be done from your component. Once successful store your client token for future use.
>
> ```tsx Typescript theme={"dark"}
> const CheckoutScreen = (props: any) => {
>   // ...
>
>   const onUniversalCheckoutButtonTapped = async () => {
>     try {
>       // ...
>       // Ask your backend to create a client session
>       const clientToken = await createClientSession();
>     } catch (err) {
>       // Handle error
>     }
>   };
> };
> ```
>
> See [Manage Client Sessions](https://primer.io/docs/checkout/client-session.md) for more.
>
> Now, call the `showUniversalCheckout(clientToken)` function, with the client token to present Universal Checkout.
>
> ```tsx Typescript theme={"dark"}
> const CheckoutScreen = async (props: any) => {
>   // ...
>
>   const onUniversalCheckoutButtonTapped = async () => {
>     // ...
>
>     try {
>       // Ask your backend to create a client session
>       const clientToken = await createClientSession();
>
>       // Present Universal Checkout
>       await Primer.showUniversalCheckout(clientToken);
>     } catch (err) {
>       // Handle error
>     }
>   };
> };
> ```
>
> > [!NOTE]
> >
> > You should now be able to see Universal Checkout! The user can now interact with Universal Checkout, and the SDK will create the payment. The payment’s data will be returned on `primerDidCompleteCheckoutWithData(_:)` configured in the previous step.
>
> When the user interacts with the checkout and clicks to pay, the Universal Checkout:
>
> * Shows the payment method screen if applicable
> * Tokenizes the payment method for payment
> * Creates the payment
> * Handles any other interactions (e.g. 3DS)
>
> ## Handle successful payments
>
> ### Listen to callback
>
> This function will notify you of a successful payment
>
> ```tsx Typescript theme={"dark"}
> const onCheckoutComplete = (checkoutData: PrimerCheckoutData) => {
>   // Show a success screen
> };
> ```
>
> ### Handle webhooks in your dashboard
>
> To receive updates about the status of your payments you’ll need to listen to webhooks. This is particularly useful for updating an order or any other data stored server-side.
>
> Head to the [Developers section](https://dashboard.primer.io/developers) of the Primer Dashboard to setup and test a webhook for `PAYMENT.STATUS` event.
>
> If you are not yet ready to receive webhooks, you can use [https://webhook.site](https://webhook.site/) to test your implementation.
>
> ## Handle failed payments
>
> Any errors, cancelled payment interactions or failed payments will trigger the `onError` callback
>
> ```tsx Typescript theme={"dark"}
> const onError = (
> 	error: PrimerError,
> 	checkoutData: PrimerCheckoutData | null,
> 	handler: PrimerErrorHandler | undefined,
> ) => {
>   // 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
>   handler.showErrorMessage();
>
>   // Show a custom error message
>   handler.showErrorMessage('This is my custom error message');
> ```
>
> > [!WARNING]
> >
> > Not all failures are retryable. A client-side error doesn't always mean the payment failed, and decline reasons are intentionally generic. Verify the payment status server-side — through [payment webhooks](https://primer.io/docs/get-started/payment-updates.md) or the Payments API — before asking the user to pay again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.