# Accept payments via Revolut Pay - Web

Welcome to the implementation guide for Revolut Pay on web! In this tutorial, we'll walk you through the implementation process of Revolut Pay, powered by the Revolut Checkout Widget.

Revolut Pay allows your customers to pay with their card or Revolut bank account, delivering a streamlined checkout experience to your customers through your website.

![Revolut Pay - Web](/img/accept-payments/payment-methods/revolut-pay/revolut-pay-web.png 'Revolut Pay - Web')

## How it works

From an implementation perspective, the Revolut Pay SDK works with the following components:

1. **Server-side:** A server-side endpoint is required to securely communicate with the Merchant API to [create orders](/docs/api/merchant#create-order).
1. **Client-side:** The SDK uses your public API key to initialise a `RevolutCheckout.payments()` instance, which gives you access to the `revolutPay` module. The Revolut Pay button is then configured and mounted, using a `token` from the created order to initiate the payment. The SDK handles the payment flow, including other actions like redirection or authentication.
1. **Endpoint for webhooks:** Your server should listen for webhook events to reliably track the payment lifecycle and handle critical backend processes like updating order status, managing inventory, or initiating shipping. For more information, see: [Use webhooks to keep track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks).

The payment flow is slightly different based on whether your customer has the Revolut app installed:

- ![Revolut app installed]
  1. The customer goes to the checkout page and taps the **Revolut Pay** button.
  1. Your frontend uses your server endpoint to create an order and obtains the order `token` via the [Merchant API: Create an order](/docs/api/merchant#create-order).
  1. The **Revolut Pay SDK** opens the Revolut app, where they review and authorise the payment.
  1. The SDK processes the payment and presents the payment result to the customer. You can handle this result in different ways, as you will see in the [Handle payment results](#6-handle-payment-results) section.
  1. Your server receives webhook notifications about each event you're subscribed to. While this is optional, it's **strongly recommended** as the most reliable way to track the payment lifecycle. For more information, see: [Use webhooks to keep track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks).

- ![Revolut app not installed]
  1. The customer goes to the checkout page and taps the **Revolut Pay** button.
  1. Your frontend uses your server endpoint to create an order and obtains the order `token` via the [Merchant API: Create an order](/docs/api/merchant#create-order).
  1. The **Revolut Pay SDK** redirects the user to a secure Revolut payment page, where they can review and authorise the payment.
  1. The customer is redirected back to your website, where the final payment result is handled. You can manage this with redirect URLs, as you will see in the [Handle payment results](#6-handle-payment-results) section.
  1. Your server receives webhook notifications about each event you're subscribed to. While this is optional, it's **strongly recommended** as the most reliable way to track the payment lifecycle. For more information, see: [Use webhooks to keep track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks).

:::info
For more information about the order and payment lifecycle in the Merchant API, see: [Order and payment lifecycle](/docs/guides/merchant/reference/order-lifecycle).
:::

### Implementation overview

Check the following high-level overview on how to implement Revolut Pay on your website:

1. [Set up an endpoint for creating orders](#1-set-up-an-endpoint-for-creating-orders)
1. [Install Revolut Checkout package](#2-install-revolut-checkout-package)
1. [Initialise Revolut Pay SDK](#3-initialise-revolut-pay-sdk)
1. [Configure Revolut Pay SDK](#4-configure-revolut-pay-sdk)
1. [Mount Revolut Pay button](#5-mount-revolut-pay-button)
1. [Handle payment results](#6-handle-payment-results)
1. [Verify payment on your redirect page (for redirect flows)](#7-verify-payment-on-your-redirect-page-for-redirect-flows)

The following sections describe each step in detail, to see [working examples](#examples) you can skip ahead.

### Integration options

To handle the payment result, you have two primary integration paths, which are explained in detail in the [Handle payment results](#6-handle-payment-results) section:

- **Event listening with mobile redirects:** Use the `.on()` method to handle payment outcomes in your client-side code. This is the standard approach for desktop, you should also provide `mobileRedirectUrls` to handle redirection on mobile devices.
- **Redirects only:** Use the `redirectUrls` option. This will disable the `.on()` event handler entirely and redirect the user to a new URL on both desktop and mobile. This is a simpler integration path suitable for multi-page applications.

### Before you begin

Before you start this tutorial, ensure you have completed the following steps:

- [Get started: 1. Apply for a Merchant account](/docs/guides/merchant/get-started)
- [Get started: 2. Generate the API key](/docs/guides/merchant/get-started)

## Implement Revolut Pay

This section walks you through the server- and client-side implementation step by step.

:::info
The SDK supports both `async/await` syntax and the traditional Promise-based `.then()` syntax. You can see examples of both at each step of the guide.
:::

### 1. Set up an endpoint for creating orders

Before implementing the client-side widget, you must first create a dedicated endpoint on your server. This is a critical security step, as your secret API key must never be exposed on the client side.

The role of this server-side endpoint is to act as a secure bridge between your frontend and the Merchant API. When a customer initiates a payment on your website, your frontend will call this endpoint. Your endpoint is then responsible for:

1. Receiving the checkout details (e.g., `amount`, `currency`) from the frontend request.
1. Securely calling the [Merchant API: Create an order](/docs/api/merchant#create-order) endpoint with the received details.
1. Receiving the order details, including the public `token`, back from the Merchant API.
1. Passing this `token` back to your frontend in the response.

Later, in the client-side configuration, the `createOrder` callback function will call this endpoint to fetch the `token`, which is required to initialise and display the Revolut Pay button.

Below is an example of the JSON response your endpoint will receive from the Merchant API after successfully creating an order. The crucial field to extract and return to your frontend is the `token`.

```json {3}
{
  "id": "6516e61c-d279-a454-a837-bc52ce55ed49",
  "token": "0adc0e3c-ab44-4f33-bcc0-534ded7354ce",
  "type": "payment",
  "state": "pending",
  "created_at": "2023-09-29T14:58:36.079398Z",
  "updated_at": "2023-09-29T14:58:36.079398Z",
  "amount": 1000,
  "currency": "GBP",
  "outstanding_amount": 1000,
  "capture_mode": "automatic",
  "checkout_url": "https://checkout.revolut.com/payment-link/0adc0e3c-ab44-4f33-bcc0-534ded7354ce",
  "enforce_challenge": "automatic"
}
```

### 2. Install Revolut Checkout package

Before you begin the client-side integration, add the Revolut Checkout package to your project using your preferred package manager. This package is necessary to interact with the Revolut Pay SDK.

:::warning
Make sure you're on the latest version of the [`@revolut/checkout` library](https://www.npmjs.com/package/@revolut/checkout).
:::

```install
npm install @revolut/checkout
```

:::info
Alternatively, you can add the widget to your code base by adding the embed script to your page directly. To learn more, see: [Installation](/docs/sdks/merchant-web-sdk/introduction#installation).
:::

### 3. Initialise Revolut Pay SDK

Import `RevolutCheckout` and call `RevolutCheckout.payments()` with your **Public API key** to get a `revolutPay` instance. You can also pass an optional `locale` parameter here, which is useful if you want to align the language of the Revolut Pay widget with your site's language selector.

- ![With async await]

  ```ts [my-app.js]
  import RevolutCheckout from '@revolut/checkout'

  const { revolutPay } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  // Configuration code will go here
  ```

- ![Without async await]

  ```ts [my-app.js]
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((paymentInstance) => {
    const revolutPay = paymentInstance.revolutPay

    // Configuration code will go here
  })
  ```

### 4. Configure Revolut Pay SDK

Next, create a `paymentOptions` object. This object defines the core details of the payment and will be passed to the `revolutPay.mount()` method in the next step.

The most critical parameter is `createOrder`. This is a function you define that calls your backend to create an order via the Merchant API and returns the `token` (as `publicId`).

- ![With async await]

  ```js [my-app.js] {8-20}
  import RevolutCheckout from '@revolut/checkout'

  const { revolutPay } = await RevolutCheckout.payments({
    locale: 'en',
    publicToken: '<yourPublicApiKey>',
  })

  const paymentOptions = {
    currency: 'GBP', // 3-letter currency code
    totalAmount: 1000, // in lowest denomination e.g., cents

    createOrder: async () => {
      // Call your backend here to create an order
      // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    // You can put other optional parameters here
  }
  ```

- ![Without async await]

  ```js [my-app.js] {9-23}
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    locale: 'en',
    publicToken: '<yourPublicApiKey>',
  }).then((paymentInstance) => {
    const revolutPay = paymentInstance.revolutPay

    const paymentOptions = {
      currency: 'GBP', // 3-letter currency code
      totalAmount: 1000, // in lowest denomination e.g., cents

      createOrder: () => {
        // Call your backend here to create an order
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        return yourServerSideCall().then((order) => ({
          publicId: order.token,
        }))
      },

      // You can put other optional parameters here
    }
  })
  ```

<!-- -->

- `RevolutCheckout.payments(...)` initialises the Revolut Checkout module and returns an object containing the `revolutPay` factory. You must pass your `locale` and `publicToken` (your merchant public API key) here.
- `revolutPay` is the factory that creates a Revolut Pay instance, which exposes two methods:
  - `.mount()` - renders the Revolut Pay button in a specified container, based on the settings you defined.
  - `.on()` - registers an event handler to listen for payment status changes.

- `createOrder` is a function you define (e.g. `yourServerSideCall()`) that takes the order details from your frontend (`amount`, `currency`, etc.) and passes them to your backend.
  1.  Your backend calls the [Merchant API: Create an order](/docs/api/merchant#create-order) endpoint with that payload.
  1.  The Merchant API responds with a `token`.
  1.  You return `{ publicId: order.token }` to the widget, so it can start the checkout session.

:::info
For a more detailed reference of the Revolut Pay options and button styling, see: [Revolut Pay](/docs/sdks/merchant-web-sdk/payment-methods/revolut-pay) and [Revolut Pay button guidelines](/docs/resources/revolut-pay-button-guidelines).
:::

#### Additional settings

The Revolut Pay integration also offers a range of options for enhanced functionality. You can include these inside the `paymentOptions` object.

- **`savePaymentMethodForMerchant`**: Set to `true` to save the customer's payment method for future merchant-initiated transactions (e.g., subscriptions). For more information, see: [Charge a customer's saved payment method](/docs/guides/merchant/optimise-checkout/save-payment-methods/charge-saved-payment-method), [Manage subscriptions](/docs/guides/merchant/optimise-checkout/save-payment-methods/subscription-management).

  :::info
  To save a payment method, you must either create a `customer` object during [order creation](/docs/api/merchant#create-order) or provide a `customer.id` for an existing customer.
  :::

- **`requestShipping`**: Set to `true` to enable Fast checkout.This allows Revolut Pay to collect shipping address and delivery methods from the customer, letting your application skip the shipping flow and allowing the customer to use their details already stored in Revolut Pay.

:::warning
Your backend must support the Fast checkout flow for this feature to work. See the [Fast checkout guide](/docs/guides/merchant/optimise-checkout/fast-checkout) for details.
:::

- **`buttonStyle`**: Customise the look and feel of the button.
- **`customer`**: Pre-fill the customer's details to speed up the checkout process.
- **`validate`**: Provide a function that performs validation before a payment is initiated.

### 5. Mount Revolut Pay button

Now, render the Revolut Pay button on your page.

#### 5.1 Add a DOM element

First, add an empty `<div>` container to your HTML file where you want the button to appear.

```html [my-page.html]
<!-- ... -->

<div id="revolut-pay"></div>

<!-- ... -->
```

#### 5.2 Mount the button

Call the `.mount()` method from your `revolutPay` instance, passing the container's selector and the `paymentOptions` you configured.

- ![With async await]

  ```js [my-app.js] {22}
  import RevolutCheckout from '@revolut/checkout'

  const { revolutPay } = await RevolutCheckout.payments({
    locale: 'en',
    publicToken: '<yourPublicApiKey>',
  })

  const paymentOptions = {
    currency: 'GBP',
    totalAmount: 1000,

    createOrder: async () => {
      // Call your backend here to create an order
      // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    // You can put other optional parameters here
  }

  revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
  ```

- ![Without async await]

  ```js [my-app.js] {24}
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    locale: 'en',
    publicToken: '<yourPublicApiKey>',
  }).then((paymentInstance) => {
    const revolutPay = paymentInstance.revolutPay

    const paymentOptions = {
      currency: 'GBP',
      totalAmount: 1000,

      createOrder: () => {
        // Call your backend here to create an order
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        return yourServerSideCall().then((order) => ({
          publicId: order.token,
        }))
      },

      // You can put other optional parameters here
    }

    revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
  })
  ```

### 6. Handle payment results

:::warning
Before choosing your method, it's crucial to understand the roles of client-side events and server-side webhooks:

- **Widget events (`.on('payment', ...)`):** These are perfect for handling frontend logic, such as displaying a success message, updating the UI, or showing an error to the customer. However, their delivery is **not guaranteed**. Factors like the user's browser performance, network connectivity, or ad-blockers can prevent these events from firing.
- **Webhooks:** These are server-to-server notifications that we **guarantee** to send for every payment status change. You **must** rely on webhooks for all critical **backend logic**, such as confirming the order, releasing digital goods, or starting the shipping process.

**Never use widget events or client-side redirects as the sole trigger for critical backend logic.**
:::

In this step, you decide how to handle payment results. You have the following integration paths to choose from.

- ![Event listening with mobile redirects]

  This hybrid approach combines the benefits of both methods. It uses event-driven control for a dynamic UI on desktop, while using redirects on mobile to ensure a robust payment flow, as widget events are not always guaranteed to fire in mobile browser scenarios.

  Remember, the `.on('payment', ...)` handler in this context should **only** be used to manage the user interface on the frontend (e.g., showing a confirmation modal). Your backend must still rely on webhooks to confirm the payment status before fulfilling the order.

  :::tip
  **When to implement event listening and mobile redirect URLs:**
  - You maintain a stateful checkout page and users see it in a different state when they are redirected after payment
  - Your checkout page does not initialise Revolut Pay immediately when opened
    :::

  - ![With async await]

    ```ts [my-app.js] {19-55}
    import RevolutCheckout from '@revolut/checkout'

    const { revolutPay } = await RevolutCheckout.payments({
      locale: 'en',
      publicToken: '<yourPublicApiKey>'
    })

    const paymentOptions = {
      currency: 'GBP',
      totalAmount: 1000,

      createOrder: async () => {
        // Call your backend here to create an order
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        const order = await yourServerSideCall()
        return { publicId: order.token }
      },

      mobileRedirectUrls: {
        success: 'https://www.example.com/success',
        failure: 'https://www.example.com/failure',
        cancel: 'https://www.example.com/cancel'
      },

      // You can put other optional parameters here
    }

    revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)

    revolutPay.on('payment', (event) => {
      switch (event.type) {
        case 'cancel': {
          if (event.dropOffState === 'payment_summary') {
            // Do something to handle payment cancellation,
            // if the payment was abandoned on the payment summary screen
            window.alert('The payment was cancelled.')
          }
          break
        }

        case 'success':
          onSuccess() {
            // Do something to handle successful payments
            window.alert('The payment was successful. Thank you!')
          }
          break

        case 'error':
          onError(event.error) {
            // Do something to handle payment errors
            window.alert(`Something went wrong. ${error}`)
          }
          break
      }
    })
    ```

  - ![Without async await]

    ```js [my-app.js] {21-57}
    import RevolutCheckout from '@revolut/checkout'

    RevolutCheckout.payments({
      locale: 'en',
      publicToken: '<yourPublicApiKey>',
    }).then((paymentInstance) => {
      const revolutPay = paymentInstance.revolutPay

      const paymentOptions = {
        currency: 'GBP',
        totalAmount: 1000,

        createOrder: () => {
          // Call your backend here to create an order

          return yourServerSideCall().then((order) => ({
            publicId: order.token,
          }))
        },

        mobileRedirectUrls: {
          success: 'https://www.example.com/success',
          failure: 'https://www.example.com/failure',
          cancel: 'https://www.example.com/cancel'
        },

        // You can put other optional parameters here
      }

      revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)

      revolutPay.on('payment', (event) => {
        switch (event.type) {
          case 'cancel': {
            if (event.dropOffState === 'payment_summary') {
              // Do something to handle payment cancellation,
              // if the payment was abandoned on the payment summary screen
              window.alert('The payment was cancelled.')
            }
            break
          }

          case 'success':
            onSuccess() {
              // Do something to handle successful payments
              window.alert('The payment was successful. Thank you!')
            }
            break

          case 'error':
            onError(event.error) {
              // Do something to handle payment errors
              window.alert(`Something went wrong. ${error}`)
            }
            break
        }
      })
    })
    ```

  :::info
  For more information about listening to `payment` events, see: [Revolut Pay](/docs/sdks/merchant-web-sdk/payment-methods/revolut-pay).
  :::

- ![Redirect URLs]

  This is the simplest path. The widget automatically redirects the customer to a new page for all outcomes, on all devices. Simply add a `redirectUrls` object to your `paymentOptions`. No event listener is needed.

  :::tip
  **When to implement redirect URLs:**
  - You have limited technical resources
  - You use an older tech stack
  - You have a multi-page application
  - You need to redirect customers to a new page upon payment
    :::

  - ![With async await]

    ```ts [my-app.js] {19-23}
    import RevolutCheckout from '@revolut/checkout'

    const { revolutPay } = await RevolutCheckout.payments({
      locale: 'en',
      publicToken: '<yourPublicApiKey>',
    })

    const paymentOptions = {
      currency: 'GBP',
      totalAmount: 1000,

      createOrder: async () => {
        // Call your backend here to create an order
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        const order = await yourServerSideCall()
        return { publicId: order.token }
      },

      redirectUrls: {
        success: 'https://www.example.com/success',
        failure: 'https://www.example.com/failure',
        cancel: 'https://www.example.com/cancel',
      },

      // You can put other optional parameters here
    }

    revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
    ```

  - ![Without async await]

    ```js [my-app.js] {21-25}
    import RevolutCheckout from '@revolut/checkout'

    RevolutCheckout.payments({
      locale: 'en',
      publicToken: '<yourPublicApiKey>',
    }).then((paymentInstance) => {
      const revolutPay = paymentInstance.revolutPay

      const paymentOptions = {
        currency: 'GBP',
        totalAmount: 1000,

        createOrder: () => {
          // Call your backend here to create an order
          // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
          return yourServerSideCall().then((order) => ({
            publicId: order.token,
          }))
        },

        redirectUrls: {
          success: 'https://www.example.com/success',
          failure: 'https://www.example.com/failure',
          cancel: 'https://www.example.com/cancel',
        },

        // You can put other optional parameters here
      }

      revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
    })
    ```

  If you use this method, proceed to the next step to handle the redirect.

### 7. Verify payment on your redirect page (for redirect flows)

This step is necessary if you are using `redirectUrls` or `mobileRedirectUrls`.

When a customer is redirected to your `success`, `failure`, or `cancel` page, you must verify the final state of the payment before fulfilling the order.

1. **Retrieve the order ID:** On your redirect page, get the order's public ID from the URL. Revolut Pay appends it as a query parameter named `_rp_oid`.

   ```js
   // On your success/failure page:
   import { getRevolutPayOrderIdURLParam } from '@revolut/checkout'

   const revolutPublicOrderId = getRevolutPayOrderIdURLParam()
   ```

1. **Verify with your backend:** Send this ID to your server.
1. **Check order status:** From your server, make a secure API call to the [Merchant API: Retrieve an order](/docs/api/merchant#retrieve-order) endpoint using the order ID.
1. **Confirm fulfillment:** Only fulfill the order (e.g., ship goods, grant access) if the `state` in the API response is `completed`. Display a corresponding success or failure message to the customer.

This server-side verification can be a crucial security step to prevent fraud.

## Examples

:::tip
**Looking for more inspiration?**

- Check out our live [Revolut Pay demo](https://github.com/revolut-engineering/revolut-checkout-example/tree/main/revolut-pay-example) for a practical demonstration of this integration.
- Explore our [integration examples repository](https://github.com/revolut-engineering/revolut-checkout-example) to discover all available examples and see how different payment solutions are implemented.
  :::

The examples below assume you added the DOM container on your webpage where you want to render the Revolut Pay button:

```html [my-page.html]
<!-- ... -->

<div id="revolut-pay"></div>

<!-- ... -->
```

### Example with event listening and mobile redirect URLs

- ![With async await]

  ```ts [my-app.js] {13-53}
  // 1. Import and initialise the SDK
  import RevolutCheckout from '@revolut/checkout'

  const { revolutPay } = await RevolutCheckout.payments({
    publicToken: '<yourPublicAPIKey>',
  })

  // 2. Configure payment options with mobile redirects
  const paymentOptions = {
    currency: 'GBP',
    totalAmount: 1000,
    mobileRedirectUrls: {
      success: 'https://www.example.com/success',
      failure: 'https://www.example.com/failure',
      cancel: 'https://www.example.com/cancel',
    },

    createOrder: async () => {
      // Call your backend here to create an order
      // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    // You can put other optional parameters here
  }

  // 3. Mount the button
  revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)

  // 4. Hanlde payment events on desktop
  // This event handler is for UI updates on desktop ONLY
  revolutPay.on('payment', (event) => {
    switch (event.type) {
      case 'cancel': {
        if (event.dropOffState === 'payment_summary') {
          log('what a shame, please complete your payment')
        }
        break
      }

      case 'success':
        onSuccess()
        break

      case 'error':
        onError(event.error)
        break
    }
  })
  ```

- ![Without async await]

  ```js [my-app.js] {13-53}
  // 1. Import and initialise the SDK
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    publicToken: '<yourPublicAPIKey>',
  }).then((paymentInstance) => {
    const revolutPay = paymentInstance.revolutPay

    // 2. Configure payment options with mobile redirects
    const paymentOptions = {
      currency: 'GBP',
      totalAmount: 1000,
      mobileRedirectUrls: {
        success: 'https://www.example.com/success',
        failure: 'https://www.example.com/failure',
        cancel: 'https://www.example.com/cancel',
      },

      createOrder: () => {
        // Call your backend here to create an order
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        return yourServerSideCall().then((order) => ({
          publicId: order.token,
        }))
      },

      // You can put other optional parameters here
    }

    // 3. Mount the button
    revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)

    // 4. Hanlde payment events on desktop
    // This event handler is for UI updates on desktop ONLY
    revolutPay.on('payment', (event) => {
      switch (event.type) {
        case 'cancel': {
          if (event.dropOffState === 'payment_summary') {
            log('what a shame, please complete your payment')
          }
          break
        }

        case 'success':
          onSuccess()
          break

        case 'error':
          onError(event.error)
          break
      }
    })
  })
  ```

### Example with redirect URLs

- ![With async await]

  ```js [my-app.js] {12-16}
  // 1. Import and initialise the SDK
  import RevolutCheckout from '@revolut/checkout'

  const { revolutPay } = await RevolutCheckout.payments({
    publicToken: '<yourPublicApiKey>',
  })

  // 2. Configure payment options with redirects
  const paymentOptions = {
    currency: 'GBP',
    totalAmount: 1000,
    redirectUrls: {
      success: 'https://www.example.com/success',
      failure: 'https://www.example.com/failure',
      cancel: 'https://www.example.com/cancel',
    },

    createOrder: async () => {
      // Call your backend here to create an order
      // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
      const order = await yourServerSideCall()
      return { publicId: order.token }
    },

    // You can put other optional parameters here
  }

  // 3. Mount the button
  revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
  ```

- ![Without async await]

  ```js [your-page.js] {12-16}
  // 1. Import and initialise the SDK
  import RevolutCheckout from '@revolut/checkout'

  RevolutCheckout.payments({
    publicToken: '<yourPublicApiKey>',
  }).then((paymentInstance) => {
    const revolutPay = paymentInstance.revolutPay

    const paymentOptions = {
      currency: 'GBP',
      totalAmount: 1000,
      redirectUrls: {
        success: 'https://www.example.com/success',
        failure: 'https://www.example.com/failure',
        cancel: 'https://www.example.com/cancel',
      },

      // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
      createOrder: () => {
        // Call your backend here to create an order
        return yourServerSideCall().then((order) => ({
          publicId: order.token,
        }))
      },

      // You can put other optional parameters here
    }

    // 3. Mount the button
    revolutPay.mount(document.getElementById('revolut-pay'), paymentOptions)
  })
  ```

## Implementation checklist

Before deploying your implementation to your production environment, complete the checklist below to see if everything works as expected, using Merchant API's Sandbox environment. To test in Sandbox environment, set the base URL of your API calls to: `sandbox-merchant.revolut.com/`.

As Revolut Pay has more complex user flows compared to other payment methods, the following user journey paths need to be tested (besides general tests):

- [Desktop flow](#desktop-flow)
- [Mobile flow (without Revolut app installed)](#mobile-flow-without-revolut-app-installed)
- [Mobile flow (with Revolut app installed)](#mobile-flow-with-revolut-app-installed)

To see if your implementation is able to manage all flows, check each flow against the [checks below](#general-checks).

:::info
For more information about Revolut Pay payment flows in Sandbox, see: [Test flows](/docs/guides/merchant/test-and-go-live/testing/test-flows#revolut-pay).
:::

### Desktop flow

This is the least complex of user flows. Steps are the following:

1. User clicks Revolut Pay button on merchant's site.
1. User journey is completed in a popup.
1. Payment completed or failed, respective message is shown to customer.

### Mobile flow (without Revolut app installed)

This user flow represents the case where the Revolut app is not installed on the customer's phone:

1. User clicks Revolut Pay button on merchant's site using their mobile browser.
1. Client SDK attempts to open the Revolut app.
1. If it is not installed, user is redirected to a standalone page where the payment flow continues.
1. Payment completed or failed, the customer should be redirected to the merchant site and respective message is shown to customer.

:::tip
You can test this flow with your browser's built-in mobile browser simulator. Available via the browser's developer tools.
:::

### Mobile flow (with Revolut app installed)

This user flow represent the case, where the Revolut app is installed on the customer's phone:

1. User clicks Revolut Pay button on merchant's site, on his mobile browser.
1. Client SDK attempts to open the Revolut app.
1. If it is installed, Revolut app is opened where the payment flow continues.
1. Payment completed or failed, Revolut app attempts to redirect user to the same browser where the flow started.
   :::warning
   Only **Google Chrome**, **Safari**, **Samsung Internet Browser**, **Firefox**, and **Opera** are supported. If the user begins on any other browser, successful redirection to the same browser is not guaranteed. The user will be redirected to their default mobile browser.
   :::
1. A self-closing page is opened, user arrives at the tab where the flow started.

### General checks

- [ ] Revolut Pay button is rendered at the intended DOM element.

- [ ] Your backend creates the order successfully when clicking the button.

- [ ] Order `token` is successfully fetched.

- [ ] Checkout is successful with [test cards for successful payment](/docs/guides/merchant/test-and-go-live/testing/test-cards#test-for-successful-payments).

- [ ] Checkout errors are handled as expected by your application.
    - [ ] Check if your backend handles all cases of `cancel` events, and you manage every `dropOffState` as intended. For more information about the cancellation events, see: [Revolut Pay](/docs/sdks/merchant-web-sdk/payment-methods/revolut-pay).

    - [ ] Failed payments with [test cards for error cases](/docs/guides/merchant/test-and-go-live/testing/test-cards#test-for-error-cases).

#### New customer

- [ ] Customer is created in the Merchant API upon completing checkout.
- [ ] Customer details (email, payment methods, shipping details) saved in the Merchant API.
- [ ] On next checkout customer is already logged in.

#### Existing customer

- [ ] After first login, customer is already logged in on next checkout.
- [ ] Existing customer has their details returned.
- [ ] New payment method is saved to customer successfully.
- [ ] Shipping details are returned/saved to the customer successfully.

#### Event listening with mobile redirect URLs

If you implemented Revolut Pay with event listening and mobile redirect URLs:

- [ ] All payment events are listened to, when payment was made on desktop.
- [ ] Payment events trigger expected behavior on your backend.
- [ ] Redirect pages are always opened on mobile.
- [ ] Success or failure messaging work on your redirect pages on mobile.

#### Redirect URLs

If you implemented Revolut Pay with redirect URLs:

- [ ] Redirect pages are always opened, both on desktop and mobile.
- [ ] Success or failure messaging work on your redirect pages, both on desktop and mobile.

If your implementation handles all these cases as you expect in Sandbox, it is advised you also test your implementation in production before going live. Once your implementation passes all the checks in both environments, you can safely go live with your implementation.

These checks only cover the implementation path described in this tutorial, if your application handles more features of the Merchant API, see the [Merchant API: Implementation checklists](/docs/guides/merchant/test-and-go-live/testing/implementation-checklists).

:::tip
**Congratulations!** You've successfully implemented Revolut Pay and are ready to accept the first payment.
:::

## What's next

- [Check our integration example on GitHub](https://github.com/revolut-engineering/revolut-checkout-example/tree/main/revolut-pay-example)
- [Learn about Sandbox test flows](/docs/guides/merchant/test-and-go-live/testing/test-flows)
- [See the full list of parameters in the SDK reference](/docs/sdks/merchant-web-sdk/payment-methods/revolut-pay)
- [Review the Revolut Pay marketing assets and guidelines](/docs/resources/marketing-guidelines)
- [Review the Revolut Pay button guidelines](/docs/resources/revolut-pay-button-guidelines)
- [Learn about the order and payment lifecycle](/docs/guides/merchant/reference/order-lifecycle)
- [Learn more about using webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks)