Android SDK

Learn how to use the Android version of our mobile SDK to quickly integrate our Payments product into your app.

With the TrueLayer SDK for Android, you can quickly add a user interface to your app to initiate an open banking payment, or to create a recurring payment mandate. This page guides you through how to set up and use the TrueLayer SDK in your Android app.

1920

Image of what the native screens look like on an Android device. There are two screenshots in the image, one shows the bank selection screen and the other shows the payment confirmation screen.

The Android SDK presents native screens that allows your users to select their bank and consent to the payment or mandate. Then, it redirects them to their banking app or website to authorise the payment or mandate. It also handles network requests and errors and gives you some options to customise the user interface.

Compatibility

The SDK is designed to work with Android 7.0 (API level 24) and above. It is currently optimised for the UK, Ireland, France, Germany, Spain, the Netherlands, and Lithuania. Beta testers can also use the Android SDK for banks in Austria, Belgium, Finland, Poland and Portugal. The user interface can be displayed in English, Spanish, French, German, Dutch, Portuguese, Polish, Finnish and Lithuanian.

Payment journey

  1. The user selects to pay through an Instant Bank Payment, or to set up a recurring payment mandate.
  2. Your backend creates a payment or mandate with TrueLayer and receives back a token.
  3. Your app uses the id and resource_token and initialises the SDK.
  4. Your user selects and confirms their bank on the screen of the mobile SDK. If the bank needs additional information from the user, such as IBAN or branch, then the SDK asks the user to enter these.
  5. For banks that use a redirect flow, the mobile SDK redirects your user to their bank's website or app. Your user authorises the payment in their bank's website or app.
  6. For banks that use an embedded flow, the mobile SDK collects the user's credentials and handles the strong customer authentication in order to authorise the payment.
  7. Once the authorisation is complete, the user is redirected to your redirect_url. You can then confirm the result to your user.
2441

Image containing a diagram that shows the payment journey with mobile SDK integration.

Before you begin

Before you can use the SDK, you'll need to register a redirect_uri in our developer console. Your user will be redirected back to your website or application at the end of the payment journey. This redirect_uri defines where they will be redirected to.

Step 1: Install the SDK

Before you add the SDK to our project, make sure that your build.gradle configuration is configured to support minSdk 26 (Android 8.0) or higher as well the packagingOptions below:

android {
    defaultConfig {
        applicationId "com.example.myapp"
        minSdk 26 // Sdk 26 or higher supported
    }


    packagingOptions {
        resources {
            pickFirsts += ['META-INF/LICENSE-MIT']
        }
    }
}

To add the SDK to your project, simply include TrueLayer Payments SDK to your dependencies.

// Add to your projects `build.gradle`.
implementation "com.truelayer.payments:ui:2.0.0"

Step 2: Initialise the SDK

To use the SDK, you have to first initialise it before invoking any other SDK method. The following code sample is an example of initialising the SDK:

import com.truelayer.payments.core.domain.configuration.Environment
import com.truelayer.payments.core.domain.configuration.HttpConnectionConfiguration
import com.truelayer.payments.core.domain.configuration.HttpLoggingLevel
import com.truelayer.payments.ui.TrueLayerUI

// Initialize the SDK with your application context.
TrueLayerUI.init(context = applicationContext) {
    // optionally choose which environment you want to use: PRODUCTION or SANDBOX
    environment = Environment.PRODUCTION
    // Make your own custom http configuration, stating custom timeout and http request logging level
    httpConnection = HttpConnectionConfiguration(
        httpDebugLoggingLevel = HttpLoggingLevel.None
    )
}

If you are using AppStartup

If you are using AppStartup you will need to add WorkManagerInitializer to your dependencies:

import androidx.work.WorkManagerInitializer
(...)
override fun dependencies(): List<Class<out Initializer<*>>> {
    return listOf(WorkManagerInitializer::class.java)
}

Step 3: Process a payment

Depending on your preferred approach, you can choose to integrate the SDK into your payment process with either AndroidX Activity, with the older Android Activity or with Jetpack Compose.

In both cases, you'll need the id and the resourceToken obtained from the backend. The fields mentioned earlier will be passed to the SDK via PaymentContext or MandateContext. The following kdoc explains all the available parameters:

/**
 * ProcessorContext to identify payment or mandate to be processed
 * [Payment API](https://docs.trulayer.com/)
 *
 * @property id payment or mandate identifier obtained from
 *                     create payment API [crate payment API](https://docs.truelayer.com/reference/create-payment)
 *                     or create mandate API [crate payment API](https://docs.truelayer.com/reference/create-mandate)
 * @property resourceToken resource token obtained from
 *                     create payment API [crate payment API](https://docs.truelayer.com/reference/create-payment)
 *                     or create mandate API [crate payment API](https://docs.truelayer.com/reference/create-mandate)
 * @property redirectUri uri that will be invoked once payment authorization is completed
 */
@Parcelize
sealed class ProcessorContext : Parcelable {
    abstract val id: String
    abstract val resourceToken: String
    abstract val redirectUri: String

    /**
     * PaymentContext to identify payment to be processed
     * [Payment API](https://docs.trulayer.com/)
     *
     * @property id payment identifier obtained from
     *                     create payment API [crate payment API](https://docs.truelayer.com/reference/create-payment)
     * @property resourceToken resource token obtained from
     *                     create payment API [crate payment API](https://docs.truelayer.com/reference/create-payment)
     * @property redirectUri uri that will be invoked once payment authorization is completed
     * @property preferences object containing various preferences that allow to override default SDK
     *                     behaviour in certain aspects.
     */
    @Parcelize
    data class PaymentContext(
        override val id: String,
        override val resourceToken: String,
        override val redirectUri: String,
        val preferences: PaymentPreferences? = null
    ) : ProcessorContext()

    /**
     * MandateContext to identify mandate to be processed
     * [Payment API](https://docs.trulayer.com/)
     *
     * @property id mandate identifier obtained from
     *                     create mandate API [crate payment API](https://docs.truelayer.com/reference/create-mandate)
     * @property resourceToken resource token obtained from
     *                     create mandate API [crate payment API](https://docs.truelayer.com/reference/create-mandate)
     * @property redirectUri uri that will be invoked once payment authorization is completed
     * @property preferences object containing various preferences that allow to override default SDK
     *                     behaviour in certain aspects.
     */
    @Parcelize
    data class MandateContext(
        override val id: String,
        override val resourceToken: String,
        override val redirectUri: String,
        val preferences: MandatePreferences? = null
    ) : ProcessorContext()

    /**
     * Preferences class that allows to set preferences and override default SDK behaviour
     * in certain aspects.
     *
     * @property preferredCountryCode (optional) in case there are available payment providers from
     *                      multiple countries the SDK will try select most appropriate one automatically.
     *                      You may want to override that behaviour.
     *                      By setting the preferredCountryCode (ISO 3166-1 alpha-2, example: "GB" or "FR"),
     *                      the SDK will try first to select preferred country before falling back to the
     *                      auto selection.
     * @property paymentUseCase allows to override the default use case. The result of the override will cause
     *                      the text on the payment review screen to update accordingly
     */
    @Parcelize
    data class PaymentPreferences(
        val preferredCountryCode: String? = null,
        val paymentUseCase: PaymentUseCase = PaymentUseCase.default
    ) : Parcelable {
        internal fun intoInternalPreferences() =
            com.truelayer.payments.ui.models.PaymentContext.Preferences(
                preferredCountryCode = preferredCountryCode,
                paymentUseCase = paymentUseCase
            )
    }

    /**
     * Preferences class that allows to set preferences and override default SDK behaviour
     * in certain aspects.
     *
     * @property preferredCountryCode (optional) in case there are available mandate providers from
     *                      multiple countries the SDK will try select most appropriate one automatically.
     *                      You may want to override that behaviour.
     *                      By setting the preferredCountryCode (ISO 3166-1 alpha-2, example: "GB" or "FR"),
     *                      the SDK will try first to select preferred country before falling back to the
     *                      auto selection.
     */
    @Parcelize
    data class MandatePreferences(
        val preferredCountryCode: String? = null
    ) : Parcelable {
        internal fun intoInternalPreferences() =
            com.truelayer.payments.ui.models.PaymentContext.Preferences(
                preferredCountryCode = preferredCountryCode
            )
    }
}

Option 1: Process a payment with an AndroidX Activity integration

import android.widget.Toast
import com.truelayer.payments.ui.screens.processor.ProcessorContext.PaymentContext
import com.truelayer.payments.ui.screens.processor.ProcessorActivityContract
import com.truelayer.payments.ui.screens.processor.ProcessorResult

// Register for the end result.
val contract = ProcessorActivityContract()
val flowResult = registerForActivityResult(contract) {
    val text = when (it) {
        is ProcessorResult.Failure -> {
            "Failure ${it.reason}"
        }
        is ProcessorResult.Successful -> {
            "Successful ${it.step}"
        }
    }
    // present the final result
    Toast.makeText(this, text, Toast.LENGTH_LONG).show()
}

// Obtain your payment context from your backend
val paymentContext = PaymentContext(
    id =  "your-payment-identifier",
    resourceToken = "payment-resource-token",
    redirectUri = "redirect-uri-that-will-be-invoked-when-coming-back-from-bank"
)

// 🚀 Launch the payment flow.
flowResult.launch(paymentContext)

Option 2: Process a payment with an Android Activity integration

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import com.truelayer.payments.core.domain.configuration.Environment
import com.truelayer.payments.ui.TrueLayerUI
import com.truelayer.payments.ui.screens.processor.ProcessorContext.PaymentContext
import com.truelayer.payments.ui.screens.processor.ProcessorActivityContract
import com.truelayer.payments.ui.screens.processor.ProcessorResult

class ActivityIntegration : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Initialise the payments configuration
        TrueLayerUI.init(context = applicationContext) {
            environment = Environment.PRODUCTION
            httpConnection = HttpConnectionConfiguration(
                httpDebugLoggingLevel = HttpLoggingLevel.None
            )
        }
        
        // Obtain your payment context from your backend
        val paymentContext = PaymentContext(
            id =  "your-payment-identifier",
            resourceToken = "payment-resource-token",
            redirectUri = "redirect-uri-that-will-be-invoked-when-coming-back-from-bank"
        )
        // Create an intent to launch the SDK
        val intent = ProcessorActivityContract().createIntent(this, paymentContext)
        // 🚀 Launch the SDK
        startActivityForResult(intent, 0)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        // Act on the SDK result
        val text = when (val result = ProcessorResult.unwrapResult(data)) {
            is ProcessorResult.Failure -> "Failure ${result.reason}"
            is ProcessorResult.Successful -> "Successful ${result.step}"
            null -> "Activity result failed."
        }
        Toast.makeText(this, text, Toast.LENGTH_LONG).show()
    }
}

Option 3: Process a payment with a Jetpack Compose integration

The following example shows how to process a payment with a Jetpack Compose integration:

// Your payments custom theme or use the provided defaults as below
val theme = TrueLayerTheme(
    lightPalette = LightColorDefaults,
    darkPalette = DarkColorDefaults,
    typography = TypographyDefaults
)

// Obtain your payment context from your backend
val paymentContext = PaymentContext(
    id =  "your-payment-identifier",
    resourceToken = "payment-resource-token",
    redirectUri = "redirect-uri-that-will-be-invoked-when-coming-back-from-bank"
)

setContent {
    Theme(
        theme = theme
    ) {
        Processor(
            context = paymentContext,
            onSuccess = { successStep ->
                // action on success
            },
            onFailure = { failureReason ->
                // action on failure
            },
        )
    }
}

Step 4: Confirm the result

Processor results - success or error

Regardless of the integration route you are going to select in the end you will be presented with a result. It can be success or error.

In case of error you will have a failure reason. It will be one of the following:

  • NoInternet There was a problem connecting to the internet. Most likely when the user is not connected to the internet. It can also be caused by a network configuration, if no route to TrueLayer services can be established.
  • UserAborted When the user purposefully aborted the payment process.
  • UserAbortedFailedToNotifyBackend User aborted but the application failed to notify the backend, or the backend responded with an error.
  • CommunicationIssue Communication issue. When something goes wrong with the communication with the backend. It may be an invalid payment token, or just the payment service responding in unexpected manner.
  • ConnectionSecurityIssue This will be returned when secure a connection to payment services could not be established.
  • PaymentFailed When the payment is in failed state. A recovery from this state is not possible.
  • WaitAbandoned The user purposefully abandoned the payment flow at the point when the flow was awaiting authorization from the bank. The authorization from the bank may or may not depend on a user action, and the payment may still complete.
  • WaitTokenExpired The authorization waiting time was so long that the resource token has expired. We are no longer able to query the payment status.
  • ProcessorContextNotAvailable A technical failure. It was not possible to read the ProcessorContext in which you've set the payment/mandate id and resource_token. If you see this error, check that you're following the steps in this guide correctly, or raise a support ticket with us if you need further help.
  • Unknown The SDK was unable to identify a reason for the failure.

When a success result is presented, it means that the SDK has completed its role in initiating the payment. However, it doesn't necessarily mean that the payment itself is complete. To help understand the context in which the SDK completes its task, it also provides the step at which the flow completed:

  • Redirect When the user was redirected to their bank to authorize the payment.
  • Wait When the SDK flow is complete, but a decoupled authorisation action is still pending with the user and/or the bank.
  • Authorized When the user authorized the payment with the bank.
  • Successful When the bank has confirmed that they have accepted the payment request, and they intend to execute it.
  • Settled When the funds have reached the destination.

Before acting on the result of the SDK completing its process, it's important to understand the status of the payment or mandate.

Get the payment status

As a best practice, we recommend that you obtain the payment status or mandate status from your backend through webhook notifications.

However, the SDK also offers functions to fetch the current status of a payment or mandate.

val outcome = TrueLayerUI.getPaymentStatus("your-payment-identifier", "payment-resource-token")
val outcome = TrueLayerUI.getMandateStatus("your-mandate-identifier", "mandate-resource-token")

For more details on these statuses, see the API docs for payments and API docs for mandates.

Handle Redirects from the bank

At the end of a redirect flow the bank app will relaunch your app with the redirect-uri you provided on the console.

In your activity that's launched when a deep link is triggered you can fetch the redirect parameters with the convenience method below.

val params = intent.data.extractTrueLayerRedirectParams()

This can include either the payment_id or mandate_id. For more information on handling deep links you can follow the documentation here.

Customise colours and styles

It is possible to customise the colours and typography of the SDK. The customisation process depends on the chosen integration type.

When integrating via Jetpack Compose

When integrating via Jetpack Compose, to change colours or typography, you have to provide your own version of LightColorDefaults, DarkColorDefaults and TypographyDefaults. You can use .copy() to override just a single colour. For example, define your theme as in the example below, and include this with your code to process the payment (see Step 3 above).

TrueLayerTheme(
    lightPalette = LightColorDefaults.copy(primary = Color.Red),
    darkPalette = DarkColorDefaults.copy(primary = Color.DarkGray),
    typography = TypographyDefaults
)

To find a full list of colours you can override, you can check out the details of androidx.compose.material.Colors in the Android reference documentation.

Material You

The SDK supports Material You and dynamic colours. This can be enabled when creating a theme as below:

TrueLayerTheme(
    lightPalette = LightColorDefaults.copy(primary = Color.Red),
    darkPalette = DarkColorDefaults.copy(primary = Color.DarkGray),
    dynamicColorsEnabled = true
)

Material 3 migration

The SDK now uses Material 3 components and themes. If you're using Material 2, to customise the theme of the SDK you'll need to make the following changes.

  1. Add the Compose Material 3 library
// Add to your projects `build.gradle`.
implementation "androidx.compose.material3:material3:1.0.0-alpha15"
  1. Update the names of colours and typography styles you are overriding by following the migration guide here
  2. If you're overriding shapes, you'll need to replace androidx.compose.material.Shapes with androidx.compose.material3.Shapes.

When the theme is not explicit

You may choose to integrate the SDK using Theme without parameters. Like this:

setContent {
    Theme {
        Processor(
            ...
        )
    }
}

In this case SDK will read all styles from the Context of the enclosing Activity. Please make sure that the enclosing activity style is based on the NoActionBar variant.

When integrating via Android Activity or AndroidX Activity

When integrating via Android Activity or AndroidX Activity, you can define styles of the SDK views using XML files. You need to override style TrueLayerPaymentsTheme as this is used by the SDK to style internal Activities. This can be done by defining style in your theme.xml file.

<style name="TrueLayerPaymentsTheme" parent="TrueLayerPaymentsParentTheme">
    <!-- Primary brand color. -->
    <item name="colorPrimary">@android:color/holo_red_light</item>
    <item name="colorPrimaryVariant">@android:color/holo_red_dark</item>
    <!-- Text colors -->
    <item name="android:textColorPrimary">@color/textColorPrimary</item>
    <item name="android:textColorSecondary">@color/textColorSecondary</item>
    <!-- Status bar -->
    <item name="android:statusBarColor">?attr/colorPrimary</item>
    <item name="android:windowLightStatusBar" tools:targetApi="m">true</item>
</style>

Although it is possible to set your application theme as a parent, we recommended you use the TrueLayerPaymentsParentTheme as your parent to keep up with any SDK updates. If you're using your application theme, make sure that the parent of the TrueLayerPaymentsTheme is the NoActionBar variant.

Remember to assign this style to your activity in the AndroidManifest.xml file.

<activity android:name=".screens.examples.ActivityXIntegration"
            android:theme="@style/TrueLayerPaymentsTheme"/>

Migrating from version 1.x.x of the SDK

If you're migrating to version 2.x.x from version 1.x.x of the SDK, you need to update your project for the following changes.

If you were processing a payment with a Jetpack Compose integration please remove the following code from your activity hosting. This code was required in previous versions, but will now cause user interface issues.

WindowCompat.setDecorFitsSystemWindows(window, false) 
 @Suppress("DEPRECATION")
 window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)

There are some naming changes. The following classes and functions have been moved:

  • com.truelayer.payments.ui.models.PaymentContext -> com.truelayer.payments.ui.screens.processor.ProcessorContext.PaymentContext
  • com.truelayer.payments.ui.screens.coordinator.FlowCoordinatorResult -> com.truelayer.payments.ui.screens.processor.ProcessorResult
    ** com.truelayer.payments.ui.screens.coordinator.FlowCoordinatorActivityContract -> com.truelayer.payments.ui.screens.processor.ProcessorActivityContract
  • com.truelayer.payments.ui.screens.coordinator.FlowCoordinatorActivity -> com.truelayer.payments.ui.screens.processor.ProcessorActivity
  • com.truelayer.payments.ui.screens.coordinator.FlowCoordinator -> com.truelayer.payments.ui.screens.processor.Processor
  • com.truelayer.payments.ui.models.PaymentUseCase -> com.truelayer.payments.ui.screens.processor.PaymentUseCase
  • com.truelayer.payments.ui.models.PaymentContext.Preferences -> com.truelayer.payments.ui.screens.processor.ProcessorContext.PaymentPreferences
  • PaymentContext.paymentId -> PaymentContext.id

The SDK now uses Kotlin version 1.7.0 and a minimum SDK version 26 (previously it was 24)

The SDK has removed its dependency on com.google.android.material:material, replacing it with androidx.compose.material3:material3.

The dependency on com.google.accompanist:accompanist-insets has been removed.