Blog
México
Chile
Engineering

Cómo programar con plata

En sistemas que mueven plata, un bug puede costarte millones. Literal. Te cuento cómo puedes implementar estructuras de datos que modelen flujos de plata de forma segura.

Tabla de Contenidos

Antes de los sistemas digitales, si tenías un comercio, tus clientes te pagaban en efectivo. Si eras ordenado, llevabas un registro manual. Y si algo no cuadraba, te dabas cuenta rápido, porque el flujo era manejable.

Hoy, los pagos son digitales y el flujo es enorme. Hay terceros —como nosotros en Fintoc— que procesamos miles de transacciones por segundo, moviendo plata que no es nuestra. Y cuando estás en esa posición, no puedes darte el lujo de perder ni un solo peso.

Ahí es donde entra la ingeniería. Porque programar con plata no es como programar cualquier otra cosa. Puedes ver la charla o si estás con poco tiempo acá te cuento cómo lo hacemos en Fintoc en menos de 5 minutos.

Contexto

En Fintoc conectamos personas con comercios. Para eso ofrecemos una API que permite que las personas paguen haciendo una transferencia desde su cuenta a una de Fintoc (payments) y después Fintoc se encarga de mover todo lo que tiene en sus cuentas a cada comercio (payouts).

Esto pasa todos los días, con miles de pagos y cientos de comercios distintos. Hay personas pagando a muchos comercios al mismo tiempo y muchos comercios recibiendo pagos de muchas personas. Y todo tiene que cuadrar.

Tenemos que estar muy seguros de que cada peso que movemos, no se va a perder.

Mover plata no es trivial

Por lo mismo, tenemos que estar seguros de dos cosas:

  • Que no se pierda ni un solo peso.
  • Que nunca “inventemos” plata.

Cada peso tiene que estar justificado: quién lo hizo, cuánto le debemos al comercio y cuándo hay que transferirlo.

La solución más intuitiva sería: sumar todos los pagos del día y transferir.

Esta ecuación representa el balance que tiene el comercio al final del día, que es la suma de todos los pagos.

Pero esta simplificación de su balance tiene algunas limitantes:

  • No considera devoluciones, comisiones ni errores.
  • Es difícil de auditar si algo no cuadra con el banco.
  • Responder preguntas para un momento específico del pasado es mucho más complejo y pesado de calcular. ¿Qué balance tenía un cliente el martes pasado a las 17:00?

Para considerar el monto necesario para realizar devoluciones en el balance este debe ser considerado en nuestra ecuación.

Y si queremos descontar nuestras comisiones de su balance también debe ser considerado. Empezamos a notar un patrón. Cada vez que queremos agregar un nuevo comportamiento, esta ecuación empieza a crecer. Esto pasa porque estamos acoplando nuestra contabilidad con nuestro producto.

Cada vez que agregamos un nuevo comportamiento (como devoluciones, fees o intentos fallidos), la query crece y se vuelve más difícil de mantener.

Esto se complica aún más en el minuto que queramos contrastar la realidad de nuestro negocio con la del banco. Si por alguna razón algo no calza, encontrar dónde está esa inconsistencia es muy complicado porque la verdad de lo que debía suceder en la cuenta de Fintoc está distribuida por muchos lados.

El problema no era técnico, era contable.

La solución: contabilidad de doble entrada

La forma de resolver esto ya existe hace más de 500 años: la contabilidad de doble entrada o  ledger de doble entrada.

¿Qué es este ledger? Es un sistema que permite registrar movimientos de plata. Con este nos aseguramos de que no vamos a inventar plata porque cada movimiento que se registra tiene una explicación de por qué esta ahí.

En Fintoc implementamos un ledger de doble entrada donde cada movimiento afecta a dos cuentas:

En Fintoc, los activos representan lo que recibimos y pasivos lo que debemos.

¿Cómo lo usamos? Registrando cada movimiento de plata en distintas cuentas. Cuentas que indican cuánto tenemos de un tipo específico de activo o pasivo. Cada movimiento de plata que cause un aumento o disminución de un activo o pasivo, hace que el balance de su cuenta cambie.

Siempre un movimiento afecta por lo menos a 2 cuentas. La variación resultante en activos y pasivos por un movimiento debe ser siempre la misma (activos - pasivos = 0).

Y así cuando comparamos nuestras cuentas de activos y pasivos, podemos ver que el equilibrio entre ellos se mantiene. Todos los movimientos que ocurren en Fintoc se escriben de esta forma y cada operación que se escribe en el ledger a partir de un movimiento se agrupa en lo que llamamos una transacción.

Cada transacción en el ledger se descompone en múltiples movimientos que actualizan diferentes cuentas.

Por diseño cada transacción debe mantener el equilibrio entre activos y pasivos, y así, el sistema no puede inventar ni perder plata, porque cada transacción tiene que cuadrar. Si activos y pasivos no se equilibran, el sistema simplemente no permite esa transacción.

Con esta forma de registrar nuestros movimientos, saber el balance de cada cliente se vuelve trivial y nuestras operaciones mucho más simples.

Veamos esto en un ejemplo:

¿Cuánto saldo tiene el comercio “La Pepe Store” en este momento?

Balance actual de Pepe Store
Ya no hay que preocuparse por devoluciones ni comisiones, solo se revisa el balance actual.
Imagen explicativa de balance histórico
La query ya no es compleja. La tabla muestra la entrada que ocurrió justo antes del martes pasado a las 17:00 y vemos que su saldo en ese minuto era de $720.

¿Cuánto era el saldo de “La Pepe Store” el martes pasado a las 17:00?

Concurrencia: cuando mucho pasa al mismo tiempo

Esta forma de programar con plata funciona muy bien mientras el flujo es bajo. Para actualizar el balance de una cuenta, primero hay que conocer el balance previo. Y eso está bien, hasta que todo empieza a pasar al mismo tiempo.

Dos pagos queriendo escribir sobre el mismo balance al mismo tiempo.

Cuando tienes miles de pagos ocurriendo en paralelo, puede pasar que dos operaciones intenten escribir sobre una misma cuenta al mismo tiempo. Las dos leen el mismo estado previo y calculan un nuevo balance sin saber lo que está escribiendo la otra.

Para evitar lo anterior y no perder información, necesitamos que los pagos se escriban en orden: el primero actualiza el balance y el segundo lo hace tomando en cuenta ese resultado. Para eso realizamos locks en nuestra base de datos.

¿El problema? Si 1000 pagos quieren actualizar la misma cuenta en paralelo, el último en escribir tiene que esperar que los 999 anteriores terminen. Cuando esto pasa, tu base de datos estará al límite y tu sistema empieza a degradarse: jobs más lentos y colas que crecen sin parar. Todo se vuelve más lento y todo empieza a fallar.

Esto nos pasó hace 1 año cuando nuestro flujo empezó a aumentar y nos dimos cuenta de que no habíamos considerado una alternativa: No todos los registros necesitan escribirse de inmediato en el ledger. Podemos esperar un poco y evitar pedir locks de forma innecesaria. Para esto podemos hacer uso de una cola y permitir que los eventos se procesen en orden, evitando carga innecesaria en el sistema.

Cada pago genera un evento que entra en una cola. Procesamos uno por uno, en orden y sin duplicar.

¿Eso era todo?

Si los eventos llegan más rápido de lo que los podemos procesar, el Ledger se atrasa. Y cuando nuestras decisiones se basan en un estado desactualizado, podemos tomar decisiones equivocadas.

Si llegas a ese punto es importante notar que muchos de estos eventos ya ocurrieron y tienen que ser registrados eventualmente, por lo que puedes usar una estrategia que permita acelerar las escrituras sin tener que procesar cada evento de forma independiente. El detalle de cómo coordinamos esto es materia para otro blog.

Programar con plata o escalar sistemas, son algunos de los temas que se hablan en Tech Talks by Fintoc. Si te gustaría ir al próximo, inscríbete a nuestro newsletter o síguenos en LinkedIn e Instagram para estar atento.

Escrito por
Juan Pablo Jofré
Software Engineer
Florencia Rostion
Content Manager
Cada pago importa
Con Fintoc puedes recibir, mover y gestionar todos los pagos de tu negocio
Habla con nosotros