Android
35 min
integración desde archivo aar generar el aar ejecutando el script de build cd sdconn android /build aar sh copiar dist/sdconn release aar al directorio libs/ del módulo de la aplicación agregar la dependencia en build gradle kts dependencies { implementation(files("libs/sdconn release aar")) // dependencias transitivas (requeridas) implementation("com github mik3y\ usb serial for android 3 8 1") implementation("com squareup okhttp3\ okhttp 4 12 0") implementation("org jetbrains kotlinx\ kotlinx serialization json 1 7 3") implementation("org jetbrains kotlinx\ kotlinx coroutines android 1 9 0") implementation("androidx security\ security crypto 1 1 0 alpha06") } agregar el repositorio de jitpack en settings gradle kts (necesario para usb serial for android) dependencyresolutionmanagement { repositories { google() mavencentral() maven { url = uri("https //jitpack io") } } } desde módulo local si se prefiere incluir el sdk como módulo del proyecto copiar la carpeta sdconn/ al proyecto agregar en settings gradle kts include("\ sdconn") agregar la dependencia dependencies { implementation(project("\ sdconn")) } permisos el sdk declara estos permisos en su manifiesto (se fusionan automáticamente) \<uses permission android\ name="android permission internet" /> \<uses feature android\ name="android hardware usb host" android\ required="false" /> internet — necesario para comunicarse con el dispositivo por http y con la api de mobbex usb host — necesario solo para conexión serial declarado como required="false" para que la app funcione en dispositivos sin usb host (usando solo http) permisos usb cuando se conecta un dispositivo usb, android muestra un diálogo pidiendo permiso al usuario si se desea aceptar automáticamente, agregar un intent filter en la activity principal \<activity android\ name=" mainactivity"> \<intent filter> \<action android\ name="android hardware usb action usb device attached" /> \</intent filter> \<meta data android\ name="android hardware usb action usb device attached" android\ resource="@xml/device filter" /> \</activity> con res/xml/device filter xml \<?xml version="1 0" encoding="utf 8"?> \<resources> \<usb device /> \</resources> inicialización import com mobbex sdconn sdconn import com mobbex sdconn model sdconnconfig // con valores por defecto val sdconn = sdconn(context) // con configuración personalizada val sdconn = sdconn(context, sdconnconfig( serialbaudrate = 115200, mobbexapibase = "https //api mobbex com", debug = true )) se recomienda crear una única instancia de sdconn a nivel de application o usando inyección de dependencias configurar credenciales las credenciales son la clave api y el token de acceso de mobbex son necesarias para que el sdk pueda crear operaciones de pago sdconn setcredentials( apikey = "clave api mobbex", accesstoken = "token de acceso mobbex" ) las credenciales se almacenan cifradas con encryptedsharedpreferences (aes 256) persisten entre sesiones de la aplicación verificar si están configuradas if (sdconn credentialsconfigured) { // listo para operar } borrar credenciales sdconn clearcredentials() conectar un dispositivo opción 1 serial (usb) try { sdconn connectserial() // conectado — el estado cambia a connected(mode="serial", detail="portname") } catch (e sdconnexception) { // no se encontró dispositivo usb serial } el sdk busca todos los drivers usb serial disponibles abre cada puerto y envía un mensaje hello el primer dispositivo que responda queda conectado si ninguno responde, lanza sdconnexception opción 2 red (http) la conexión http requiere un proceso de emparejamiento con pin // 1 solicitar emparejamiento — el dispositivo muestra un pin de 6 dígitos sdconn requestpairing(ip = "192 168 1 50", port = 5000) // 2 el usuario lee el pin de la pantalla del dispositivo // 3 confirmar con el pin val device = sdconn confirmpairing( ip = "192 168 1 50", port = 5000, pin = "847293" ) // conectado — el dispositivo queda en modo pinpad // se guarda un token de confianza para futuras reconexiones conexión http directa (sin emparejamiento) si solo se necesita conectar sin el flujo de pairing (por ejemplo, para pruebas) sdconn connecthttp(ip = "192 168 1 50", port = 5000) reconexión automática una vez emparejado un dispositivo, se puede reconectar sin pin if (sdconn hassaveddevice()) { try { val device = sdconn reconnect() // reconectado a ${device name} en ${device ip} ${device port} } catch (e sdconnexception) { // dispositivo no alcanzable o token rechazado } } observar el estado de conexión lifecyclescope launch { sdconn connectionstate collect { state > when (state) { is connectionstate disconnected > showdisconnected() is connectionstate searching > showsearching() is connectionstate connected > showconnected(state mode, state detail) is connectionstate error > showerror(state message) } } } leer una tarjeta la operación de lectura ejecuta internamente obtiene información del dispositivo (serial number, modelo) obtiene la ubicación gps del dispositivo crea una operación en la api de mobbex (o usa un token externo si se proporciona) envía el token al dispositivo para que lea la tarjeta devuelve los datos de la tarjeta junto con el intenttoken utilizado flujo estándar (genera token contra api) import com mobbex sdconn model readrequest val result = sdconn read(readrequest( total = 15 00, currency = "ars", hideamount = false, test = false // true para operaciones de prueba )) if (result result) { val sourcedata = result sourcedata val token = result intenttoken // token generado por la api de mobbex // datos de la tarjeta leída pin, emvdata, cryptogram, ksn, track2 } else { val error = result error // "timeout", "cancelled", "not in pinpad mode", etc } flujo con token externo cuando ya se tiene un intenttoken generado externamente (por ejemplo, desde un backend propio), se puede enviar directamente en este caso el sdk no contacta a la api de mobbex y además incluye la geolocalización del dispositivo en la respuesta val result = sdconn read(readrequest( total = 15 00, currency = "ars", intenttoken = "eyjhbgcioijiuzi1niis " // token externo )) if (result result) { val sourcedata = result sourcedata val token = result intenttoken // mismo token enviado val geo = result geolocation // \[latitud, longitud] o null // procesar datos } nota cuando se usa un token externo no es necesario tener configuradas las credenciales de mobbex flujo qr para pagos por qr, enviar el campo source con la referencia del medio de pago val result = sdconn read(readrequest( total = 15 00, currency = "ars", source = "arg qr" // activa el flujo qr )) if (result result) { val operationid = result operationid // id de la operación val source = result source // "arg qr" val status = result status // { code "100", text "pendiente" } val token = result intenttoken // token utilizado // el qr se muestra en el dispositivo pos automáticamente // consultar el estado de la operación externamente // cuando el usuario escanee el qr y pague, el estado cambiará } else { val error = result error } nota el flujo qr siempre requiere credenciales configuradas la respuesta no contiene sourcedata ya que no se lee una tarjeta para cancelar la visualización del qr, llamar a cancelread() cancelar lectura sdconn cancelread() funciona tanto para cancelar una lectura de tarjeta como para quitar un qr de la pantalla del dispositivo mostrar resultado en pantalla después de procesar el pago en el servidor, mostrar el resultado en la pantalla del dispositivo import com mobbex sdconn model displayrequest // vista "result" — pago aprobado (fire and forget) sdconn display(displayrequest( view = "result", // o simplemente omitir (es el defecto) status = "success", title = "pago aprobado", subtitle = "visa 1234", total = 15 00, currency = "ars", text = "id 123456", timeout = 10 )) // vista "result" — pago rechazado sdconn display(displayrequest( status = "error", title = "rechazado", timeout = 5 )) // vista "loading" — procesando (fire and forget) sdconn display(displayrequest( view = "loading", title = "procesando pago ", subtitle = "no retire la tarjeta", timeout = 30 )) // vista "choose" — selección de cuotas (bloqueante) val displayresult = sdconn display(displayrequest( view = "choose", title = "seleccione cuotas", subtitle = "visa 1234", values = listof( displayoption(label = "1 pago $15 00", value = "1"), displayoption(label = "3 cuotas $5 75 c/u", value = "3"), displayoption(label = "6 cuotas $3 08 c/u", value = "6") ), timeout = 60 )) if (displayresult != null && displayresult result) { val selected = displayresult selected // selected? label = "3 cuotas $5 75 c/u" // selected? value = "3" } else { // timeout o error } // ocultar pantalla antes del timeout sdconn dismissdisplay() vistas de display vista comportamiento retorna "result" muestra resultado con animación fire and forget null "loading" muestra spinner de carga fire and forget null "choose" muestra opciones bloqueante hasta selección o timeout displayresult valores de status (para vista result) por nombre valor animación "success" círculo verde con check "error" círculo rojo con x "neutral" círculo naranja con reloj "none" sin animación, fondo blanco por código numérico códigos se resuelve a 3, 4, 200, 300 302, 605, 800 success 400 417, 500, 603, 604 error 1, 2, 100, 210, 600 602, 610 neutral otros none desconectar // desconectar sin borrar datos guardados sdconn disconnect() // desconectar y borrar dispositivo + token de confianza sdconn forgetdevice() cambiar modo del dispositivo sdconn setmode("pinpad") modo descripción normal modo pos estándar con pantalla de ingreso de monto slave modo cola recibe trabajos de pago desde una cola transit lectura continua de tarjetas para peajes/transporte pinpad modo pinpad espera comandos de lectura del controlador información del dispositivo val info = sdconn getdeviceinfo() // info type, info serialnumber, info deviceextras val location = sdconn getdevicelocation() // location latitude, location longitude, location accuracy la información del dispositivo se cachea tras la primera consulta la ubicación se consulta en tiempo real flujo completo de ejemplo class paymentactivity appcompatactivity() { private lateinit var sdconn sdconn override fun oncreate(savedinstancestate bundle?) { super oncreate(savedinstancestate) sdconn = sdconn(this, sdconnconfig(debug = true)) sdconn setcredentials("mi api key", "mi access token") lifecyclescope launch { // 1 conectar por serial try { sdconn connectserial() } catch (e exception) { showerror("no se encontró dispositivo usb") return\@launch } // 2 leer tarjeta val result = sdconn read(readrequest(total = 100 00)) if (result result) { // 3 enviar sourcedata al servidor para procesar el pago val paymentok = processpaymentonserver(result sourcedata!!) // 4 mostrar resultado en el dispositivo if (paymentok) { sdconn display(displayrequest( status = "success", title = "aprobado", total = 100 00 )) } else { sdconn display(displayrequest( status = "error", title = "rechazado" )) } } else { showerror(result error ? "error desconocido") } } } override fun ondestroy() { super ondestroy() sdconn disconnect() } } troubleshooting no se detecta el dispositivo usb verificar que el dispositivo android soporte usb host (la mayoría de tablets y pos lo soportan) verificar que el cable usb sea de datos (no solo de carga) verificar que el usuario haya aceptado el diálogo de permisos usb activar debug = true en la configuración para ver logs detallados en logcat error "device not reachable" en conexión http verificar que el dispositivo pos y el dispositivo android estén en la misma red wi fi verificar que el puerto (por defecto 5000) esté correcto probar la conexión con un navegador http //\<ip> 5000/device/hello error "mobbex credentials not configured" llamar a sdconn setcredentials(apikey, accesstoken) antes de ejecutar read() verificar que las credenciales sean válidas error "timeout" en lectura de tarjeta el dispositivo no detectó una tarjeta en 120 segundos verificar que el dispositivo esté en modo pinpad verificar que el lector de tarjetas del dispositivo funcione correctamente descarga