Lectura de tarjetas
ℹ️
Este proceso es obligatorio
En esta fase se procede a la recuperación de datos de la tarjeta mediante la implementación del estándar EMV, haciendo uso del kernel del dispositivo.
Requisitos
- Inyección y configuración de llaves
- Listado de tags EMV
- Monto de la transacción
- Tipo de transacción
- Código de país
- Código de moneda
- Identificador de la adquirencia (Acquirer ID)
Resultado
- Después del proceso de lectura, se obtendrá un objeto con todos los datos de la tarjeta requeridos para procesar operaciones de pago, anulaciones o devoluciones.
Paso a paso
- Instanciar el objeto
CardProcessData. - Invocar el método
findCardProcessy pasarle como parámetro:- El objeto
OperationFlow - Instancia del objeto
CardData() - El contexto
- El modo de lectura a procesar
- El objeto
- Configurar los observers para recibir el resultado de la lectura.
Instanciar objeto OperationFlow
- Amount: Objeto que contiene información del monto de la transacción en formato ISO. Es decir, los 2 últimos dígitos representan los decimales y no lleva punto decimal. Ejemplo: $10.00 = 1000
- Capture: Objeto que contiene información para la captura de los datos de la tarjeta. Se requiere instanciar los objetos:
- Capture(): Instanciar el objeto así:
operationFlow.capture = Capture() - Card(): Instanciar el objeto. Así:
operationFlow.capture!!.card = Card() - Holder(): Instanciar el objeto. Así:
operationFlow.capture!!.card.holder = Holder() - Identification(): Instanciar el objeto. Así:
operationFlow.capture!!.card.holder.identification = Identification() - Terminal(): Instanciar el objeto. Así:
operationFlow.terminal = Terminal()
- Capture(): Instanciar el objeto así:
- TransactionType: Tipo de transacción. Este objeto es un enum con los siguientes valores:
enum class OperationType {
PAYMENT,
REFUND,
ANNULMENT,
REVERSE
}Instanciar objeto CardData
- countryCode: Código ISO de país. Ver Tabla de código de país. Ejemplo:
032 - acquirerId: Código del adquirente. Ver Tabla de adquirencias. Ejemplo:
GPS - tagList: Lista de
String List<String>que contiene el listado de tags EMV que se van a recuperar durante la lectura. Este listado depende de cada adquirencia.
Instanciar objeto Amount
-
Breakdown: Listado de desglose de montos. Está compuesta por monto y descripción. Listado de valores:
- OPERATION: si la transacción tiene propina, aquí va el valor base. Si la transacción no tiene propina, aquí va el valor total.
- TIP: (aplica cuando la transacción tiene propina, aquí solo va el valor de la propina).
-
Currency: Código de moneda. Posibles valores:
- ARS = Pesos Argentinos
- MX = Pesos Mexicanos
- USD = Dólares americanos. Disponible para Argentina.
-
Total: Monto total de la transacción en formato ISO. Ejemplo: $25.50 = 2550
Ejemplo de implementación
val cardProcessData = CardProcessData()
cardProcessData.findCardProcess(
operationFlow = doOperationFlow(amount),
cardData = doCardData(),
context = this,
inputModeType = InputMode.ALL
)
cardProcessData.selectApp.observe(this, selectAppObserver)
cardProcessData.navigate.observe(this, navigateObserver)
private fun doOperationFlow(
baseAmount: String,
tipAmount: String,
totalAmount: String
): OperationFlow {
operationFlow.amount = Amount()
// Crear breakdown para el monto base (siempre presente)
val breakdownAmount = Breakdown()
breakdownAmount.description = OPERATION
breakdownAmount.amount = StringUtils.notFormatAmount(baseAmount)
Log.i(TAG, "AMOUNT: ${breakdownAmount.amount}")
// Crear lista de breakdowns
val breakdownList = mutableListOf<Breakdown>()
breakdownList.add(breakdownAmount)
// Agregar breakdown de propina solo si es mayor a 0
val tipAmountValue = tipAmount.toIntOrNull() ?: 0
if (tipAmountValue > 0) {
val breakdownTipAmount = Breakdown()
breakdownTipAmount.description = TIP
breakdownTipAmount.amount = StringUtils.notFormatAmount(tipAmount)
breakdownList.add(breakdownTipAmount)
Log.i(TAG, "TIP: ${breakdownTipAmount.amount}")
} else {
Log.i(TAG, "No tip amount")
}
operationFlow.capture = Capture()
operationFlow.capture!!.card = Card()
operationFlow.apply {
amount?.let {
it.total = StringUtils.notFormatAmount(totalAmount)
it.currency = currency
it.breakdown = breakdownList
}
}
if (operationType.isNotNull()) {
if (operationType != "PAYMENT" && operationType != "PREAUTHORIZATION") {
if (currency == CURRENCY_LABEL_MX) {
operationFlow.transactionType = OperationType.REFUND
} else {
if (isToday(transaction.operation.datetime)) {
operationFlow.transactionType = OperationType.ANNULMENT
} else {
operationFlow.transactionType = OperationType.REFUND
}
}
//Se agregan identificadores del pago original
operationFlow.acquirer_id = transaction.operation.acquirer_id
operationFlow.payment_id = transaction.operation.id
} else {
when (operationType) {
OperationType.PAYMENT.name -> operationFlow.transactionType =
OperationType.PAYMENT
OperationType.PREAUTHORIZATION.name -> operationFlow.transactionType =
OperationType.PREAUTHORIZATION
}
}
} else {
operationFlow.transactionType = OperationType.PAYMENT
}
//inicializar otros objetos
operationFlow.capture!!.card?.holder = Holder()
operationFlow.terminal = Terminal()
OperationFlowHolder.operationFlow = operationFlow
return operationFlow
}
private fun doCardData(): CardData {
return CardData(
countryCode = Country.ARG.code,
acquirerId = Acquirer.GPS.name,
tagList = doTagListTest()
)
}
private val navigateObserver: (Any) -> Unit = {
when (it) {
is Bundle -> {
val status = it.get("status")
val statusResult: StatusResult = status as StatusResult
val intent = Intent(this, CardErrorActivity::class.java)
intent.putExtra("status", statusResult)
startActivity(intent)
}
is String -> {
//Validar BIN
val bundle = Bundle().apply {
putString("bin", it)
}
val intent = Intent(this, CardRulesValidationActivity::class.java).apply {
putExtras(bundle)
}
startActivity(intent)
}
}
}Listado de TAGs EMV por adquirencia
🇦🇷 Global Processing
- 9F26
- 82
- 9F36
- 9F10
- 9F33
- 95
- 9F37
- 9A
- 9C
- 9F02
- 9F03
- 9F27
- 9F34
- 5F2A
- 9F1A
- 5F25
- 84
- 9F1E
- 9F6E
🇲🇽 Banorte
- 4F
- 50
- 57
- 5A
- 82
- 84
- 8A
- 95
- 9A
- 9B
- 9C
- 5F20
- 5F24
- 5F25
- 5F28
- 5F2A
- 5F30
- 5F34
- 9F02
- 9F03
- 9F07
- 9F09
- 9F0D
- 9F0E
- 9F0F
- 9F10
- 9F12
- 9F15
- 9F1A
- 9F1C
- 9F1E
- 9F21
- 9F26
- 9F27
- 9F33
- 9F34
- 9F35
- 9F36
- 9F37
- 9F39
- 9F41
- 9F53
- 9F6E
Códigos de respuesta
El resultado se recibe mediante el observer navigateObserver. En la siguiente tabla se muestran los posibles objetos que se pueden recibir como respuesta:
| Objeto | Descripción |
|---|---|
| ReaderStatus | Este objeto se devuelve en caso de fallo durante el proceso de lectura. |
| String | Este objeto se devuelve cuando el proceso de lectura es exitoso, retorna el BIN de la tarjeta, los primeros 8 dígitos del número de la tarjeta. |
Objeto ReaderStatus
- type: Campo de tipo MessageType, posibles valores:
- ERROR → El proceso de lectura terminó con errores.
- INFO → Se recomienda mostrar el mensaje informativo en la pantalla de la terminal.
- SUCCESS → El proceso de lectura terminó correctamente.
- code: Campo de tipo ReaderCode, posibles valores:
- SUCCESS → Indica que el proceso interno del kernel se ejecutó sin errores.
- FALLBACK → Indica que la lectura fue fallback.
- ERROR → Indica que el proceso interno del kernel se ejecutó con errores.
- description: Campo de tipo String, contiene una descripción del código de error.
Lista de posibles códigos de error
| Código | Descripción |
|---|---|
| MULT_CARD | Se detectaron varias tarjetas contactless en el lector. |
| USE_ICC_CARD | No se detectó el chip de la tarjeta, se permite deslizar la banda. |
| NO_EMV_APPS | No se encontró configuración EMV para esta tarjeta, se permite deslizar la banda. |
| TIMEOUT_ERROR | Se excedió el tiempo máximo de lectura. |
| BAD_SWIPE_ERROR | La banda de la tarjeta está dañada. |