Saltar al contenido principal

Integración de app web

Dispara pagos NFC en la app MONEI Pay desde un POS basado en navegador (una pantalla de pedidos web, un POS en tablet, un enlace de pago de back-office). Tu página web abre el deep link monei-pay://; MONEI Pay gestiona el pago por contacto; el resultado vuelve a tu backend mediante un webhook firmado y al navegador mediante una redirección.

No hay SDK que instalar — esta es una integración pura de HTTP + esquema de URL. Úsala cuando tu POS es HTML/JS y no tienes una app móvil nativa propia.

Cómo funciona

  1. El navegador pide a tu backend que inicie un pago.
  2. Tu backend genera un token de autenticación POS, construye el deep link con callback_url (tu webhook) y complete_url (donde Safari devuelve al usuario) y lo retorna al navegador.
  3. El navegador abre el deep link. iOS / Android solicita al usuario que cambie a MONEI Pay.
  4. El usuario acerca su tarjeta al dispositivo que ejecuta MONEI Pay.
  5. MONEI envía un webhook firmado a tu callback_url — este es el resultado autoritativo. Completa el pedido aquí.
  6. MONEI Pay abre complete_url en el navegador original; tu página lee los parámetros de consulta del resultado y luego consulta tu backend para confirmar.

Los dos canales son independientes a propósito: el webhook es el registro de confianza (HMAC MONEI-Signature), la redirección es un detalle de UX que puede suplantarse. Confirma siempre mediante el webhook o Obtener pago antes de completar el pedido. Consulta Entrega de resultados.

Requisitos previos

  • Cuenta MONEI y clave de API
  • Un dispositivo con MONEI Pay (el iPhone / Android del comercio — el dispositivo que físicamente acerca la tarjeta)
  • Un backend capaz de generar tokens de autenticación POS y alojar el webhook callback_url
  • Un endpoint HTTPS bajo tu control (obligatorio para callback_url — las IPs locales y http:// están bloqueadas)

Paso 1: Endpoint del lado del servidor

Tu backend expone un pequeño endpoint que el navegador llama para iniciar un pago. Genera el token de autenticación, persiste un registro de pago y devuelve el deep link.

server.js
import express from 'express';
import {Monei} from '@monei-js/node-sdk';
import {randomUUID} from 'crypto';

const app = express();
app.use(express.json());

const monei = new Monei('YOUR_API_KEY');

app.post('/create-payment', async (req, res) => {
const {amount, orderId} = req.body;
const {token} = await monei.posAuthToken.create({});

// Store a row so the webhook handler and /payment-status can find it
const paymentId = randomUUID();
await db.payments.insert({id: paymentId, orderId, status: 'PENDING'});

const params = new URLSearchParams({
amount: String(amount),
auth_token: token,
order_id: orderId,
callback_url: `https://merchant.com/webhooks/monei`,
complete_url: `https://merchant.com/checkout/done?paymentId=${paymentId}`
});

res.json({
deepLink: `monei-pay://accept-payment?${params.toString()}`,
paymentId
});
});
order_id para la conciliación

Pasa tu propio order_id para que aparezca como orderId del pago en el webhook firmado callback_url. Evita la consulta adicional a la base de datos al correlacionar con tu pedido POS. Si se omite, MONEI genera uno. Consulta Conciliación.

Cuando el cajero hace clic en Cobrar en tu POS web, obtén el deep link y navega hacia él.

checkout.html
<button id="charge">Charge €15.00</button>

<script>
document.getElementById('charge').addEventListener('click', async () => {
const res = await fetch('/create-payment', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({amount: 1500, orderId: 'order_42'})
});
const {deepLink, paymentId} = await res.json();

// Persist locally so the return page can find the right payment
sessionStorage.setItem('pendingPaymentId', paymentId);

// Hand off to MONEI Pay. On iOS Safari prompts to switch apps.
window.location.href = deepLink;
});
</script>
Dispara desde un gesto del usuario

Safari y Chrome solo permiten la navegación con esquema de app cuando ocurre dentro de un gestor de clic real (o pointerdown / touchend). No abras el deep link desde setTimeout, callbacks de red o carga de página — el navegador lo bloqueará como popup.

Particularidades de Safari

  • Safari muestra una hoja de confirmación ("¿Abrir en MONEI Pay?") la primera vez que un dominio activa monei-pay://. El usuario debe pulsar Abrir.
  • Si MONEI Pay no está instalado, Safari permanece en tu página silenciosamente. No verás ningún evento de error. Detéctalo con un temporizador de reserva:
window.location.href = deepLink;

setTimeout(() => {
// Page is still visible → user dismissed the sheet or MONEI Pay isn't installed
if (!document.hidden) {
alert('MONEI Pay does not appear to be installed on this device.');
}
}, 2000);
  • Un <iframe> embebido no puede navegar la ubicación de nivel superior a un esquema personalizado. Si tu POS se ejecuta dentro de un iframe, abre el deep link mediante window.top.location.href = ... (requiere mismo origen o allow-top-navigation).

Paso 3: Gestiona el retorno en complete_url

Tras completar (o cancelar) el pago por contacto, MONEI Pay abre complete_url en el mismo navegador. Trata sus parámetros de consulta solo como una pista — confirma mediante el webhook o Obtener pago.

checkout/done.html
<script>
const params = new URLSearchParams(location.search);
const paymentId = params.get('paymentId');

if (params.get('success') === 'true') {
// Optimistically show "processing"; confirm from your backend below
document.body.innerHTML = '<p>Confirming payment…</p>';
} else {
document.body.innerHTML = `<p>Payment failed: ${params.get('error')}</p>`;
}

// Poll your backend — the webhook is the source of truth
async function poll() {
const res = await fetch(`/payment-status/${paymentId}`);
const {status} = await res.json();
if (status === 'COMPLETED') {
document.body.innerHTML = '<p>Paid ✓</p>';
} else if (status === 'FAILED') {
document.body.innerHTML = '<p>Payment failed</p>';
} else {
setTimeout(poll, 1000);
}
}
if (params.get('success') === 'true') poll();
</script>

Los parámetros de consulta de complete_url están documentados en Entrega de resultados.

Paso 4: Recibe y verifica el webhook

El callback_url que configuraste en el Paso 1 recibe un POST firmado cuando finaliza el pago. Verifica el HMAC MONEI-Signature antes de confiar en la carga útil.

webhook-handler.js
import {Monei} from '@monei-js/node-sdk';
const monei = new Monei('YOUR_API_KEY');

app.post('/webhooks/monei', express.raw({type: '*/*'}), async (req, res) => {
const signature = req.headers['monei-signature'];
let payment;
try {
payment = monei.verifySignature(req.body.toString('utf8'), signature);
} catch {
return res.status(401).send('Invalid signature');
}

// payment.orderId is what you sent as order_id in the deep link
await db.payments.update(
{orderId: payment.orderId},
{status: payment.status, monei_id: payment.id}
);
res.status(200).send('OK');
});

Guía completa de verificación de firma: Verificar firma.

Resumen del modelo de confianza

CanalDirecciónConfianzaUsar para
callback_urlServidor → servidorDe confianzaCompletar pedidos, liquidación, lógica de negocio
complete_urlRedirección navegadorNO de confianzaSolo retroalimentación UX — confirma mediante el webhook antes

Nunca completes un pedido basándote únicamente en los parámetros de consulta de complete_url. El cajero puede construir cualquier URL.

Problemas frecuentes

SíntomaCausaSolución
Safari no hace nada al hacer clicNavegación no dentro de un gesto de usuario, o bloqueador de popupsDispara desde un evento click / pointer; evita setTimeout / async-then-navigate
El navegador muestra "No se puede abrir la página"MONEI Pay no está instalado en este dispositivoInstala MONEI Pay desde el App Store / Play Store en el dispositivo que acerca la tarjeta
El webhook nunca se disparacallback_url es http://, una IP privada o supera los 2048 caracteresUsa una URL https:// pública. El cliente con DNS fijo de MONEI también bloquea RFC1918 / loopback / IMDS
El webhook se dispara pero falta mi order_idEl cajero usa una versión antigua de MONEI Pay (anterior a 2.7.3) que no analizaba order_idAsegúrate de que el dispositivo ejecuta la última versión de MONEI Pay desde la tienda
Dos pestañas del navegador saltan a MONEI PayAmbas abrieron el deep link en el mismo grupo de ventanaUsa una sola pestaña POS; el deep link debe estar vinculado a un único pago en vuelo
complete_url se abre pero perdió la sesiónSafari la abre en una pestaña nueva sin tus cookiesLleva el estado en la URL (query paymentId) y confirma mediante tu backend en lugar de depender de la sesión
El usuario recarga la página complete_urlIdempotencia de tu ladoEl endpoint /payment-status/{id} del servidor debe ser seguro de llamar repetidamente

Referencia