# 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](https://moovfinancial.github.io/moov-android/moov-android/io.moov.sdk/index.html) documentation.

## [Prerequisites](#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](#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](https://play.google.com/console/)
2. Navigate to **Release &gt; App Integrity &gt; Store listing visibility**
3. Enable **Strong integrity checks**

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

## [Register application](#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](/api/sources/terminal-applications/create/)

```zsh
curl -X POST "/terminal-applications" \
  -H "Authorization: Bearer {token}" \
  --data '{
    "platform": "android"
  }'\
```

2. 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
3. Once the application is enabled, you must link the app's `TerminalApplication` to a merchant account via the link terminal application [endpoint](/api/sources/terminal-applications/link/)

```zsh
curl -X POST "/accounts/{accountID}/terminal-applications" \
  -H "Authorization: Bearer {token}" \
  --data '{
    "terminalApplicationID": "string"
  }'\
```

4. When a new version of the application is published to Google Play, register this version via the terminal applications version [endpoint](/api/sources/terminal-applications/create-versions/) `POST /terminal-applications/{terminalApplicationID}/versions`

```zsh
curl -X POST "https://api.moov.io/terminal-applications/{terminalApplicationID}/versions" \
  -H "Authorization: Bearer {token}" \
  --data-raw '{
    "version": "20440059"
  }'\
```

## [Initialize SDK](#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.

```kotlin
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](#create-transfer)

The main entry point is a factory method: [MoovSDK.createTerminal](https://moovfinancial.github.io/moov-android/moov-android/io.moov.sdk/-moov-s-d-k/-factory/create-terminal.html). 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

[Directly create transfer](#tab-685179324-0-0) [SDK authorization &amp; transfer API](#tab-685179324-0-1)

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.

```kotlin
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/money-movement/transfers/create/) 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.

```kotlin
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)

[Test mode](/guides/get-started/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:

```kotlin
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](#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](/guides/developer-tools/test-data/#link-cards-card-acceptance) and [decline](/guides/developer-tools/test-data/#declined-card-transfers) scenarios. See the [Tap to Pay](/guides/developer-tools/test-data/#tap-to-pay-on-mobile) section of the test mode data reference 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.
