Saltar al contenido
Payment Emulator LabDocumentaciónEntrar a la consola

Funcionamiento interno

Banco falso y contabilidad

El libro mayor incrustado modela las partes de la contabilidad de una pasarelaque las pruebas de integración necesitan. No es un banco de producción: notiene dinero real, ni número

6 min de lectura

Índice / Index · English

Traducción de docs/en/runtime/banking.md, que es el original. Si discrepan, manda el inglés.

El libro mayor incrustado modela las partes de la contabilidad de una pasarela que las pruebas de integración necesitan. No es un banco de producción: no tiene dinero real, ni números de cuenta completos, ni PAN, ni CVV.

#Por qué existe

Una pasarela de pagos no transfiere la cartera de un cliente directamente a un comercio. Recibe o compromete fondos, los compensa, se queda una comisión, retiene el neto del comercio durante un periodo y por fin lo hace retirable. Las devoluciones y los contracargos tienen que revertir esos hechos sin corromper los libros.

apps/api/src/modules/ledger/ implementa ese comportamiento una vez para todos los proveedores. Los plugins declaran los números de comisión y de plazo; nunca escriben asientos.

#Primitivas contables

  • Cuenta: pertenece a una aplicación y a una moneda, con lado normal de debe o de haber.
  • Asiento: suceso económico inmutable, con referencia y metadatos.
  • Apunte: debe o haber positivo en unidades mínimas, dentro de un asiento.
  • Retención: compromiso contra una cartera por un pago autorizado y todavía sin capturar.
  • Saldo: derivado de todos los apuntes de una cuenta; nunca guardado en una columna mutable.

LedgerService.postWithEntityManager() rechaza, antes de volcar: menos de dos apuntes, importes no positivos, cuentas de aplicaciones o monedas mezcladas, y totales de debe y haber que no cuadren.

#Plan de cuentas

Las cuentas se provisionan en su primer uso, por aplicación y moneda, en apps/api/src/modules/ledger/accounts.ts.

Código Qué es Lado normal
asset:cash:{moneda} Efectivo dentro de la pasarela; por aquí entran los pagos externos y salen los retiros Debe
liability:wallet:customer:{id} Saldo que se le debe a un cliente Haber
clearing:{proveedor}:{moneda} Dinero en tránsito dentro de la pasarela Haber
liability:reserve:merchant:{id} Neto capturado del comercio, todavía retenido Haber
liability:payable:merchant:{id} Dinero del comercio liberado y disponible para retirar Haber
revenue:fee:{proveedor} Ingresos por comisión de la pasarela Haber

#Movimientos de un pago

Suceso Debe Haber
Captura desde saldo cartera del cliente clearing
Captura por método externo efectivo del sistema clearing
Acreditación clearing reserva del comercio + ingreso por comisión
Liquidación T+N reserva del comercio pagadero del comercio
Retiro pagadero del comercio efectivo del sistema
Reverso de devolución reserva o pagadero del comercio (+ comisión si se devuelve) clearing
Pago de la devolución clearing cartera original o efectivo del sistema
Contracargo pagadero del comercio clearing + ingreso por comisión de disputa

El saldo de un comercio puede quedar negativo tras una devolución o un contracargo. Negarse a anotar esa deuda haría que el libro mayor pareciera más seguro y fuera menos veraz. Sólo los retiros exigen saldo pagadero suficiente.

#Autorización y captura

Un pago con captura manual llega a authorized y crea una LedgerHoldEntity contra la cartera del pagador. Todavía no se mueve dinero. La captura cierra la retención como capturada y escribe los asientos normales de captura y acreditación; la cancelación la libera. El saldo disponible de una cartera es el saldo derivado de los apuntes menos las retenciones activas.

#Liquidación

Al capturar se calcula releaseAt a partir del perfil contable efectivo del proveedor. El worker llama a SettlementService.runDue() cada 60 segundos por defecto. Desde el plano de control se puede mover el reloj de pruebas con POST /applications/{applicationId}/ledger/run-settlement y un valor asOf.

Cada liberación toma un lock consultivo de PostgreSQL y relee el intento dentro de la transacción, así que dos workers solapados no pueden liberar la misma reserva dos veces.

#Valores del proveedor y ajustes de la instalación

Los plugins declaran fee.percentBasisPoints, fee.fixedMinor, releaseDays, refundReturnsFee, disputeFeeMinor, las monedas y una moneda por defecto. GatewaysService puede sobrescribir sólo los números contables y las monedas de una instalación. refundReturnsFee sigue siendo comportamiento y no se puede editar desde la base de datos.

#Bancos

banks es el catálogo de instituciones donde se puede abrir una cuenta. Un banco lleva tres identificadores con tres trabajos, y no son intercambiables:

Campo Para
id La referencia opaca a la que apunta la clave ajena (bnk_…)
code Lo que escribe una persona, una semilla o una prueba: banco-ribera
prefix La cabeza de todo número de cuenta que ese banco emite

code y prefix son identidad, no datos: ninguno aparece en UpdateBankDto. El prefijo en particular no se puede editar porque de una cuenta sólo se guardan sus cuatro últimos dígitos — el prefijo no es recuperable de nada de lo escrito, así que cambiarlo reescribiría lo que toda cuenta existente afirma sobre sí misma, sin que quede nada capaz de contradecirlo.

Un número de cuenta se comprueba contra su banco una vez, en el único momento en que el número entero existe: tiene que empezar por el prefijo y medir accountNumberLength. No hay dígito de control, a propósito — quien desarrolla tiene que poder teclear un número a mano, y una suma de comprobación haría inválido todo lo tecleado. Tanto el generador que hay detrás de POST /banks/:id/account-number como la comprobación que acepta una cuenta nueva salen de modules/banks/account-number.ts, así que no pueden desviarse.

El catálogo inicial viaja en la migración y no en la semilla, porque bank_accounts.bank_id es not null: una instalación migrada sin bancos tiene un formulario que nadie puede enviar. Sus marcas se dibujan en la consola (app/assets/images/banks/) y no se guardan en la API — el mismo reparto que mantiene el comportamiento de un proveedor aquí y su identidad visual en el checkout.

Escribir es sólo de administrador; leer está abierto a todos los roles, porque elegir dónde está tu cuenta significa ver la lista. DELETE /banks/:id apaga en vez de borrar, y la clave ajena es restrict.

#Cuentas bancarias externas

bank_accounts son contrapartes de personas, no cuentas del libro mayor. Cada una pertenece a un banco. Los asientos de fondeo y de retiro apuntan a su identificador, al identificador y al nombre de su banco, y a los cuatro últimos caracteres, en los metadatos — el nombre como instantánea, para que renombrar un banco después no reescriba lo que dice un retiro antiguo. El historial de movimientos se deriva de los asientos, no se duplica en un segundo registro. Borrar desde la API cierra la cuenta, para que los asientos viejos conserven una referencia válida.

Ni el banco ni el número se pueden editar después: mover una cuenta a otra institución no es una edición, es otra cuenta, y el número que se comprobó contra el prefijo antiguo ya no existe para comprobarlo contra uno nuevo.

#Dónde tocar

  • Motor genérico de apuntes cuadrados: ledger.service.ts.
  • Movimientos de dinero de la pasarela: gateway-ledger.service.ts.
  • Códigos de cuenta y lados normales: accounts.ts.
  • Liberación por reloj: settlement.service.ts y apps/api/src/worker.ts.
  • Vistas y retiros del plano de control: ledger.controller.ts.
  • Propiedad de las cuentas externas: modules/bank-accounts/.
  • El catálogo de instituciones y la forma de sus números: modules/banks/.
  • Declaraciones de comisión y plazo: src/providers/<id>/<id>.plugin.ts.

Nunca escribas ledger_entries ni ledger_postings directamente, ni añadas una columna de saldo mutable, ni dupliques los movimientos bancarios fuera del libro mayor.

#Pruebas

apps/api/test/ledger-invariants.spec.ts usa fast-check para los invariantes de comisión y de apuntes. test/integration/gateway-ledger.spec.ts, bank-accounts.spec.ts, banks.spec.ts, checkout.spec.ts y la conformidad de proveedores cubren flujos reales contra la base, concurrencia, retenciones, liquidación, devoluciones y retiros.

Payment Emulator Lab · RonuSoftwareMIT