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
- Proceso de inicialización
- Montos de la transacción
- Tipo de transacción
- Código de moneda de la transacción
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 - El contexto
- El modo de lectura a procesar
- El número de tarjeta cifrado: Aplica para anulaciones y devoluciones. Enviar el número de tarjeta cifrado retornado en la consulta de transacciones, aplica para validar que se está usando la misma tarjeta del pago original, en caso de no requerirlo, enviar null.
- 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() - 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,
PREAUTHORIZATION,
REFUND,
ANNULMENT,
POSTAUTHORIZATION
}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),
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 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)
}
}
}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. |