Idempotencia en pagos: por qué un doble clic no debe cobrar dos veces
Trabajando en el equipo de pagos aprendí una lección que parece pequeña pero lo es todo: una operación de pago debe poder repetirse sin cobrar dos veces. A eso lo llamamos idempotencia.
El problema
El usuario pulsa "Pagar", la red va lenta, no ve respuesta y vuelve a pulsar. O tu propio sistema reintenta una petición que falló a medias. Sin protección, acabas de cobrar dos veces. En pagos eso no es un bug, es un incidente.
La idea: la clave de idempotencia
Cada intento de pago lleva una clave de idempotencia única que genera el cliente. El servidor guarda el resultado asociado a esa clave:
- Si la clave es nueva → procesa el pago y guarda el resultado.
- Si la clave ya existe → devuelve el resultado guardado, sin volver a cobrar.
Pasarelas como Stripe o Adyen lo soportan de serie con una cabecera Idempotency-Key. Pero tu propio backend también debe ser idempotente de punta a punta.
Cómo lo implemento
- Genera la clave antes de mostrar el botón de pago, no después.
- Persiste
(clave → estado, resultado)de forma atómica: una restricciónUNIQUEen base de datos es tu mejor amiga. - Trata los webhooks como idempotentes también: el mismo evento puede llegarte varias veces y en distinto orden.
- Distingue "en curso" de "completado" para no procesar dos peticiones concurrentes con la misma clave.
Lo que me llevo
La idempotencia no es solo para pagos: reintentos, colas de mensajes, jobs… cualquier cosa que pueda ejecutarse "más de una vez" la necesita. En pagos, simplemente, no es negociable.
¿Montando una pasarela o un sistema de cobros? Escríbeme desde la página de inicio.