Use MiFare contactless card on a device

Latin America

With the Clover Android SDK, your app can interact with non-financial MiFare contactless cards on a Clover device. This tutorial walks you through using the MiFare APIs in a sample app to connect to the service, read a card, and handle the result.

Prerequisites

To use the MiFare APIs, you must configure your project and ensure your test environment meets the minimum requirements.

System requirements

Clover devices: Flex 4, Flex Pocket, Flex 3, Mini 3, Kiosk, Station Duo 2. This feature is only available for the Latin America region.

ROM: Q2 2025 release or later.

Core Payments App: Version released on or after March 31, 2025.

Supported MiFare Cards:

  • MiFare Classic 1K / 4K
    • Read UID & retrieve data
    • Write data
  • MiFare Ultralight
    • Read UID & retrieve data
  • MiFare Ultralight EV1
    • Read UID & retrieve data

Limitations

Secure access module (SAM) not supported: Clover devices do not have a built-in SAM slot. For use cases that require interaction with a SAM card, you must use an external card reader.

Build configuration

The Clover Android SDK is published on Maven Central. Therefore, you must add that repository and the dependencies to your build configurations by performing the following steps:

  1. Open your project-level build.gradle file.
  2. In the repositories block, add mavenCentral() as a repository.
repositories {
  mavenCentral()
  google()
}
  1. Open your module-level build.gradle file.
  2. In the dependencies block, add the following implementation statement for the MiFare-compatible SDK version:
dependencies {
  ...
  implementation "com.clover.sdk:clover-android-sdk:323"
}

1. Connect to the MiFare service

Get an instance of the MifareCardReaderClient and establish a connection. This requires an IMifareCardReaderClientListener to receive onConnected and onDisconnect callbacks.

Important: All card operations must be completed within a 30-second timeout period. If an operation exceeds this limit, the client disconnects, and you need to reconnect.

import com.clover.sdk.v3.mifare.connector.IMifareCardReaderClientListener
import com.clover.sdk.v3.mifare.connector.MifareCardReaderClient

private var mifareCardReaderClient: MifareCardReaderClient? = null

private fun connectToMifareService() {
    mifareCardReaderClient = MifareCardReaderClient.getInstance(applicationContext)
    mifareCardReaderClient?.connect(object : IMifareCardReaderClientListener {
        override fun onConnected() {
            // Service is connected. Ready to perform card operations.
        }

        override fun onDisconnect() {
            // Service is disconnected.
        }
    })
}

2. Read Card ID

After the nConnected() callback fires, you can retrieve the card's unique identifier (ID). This operation does not require an authentication key.

import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException

fun readCardID() {
    try {
        // 1. Call the read ID method
        val result: MifareCard? = mifareCardReaderClient?.cardUIDRead()

        // 2. Handle the result
        if (result != null) {
            // Success: process the result.uid
        } else {
            // Handle null response
        }
    } catch (e: MifareCardReaderException) {
        // Handle exception
    }
}

3. Read a MiFare Classic card

After the onConnected() callback fires, you can perform card operations. Reading a MiFare Classic card requires providing a key for authentication.

Before creating a key, it's important to understand the MifareCardKey parameters:

  • keyType: Specifies whether to use Key A (0x0A) or Key B (0x0B) for authentication.
  • keyNum: Slot number (1-8) in the device's secure keystore where the key will be loaded.
  • keyVersion: Integer representing the version of the key, for management purposes.
  • keyData: 6-byte key as a hexadecimal string.

Alternative key approach

An alternative approach may be available depending on the device ROM version. This method allows you to load both Key A and Key B in a single call using a 12-byte key string. The first 6 bytes are treated as Key A and the second 6 bytes as Key B. The keyType parameter determines which half of the key is used for the operation.

mifareCardKey = MifareCardKey(0x0A, 0, 1, "B98950CD89AAC9F950CD89BB")

import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException

fun readMiFareClassicCard() {
    try {
        // 1. Create the key for authentication
        val cardKey = MifareCardKey(0x0A, 0, 1, "FFFFFFFFFFFF")

        // 2. Build the read request
        val request = MifareCardDataRequest(4, 3, cardKey)
        // This request reads 3 blocks, starting from absolute block 4.
        // On a MiFare 1K card, this corresponds to Sector 1, blocks 0, 1, and 2.

        // 3. Call the read method
        val result: MifareCard? = mifareCardReaderClient?.cardClassicRead(request)

        // 4. Handle the result
        if (result != null) {
            // Success: process the result.cardData
        } else {
            // Handle null response
        }
    } catch (e: MifareCardReaderException) {
        // Handle exception
    }
}

Key Management

Key Slots:
The Clover device's secure keystore has a maximum of 8 slots available for loading MiFare keys. You must manage which key is loaded into each slot (keyNum).

Key Types:
Ensure you are using the correct keyType as defined for the Clover SDK (for example, 0x0A for Key A and 0x0B for Key B), as this may differ from other native Android implementations.

4. Write to a MiFare Classic card

Writing data to a card follows a similar process to reading it. You must provide the data to be written as a hexadecimal string in the MifareCardDataRequest.

import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException

fun writeMiFareClassicCard() {
    try {
        // 1. Create the key for authentication
        val cardKey = MifareCardKey(0x0A, 0, 1, "FFFFFFFFFFFF")

        // 2. Define the data to write (must be a hex string)
        val dataToWrite = "01020304050607080910111213141516"

        // 3. Build the write request
        val request = MifareCardDataRequest(4, 3, cardKey, dataToWrite)
        // This request writes to 3 blocks, starting from absolute block 4.
        // On a MiFare 1K card, this corresponds to Sector 1, blocks 0, 1, and 2.

        // 4. Call the write method
        val success: Boolean? = mifareCardReaderClient?.cardClassicWrite(request)

        // 5. Handle the result
        if (success == true) {
            // Write operation was successful
        } else {
            // Write operation failed
        }
    } catch (e: MifareCardReaderException) {
        // Handle exception
    }
}

5. Disconnect from the service

When your application completes its operations, call the disconnect() method to release the service. To interrupt an operation that is already in progress, call the cancel() method.

fun cleanup() {
    // Disconnect after a completed operation
    mifareCardReaderClient?.disconnect()
}

fun interruptOperation() {
    // Cancel an in-progress operation
    mifareCardReaderClient?.cancel()
}