Tap to Pay on Android

Install the separate Android Tap to Pay SDK, authenticate, and process contactless payments on a phone.

Tap to Pay on Android uses a separate SDK from the Bluetooth reader SDK. Its dependencies, authentication, payment events, and device security requirements are specific to this package.

Prerequisites

  • Kotlin version: 2.0 or later (2.2.x recommended; required for SDK 1.1.6+)
  • minSDK: 30 or later
  • targetSDK/compileSDK: 36 or later
  • Android Gradle Plugin: 7.3.0 or later
  • Java 17 or later
  • Core library desugaring(Opens in a new tab) enabled (required since SDK version 1.1.1)
  • Maven repository credentials from SumUp. Request SDK access before starting the integration.
  • A Secret API key, if not using OAuth 2.0 authentication. You can generate a Secret API key on the API keys page(Opens in a new tab) in the SumUp Dashboard.

Hardware requirements

  • NFC-enabled Android device (emulators are not supported)
  • Android 11 or later

Integration

You can use the sample app provided in the GitHub repository(Opens in a new tab) as a reference.

Note

If your app shuts down immediately upon start, try disconnecting your phone and launching the app from your phone and not your IDE.

Dependencies

  1. Add the following to the Gradle dependencies:

    allprojects {
        repositories {
            maven {
                url = uri("https://maven.sumup.com/releases")
            }
            maven {
                url = uri("https://tap-to-pay-sdk.fleet.live.sumup.net/")
                credentials {
                    username = "your_username"  // The maven credentials are provided by SumUp
                    password = "your_password"
                }
            }
            google()
            mavenCentral()
        }
    }
  2. Add the dependency to a module build.gradle:

    implementation("com.sumup.tap-to-pay:utopia-sdk:1.1.6")
  3. Starting from SDK version 1.1.1, core library desugaring(Opens in a new tab) is required. Add the following to your module build.gradle:

    android {
        compileOptions {
            isCoreLibraryDesugaringEnabled = true
        }
    }
    
    dependencies {
        coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
    }

Authentication

Your app authenticates the merchant. Implement AuthTokenProvider to return the access token, then pass it to init. The SDK does not display a login screen.

interface AuthTokenProvider {
    fun getAccessToken(): String
}

Choose one of these authentication methods:

  1. Using the OAuth 2.0 flow: Use the authorization code flow to obtain a merchant access token. Return it from AuthTokenProvider.getAccessToken(). This is the recommended approach.

  2. Using API Key: Create a key on the API keys page(Opens in a new tab) in the SumUp Dashboard. Return it from AuthTokenProvider.getAccessToken().

⚠️ Important: Keep API keys in secure storage. Do not embed them in your app binary, commit them to source control, or include them in logs.

Using API

The TapToPay interface provides methods to interact with the SDK. To get an implementation of the TapToPay interface, call:

val tapToPay = TapToPayApiProvider.provide(applicationContext)

Use your Android application context for applicationContext.

The TapToPay interface has the following methods:

1. Initialization

suspend fun init(authTokenProvider: AuthTokenProvider): Result<Unit>

The init method initializes the session. The AuthTokenProvider interface is responsible for providing the access token to the SDK (see Authentication). Call init once per app lifecycle, as early as possible after your app starts.

Check the returned Result before starting a payment. Initialization returns Result.Success when the session is ready, or Result.Failure with an exception.

2. Start payment

fun startPayment(
    checkoutData: CheckoutData,
    skipSuccessScreen: Boolean = false,
    timeoutCardWaitSeconds: Int? = null,
): Flow<PaymentEvent>

The startPayment method initiates the payment process. It returns a Flow that emits PaymentEvent or throws an exception with an error message.

Handle the payment events listed in the Tap to Pay event reference, including success, cancellation, failure, and unknown results.

Parameters

checkoutData - The checkout data object.

skipSuccessScreen - When true, the SDK closes the payment flow immediately after a successful transaction without showing the built-in success screen. The consumer app is then responsible for displaying its own success UI. Defaults to false.

timeoutCardWaitSeconds - Maximum time (in seconds) the SDK waits for the cardholder to present a card during the “Tap card” step (SDK 1.1.5+). When null (the default), the SDK uses its built-in default of 240 seconds. Values are clamped to the supported range [60, 240] seconds.

data class CheckoutData(
    val totalAmount: Long,
    val tipsAmount: Long?,
    val vatAmount: Long?,
    val clientUniqueTransactionId: String,
    val customItems: List<CustomItem>?,
    val priceItems: List<PriceItem>?,
    val products: List<ProductModel>?,
    val processCardAs: ProcessCardAs?,
    val affiliateData: AffiliateModel?
) : Serializable

Where:

  • totalAmount - The amount expressed in the minor unit of the currency. Total amount includes tip amount and VAT amount.
  • tipsAmount - The amount of tips expressed in the minor unit of the currency. The total already includes the tip. Ignored if null.
  • vatAmount - The amount of VAT expressed in the minor unit of the currency. The total already includes VAT. Ignored if null.
  • clientUniqueTransactionId - A unique identifier for this payment attempt. You can use a random UUID.
  • customItems - The list of custom items. Set null if not used.
  • priceItems - The list of price items. Set null if not used.
  • products - The list of product items. Set null if not used.
  • processCardAs - The type of the card processing. The default value is null. The possible values are ProcessCardAs.Credit(val instalments: Int) and ProcessCardAs.Debit, where instalments is the number of installments. This parameter is optional and only applicable to some markets, such as Brazil, where the card type selection and installments are supported.
  • affiliateData - The affiliate data refers to tracking and attributing transactions to specific affiliates, integrators, or referral sources.

Amount format: Provide amounts in the minor units expected by this SDK: These currencies use two decimal places: AUD, BGN, BRL, CHF, CLP, COP, CZK, DKK, EUR, GBP, HRK, HUF, NOK, PEN, PLN, RON, SEK, USD.

For example, an amount of $12.34 corresponds to a value of 1234, $11.00 corresponds to a value of 1100.

HUF, CLP, and COP: Although these amounts are displayed without decimal places, this SDK still expects you to multiply them by 100.

For example, Ft100 should be provided as 10000.

The AffiliateModel data type has the following parameters:

data class AffiliateModel(
    val key: String,
    val foreignTransactionId: String? = null,
    val tags: Map<String, String>? = null
) : Serializable

Where:

  • key - The primary identifier for the affiliate or integrator partner.
  • foreignTransactionId - An optional reference to an external transaction ID (from the integrator’s system).
  • tags - Flexible key-value pairs for additional metadata about the transaction or affiliate.

Create a payment with the required fields:

fun startPayment() {
    tapToPay.startPayment(
        checkoutData = CheckoutData(
            totalAmount = 1234, // 12.34 EUR
            clientUniqueTransactionId = "123",
            tipsAmount = null,
            vatAmount = null,
            customItems = null,
            priceItems = null,
            processCardAs = null,
        ),
    ).collectLatest {
        Log.d("Payment event: $it")
    }
}

To customize the payment flow behavior:

fun startPaymentCustomized() {
    tapToPay.startPayment(
        checkoutData = CheckoutData(
            totalAmount = 1234, // 12.34 EUR
            clientUniqueTransactionId = "123",
            tipsAmount = null,
            vatAmount = null,
            customItems = null,
            priceItems = null,
            processCardAs = null,
        ),
        skipSuccessScreen = true,
        timeoutCardWaitSeconds = 120, // wait 2 minutes for card tap
    ).collectLatest {
        Log.d("Payment event: $it")
    }
}

3. Tear down

suspend fun tearDown(): Result<Unit>

Call tearDown when the user logs out or your app closes the SDK session. It logs out the merchant, clears keys and other sensitive data, and returns a Result indicating success or failure.

Testing the SDK

SDK v1.1.0 and later supports sandbox merchant accounts. Create a sandbox merchant, then configure OAuth 2.0(Opens in a new tab) or create an API key(Opens in a new tab) in that sandbox merchant. Provide its token through AuthTokenProvider.

Follow the shared payment testing guide for successful payments and simulated declines. The device and build requirements below are specific to the Android Tap to Pay SDK.

Sandbox merchant accounts allow debugging without moving real funds. Live payments require a build that is not debuggable, with USB debugging and developer options disabled on the device.

Use a live merchant account only when you are ready to process real payments.

Before testing in production, make sure of the following:

  1. Build the app with debugging disabled. Otherwise, the SDK’s device security check fails and prevents payments.

       buildTypes {
           release {
               isDebuggable = false
           }
       }
  2. You have USB debugging disabled on your device. Even if you install the app through a cable, disable the USB debugging after installation.

  3. You have Developer Mode disabled on your device. Even if you install the app through a cable, disable the Developer Mode after installation.

On some devices (e.g. Samsung), you still have to disable USB debugging before disabling the Developer Mode. It is still possible to have a USB debugging enabled and Developer Mode disabled, but it depends on the device manufacturer.

Testing in specific regions

Brazil and Chile

To test the Android Tap-to-Pay SDK in Brazil and Chile, you should set processCardAs for each transaction. For example:

tapToPay.startPayment(
    CheckoutData(
        totalAmount = amount,
        clientUniqueTransactionId = UUID.randomUUID().toString(),
        tipsAmount = null,
        vatAmount = null,
        customItems = null,
        products = null,
        priceItems = null,
        processCardAs = ProcessCardAs.Credit(instalments = 5), // or ProcessCardAs.Debit
        affiliateData = null
    )
)

Additionally, PIN verification for transactions is mandatory for these countries. However, the PIN entry screen will appear only if Developer Options are disabled on the device - otherwise, the transaction will fail with an error.

Next step

Look up Tap to Pay exceptions and error codes.

Type to search…