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 action = ActionType.valueOf(statusResult.readerAction ?: EMPTY)
when (action) {
ActionType.START_READER_AGAIN_WITH_SWIPE -> {
cardProcessData.setFallbackFlag() // Importante para notificar al adquirente que fue fallback banda, no requerido para los otros actions, solo para START_READER_AGAIN_WITH_SWIPE
instructionMessageState.value = status.message ?: "Use la banda"
cardProcessData.findCardProcess(
operationFlow = operationFlow,
context = this,
inputModeType = InputMode.SWIPE,
encryptedPanToValidate = if (operationType != "PAYMENT" && operationType != "PREAUTHORIZATION") {
transaction.card.pan
} else null
)
}
ActionType.START_READER_AGAIN_WITH_CONTACT -> {
// Action para solicitar nuevamente la lectura insertando la tarjeta
}
ActionType.START_READER_AGAIN_WITH_CONTACTLESS -> {
// Action para solicitar nuevamente la lectura acercando la tarjeta
}
else -> {
// Mostrar mensaje de error: statusResult.description
}
}
}
is String -> {
//La lectura fue exitosa, continuar a validar BIN
val bundle = Bundle().apply {
putString("bin", it)
}
val intent = Intent(this, CardRulesValidationActivity::class.java).apply {
putExtras(bundle)
}
startActivity(intent)
}
}
}Observadores de respuesta
El resultado se recibe mediante 2 observadores: observer navigateObserver y readerInfoMessage.
En la siguiente tabla se muestran los posibles objetos que se pueden recibir como respuesta de navigateObserver:
| Objeto | Descripción |
|---|---|
| Bundle de tipo StatusResult | 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. |
Para el observer readerInfoMessage el tipo de objeto a recibir es un String. Estos son notificaciones del kernel que se puede o no mostrar al usuario. Por ejemplo, notifican cuando la tarjeta se detectó ya sea por banda, contactless o chip. Este código de ejemplo muestra cómo implementarlos:
cardProcessData.readerInfoMessage.observe(this) { infoMessages ->
infoMessages?.let { infoMessagesObserver(it) }
}
private val infoMessagesObserver: (DataToast) -> Unit = { infoMessages ->
Log.i(TAG, "${infoMessages.message}")
}Objeto StatusResult
| Campo | Descripción |
|---|---|
| code | Campo de tipo String. Contiene los posibles errores en la lectura de tarjetas. |
| description | Campo de tipo String. Contiene una descripción del código de error. |
| readerStatusType | Campo de tipo String que se puede mapear el Enum MessageType. Contiene el tipo de error: INFO es mensaje informativo. ERROR es el tipo de mensaje que requiere evaluar la acción sugerida por el SDK. |
| readerAction | Campo de tipo String que se puede mapear el Enum ActionType. Contiene la acción que se espera de la App. |
Campo readerAction
| Valor | Descripción |
|---|---|
| START_READER_AGAIN_WITH_CONTACT | Se recomienda iniciar nuevamente la lectura insertando la tarjeta. |
| START_READER_AGAIN_WITH_SWIPE | Se recomienda iniciar nuevamente la lectura deslizando la tarjeta. |
| START_READER_AGAIN_WITH_CONTACTLESS | Se requiere acercar nuevamente la tarjeta. |
| RETRY | Se recomienda intentar nuevamente la lectura. |
| TRY_ANOTHER_CARD | Se recomienda intentar nuevamente la lectura usando una tarjeta diferente. |
Para la implementación en Apps externas, se recomienda evaluar el readerAction para tomar una decisión, sin embargo, a continuación se muestran los posibles errores que puede retornar el kernel en el campo code:
Lista de posibles códigos de error
| Código | Descripción |
|---|---|
| PLS_USE_CONTACT_IC_CARD | Pruebe insertando la tarjeta. |
| SEE_PHONE_REMOVE_AND_PRESENT_CARD | Lea las instrucciones en su teléfono. |
| PLS_SECOND_TAP_CARD | Acerque la tarjeta nuevamente. |
| USE_MAG_STRIPE | Use banda. |
| TAP_CARD_DETECTED | Tarjeta detectada. |
| INSERTED_CARD | Tarjeta insertada. |
| MSR | Tarjeta detectada. |
| OFFLINE_DECLINED | Tarjeta no aceptada, intente con otra tarjeta. |
| DECLINE_OFFLINE | Intente con otra tarjeta. |
| END_APPLICATION | Pruebe insertando la tarjeta. |
| DISPLAY_BALANCE | Intente con otra tarjeta. |
| APPLICATION_BLOCKED | Tarjeta bloqueada, intente con otra tarjeta. |
| TRY_AGAIN_RESENT_CARD | Pruebe insertando la tarjeta. |
| INSERT_SWIPE_OR_TRY_ANOTHER_CARD | Pruebe insertando la tarjeta. |
| TERMINATE | Pruebe insertando la tarjeta. |
| PROCESSING_ERROR | Intente con otra tarjeta. |
| OTHER_INTERFACES | Pruebe insertando la tarjeta. |
| RETRY | Ocurrió un error, intente nuevamente. |
| NO_CARD | Tarjeta no detectada, pruebe insertando la tarjeta. |
| NOT_ICC | No se pudo leer la tarjeta, use banda. |
| BAD_SWIPE | La banda está dañada, pruebe insertando o acercando la tarjeta. |
| USE_ICC_CARD | Pruebe insertando la tarjeta. |
| NEED_FALLBACK | Use banda. |
| TIMEOUT | Se agotó el tiempo de lectura. |
| DEVICE_BUSY | El lector está ocupado, intente nuevamente. |
| MULT_CARD | Presente sólo una tarjeta. |
| TERMINATED | Pruebe insertando la tarjeta. |
| CANCELED | Se canceló la lectura de tarjeta. |
| CANCELED_OR_TIMEOUT | Se canceló la lectura de tarjeta. |
| NO_EMV_APPS | Intente deslizando la tarjeta. |
| SELECT_APP_FAIL | Intente con otra tarjeta. |
| CARD_ERROR | Tarjeta bloqueada. |
| INVALID_ICC_DATA | Intente con otra tarjeta. |
| APPLICATION_BLOCKED_APP_FAIL | Tarjeta bloqueada. |
| CARD_BLOCKED_APP_FAIL | Tarjeta bloqueada. |
| KERNEL_ERR | Intente con otra tarjeta. |
| ERR_MULT_CARD | Intente con otra tarjeta. |
| ERR_CHECK_CARD | Intente con otra tarjeta. |