Tap to Pay on Android

Moov's Tap to Pay technology allows merchants to accept contactless payments using an Android device's built-in NFC capabilities. Customers can tap their contactless card or mobile wallet against the merchant's phone to securely complete transactions.

This guide outlines how to set up Tap to Pay for Android. For the best results, merchants should implement code that prevents the screen or application from sleeping. If the screen or application sleeps while a transaction is in-flight, processing may be interrupted or fail. Additionally, ensure developer mode on the terminal device is turned off before starting any transaction in production mode.

To use Tap to Pay, you'll need to integrate with Moov's SDKs. Integration consists of two main phases:

  1. Onboard Terminal Applications – Register terminal applications then link them with a merchant
  2. Accept and Process Payments – Configure the merchant’s mobile application to process contactless payments

View the Android SDK documentation.

Prerequisites

Devices must meet the requirements laid out in the prerequisite section below.

Operating System

  • Android 10.0 (API 29) minimum
  • Google Play Services v11+
  • Official Android release

Hardware Features

  • Active NFC antenna
  • ARM processor architecture
  • Secure element for key storage
  • Unmodified manufacturer firmware

Security Status

  • Passes Google Play Integrity verification
  • No root access detected
  • Original bootloader intact
  • Accurate system time

Set up app through Google Play

When distributing the application through Google Play, we recommend limiting the store listing visibility to devices that pass strong integrity checks.

Google Play can verify that devices pass integrity checks before the store listing is made visible to users. Enable this feature to limit your app's distribution to unreliable or unknown devices. This will not affect the app's Google Play discoverability or search ranking.

  1. Log in to Google Play Console
  2. Navigate to Release > App Integrity > Store listing visibility
  3. Enable Strong integrity checks

This process prevents installation on compromised devices that could pose security risks.

Register application

Applications must be registered with Moov to be authorized to use our Tap to Pay SDK. The process for application registration is as follows:

  1. Create a TerminalApplication via the create terminal application endpoint
1
2
3
4
5
curl -X POST "/terminal-applications" \
  -H "Authorization: Bearer {token}" \
  --data '{
    "platform": "android"
  }'\
  1. The application will move from pending to enabled once approved. You can subscribe to the terminalAppRegistration.updated webhook to be notified of the status updates

  2. Once the application is enabled, you must link the app's TerminalApplication to a merchant account via the link terminal application endpoint

1
2
3
4
5
curl -X POST "/accounts/{accountID}/terminal-applications" \
  -H "Authorization: Bearer {token}" \
  --data '{
    "terminalApplicationID": "UUID"
  }'\
  1. When a new version of the application is published to Google Play, register this version via the terminal applications version endpoint POST /terminal-applications/{terminalApplicationID}/versions
1
2
3
4
5
curl -X POST "https://api.moov.io/terminal-applications/{terminalApplicationID}/versions" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "version": "20440059"
  }'\

Initialize SDK

SDK initialization requires MoovSDK.onApplicationCreated to be called in the app's Application.onCreate method. Additionally,MoovSDK.isApplicationProcess should be used to determine whether or not the app should proceed with any initialization logic in its onCreate method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()

    // initialize the Moov SDK
    MoovSDK.onApplicationCreated(this)

    // return if not being called from the main process
    if (!MoovSDK.isApplicationProcess()) {
      return
    }

    <!-- proceed to initialize the app -->
  }
}

Create transfer

The main entry point is a factory method: MoovSDK.createTerminal. This initializes a terminal instance which ensures a secure environment. This method should be called as early in the application's lifecycle as is practical, as it may take a significant amount of time to complete the device attestation.

Creating an EMV authorization will grab exclusive access to the device's NFC reader, allowing tap of an EMV card.

There are two main paths to accept EMV payments:

  • Directly create a transfer from the SDK
  • Create an authorization via the SDK and create the transfer separately

This method accepts the EMV tap, authorizes the transaction with the card network, creates a transfer on the Moov platform, and returns the transfer ID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
val terminalConfig = fetchTerminalConfig() // fetch the terminal config from the Moov API
val terminal = MoovSDK.createTerminal(
     applicationContext,
     terminalConfig,
)

val transferID = terminal.createTapTransfer(
  CreateTransferParams(
    TapAuthorizationParams(
      amount = 100L,
      currency = Currency.getInstance("USD"),
      customerAccountID = "962aa592-5993-49d6-ae54-d6d6ab917c66",
    ),
    partnerAccountID = "fd35bc72-fa52-4fef-a0f5-f99fa1280aeb",
    destinationPaymentMethodID = "19a76ccb-be73-4394-a9ca-701c0a20fd8a",
    description = "A fancy widget",
)) {
    LOG.d(TAG, "EMV message received: $it")
  }

This method accepts the EMV tap, authorizes the transaction with the card network, and returns a token which can be passed into the create transfer API.

If the authorization token expires before creating a transfer, the card authorization is reverted (voided). You should ensure transfers are created promptly after receiving the token and within the token’s validity period (set in the exp claim of the JWT). JWTs with an expired exp claim will not be accepted for processing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
val terminalConfig = fetchTerminalConfig() // fetch the terminal config from the Moov API
val terminal = MoovSDK.createTerminal(
  applicationContext,
  terminalConfig,
)

val authorization = terminal.createTapAuthorization(
  TapAuthorizationParams(
    amount = 100L,
    currency = Currency.getInstance("USD"),
    customerAccountID = "962aa592-5993-49d6-ae54-d6d6ab917c66",
  )) {
      LOG.d(TAG, "EMV message received: $it")
    }

// now, create the transfer externally using the authorization as the source

Test mode

Test mode allows you to explore and verify your integration with the Moov SDK before processing live payments. The terminal configuration fetched from the Moov API determines whether the SDK operates in test or production mode. When a terminal is created with a test configuration, the returned Terminal implements the SandboxTerminal interface, which provides access to testing specific functionality.

To use test mode, you'll need to use a bearer token, which is generated when you create a Moov account. You can check for test mode and access specific methods to test different card scenarios:

1
2
3
4
5
6
7
8
9
val result = MoovSDK.createTerminal(context, config)
if (result is TerminalCreationResult.Created) {
    val terminal = result.terminal
    if (terminal is SandboxTerminal) {
        // Access sandbox-specific methods
        terminal.getTestCases() // Browse available test cards
        terminal.selectTestCard("4000000000000010") // Select decline card
    }
}

Additionally, you can add a dropdown menu to your application's UI that allows users to select a test card. We recommend placing this dropdown on the same screen as the transaction amount entry field. Note that this feature is available only in test mode — when running in production, users must tap a physical card on the device.

Test cards

Use getTestCases() to retrieve the list of available test cards. Each test card is tied to a specific authorization outcome, allowing you to test both approve and decline scenarios. See the Tap to Pay section of the test mode guide for available test cards.

Call selectTestCard() with the desired card number before initiating a tap authorization. The SDK will simulate that card being tapped, returning the corresponding approve or decline response without requiring a physical card tap.

Summary Beta