Tap to Pay
Esta guía explica cómo integrar tu app Android con la app de Sipay Softpos (SoftPOS) mediante Android Intents, de forma App‑to ‑App. En la práctica, tu app “lanza” Sipay Softpos con un Intent y un JSON que describe la operación (venta, reembolso, anulación, etc.). Sipay Softpos procesa el pago de forma segura en el dispositivo y te devuelve el resultado. Por qué te interesa:
- Integración rápida: sin SDK pesado ni manejo de datos de tarjeta en tu app.
- Menor coste de cumplimiento: la app de Sipay Softpos se encarga del cumplimiento EMV/PCI.
- Mejor experiencia: cobros contactless directamente en el móvil del comercio.
Recuerda que debes permitir en el dispositivo Android donde instale Sipay Sofpos los permisos de geolocalización precisa y habilitar NFC.
¿Qué es un Android Intent App‑to‑App?
Es el mecanismo nativo de Android para pedir a otra app que realice una acción con unos datos. Tú defines una “acción” y envías un “payload” (JSON en String) a Sipay Softpos. Sipay Softpos muestra su UI, realiza el cobro y te devuelve el control con el resultado.
Ventajas
- Seguridad: tu app no toca PAN ni PIN; lo gestiona Sipay Softpos.
- Aislamiento: actualizaciones de Sipay Softpos no rompen tu app si mantienes el contrato del Intent.
Requisitos previos
Dispositivo Android con NFC y compatible con SoftPOS. App de Sipay Softpos instalada en el dispositivo. Manifest: declara la app de Sipay Softpos para que Android permita descubrirla:
<queries>
<package android:name="es.sipay.softpos" />
</queries>
Sustituye com.Sipay Softpos.softpos.app por el package real de Sipay Softpos que uses en pruebas/producción.
- Convenciones de formato:
- Cantidades en el payload como String con punto y dos decimales (ej.: "10.00"). Aunque en España usamos coma decimal, el JSON debe usar punto.
- Códigos de idioma de 2 letras (ej.: "es", "en").
Dispositivo
- Dispositivo Android con NFC habilitado.
- Almacén de claves respaldado por hardware, es decir, TEE en un SoC (Sistema en Chip).
- Servicios de Google Mobile instalados.
Android OS
- Android 8.1 o superior en producción.
- La versión de Android debe tener soporte activo y parches de seguridad en los últimos seis meses.
- No debe estar rooteado ni marcado como manipulado.
Sipay Softpos
- Diseñado para funcionar solo en línea, es decir, con conexión a internet.
- Las opciones de desarrollador deben estar siempre desactivadas.
- Debe instalarse desde Google PlayStore.
App‑to‑App: Lanzar Sipay Softpos desde tu app Android
Paso 1. Construye el Intent
// Recomendado: usa el package de TU app para componer la acción y la clave del extra
// es.sipay.softpos
val myPackage = applicationContext.packageName
val intent = Intent("$myPackage")
// configJsonString: JSON en String con la operación y sus datos (ver ejemplos más abajo)
intent.putExtra("$myPackage.CONFIGURATION", configJsonString)
// Verifica que existe una app que pueda manejar este Intent (Sipay Softpos)
if (intent.resolveActivity(packageManager) != null) {
startActivityForResult(intent, PHOS_REQUEST_CODE) // API tradicional
// Alternativa moderna: registerForActivityResult con un ActivityResultContract personalizado
} else {
// Manejo de error: Sipay Softpos no instalado
}
Paso 2. Estructura general del payload (JSON en String)
{
"operation": "sale | refund | void | sso_login | history | receipt | language",
"data": { /* campos específicos */ }
}
Paso 3. Operaciones soportadas y ejemplos
Ejemplo de venta
{
"operation": "sale",
"data": {
"amount": "10.00",
"tip": "2.00",
"order_reference": "REF-123",
"show_result": "true",
"extras": {
"channel": "INSTORE",
"correlation_id": "c0ffee-123"
}
}
}
Ejemplo de venta con desglose de pagos
{
"operation": "sale",
"data": {
"amount": "10.00",
"tip": "2.00",
"order_reference": "1769096164229",
"show_result": true,
"extras": {
"breakdown": "[{\"id\":\"transaction_1\",\"amount\":0.99,\"description\":\"some description for transaction 1\"},{\"id\":\"transaction_2\",\"amount\":9.0,\"description\":\"some description for transaction 2\"}]"
}
}
}
Ejemplo de venta con conciliación bancaria
{
"operation": "sale",
"data": {
"amount": "10.00",
"tip": "2.00",
"order_reference": "1769096545437",
"show_result": true,
"extras": {
"reference": "1234ABCD"
}
}
}
Ejemplo de venta con desglose de pagos y conciliación bancaria
{
"operation": "sale",
"data": {
"amount": "10.00",
"tip": "2.00",
"order_reference": "1769096696462",
"show_result": true,
"extras": {
"reference": "1234ABCD",
"breakdown": "[{\"id\":\"transaction_1\",\"amount\":0.99,\"description\":\"some description for transaction 1\"},{\"id\":\"transaction_2\",\"amount\":9.0,\"description\":\"some description for transaction 2\"}]"
}
}
}
Ejemplo de devolución
Recuerda haber almacenado el transaction_key recibido en el JSON de respuesta de la venta.
{
"operation": "refund",
"data": {
"transaction_id": "TX-20240801-0001",
"amount": "5.00",
"extras": {
"reason": "Devolución parcial"
}
}
}
Ejemplo de anulación
Recuerda haber almacenado el transaction_key recibido en el JSON de respuesta de la venta.
{
"operation": "void",
"data": {
"transaction_id": "TX-20240801-0001",
"extras": {
"user": "seller-42"
}
}
}
Paso 4. Recibir el resultado (onActivityResult)
- App‑to‑App devuelve control a tu Activity.
- Comportamiento típico:
- resultCode: RESULT_OK | RESULT_CANCELED
- data: Intent con extras (p. ej., status, response_code, transaction_id, message)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PHOS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) {
val status = data.getStringExtra("status") // por confirmar clave exacta
val responseCode = data.getStringExtra("code") // por confirmar
val transactionId = data.getStringExtra("tx_id") // por confirmar
// TODO: mapear estados, persistir, actualizar UI
} else {
// Cancelación por usuario o error
}
}
}
Ejemplo del resultado:
{
"resultCode": 0,
"responseBody": {
"transaction": {
"isTransaction3D": false,
"status": 1,
"operation": "sale",
"add date": "2026-01-20T09:45:1OUTC",
"amount": 1,
"tip_amount": 0,
"currency": "EUR",
"voidable": true,
"refundable_amount": 1,
"sca_type": 1,
"retrieval_reference_number": "95828e143bc80437555e328ec6d0ad4d",
"surcharge_amount": 0,
"payment_method": 0,
"instalments_number": 0,
"order_reference": "1768898601100",
"transaction_key": "95828e143bc80437555e328ec6d0ad4d",
"stan": "000561",
"auth_code": "889822",
"application_id": "A0000000031010",
"card": "416598XXXXXX8985",
"card_type": "VISA",
"action_code": 0,
"message": ""
},
"mid": "399",
"tid": "02385",
"extras": {
"order_reference": "1768898601100"
}
}
}
Códigos de respuesta resultCode
| Código | Descripción |
|---|---|
| 0 | Transaction success |
| 3000 | Transaction cancelled |
| 3001 | Transaction declined |
| 3002 | Card read failed |
| 3003 | Connectivity failed |
| 3004 | Unknown result |
Códigos de respuesta status
| Código | Descripción |
|---|---|
| 0 | Pending |
| 1 | Successful |
| -1 | Failed |
| 999 | Unknown |
Códigos de respuesta action_code
Este código de respuesta viene en el contenido del campo action_code del JSON de respuesta de la venta.
El código 0 es transacción correcta, cualquier otro código es un error y debe ser revisado con los valores de esta tabla.
| Código | Texto en Español |
|---|---|
| 01 | Consulte al emisor (Refer to card issuer) |
| 02 | Consulte al emisor (Refer to card issuer) |
| 03 | Número de comercio inválido |
| 04 | Retener tarjeta (Pick-up) |
| 05 | Denegada / No honrar |
| 06 | Error del sistema |
| 07 | Retener tarjeta |
| 08 | Consulte al emisor |
| 09 | Por favor, espere |
| 10 | Aprobación parcial |
| 12 | Transacción inválida |
| 13 | Importe inválido |
| 14 | Tarjeta inválida |
| 15 | No se puede enrutar al emisor |
| 21 | No se ha tomado ninguna acción |
| 25 | No se puede localizar el registro |
| 28 | El archivo no es accesible |
| 30 | Error del sistema |
| 31 | Error de formato |
| 33 | Tarjeta caducada |
| 34 | Retener tarjeta |
| 36 | Tarjeta restringida. Retener tarjeta |
| 37 | Retener tarjeta |
| 38 | Intentos de entrada de PIN excedidos |
| 40 | Función no soportada |
| 41 | Tarjeta reportada como perdida. Retener tarjeta |
| 43 | Tarjeta reportada como robada. Retener tarjeta |
| 51 | Fondos insuficientes |
| 52 | Sin cuenta corriente |
| 53 | Sin cuenta de ahorro |
| 54 | Tarjeta caducada |
| 55 | PIN incorrecto |
| 56 | Tarjeta desconocida |
| 57 | Transacción original no encontrada |
| 58 | Terminal desconocido |
| 60 | Se requiere PIN |
| 61 | Límite de retiro excedido |
| 62 | Tarjeta restringida |
| 63 | Violación de seguridad |
| 64 | Importe superior a la transacción original |
| 65 | Reintentar en modo contacto (insertar tarjeta) |
| 66 | Retener tarjeta |
| 67 | Retener tarjeta |
| 68 | Respuesta retrasada |
| 75 | Intentos de entrada de PIN excedidos |
| 76 | PIN incorrecto |
| 77 | El emisor no soporta el servicio |
| 78 | Cliente no elegible para POS |
| 79 | Error del sistema |
| 80 | Error de red |
| 81 | Error criptográfico de PIN |
| 82 | Tiempo de espera de la transacción agotado |
| 83 | Fallo de comunicación |
| 85 | Fallo verificación número de cuenta, dirección o CVV2 |
| 86 | Validación de PIN no posible |
| 87 | Importe de reembolso (cashback) rechazado |
| 88 | Fallo criptográfico |
| 89 | Fallo de autenticación |
| 91 | Emisor temporalmente no disponible |
| 92 | Tipo de tarjeta inválido |
| 94 | Transacción duplicada |
| 95 | Error de conciliación |
| 96 | Error del sistema |
| 97 | Error del sistema |
| 98 | Error del sistema |
| 99 | Error del sistema |
| 11008 | Error de procesamiento |
| 11009 | PIN Pad no inicializado |
| 11010 | Reintentar en modo contacto |
Funcionalidades adicionales
Conciliación bancaria
Si nececesitas enviar un identificador para la conciliación bancaria, puedes hacerlo mediante el campo TREF en el payload de extras.
"extras" : {
"reference": "1234ABCD"
}
Recuerda respetar las reglas de validación de la entidad bancaria para el campo P37. Más información.
Desglose de pagos
Si necesitas enviar un desglose de pagos, puedes hacerlo mediante el campo breakdown en el payload de extras.
"extras" : {
"breakdown": "[{\"id\":\"transaction_1\",\"amount\":0.99,\"description\":\"some description for transaction 1\"},{\"id\":\"transaction_2\",\"amount\":9.0,\"description\":\"some description for transaction 2\"}]"
}
breakdown es un array de objetos con los siguientes campos:
id: identificador de la transacción.amount: importe de la transacción.description: descripción de la transacción.
El campo description es opcional y puede contener hasta 255 caracteres alfanuméricos.
Buenas prácticas de integración
- Verifica la disponibilidad de Sipay Softpos antes de lanzar el Intent; guía al usuario a instalarla si falta.
- No registres datos sensibles en logs (emails/teléfonos de recibos, etc.).
- Establece timeouts y reintentos idempotentes; usa order_reference y un correlation_id en extras.
- Si usas SSO: tokens con TTL corto, firma asimétrica (p. ej. RS256), rotación de claves.
- Internacionalización: cambia el idioma de Sipay Softpos vía operación language cuando tu app cambie de idioma.
Pruebas recomendadas
- Ventas aprobadas/denegadas (fondos insuficientes, tarjeta expirada, PIN incorrecto).
- Propina: con y sin tip; reglas de redondeo.
- Void inmediato vs refund diferido.
- Envío de recibos vía email/SMS.
- Web‑to‑App: valida notificaciones backend y estados en tiempo real.
- Rendimiento: tiempos de autorización y de UI.
Checklist rápido:
- App de Sipay Softpos instalada y discoverable (resolveActivity).
- JSON válido con punto decimal en importes.
- Manejo de RESULT_OK/RESULT_CANCELED.
- Persistencia de transaction_id y response_code.
- Reintentos idempotentes con order_reference.
FAQ
- ¿Necesito permisos especiales? No, salvo declarar el package de Sipay Softpos en
<queries>del manifest. - ¿Mi app verá datos de tarjeta? No. Sipay Softpos gestiona EMV/PCI.
- ¿Puedo usar coma decimal? En pantalla sí, pero en el JSON usa punto y dos decimales.
- ¿Hay callback en Web‑to‑App? No. Usa notificaciones de backend.
Preguntas para terminar tu integración con éxito
Compártenos esta información para adaptar la guía a tu caso y validar en preproducción/producción:
- Identificadores técnicos
- PackageName de tu app Android (prod y preprod).
- PackageName exacto de Sipay Softpos que instalarás (prod y preprod).
- ¿Confirmas uso de action "
<tuPackage>" y extra "<tuPackage>.CONFIGURATION"?
- Contrato de respuesta
- ¿Qué claves de respuesta necesitas que validemos en onActivityResult? (transaction_id, status, response_code, message).
- ¿Quieres un esquema de errores y ejemplos JSON en la doc?
- Autenticación y SSO
- ¿Usarás SSO desde el inicio? issuer, formato y caducidad del token.
- ¿Endpoint/backend para emitir y validar tokens listo?
- Reglas de negocio
- ¿Habrá propina? Reglas de redondeo e importe máximo.
- ¿Permitirás refund parcial? ¿Ventanas temporales para void vs refund?
- Moneda: ¿solo EUR o multi‑moneda?
- Recibos y marca
- ¿Envío de recibos lo hará Sipay Softpos directamente o quieres pasar por tu pasarela?
- ¿Branding (logo/textos) personalizable?
- Web‑to‑App
- ¿Usarás Web‑to‑App? Confirma el flujo de notificaciones servidor‑a‑servidor.
- Operativa y soporte
- Datos de contacto de soporte técnico, horarios, y acuerdos de nivel de servicio (SLA).