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.
From an implementation perspective, the Revolut Pay SDK works with the following components:
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.The payment flow is slightly different based on whether your customer has the Revolut app installed:
token
via the Merchant API: Create an order.For more information about the order and payment lifecycle in the Merchant API, see: Order and payment lifecycle.
Check the following high-level overview on how to implement Revolut Pay on your website:
The following sections describe each step in detail, to see working examples you can skip ahead.
To handle the payment result, you have two primary integration paths, which are explained in detail in the Handle payment results section:
.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.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 start this tutorial, ensure you have completed the following steps:
This section walks you through the server- and client-side implementation step by step.
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.
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:
amount
, currency
) from the frontend request.token
, back from the Merchant API.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
.
{
"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"
}
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.
Make sure you're on the latest version of the @revolut/checkout
library.
npm install @revolut/checkout
Alternatively, you can add the widget to your code base by adding the embed script to your page directly. To learn more, see: Adding the embed script.
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.
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
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
).
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/merchant/create-order
const order = await yourServerSideCall()
return { 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.token
.{ publicId: order.token }
to the widget, so it can start the checkout session.For a more detailed reference of the Revolut Pay options and button styling, see: Payments.revolutPay reference and Revolut Pay button guidelines.
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, Manage subscriptions.
To save a payment method, you must either create a customer
object during order creation 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.
Your backend must support the Fast checkout flow for this feature to work. See the Fast checkout guide 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.
Now, render the Revolut Pay button on your page.
First, add an empty <div>
container to your HTML file where you want the button to appear.
<...>
<div id="revolut-pay"></div>
<...>
Call the .mount()
method from your revolutPay
instance, passing the container's selector and the paymentOptions
you configured.
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/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)
Before choosing your method, it's crucial to understand the roles of client-side events and server-side webhooks:
.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.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.
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.
When to implement event listening and mobile redirect URLs:
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/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
}
})
For more information about listening to payment
events, see: Payments.revolutPay: on(event, callback)
.
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.
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
.
// On your success/failure page:
import { getRevolutPayOrderIdURLParam } from '@revolut/checkout'
const revolutPublicOrderId = getRevolutPayOrderIdURLParam()
Verify with your backend: Send this ID to your server.
Check order status: From your server, make a secure API call to the Merchant API: Retrieve an order endpoint using the order ID.
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.
Looking for more inspiration?
The examples below assume you added the DOM container on your webpage where you want to render the Revolut Pay button:
<...>
<div id='revolut-pay'></div>
<...>
// 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/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
}
})
// 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/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)
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):
To see if your implementation is able to manage all flows, check each flow against the checks below.
For more information about Revolut Pay payment flows in Sandbox, see: Test flows.
This is the least complex of user flows. Steps are the following:
This user flow represents the case where the Revolut app is not installed on the customer's phone:
You can test this flow with your browser's built-in mobile browser simulator. Available via the browser's developer tools.
This user flow represent the case, where the Revolut app is installed on the customer's phone:
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.
token
is successfully fetched.cancel
events, and you manage every dropOffState
as intended. For more information about the cancellation events, see: Payments.revolutPay: Event object.If you implemented Revolut Pay with event listening and mobile redirect URLs:
If you implemented Revolut Pay with redirect URLs:
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.
Congratulations! You've successfully implemented Revolut Pay and are ready to accept the first payment.