Create payments or mandates with the Android SDK

Learn how to configure the Android SDK with the Payments API v3 to create payments or mandates.

📘

Differences between configuring payments and mandates

The integration process for payments and mandates with the Android SDK is largely the same. Where applicable, code blocks on this page have an adjusted version for each of them.

Before you begin

Before you can use the SDK, you'll need to register a redirect_uri in Console. Your user is redirected back to your redriect_uri, typically your website or application, at the end of the payment or mandate journey.

Configuration overview

There are four steps to configuring the Android SDK with the Payments API v3:

  1. Install the SDK.
  2. Initialise the SDK.
  3. Process a payment or mandate.
    As part of this, you have to select one of three integrations to process the payment or mandate:
    • AndroidX activity
    • Android activity
    • Jetpack compose
  4. Confirm the result.

Install the SDK

Before you add the SDK to your project, ensure your build.gradle configuration is configured to support minSdk 24 (Android 7.0) or higher, and configure the packagingOptions below:

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


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

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

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

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
    )
}

Initialise with AppStartup

If you are using AppStartup for your integration, you need to also add WorkManagerInitializer to your dependencies:

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

Process a payment

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

Whichever approach you use, you need to provide the id and the resourceToken obtained from the backend at payment creation. You pass the id and resourceToken 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
            )
    }
}

AndroidX Activity integration

This example shows how to 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)

Android Activity integration

This example shows how to 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()
    }
}

Jetpack Compose integration

This 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
            },
        )
    }
}

Confirm the result

Whichever integration route you select, the payment presents you with a result at the end. It can be success or error.

Error results

A payment can fail due to the following causes.

Payment resultDescription
NoInternetThere 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.
UserAbortedWhen the user purposefully aborted the payment process.
UserAbortedFailedToNotifyBackendUser aborted but the application failed to notify the backend, or the backend responded with an error.
CommunicationIssueCommunication 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.
ConnectionSecurityIssueThis will be returned when secure a connection to payment services could not be established.
PaymentFailedWhen the payment is in failed state. A recovery from this state is not possible.
WaitAbandonedThe 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.
WaitTokenExpiredThe authorization waiting time was so long that the resource token has expired. We are no longer able to query the payment status.
ProcessorContextNotAvailableA 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.
UnknownThe SDK was unable to identify a reason for the failure.

Success results

When a success result is presented, it means 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 where the SDK completed its task, it also provides the step at which the flow completed:

Payment resultDescription
RedirectWhen the user was redirected to their bank to authorize the payment.
WaitWhen the SDK flow is complete, but a decoupled authorisation action is still pending with the user and/or the bank.
AuthorizedWhen the user authorized the payment with the bank.
SuccessfulWhen the bank has confirmed that they have accepted the payment request, and they intend to execute it.
SettledWhen the funds have reached the destination.

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

Get the payment or mandate 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 launches 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.