Integración con la terminal VPOSCOIN
La terminal es un puente: tu aplicación le manda comandos JSON y ella ejecuta las operaciones de pago contra el SDK de MIT sobre una PAX IM30. Un comando es una línea de JSON; una respuesta, otra.
Formato
Una línea de JSON por mensaje. La terminal responde siempre con un objeto que lleva
el campo response y cierra con \r\n.
Orden
Los comandos entran a una cola FIFO con un único consumidor. Un comando lento no adelanta a otro, y una excepción no mata la cola.
Asincronía
Casi nada responde de forma síncrona. Un PAYMENT devuelve un eco al
instante y el resultado real llega minutos después.
LOGIN_MIT,
INIT, PAYMENT, REFUND y DEVICEINFO
retornan de inmediato y su resultado llega después, por un callback del SDK, sin
identificador que lo ate al comando que lo pidió. Si tu integración espera una respuesta
síncrona, se va a quedar esperando.
Conexión
Hay dos enlaces físicos posibles y pueden estar activos a la vez. El modo se consulta con
GETTRANSPORT y se cambia con SETTRANSPORT.
| Modo | Enlace | Del lado del PC / kiosko |
|---|---|---|
usbhostpor omisión |
Puerto USB Host + adaptador FTDI o Prolific | Cable null-modem contra tu puerto serie |
usbc |
Puerto USB-C en modo periférico (CDC-ACM) | Aparece como USB Virtual Serial Port (PAX), un COM estándar |
both |
Los dos a la vez | Las respuestas se difunden a todos los enlaces abiertos |
Parámetros del puerto
- Velocidad
- 115200 baudios
- Formato
- 8 bits de datos, sin paridad, 1 de parada (8N1)
- Terminador
\r\nal final de cada mensaje- Codificación
- UTF-8
both, cada kiosko ve respuestas a comandos que no mandó.
No es un defecto: TRANSACTION_RESULT y los errores del SDK salen de callbacks
asíncronos sin vínculo con quien los originó, así que contestarle solo al que preguntó
significaría perder avisos. Tu integración debe ignorar lo que no reconozca.
Handshake mínimo
La terminal considera probado el enlace en cuanto recibe una línea de JSON válida, no antes — con cualquier línea valdría el ruido del puerto. A partir de ahí deja de avisar de cables incompatibles.
{"action":"CONNECT"}— declara que hay alguien del otro lado.{"action":"LOGIN_MIT", …}— sesión y entorno.{"action":"INIT"}— lector y llaves de transacción.- Ya se puede cobrar.
Cualquier comando desconocido recibido antes de un CONNECT se
trata como si fuera CONNECT. Es tolerancia deliberada con kioskos que
arrancan hablando de otra cosa.
Entornos DEV / QA / UAT / PROD
El entorno no depende del APK instalado. Es un parámetro en tiempo de ejecución que persiste en el dispositivo y sobrevive a las actualizaciones.
server en LOGIN_MIT no conserva el entorno
actual: lo pone en PROD. La clave ausente llega como la cadena
"null" y cae en la rama por omisión. Es el valor seguro, pero significa que
cada LOGIN_MIT reescribe el entorno — para quedarte en QA hay que mandarlo
explícito cada vez.
Dos formas de comprobar en qué entorno está una terminal:
GETCONFIGy mirarserver, oGETSESSIONy mirarserver. Es la vía fiable.- Visualmente: tras un login, la pantalla muestra
SERVIDOR <entorno>durante 4 segundos solo si no es PROD. En producción no aparece nada, así que la ausencia del aviso es la confirmación.
LOGIN_VENDING se comporta al revés y conserva el entorno si
omites server. La inconsistencia es real y conviene tenerla presente.
Canal de administración remota
Además del cable, la terminal se suscribe a un canal de mensajería (Pusher, cluster
us2) por el que acepta cualquier comando del protocolo. No
hay lista blanca, con dos excepciones señaladas en la tabla de comandos.
| Concepto | Valor |
|---|---|
| Canal de comandos | El número de serie de la terminal (p. ej. 1640385865), más fleet-all para toda la flota |
| Evento entrante | rooster_pax_status |
| Evento de respuesta | client-vpos-result |
| Cluster | us2 |
Formato del evento
{
"action": "PAYMENT",
"request_id": "de91f0a5-…",
"respond": "both",
"params": { "amount": "25.00" }
}
respond decide a dónde va la respuesta, y se lee tanto al nivel del evento
como dentro de params:
| Valor | Efecto |
|---|---|
remotepor omisión | Respuesta por HTTP al endpoint de resultados |
serial | Sale por el cable, como si lo hubiera pedido el kiosko |
both | Las dos |
LOGIN_MIT remoto se ejecutara pero su respuesta se fuera solo
por el cable. El destinatario vive 150 s — por encima del watchdog de 120 s — o hasta que
llega otro comando.
Si la terminal queda incomunicada
Tres vías de rescate, en orden:
- Serial, si aún hay enlace.
- El canal remoto — la única que no depende del cable.
- ADB, que no depende de ninguna de las dos:
adb shell am start -n coincity.im30v25/mx.com.mit.mobile.integration.MainActivity \ --es set_transport usbhost
Flujo de un cobro
Lo que ve tu integración de principio a fin, con un cobro aprobado.
BAD_REQUEST y la descripción «La transacción ya fue aprobada / rechazada
a las: HH:MM:SS». No es un defecto de esta app ni de tu integración: lo añadió el SDK
de MIT en la versión 2.7. Cambiar el monto lo evita; reiniciar la app no, y la referencia
es irrelevante. En pruebas estorba constantemente — varía el monto entre intentos.
Comandos
Todo lo que la terminal acepta. Los marcados remoto también funcionan por el canal de administración; los marcados reinicia reinician la aplicación al aplicarse.
Ningún comando coincide con la búsqueda.
Respuestas no solicitadas
Mensajes que la terminal emite sin que nadie los pida. Tu integración tiene que estar preparada para recibirlos en cualquier momento.
Errores y estados de rechazo
| Respuesta | Cuándo | Qué hacer |
|---|---|---|
PAYMENT_BUSY |
Llega un PAYMENT con otro cobro en curso |
Esperar al TRANSACTION_RESULT del anterior, o mandar STOP |
ERROR / NO_SESSION |
Cobro sin sesión MIT cargada | Mandar LOGIN_MIT y reintentar |
ERROR / ER_0004 |
INIT no consigue las llaves de transacción |
Verificar el entorno con GETCONFIG. Si el login entra pero
INIT falla, suele ser aprovisionamiento del lado de MIT |
UNKOWN_ACTION |
Comando no reconocido | Revisar el nombre. Si connectFlag es falso, en cambio, el comando
se convierte en CONNECT en silencio |
REMOTE_BUSY / REMOTE_ERROR |
Fallo procesando un comando llegado por el canal remoto | Reintentar con otro request_id |
Tiempos de espera
Hay dos temporizadores y no son lo mismo:
- El watchdog de la app, 120 s desde que arranca la pantalla de espera.
Si el SDK no responde — sin internet, por ejemplo — fuerza el reset y emite un
TRANSACTION_RESULTno aprobado. - El del lector, configurable, que salta si el cliente no presenta la
tarjeta. Llega como error del SDK con código
06.
Webhook de ventas
Alternativa al serial para recibir las ventas: la terminal las entrega por HTTP POST a una URL tuya. Está apagado mientras no configures una.
{
"type": "sales",
"count": 2,
"records": [ { /* mismo objeto que TRANSACTION_RESULT */ } ]
}
- Paquetes de máximo 50 registros por envío.
- El keepalive va aparte:
{"type":"keepalive","pending":N}. - La cola vive en disco, no en memoria: la terminal se reinicia con normalidad y una cola en RAM perdería ventas ya cobradas. Tope de 2000; al pasarse se descartan los más viejos.
- El vaciado se detiene al primer paquete que falla, para conservar el orden.
- Con
send: "ALL"también llegan los cobros fallidos, marcados conpayment_failed: true. Las devoluciones no entran.
http:// falla con «Cleartext HTTP traffic not
permitted» y la cola crece sin que llegue nada. Se ve en last_error de
GETWEBHOOK.