Android - NFC Module¶
The NFC module is used to read the chip data on identity cards or passports. In order for the reading process to be successful, certain information obtained from the identity document must be provided in advance.
⚠️ Prerequisite: Maven access must be established and the Core Module must be added to the project before using the NFC Module. The NFC Module does not work alone without the Core Module being added.
Required Data¶
To successfully read the chip data, the following information must be provided:
| Data | Format | Example |
|---|---|---|
| Serial Number | Alphanumeric | A11A11111 |
| ID Validity Date | YYMMDD | 280621 |
| Date of Birth | YYMMDD | 950621 |
The NFC reading process will fail without providing this information.
Adding to the Project¶
1. Add to libs.versions.toml file:¶
[versions]
...
enqualify-plus = "x.x.x.x"
[libraries]
...
enqualify-plus-nfc = {
group = "com.enqualify.plus",
name = "nfc",
version.ref = "enqualify-plus"
}
2. Add dependency to build.gradle.kts file:¶
3. Gradle Sync¶
Synchronize Gradle files by clicking on the "Sync Now" option.
Implementation¶
1. Override onNewIntent Function¶
The onNewIntent method should be overridden in the Activity where SDK functions are used to manage intents coming to the NFC process. This is a system callback coming from AppCompatActivity, not a callback from EnQualify:
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (NfcAdapter.ACTION_TAG_DISCOVERED == intent.action ||
NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
setIntent(intent)
} else {
super.onNewIntent(intent)
}
}
2. Activity Layout — FrameLayout¶
A full-screen covering FrameLayout should be added to the layout of the Activity where the NFC process will take place. The SDK manages page transitions through this component:
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
3. Adding NFCCallbacks Interface¶
The NFCCallbacks interface contains the necessary callback methods for managing NFC processes and must work with an Activity.
Adding to the class:
Overriding the callbacks:
class EnQualifyPlusActivity : AppCompatActivity(), NFCCallbacks {
override fun initializeCompleted(modules: EnModules) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
when (modules) {
EnModules.NFC -> {
// NFC processes can be started
}
}
}
override fun initializeFailed(failureCode: FailureCode, additionalMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun nfcCompleted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun nfcFailed(nfcFailureCode: NFCFailureCode, additionalMessage: String?) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name} $nfcFailureCode: $additionalMessage")
}
override fun nfcSaveStarted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun integrationAddCompleted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun integrationAddFailed(errorMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
}
4. Creating Custom NFCReadFragment¶
The fragment performing the NFC reading process should be derived from the EnNFCBaseFragment class. This class provides basic functions for NFC operations and manages the reading stages.
class NfcReadFragment : EnNFCBaseFragment() {
private var _binding: FragmentNfcReadBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentNfcReadBinding.inflate(layoutInflater)
setupIndicators()
return binding.root
}
override fun onNfcReadStart() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
setCurrentIndicator(0, R.string.nfc_chip_scan_start)
}
}
ℹ️ Customizations can be made on the fragment depending on the design.
5. Initializing EnQualifyNFC¶
Before starting NFC processes, EnQualifyNFC.initialize() must be called. If any other process is attempted without initialization, the SDK will not work.
val sessionModel = SessionModel(
callType = "NewCustomer",
reference = UUID.randomUUID().toString()
)
val baseModel = BaseModel(
baseURL = "https://deveqmapi.enqura.com",
signallingCertificateList = listOf("enqura"),
mapiCertificateList = listOf("enqura"),
mobileUser = "mobile",
countryCode = "TUR",
locale = "TR",
useEmbeddedLocalSound = false
)
EnQualifyNFC.initialize(
context = this,
fragmentManager = supportFragmentManager,
containerID = R.id.fragmentContainer,
sessionModel = sessionModel,
baseModel = baseModel
)
Initialize Parameters¶
| Parameter | Type | Description |
|---|---|---|
context | Context | The runtime context of the application |
sessionModel | SessionModel | Session information to be used during the NFC process |
baseModel | BaseModel | Model carrying basic data during the verification process |
fragmentManager | FragmentManager | Manages the NFC fragments |
containerID | Int | The ID of the container where the NFC fragment will be added |
Automatic Flow After Initialization¶
After the initialize() call, the SDK automatically executes the Token → Session → Settings steps in order. In case of errors, the respective callbacks are triggered:
override fun tokenCreateFailed(errorMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name} $errorMessage")
}
override fun sessionAddFailed(errorMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun settingsGetFailed(errorMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
When all steps are completed, initializeCompleted is triggered and NFC processes can be started.
NFC Operations¶
Functions¶
| Function | Description |
|---|---|
addFragment(fragment) | Adds a new fragment on top of the current fragment |
replaceFragment(fragment) | Replaces the current fragment with a new one |
nfcStart(fragment, documentNo, birthDate, expiryDate) | Starts the NFC reading process |
addIntegration(integrationModel) | Adds integration data |
closeSession(isFinished: Boolean) | Closes the session |
clear() | Clears the SDK |
getSessionUId() | Returns the UId of the current session. |
Starting the NFC Reading Process¶
Data from the identity document is required for the NFC reading process. This data is typically obtained automatically from the CustomerIDDoc object after the OCR process:
EnQualifyNFC.getInstance().nfcStart(
NfcReadFragment(),
CustomerIDDoc.getInstance().documentNumber,
CustomerIDDoc.getInstance().dateOfBirthDay,
CustomerIDDoc.getInstance().expiryDate
)
Function Parameters¶
| Parameter | Type | Description |
|---|---|---|
fragment | EnNFCBaseFragment | NFC reading fragment inherited from EnNFCBaseFragment |
documentNo | String | Document/serial number |
birthDate | String | Date of birth — in YYMMDD format (e.g., 21.06.1995 → 950621) |
expiryDate | String | Validity date — in YYMMDD format (e.g., 21.06.2028 → 280621) |
ℹ️ If the NFC process is to be performed before OCR, the data must be manually entered in the formats above.
Reading Flow¶
- When Reading is Completed
When the NFC reading is completed, the SDK begins sending the read data to the backoffice automatically, and nfcSaveStarted is triggered:
override fun nfcSaveStarted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
- When Data Submission is Completed
When the data is successfully sent to the backoffice, nfcCompleted is triggered:
override fun nfcCompleted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
fragmentManager.replaceFragment(NfcResultFragment(), false)
}
Error Management¶
Critical Errors: Errors that prevent the continuation of the NFC process are communicated via nfcFailed:
override fun nfcFailed(nfcFailureCode: NFCFailureCode, additionalMessage: String?) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name} $nfcFailureCode: $additionalMessage")
}
Momentary Errors: Temporary errors such as contactless issues are communicated via the onNfcError callback inside the fragment derived from EnNFCBaseFragment:
override fun onNfcError(error: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name} : $error")
if (isAdded) setCurrentIndicator(-1, R.string.empty)
}
Adding Integration Data¶
val integrationModel = IntegrationModel(
idRegistration = IDRegistration(),
addressRegistration = AddressRegistration(),
data = ""
)
enQualifyNFC.addIntegration(integrationModel)
override fun integrationAddCompleted() {
Log.i(TAG, "integrationAddCompleted")
}
override fun integrationAddFailed(errorMessage: String) {
Log.i(TAG, "integrationAddFailed: $errorMessage")
}
Closing Session and Clearing¶
// Close the session
enQualifyNFC.closeSession(isFinished = true)
override fun sessionCloseCompleted() {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name}")
}
override fun sessionCloseFailed(errorMessage: String) {
Log.i(tag, "${object {}.javaClass.enclosingMethod?.name} $errorMessage")
}
// Clear the SDK
enQualifyNFC.clear()
ℹ️ After closing the session,
initialize()must be called again to use the SDK again.
Retrieving Session Information¶
EnNFCBaseFragment¶
EnNFCBaseFragment is the base class from which the special fragment performing the NFC reading process will be derived. It includes the necessary functions to manage reading stages, update progress indicators, and handle error messages.
Callback Functions¶
| Function | Description |
|---|---|
onNfcReadStart() | Called when NFC reading starts |
onNfcTagDetected() | Triggered when an NFC tag is read |
onNfcRead(data: Any?) | Triggered when NFC data is read |
onNfcReadFinish() | Triggered when NFC reading is completed |
onFirsLevelCompleted() | Called when the first stage of the reading process is completed |
onSecondLevelCompleted() | Called when the second stage of the reading process is completed |
onThirdLevelCompleted() | Called when the third stage of the reading process is completed |
onFourthLevelCompleted() | Called when the fourth stage of the reading process is completed |
onNfcError(error: String) | Called when a temporary error occurs during reading |
Error Codes¶
NFCFailureCode¶
enum class NFCFailureCode(errorMessage: String) {
NFCReadFailed(""),
NFCStoreFailed(""),
NFCTimeout("Nfc timeout"),
NFCDisable("NFC is disabled on the device."),
PermissionDenied("NFC permission denied"),
MissingNFCKey("Invalid or Null MRZ keys."),
BacFailed("BAC failed"),
EfComNotRead("EF_COM Access File not read"),
SodNotRead("Failed to retrieve EF.SOD file. Possible chip or file corruption"),
SodHashError("Failed to extract hash values from EF.SOD. Missing or unsupported algorithm."),
UnsupportedAlgorithm("Failed to extract hash values from EF.SOD. Missing or unsupported algorithm."),
Dg1NotRead("DG1 File not read"),
Dg11NotRead("DG11 File not read"),
Dg2NotRead("DG2 File not read"),
NotSupport("Device does not support NFC"),
Failed(""),
ChipAuthenticationFailed("Chip Authentication failed"),
ActiveAuthenticationFailed("Active Authentication failed"),
Dg14FileEmpty("DG14 File is empty"),
SodFileEmpty("SOD File is empty"),
StoredHashError(""),
CertificateValidationFailed("Certificate Validation failed"),
FidNotRead(""),
Dg14NotRead(""),
CertificateValidation(""),
HashMatchError("")
}
Proguard Rules¶
To ensure the NFC SDK works correctly in release packages, add the following rules to the proguard-rules.pro file:
-keep class net.sf.scuba.smartcards.** { *; }
-keep class org.bouncycastle.** { *; }
-dontwarn javax.naming.**
-dontwarn org.bouncycastle.cert.dane.fetcher.JndiDANEFetcherFactory
-dontwarn com.enqualify.plus.ocr.OCRCallbacks$DefaultImpls
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
Accessing Result Data¶
After the NFC reading process is completed, the read data can be accessed through the CustomerChip object:
Refer to the Core Module — CustomerChip page for documentation on the CustomerChip model and all its fields.
Callback Reference¶
Token / Session / Settings¶
| Callback | Description |
|---|---|
tokenCreateCompleted(isNewCreatedToken: Boolean) | Triggered when a token is created |
tokenCreateFailed(errorMessage: String) | Token creation error |
sessionAddFailed(errorMessage: String) | Session creation error |
settingsGetFailed(errorMessage: String) | Triggered when settings cannot be obtained |
sessionCloseCompleted(status: CallSessionTypeStatus) | Triggered when the session is closed |
sessionCloseFailed(errorMessage: String) | Session closing error |
integrationAddCompleted() | Integration data successfully added |
integrationAddFailed(errorMessage: String) | Unable to add integration data |
initializeFailed(failureCode: FailureCode, additionalMessage: String) | Core initialize error |
NFC¶
| Callback | Description |
|---|---|
initializeCompleted(module: EnModules) | Initialization completed, NFC ready |
nfcSaveStarted() | Data has begun sending to the backoffice |
nfcCompleted() | Data successfully transmitted to the backoffice |
nfcFailed(nfcFailureCode, additionalMessage) | NFC process error (critical) |