Welcome to the implementation guide for Revolut Pay on Android! This page provides a comprehensive walkthrough for integrating Revolut Pay into your Android application.
From an implementation perspective, integrating Revolut Pay into your Android app with the Revolut Pay SDK involves the following components:
The SDK automatically adapts the user experience based on whether the customer has the Revolut app installed, either by switching to the app or by presenting a secure in-app web flow, requiring no conditional logic in your implementation.
token
via the Merchant API: Create an order.For more information about the order and payment lifecycle, see: Order and payment lifecycle.
Check the following high-level procedure of implementing Revolut Pay in your Android app.
Before you start this tutorial, ensure you have completed the following steps:
When your customer enters the checkout screen, your app needs a unique, single-use token
that represents the order. This token
can only be created on your server by making a secure call to the Revolut Merchant API.
This server-side endpoint is a mandatory security requirement. Your secret API key must never be exposed in your Android application.
When your checkout screen loads, your app should call this endpoint to pre-fetch the order token
. Your endpoint is responsible for:
amount
, currency
) from your client-side request.token
, in the API response.token
from the response to your app.Your app will then store this token
. When the customer taps the Revolut Pay button, this token
will be passed to the SDK to start the payment process, as shown in a later step.
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 app 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"
}
This section covers the one-time setup tasks required to prepare your Android project for the SDK.
Add the mavenCentral()
repository to your project-level build.gradle
file if it's not already there:
allprojects {
repositories {
mavenCentral()
}
}
Add the Revolut Pay Lite SDK dependency to your module-level build.gradle
file:
implementation 'com.revolut.payments:revolutpaylite:3.0.0'
Sync your project with the Gradle files.
The minimum supported Android SDK version is API 24 (Android 7.0, Nougat).
You need to declare necessary permissions and app-querying capabilities in your AndroidManifest.xml
.
Add internet permission: The SDK required network access.
<uses-permission android:name="android.permission.INTERNET" />
Declare Revolut app query: To allow the SDK to check if the Revolut app is installed, add the <queries>
element.
<queries>
<package android:name="com.revolut.revolut" />
</queries>
A deep link is required for the Revolut app to redirect the user back to your app after payment authorisation.
In your AndroidManifest.xml
, add an <intent-filter>
to the activity that will handle the result.
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="payment-return"
android:scheme="myapp" />
</intent-filter>
</activity>
myapp
and payment-return
with a unique scheme and host for your app.launchMode
must be singleTop
to ensure the existing activity instance receives the result instead of creating a new one.In your application's entry point (e.g., the onCreate
method of your Application
class), initialise the Revolut Pay SDK with your public API key.
import com.revolut.payments.RevolutPaymentsSDK
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
RevolutPaymentsSDK.configure(
RevolutPaymentsSDK.Configuration(
merchantPublicKey = "<yourPublicApiKey>",
environment = RevolutPaymentsSDK.Environment.SANDBOX // Or .PRODUCTION
)
)
}
}
This configures the SDK with the environment for testing or live payments. The deep link URI will be provided later when creating payment orders.
Now, you will add the Revolut Pay button to your UI and connect it to your backend to handle payments.
You can add the Revolut Pay button to your checkout screen either programmatically or via XML layout.
Create an instance of the button in your activity or fragment code.
import com.revolut.payments.RevolutPaymentsSDK
import com.revolut.revolutpay.api.revolutPay
import com.revolut.revolutpay.api.button.ButtonParams
import com.revolut.revolutpay.api.button.Radius
import com.revolut.revolutpay.api.button.Size
import com.revolut.revolutpay.api.button.Variant
import com.revolut.revolutpay.api.button.VariantModes
import com.revolut.revolutpay.api.button.BoxText
// ...
val buttonParams = ButtonParams(
buttonSize = Size.LARGE,
radius = Radius.MEDIUM,
variantModes = VariantModes(lightMode = Variant.DARK, darkMode = Variant.LIGHT),
boxText = BoxText.NONE
)
val revolutPayButton = RevolutPaymentsSDK.revolutPay.provideButton(
context = this,
params = buttonParams
)
// Add the button to your view hierarchy
your_layout.addView(revolutPayButton)
For more information on available parameters or customising the button's appearance, see: Revolut Pay Android SDK and Revolut Pay button guidelines.
Now, let's wire everything up. You'll create a payment controller to manage the payment process and then configure your Revolut Pay button to use this controller when tapped.
First, create an instance of the paymentController
. This component is responsible for launching the payment flow and returning the final result (success, failure, or user cancellation) to your app.
The payment controller must be created unconditionally during Activity
or Fragment
initialisation.
This ensures that the result callback is always correctly registered, and that the payment result is handled properly even after configuration changes.
import com.revolut.payments.RevolutPaymentsSDK
import com.revolut.revolutpay.api.PaymentResult
import com.revolut.revolutpay.api.order.OrderParams
import com.revolut.revolutpay.api.revolutPay
import androidx.core.net.toUri
// Create payment controller in your Activity or Fragment
private val paymentController = RevolutPaymentsSDK.revolutPay.createController(this) { paymentResult ->
when (paymentResult) {
is PaymentResult.Success -> {
// Payment successful - update your UI
showSuccessMessage()
}
is PaymentResult.UserAbandonedPayment -> {
// User cancelled the payment
showCancelledMessage()
}
is PaymentResult.Failure -> {
// Payment failed - show error and log details
showErrorMessage()
Log.e("RevolutPay", "Payment failed", paymentResult.exception)
}
}
}
With the controller ready, you can configure the button's behaviour, also within onCreate()
or onViewCreated()
. This involves two main actions:
token
from your backend and starting the payment.// In your Activity/Fragment's onCreate() or onViewCreated()
// 1. Bind the button's loading state to payment controller's state
revolutPayButton.bindPaymentState(paymentController, this)
// 2. Set the button's click listener to start the payment
revolutPayButton.setOnClickListener {
// Show a loading indicator on the button
revolutPayButton.showBlockingLoading(true)
viewLifecycleOwner.lifecycleScope.launch {
try {
// Call your backend to get the order token
val orderToken = fetchOrderTokenFromBackend()
// Start the payment with the token (and other order details)
paymentController.pay(
OrderParams(
orderToken = orderToken,
returnUri = "myapp://payment-return".toUri(), // Must match your deep link from step 2.3
requestShipping = false, // Set to true for Fast checkout
savePaymentMethodForMerchant = false, // Set to true for MIT
customer = null // Optional customer details
)
)
} catch (e: Exception) {
// Handle any errors during the token fetch
revolutPayButton.showBlockingLoading(false)
showErrorMessage()
}
}
}
fetchOrderTokenFromBackend()
In the code above, fetchOrderTokenFromBackend()
is a placeholder for a function you must create. THis function's job is to make a network request to the server endpoint you built in Step 1. It sends the order details (like amount and currency) to your server and gets the unique order token
in return.
Because this is a network operation it must performed off the main UI thread. The example uses lifecycleScope.launch
(a Kotlin Coroutine) to handle this, which is the recommended approach.
After the customer authorises the payment (either in the Revolut app or the web flow), they are redirected back to your app using the deep link you configured in Step 2.3.
You need to catch this redirect in the Activity
you configured with the <intent-filter>
. To do this, override the onNewIntent()
method and pass the incoming URI to the Revolut Pay SDK for processing.
import com.revolut.payments.RevolutPaymentsSDK
import com.revolut.revolutpay.api.revolutPay
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.let { uri ->
// Pass the deep link URI to the Revolut Pay SDK for processing
RevolutPaymentsSDK.revolutPay.handle(uri)
}
}
The SDK processes this URI internally. You don't need to parse it yourself. The final PaymentResult
is automatically delivered to the paymentController
callback you configured in Step 4.2.
The callbacks are executed on the main thread. For network operations like creating an order, ensure you switch to a background thread (e.g., using Kotlin Coroutines).
While the PaymentResult
callback is useful for updating your app's UI (e.g., showing a "Thank you" screen), you must never use it to confirm the payment or fulfill the order.
The delivery of the client-side result isn't guaranteed; a user could lose network connection or close the app before the callback fires. Your server-to-server webhooks are the only guaranteed source of truth for the payment status.
Set up a webhook listener on your server. Always wait for a successful payment event webhook (e.g., ORDER_COMPLETED
) before capturing funds, shipping goods, or granting access to services.
For more information, see: Use webhooks to keep track of the payment lifecycle.
You can enable additional features to enhance the customer experience and boost your conversion rates.
You can configure the payment session to either collect customer shipping details or save their payment method future use. These options are configured in the OrderParams
when you initiate the payment.
requestShipping = true
in your OrderParams
. This requires your backend to support the Fast checkout flow. See the Fast Checkout guide for details.savePaymentMethodForMerchant = true
in your OrderParams
. See our guide on charging a saved payment method for details.Fast checkout and MIT are mutually exclusive. You can enable one or the other, but not both in the same transaction.
You can add a promotional banner to your order confirmation or "thank you" screen to offer rewards to new customers who sign up for a Revolut account after checkout.
We recommend implementing the promotional banner. Analysis has shown that having the widget can increase conversion to payment by ~5%.
To add the banner, call the RevolutPaymentsSDK.revolutPay.providePromotionalBannerWidget(context: Context, params: PromoBannerParams)
method. This requires PromoBannerParams
, which includes the transactionId
from your server.
// In your order confirmation Activity (e.g., OrderConfirmationActivity.kt)
import com.revolut.revolutpay.api.promobanner.Currency
import com.revolut.revolutpay.api.promobanner.Customer
import com.revolut.revolutpay.api.promobanner.DateOfBirth
import com.revolut.revolutpay.api.promobanner.PromoBannerParams
import com.revolut.revolutpay.api.promobanner.CountryCode
import com.revolut.payments.RevolutPaymentsSDK
import com.revolut.revolutpay.api.revolutPay
class OrderConfirmationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order_confirmation)
// 1. Get the order token you passed to this activity after a successful payment.
// This is the `token` from the order object you received from your server in Step 1.
val orderIdFromServer = intent.getStringExtra("ORDER_ID") ?: ""
// 2. Define the parameters for the banner.
val bannerParams = PromoBannerParams(
transactionId = orderIdFromServer,
paymentAmount = 1000, // Amount in the smallest currency unit (e.g., cents)
currency = Currency.EUR,
customer = Customer(
name = "Example Customer",
email = "example.customer@email.com",
phone = "+441234567890",
dateOfBirth = DateOfBirth(_day = 1, _month = 2, _year = 2000),
country = CountryCode.GB,
)
)
// 3. Create the banner widget.
val promotionalBanner = RevolutPaymentsSDK.revolutPay.providePromotionalBannerWidget(
context = this,
params = bannerParams
)
// 4. Add the banner to your layout.
// Assuming you have a LinearLayout with id 'banner_container' in your XML file.
val bannerContainer = findViewById<LinearLayout>(R.id.banner_container)
bannerContainer.addView(promotionalBanner)
}
}
To apply a custom theme to the banner, first define a style in a resource file:
<style name="RevolutPay_RevolutPayBanner">
<item name="revolutPay_ColorAccent">#0666EB</item>
<item name="revolutPay_ColorBackground">#F7F7F7</item>
<item name="revolutPay_BannerCornerRadius">12dp</item>
<item name="revolutPay_ComponentCornerRadius">12dp</item>
<item name="revolutPay_StrokeWidth">0dp</item>
<item name="revolutPay_StrokeColor">#BBC4CD</item>
</style>
Then, reference this theme when creating the widget:
val promotionalBanner = RevolutPaymentsSDK.revolutPay.providePromotionalBannerWidget(
context = this,
params = bannerParams,
themeId = R.style.RevolutPay_RevolutPayBanner
)
For a complete, working implementation, please refer to the example app included in our public SDK repository.
To run the example app:
The Sandbox environment is designed to replicate the production environment's behaviour, with the key difference being the absence of app redirection due to the lack of a sandbox version of the Revolut retail app.
For more information about Revolut Pay payment flows in Sandbox, see: Test flows.
Before going live, use this checklist to test your integration in both the SANDBOX
and MAIN
(production) environments.
mavenCentral()
is in build.gradle
.AndroidManifest.xml
: INTERNET
permission is present.AndroidManifest.xml
: <queries>
tag is present.AndroidManifest.xml
: Deep link <intent-filter>
is configured with a unique scheme/host.launchMode="singleTop"
.token
.PaymentResult.Success
callback.PaymentResult.Failure
callback.PaymentResult.UserAbandonedPayment
callback.onNewIntent
method.ORDER_COMPLETED
webhook.