# Accept payments via Pay by Bank - Web

Revolut's Pay by Bank widget allows you to effortlessly accept payments directly from your customers' bank accounts, offering a seamless and secure checkout experience.

In this tutorial, we'll guide you through integrating the Pay by Bank widget, powered by the [Revolut Checkout Widget](/docs/sdks/merchant-web-sdk/introduction). Customers can securely authorize payments directly from their bank accounts within your website, simplifying the payment process and enhancing user experience.

![Pay by Bank - Web](/img/accept-payments/payment-methods/pay-by-bank/pay-by-bank.gif 'Pay by Bank - Web')

:::info
Before implementing Pay by Bank, be aware of the following:

- **No sandbox environment:** Pay by Bank is not available in the [sandbox environment](https://sandbox-business.revolut.com/). Real transactions must be made to test your implementation in the **production environment**.
- **No refund support:** Pay by Bank does not support refunds through the Merchant API. If you need to refund a payment, you must process it through alternate channels outside of Revolut's payment system.
- **Pricing restrictions:** Pay by Bank is not available for merchants on unblended pricing plans.
  :::

::::details [Available banks]

:::info
The list of available banks may vary at the time of payment and depends on network status.
:::

| Country             | Banks                                                                                                                                                                                                                                 | Currency | Supported schemes                   |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------- |
| Austria (AT)        | Raiffeisen, Erste Bank, BAWAG P.S.K., EasyBank                                                                                                                                                                                        | EUR      | SEPA Credit Transfers               |
| Belgium (BE)        | ING, BNP Paribas Fortis, Hello bank!, Argenta, Crelan, KBC, CBC Banque, Beobank, Fintro                                                                                                                                               | EUR      | SEPA Credit Transfers, SEPA Instant |
| Croatia (HR)        | UniCredit, PBZ                                                                                                                                                                                                                        | EUR      | SEPA Credit Transfers               |
| Finland (FI)        | OP Bank, S-Pankki, Nordea, Danske Bank                                                                                                                                                                                                | EUR      | SEPA Credit Transfers, SEPA Instant |
| France (FR)         | BNP Mabanque, La Banque Postale, Société Générale, Crédit Agricole, Crédit Mutuel, Crédit Industriel et Commercial, Caisse d'Epargne (BPCE), Boursorama, Hello Bank!, Banque Populaire (BPCE), Crédit Mutuel de Bretagne (ARKEA), N26 | EUR      | SEPA Credit Transfers, SEPA Instant |
| Germany (DE)        | Sparkasse, Deutsche Postbank, Commerzbank, Deutsche Bank, ING, Volksbanken, N26, Comdirect Bank, Deutsche Kreditbank, Santander                                                                                                       | EUR      | SEPA Credit Transfers, SEPA Instant |
| Greece (GR)         | Piraeus Bank, Eurobank                                                                                                                                                                                                                | EUR      | SEPA Credit Transfers               |
| Ireland (IE)        | Allied Irish Bank, Bank of Ireland, Permanent TBS                                                                                                                                                                                     | EUR      | SEPA Credit Transfers               |
| Italy (IT)          | ING, Fineco, Credem Banca, Banca Intesa Sanpaolo, BancoPosta, BPER Banca, Banca Monte dei Paschi di Siena, Postepay, Crédit Agricole, Banco BPM, BNL, Unicredit                                                                       | EUR      | SEPA Credit Transfers               |
| Lithuania (LT)      | Swedbank, SEB, Luminor                                                                                                                                                                                                                | EUR      | SEPA Credit Transfers, SEPA Instant |
| Netherlands (NL)    | ABN AMRO, ING, Rabobank, SNS, ASN, RegioBank, Triodos                                                                                                                                                                                 | EUR      | SEPA Credit Transfers, SEPA Instant |
| Portugal (PT)       | CaixaBank, Millennium bcp, Banco BPI, Activo Bank, Banco Montepio, Santander, Credito Agricola, Novo Banco, Bankinter                                                                                                                 | EUR      | SEPA Credit Transfers, SEPA Instant |
| Spain (ES)          | BBVA, CaixaBank, Santander, ING, Banco de Sabadell, ImaginBank, Unicaja Banco, Bankinter, Kutxabank, Cajamar Caja Rural, Ibercaja, Openbank, EVO Banco, Banco Mediolanum                                                              | EUR      | SEPA Credit Transfers, SEPA Instant |
| United Kingdom (GB) | Lloyds Bank, Barclays, Santander, HSBC, Halifax, Nationwide, Natwest, Monzo, Bank of Scotland, Danske, RBS, TSB, Ulster Bank, First Direct, Starling, Virgin Money, Chase UK, Wise                                                    | GBP      | Faster Payments                     |

::::

## How it works

Implementing the Pay by Bank widget involves these core components:

1. **Server-side:** expose an endpoint on your backend that creates an order using the [Merchant API: Create an order](/docs/api/merchant#create-order) endpoint.
1. **Client-side:** the Revolut Checkout Widget facilitates bank selection, uses your endpoint to create the order, and handles other actions like redirection or customer authentication.
1. **Webhook endpoint (recommended):** Set up webhooks to monitor payment lifecycle events. For more details, refer to [Use webhooks to track of the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks).

The order and payment flow is similar to all payment solutions:

1. The customer goes to the checkout page.
1. The customer initiates the payment by clicking a button.
1. Your client creates an instance of the `payByBank` widget.
1. The widget shows a pop-up window to select the customer's bank, and handles additional actions, like redirection to the bank's page to complete the payment.
1. In the background, the widget uses your endpoint to create an order, initiates the payment processing, and handles payment results via event callbacks.
1. Your client responds to the callbacks to present the payment result to the customer.
1. Your server receives webhook notifications about each event you're subscribed to.

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

### Implementation overview

Here is an overview of the key integration steps:

1. [Install Revolut Checkout Widget](#1-install-revolut-checkout-widget)
1. [Set up an endpoint for creating orders](#2-set-up-an-endpoint-for-creating-orders)
1. [Initialise widget](#3-initialise-widget)
1. [Add DOM element](#4-add-dom-element)
1. [Listen to DOM events](#5-listen-to-dom-events)
1. [Configure and show the widget](#6-configure-and-show-the-widget)
1. [Listen to webhooks](#7-listen-to-webhooks)

### Before you begin

Make sure you've completed the following:

- [Apply for a Merchant account](/docs/guides/merchant/get-started)
- [Generate the API keys](/docs/guides/merchant/get-started)

## Implement the Pay by Bank widget

### 1. Install Revolut Checkout Widget

Before you begin, ensure the Revolut Checkout Widget is installed in your project. This widget is a necessary component to create and configure the Pay by Bank widget. You can install the widget via your project's package manager.

```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/get-started#installation).
:::

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

When a customer proceeds to make payment on your website, the widget will call the `createOrder` callback. You'll need to create an order based on your customer's checkout and return its `token` using the [Merchant API: Create an order](/docs/api/merchant#create-order) endpoint.

This token represents the order and is used to initialise the Pay by Bank widget. The process of creating an order and receiving a token will vary based on your backend setup.

See an example response of an order created with minimal required parameters:

```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": 5,
  "currency": "GBP",
  "outstanding_amount": 5,
  "capture_mode": "automatic",
  "checkout_url": "https://checkout.revolut.com/payment-link/0adc0e3c-ab44-4f33-bcc0-534ded7354ce",
  "enforce_challenge": "automatic"
}
```

### 3. Initialise widget

Use the `RevolutCheckout.payments()` module's `payByBank` instance with your [Merchant API Public key](https://business.revolut.com/settings/apis?tab=merchant-api) to initialise the widget.

- ![With async await]

  ```js [my-app.js]
  const { payByBank } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  // Configuration code will go here
  ```

- ![Without async await]

  ```js [my-app.js]
  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((instance) => {
    instance
      .payByBank

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

:::info
For more information about the `RevolutCheckout.payments()` function, see: [Payments module](/docs/sdks/merchant-web-sdk/initialisation/payments-module)
:::

### 4. Add DOM element

Prepare your checkout page by adding a button or similar interactive element that users will click to initiate the payment:

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

<button id="pay-by-bank">Pay by bank</button>

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

This button will trigger your JavaScript integration of the widget, so make sure you listen to events.

### 5. Listen to DOM events

Attach a click handler to your button that will trigger the widget.

- ![With async await]

  ```js [my-app.js]
  const { payByBank } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  const payByBankButton = document.getElementById('pay-by-bank')

  payByBankButton.addEventListener('click', async () => {
    // Configuration code will go here
  })
  ```

- ![Without async await]

  ```js [my-app.js]
  const payByBankButton = document.getElementById('pay-by-bank')

  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((instance) => {
    payByBankButton.addEventListener('click', () => {
      instance
        .payByBank

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

### 6. Configure and show the widget

Invoke the `payByBank({ ... })` instance and pass your configuration parameters. Then, call the `.show()` method on the returned instance to open the Pay by Bank modal.

- ![With async await]

  ```js [my-app.js]
  const { payByBank } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  const payByBankButton = document.getElementById('pay-by-bank')

  payByBankButton.addEventListener('click', async () => {
    const payByBankInstance = payByBank({
      createOrder: async () => {
        // Call your backend here to create an order and return order.token
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        const order = await yourServerSideCall()
        return { publicId: order.token }
      },
      onSuccess() {
        // Do something to handle successful payments
        window.alert('Successful payment!')
      },
      onError(error) {
        // Do something to handle payment errors
        window.alert(`Something went wrong. ${error}`)
      },
    })

    payByBankInstance.show()
  })
  ```

- ![Without async await]

  ```js [my-app.js]
  const payByBankButton = document.getElementById('pay-by-bank')

  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((instance) => {
    payByBankButton.addEventListener('click', () => {
      instance.payByBank({
        createOrder: async () => {
          // Call your backend here to create an order and return order.token
          // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
          const order = await yourServerSideCall()
          return { publicId: order.token }
        },
        onSuccess() {
          // Do something to handle successful payments
          window.alert('Successful payment!')
        },
        onError(error) {
          // Do something to handle payment errors
          window.alert(`Something went wrong. ${error}`)
        },
      })

      instance.show()
    })
  })
  ```

| Code snippet                      | Description                                                                                                                                                                                                                                                                                                                                            |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `RevolutCheckout.payments(...)`   | Initialises the Revolut Checkout payments module and returns an object containing the `payByBank` factory. Pass your `locale` and `publicToken` here.                                                                                                                                                                                                  |
| `payByBank`                       | Creates a Pay by Bank instance when called with your options. It returns a `PaymentsModulePayByBankInstance`.                                                                                                                                                                                                                                          |
| `PaymentsModulePayByBankInstance` | The instance returned by `payByBank`. It exposes `.show()` to open the bank-selection modal and `.destroy()` to remove any rendered UI and clean up.                                                                                                                                                                                                   |
| `createOrder`                     | A function you define, such as `yourServerSideCall()`, to send order details like `amount`, `currency` from your frontend to your backend. Your backend then calls [Merchant API: Create an order](/docs/api/merchant#create-order), receives the `token`, and returns `{ publicId: order.token }` to the widget so it can start the checkout session. |
| `onSuccess`                       | Runs when the payment succeeds. In this example it shows a "Successful payment!" alert. Use it for merchant-defined post-payment actions such as redirecting to a confirmation page, updating the UI with a success message, or triggering follow-up purchase logic. Any return value is ignored.                                                      |
| `onError`                         | Runs when an error occurs. In this example it shows an alert with the error message. Use it for merchant-defined error handling such as displaying a custom error screen, logging diagnostics, or retrying order creation or prompting the user to choose another bank. Any return value is ignored.                                                   |
| Instance methods                  | Once you have your instance, call `.show()` to open the Revolut Pay by Bank modal. If you need to abort before rendering, call `.destroy()` instead.                                                                                                                                                                                                   |

:::info
For more details about the available parameters, see: [Merchant Web SDK: Pay by Bank](/docs/sdks/merchant-web-sdk/payment-methods/pay-by-bank).
:::

By following these steps, you have successfully integrated the Revolut Checkout Widget's Pay by Bank module into your website. This setup allows customers to pay with their bank accounts securely.

#### Additional settings

The `payByBank` module of the Revolut Checkout Widget offers several options that allow for a customised checkout experience. Here are some additional settings you can leverage:

1. **Event handlers:**
   - `onSuccess`, `onError` are event handlers that you can define to manage the different states of the payment process - from successful transactions to errors.
   - `onCancel` can be used to handle scenarios where the payment process is interrupted or cancelled by the user.

1. **Instant payment filter:**
   - By setting `instantOnly: true`, you can restrict the list of available banks to those supporting real-time transfers. We recommend using this filter if your business fulfills orders instantly, as non-instant payments via SEPA can take up to 2 business days to arrive in your account.

### 7. Listen to webhooks

While client-side callbacks like `onSuccess` indicate the customer has completed the flow, it's crucial to confirm the final payment status on your server before fulfilling the order. Webhooks provide a reliable way to track the order and payment lifecycle asynchronously.

:::tip
**Important considerations for Pay by Bank:**

- **Settlement time:** Payments processed via non-instant methods like SEPA Credit Transfers can take up to 2 business days to settle in your account. Relying solely on client-side redirects or polling for status updates is not recommended due to these potential delays.
- **Fulfillment trigger:** You should wait for the `ORDER_COMPLETED` webhook event before fulfilling Pay by Bank orders. This event confirms that the funds are successfully settled.
  :::

**We strongly recommend implementing a webhook endpoint to listen for events.** This approach is more robust than relying on client-side actions or polling, given potential settlement delays. Setting up webhooks allows you to reliably track order status and manage fulfillment accordingly.

:::info
Follow our detailed guide to set up webhooks for your integration: [Use webhooks to track the payment lifecycle](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks).
:::

## Examples

:::tip[Looking for more inspiration?]

- Check out our live [Pay by Bank demo](https://github.com/revolut-engineering/revolut-checkout-example/tree/main/pay-by-bank-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.
  :::

Insert the following DOM element into your webpage where you want to display the Pay by Bank widget, and attach to your click handler:

```html [checkout.html]
<!-- ... -->

<button id='pay-by-bank'>Pay by bank</button>

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

### Example with minimal required parameters

- ![With async await]

  ```js [my-app.js]
  const { payByBank } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  const payByBankButton = document.getElementById('pay-by-bank')

  payByBankButton.addEventListener('click', async () => {
    const payByBankInstance = payByBank({
      createOrder: async () => {
        // Call your backend here to create an order and return order.token
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        const order = await yourServerSideCall()
        return { publicId: order.token }
      },
      onSuccess() {
        // Do something to handle successful payments
        window.alert('Successful payment!')
      },
      onError(error) {
        // Do something to handle payment errors
        window.alert(`Something went wrong. ${error}`)
      },
    })

    payByBankInstance.show()
  })
  ```

- ![Without async await]

  ```js [my-app.js]
  const payByBankButton = document.getElementById('pay-by-bank')

  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((instance) => {
    payByBankButton.addEventListener('click', () => {
      instance.payByBank({
        createOrder: async () => {
          // Call your backend here to create an order and return order.token
          // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
          const order = await yourServerSideCall()
          return { publicId: order.token }
        },
        onSuccess() {
          // Do something to handle successful payments
          window.alert('Successful payment!')
        },
        onError(error) {
          // Do something to handle payment errors
          window.alert(`Something went wrong. ${error}`)
        },
      })

      instance.show()
    })
  })
  ```

### Example with additional parameters

- ![With async await]

  ```js [my-app.js]
  const { payByBank } = await RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  })

  const payByBankButton = document.getElementById('pay-by-bank')

  payByBankButton.addEventListener('click', async () => {
    const payByBankInstance = payByBank({
      createOrder: async () => {
        // Call your backend here to create an order and return order.token
        // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
        const order = await yourServerSideCall()
        return { publicId: order.token }
      },
      instantOnly: true,
      onSuccess() {
        // Do something to handle successful payments
        window.alert('Successful payment!')
      },
      onError(error) {
        // Do something to handle payment errors
        window.alert(`Something went wrong. ${error}`)
      },
      onCancel() {
        // Do something to handle cancelled payments
        window.alert('The payment was cancelled.')
      },
    })

    payByBankInstance.show()
  })
  ```

- ![Without async await]

  ```js [my-app.js]
  const payByBankButton = document.getElementById('pay-by-bank')

  RevolutCheckout.payments({
    locale: 'en', // Optional, defaults to 'auto'
    publicToken: '<yourPublicApiKey>', // Merchant public API key
  }).then((instance) => {
    payByBankButton.addEventListener('click', () => {
      instance.payByBank({
        createOrder: async () => {
          // Call your backend here to create an order and return order.token
          // For more information, see: https://developer.revolut.com/docs/api/merchant#create-order
          const order = await yourServerSideCall()
          return { publicId: order.token }
        },
        instantOnly: true,
        onSuccess() {
          // Do something to handle successful payments
          window.alert('Successful payment!')
        },
        onError(error) {
          // Do something to handle payment errors
          window.alert(`Something went wrong. ${error}`)
        },
        onCancel() {
          // Do something to handle cancelled payments
          window.alert('The payment was cancelled.')
        },
      })

      instance.show()
    })
  })
  ```

## Implementation checklist

:::warning
Pay by Bank is not available in the [sandbox environment](https://sandbox-business.revolut.com/). You must test your implementation using real transactions in the **production environment**. We recommend using small amounts for testing purposes.
:::

Before deploying your Pay by Bank implementation to your live website, complete the checklist below to ensure everything works as expected in the production environment.

### General checks

- [ ] Pay by Bank modal opens correctly when the customer clicks the payment button.

- [ ] Your backend creates the order successfully when the widget is triggered.

- [ ] Widget successfully calls `createOrder` function to fetch and use the order `token`.

- [ ] Bank selection screen displays correctly with available banks.

- [ ] Successful payment flow:
    - [ ] Customer can select a bank and complete authentication.

    - [ ] Payment completes successfully with a real bank account.

    - [ ] Success callback (`onSuccess`) is triggered as expected.

    - [ ] Customer sees appropriate success messaging.

- [ ] Error handling works as expected:
    - [ ] Failed payments trigger the error callback (`onError`).

    - [ ] Customer sees appropriate error messaging.

    - [ ] Cancel callback (`onCancel`) is triggered when payment is abandoned.

### Webhook verification

- [ ] [Webhook endpoint is set up](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks) to receive order and payment updates.
- [ ] Webhook notifications are received correctly for all payment events.
- [ ] `ORDER_COMPLETED` webhook event is received when payment settles successfully.
- [ ] Your backend only fulfills orders after receiving the `ORDER_COMPLETED` webhook event.
- [ ] Webhook signature verification is implemented for security.

### Payment method specific checks

If you implemented the `instantOnly: true` filter:

- [ ] Only banks supporting instant payments are displayed in the bank selection.
- [ ] Payments are processed and settled in real-time.

If you allow non-instant payments (SEPA Credit Transfers):

- [ ] Non-instant banks are displayed in the bank selection.
- [ ] Your system handles the delayed settlement (up to 2 business days).
- [ ] Order fulfillment waits for the `ORDER_COMPLETED` webhook before proceeding.

### Integration pattern checks

- [ ] Event callbacks (`onSuccess`, `onError`, `onCancel`) handle UI updates correctly.
- [ ] Backend logic relies on webhooks, not client-side callbacks, for critical operations.
- [ ] Payment status is verified server-side before fulfilling orders.

Once your implementation passes all the checks in the production environment, you can confidently deploy it to your live website.

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 Pay by Bank and are ready to accept payments from your customers.
:::

## What's next

- [Check our integration example](https://github.com/revolut-engineering/revolut-checkout-example/tree/main/pay-by-bank-example) - Learn more about how to use the Revolut Checkout SDK with the Pay by Bank widget
- [Learn more about the Pay by Bank SDK](/docs/sdks/merchant-web-sdk/payment-methods/pay-by-bank)
- [Learn more about the order and payment lifecycle](/docs/guides/merchant/reference/order-lifecycle)
- [Learn more about webhooks](/docs/guides/merchant/monitor-and-observe/webhooks/using-webhooks) - Check our guide about how you can track payment lifecycle with the Merchant API's webhook service
- [Learn more about order management](/docs/api/merchant#retrieve-order-list) - Explore the full capabilities of our Orders API
- [Learn more about customer management](/docs/api/merchant#retrieve-customer-list) - Explore the full capabilities of our Customers API