Saltar al contenido
Payment Emulator LabDocumentaciónEntrar a la consola

Arquitectura

Flujos de petición críticos

Este documento sigue tres caminos que cruzan las fronteras que más importan. Losnombres de fichero son los de la implementación actual, no las rutas históricassrc/... de Graphify.

5 min de lectura

Índice / Index · English

Traducción de docs/en/architecture/request-flow.md, que es el original. Si discrepan, manda el inglés.

Este documento sigue tres caminos que cruzan las fronteras que más importan. Los nombres de fichero son los de la implementación actual, no las rutas históricas src/... de Graphify.

#1. Petición de pago de un proveedor

Ejemplo: una integración crea un PaymentIntent de Stripe o una Order de Mercado Pago.

Código del diagrama
mermaid
sequenceDiagram
    participant AUT as Aplicación en pruebas
    participant GC as GatewayController
    participant GS as GatewayService
    participant P as ProviderPlugin
    participant PS as PaymentsService
    participant DB as PostgreSQL

    AUT->>GC: Ruta del proveedor + token
    GC->>GS: handle(ruta, petición)
    GS->>DB: Resuelve la credencial de la aplicación
    GS->>P: Empareja la ruta
    GS->>DB: Resuelve el escenario y abre la traza
    GS->>P: Valida la petición
    GS->>PS: Ejecuta la orden canónica
    PS->>DB: Transacción de lock, idempotencia, estado, libro mayor y outbox
    PS->>P: toWire(vista canónica)
    P-->>AUT: Respuesta con la forma del proveedor

Paso a paso:

  1. apps/api/src/modules/gateway/gateway.controller.ts captura la ruta del proveedor. El prefijo es opcional; la credencial decide el proveedor y el inquilino.
  2. gateway.service.ts resuelve la aplicación con ApplicationsService.resolveByAccessToken y rechaza un prefijo que no coincida.
  3. plugin.routes mapea la ruta real a payment.create, payment.confirm, payment.capture, payment.cancel, payment.get o payment.refund.
  4. ScenariosService.resolve elige el escenario de la aplicación o el que pise X-Emulator-Scenario. Los eventos de traza anotan esa decisión ya tachada.
  5. plugin.validate comprueba el contrato del proveedor. Todavía no se ha escrito nada relativo al pago.
  6. GatewayService aplica la latencia, el tiempo de espera o el error HTTP del escenario.
  7. plugin.toCanonical crea un IntentCommand; PaymentsService.execute comprueba la regla de idempotencia del proveedor y entra en el único camino de escritura.
  8. IdempotencyService.run toma un lock consultivo de PostgreSQL con alcance de transacción, repite el desenlace guardado cuando toca, o corre la transacción de escritura.
  9. El estado del pago, sus transiciones, los apuntes, el recurso de proveedor y la fila de webhook que se deba se confirman juntos.
  10. plugin.toWire compone la respuesta. Las negativas del núcleo las traduce plugin.errors.shape() en la frontera de la pasarela.

Para cambiar este flujo, decide antes si la regla es propia de un proveedor (apps/api/src/providers/<id>/) o universal (core/, gateway, payments, ledger o webhooks). Un if por proveedor dentro de un servicio del núcleo es un defecto de arquitectura.

#2. El comprador completa un checkout

El enlace del checkout es una capacidad. Autenticarse es opcional: añade el pago con cartera, mientras que tarjeta, transferencia y ticket siguen disponibles de forma anónima.

Código del diagrama
mermaid
sequenceDiagram
    participant B as Navegador del comprador
    participant N as Nitro del checkout
    participant C as CheckoutService
    participant P as PaymentsService
    participant DB as PostgreSQL

    B->>N: GET de la URL del checkout
    N->>C: GET /checkout/:proveedor/:recurso
    C->>DB: Carga recurso, intento y sesión de cliente opcional
    C-->>B: Importe, métodos y sólo el saldo propio
    B->>N: Confirma el método elegido
    N->>C: POST /checkout/.../confirm
    C->>C: Exige cliente sólo para el método de cartera
    C->>P: completeFromCheckout()
    P->>DB: Lock, transición, contabilidad, outbox
    C-->>B: Vista actualizada

Ficheros implicados:

  • Páginas: apps/checkout/app/pages/mercadopago/checkout/[resourceId].vue y apps/checkout/app/pages/stripe/c/pay/[resourceId].vue.
  • Kits: apps/checkout/providers/<id>/Checkout.vue y theme.css.
  • Proxy de Nitro: apps/checkout/server/api/checkout/[provider]/[resourceId]/.
  • Superficie de la API: apps/api/src/modules/checkout/checkout.controller.ts y checkout.service.ts.
  • Escritura compartida: apps/api/src/modules/payments/payments.service.ts.

Para el método de cartera, CheckoutService resuelve una sesión de cliente válida, toma el walletKey de esa identidad y comprueba el saldo propio de esa persona. Un customerId que venga en el cuerpo nunca se cree. Para los demás métodos, fundingSource es external y el libro mayor usa el efectivo del sistema en vez de una cartera.

PaymentsService.completeFromCheckout toma un lock consultivo y refresca el intento dentro de él. Ese refresco es esencial: sin él, dos clics concurrentes pueden leer un estado rancio del identity map y cobrar más de una vez.

#3. Entrega de un webhook

Código del diagrama
mermaid
sequenceDiagram
    participant PS as PaymentsService
    participant DB as PostgreSQL
    participant OD as OutboxDispatcher
    participant Q as Transporte (Postgres, Mongo o Redis)
    participant W as Manejador del worker
    participant I as Webhook de la integración

    PS->>DB: Confirma pago + webhook_event
    OD->>DB: Reclama las filas que tocan (SKIP LOCKED)
    OD->>Q: Le entrega la entrega
    Q->>W: Despacha con reintentos
    W->>DB: Lee el evento y la aplicación
    W->>W: El plugin firma el cuerpo
    W->>I: POST al callback permitido
    W->>DB: Anota el intento y el estado final
  1. WebhooksService.schedule crea la WebhookEventEntity con el gestor transaccional del pago. Si falla cualquiera de las dos escrituras, no se confirma ninguna.
  2. OutboxDispatcher.sweep reclama las filas que tocan o que quedaron rancias con FOR UPDATE SKIP LOCKED, sella queued_at y se las entrega al transporte. Con el driver postgres no corre en absoluto: ese driver lee esas mismas filas, y dos reclamadores sobre una tabla es una carrera.
  3. Un enqueue fallido no pierde ninguna notificación; el barrido de lo rancio puede reclamar la fila otra vez.
  4. El manejador de apps/api/src/worker.ts comprueba RUNTIME_OUTBOUND_ENABLED, el protocolo de la URL y que el host resuelva a una direccion publica (WEBHOOK_ALLOWED_HOSTS son las excepciones).
  5. El plugin aporta el asunto, el cuerpo y la firma. El worker aporta el transporte y la política de reintentos.
  6. WebhookAttemptEntity anota estado HTTP, duración o error. Los escenarios pueden retrasar, duplicar o firmar mal un evento a propósito.

El modelo de entrega es al menos una vez. Las integraciones tienen que ser idempotentes; una entrega duplicada es posible en la operación y además se puede provocar a propósito para probarla.

#Peticiones del plano de control

Los controladores del plano de control declaran RolesGuard, y los que tienen alcance por aplicación declaran además ApplicationScopeGuard. Las peticiones del navegador de la consola van por apps/console/server/api/; apps/console/server/utils/api.ts reenvía el token de la persona que entró desde una cookie sellada. La ordenación, el filtrado y la búsqueda los valida la lista blanca del endpoint, y siempre se devuelve { data, meta }. Ver el contrato de listados.

La aprobación de PayPal termina en approved, sin débito ni retención. La autorización o captura del comercio es otra petición; véase PayPal.

Payment Emulator Lab · RonuSoftwareMIT