Skip to content
Payment Emulator LabDocumentationOpen console

Introduction

Payment Emulator Lab

Payment Emulator Lab is a stateful, multi-provider payment gateway emulator forintegration development. An application under test can use provider-shaped HTTPAPIs, hosted checkout

6 min read

Índice / Index · Español

Leer en español

Payment Emulator Lab is a stateful, multi-provider payment gateway emulator for integration development. An application under test can use provider-shaped HTTP APIs, hosted checkout pages and signed webhooks without real credentials, real cards, real money or a provider sandbox.

The project is deliberately contract-driven. Provider routes, request fields, statuses, errors and signatures belong at the edge, while payment state, idempotency, accounting, tracing and webhook delivery are implemented once in a provider-neutral core. A convenient shape that a real provider does not use is a bug here, even if it makes the emulator easier to implement.

#What it provides

  • Mercado Pago Orders: create, retrieve, attach a transaction, process, capture, cancel and full/partial refund.
  • Stripe PaymentIntents and Refunds, including create/confirm separation, manual capture, form-encoded SDK requests and Stripe-shaped errors.
  • PayPal Orders v2 and Payments v2: local OAuth, buyer approval, authorization, capture, void and refunds for one purchase unit and one final capture.
  • Deterministic scenarios for success, rejection, latency, HTTP failures, duplicated/invalid webhooks and timed payment transitions.
  • Tenant isolation by application credential, plus expiring applications for CI.
  • A double-entry ledger for capture, fees, reserve, settlement, withdrawal, refund and chargeback. Money is stored as bigint minor units and balances are derived from immutable postings.
  • A transactional webhook outbox with a pluggable delivery transport — PostgreSQL, MongoDB or Redis/BullMQ behind one port — and provider-specific signatures.
  • A catalogue of banks: each one owns the prefix its account numbers begin with, which is checked once, when the number still exists.
  • A Nuxt operator console and a separate Nuxt buyer checkout. Each browser talks to its own Nitro server; neither receives backend credentials.
  • Users with admin, merchant and customer roles, row-level application scope, revocable sessions and optional customer sign-in at checkout.

The exact fidelity limits are documented in the fidelity reports. In particular, the full provider OpenAPI schemas, Stripe Elements and the complete Mercado Pago/Stripe product surfaces are not emulated.

#Architecture at a glance

Diagram source
mermaid
flowchart LR
    AUT[Application under test] -->|provider API + application token| API[NestJS API]
    Operator[Operator browser] --> Console[Nuxt console / Nitro]
    Buyer[Buyer browser] --> Checkout[Nuxt checkout / Nitro]
    Console -->|user session| API
    Checkout -->|public link; optional customer session| API

    API --> Gateway[Gateway orchestration]
    Gateway --> Plugin[Provider plugin]
    Gateway --> Payments[Canonical payments]
    Payments --> Ledger[Double-entry ledger]
    Payments --> Outbox[Webhook outbox]
    API --> PG[(PostgreSQL)]
    Worker[Webhook/timer worker] --> PG
    Worker --> Transport[(Transport: PostgreSQL, MongoDB or Redis)]
    Transport --> Callback[Allow-listed webhook callback]

This is a modular, runtime-delegating architecture, not DDD. Controllers declare HTTP surfaces, services own behaviour, MikroORM's EntityManager is used directly, and shared behaviour lives in apps/api/src/core/ or the owning module. See Architecture for boundaries and request flows.

#Technology

Technology Role
Node.js 22+ / TypeScript Runtime and implementation language
NestJS 11 HTTP API and application wiring
MikroORM 7 / PostgreSQL 17 Durable state, transactions and migrations
PostgreSQL, MongoDB 7 or Redis 8 / BullMQ 6 Webhook delivery transport, chosen by WEBHOOK_QUEUE_DRIVER
Nuxt 4 / Vue 3 Operator console and buyer checkout
PrimeVue 4 Console UI only
Vitest 4 / Supertest / fast-check Unit, integration, conformance and property tests
Docker Compose Six-service local stack

Versions above are confirmed by the current manifests and lockfile.

#Quick start

Requirements: Docker with Compose. Node.js >=22.17.0 is also required for the seed, smoke tests and non-Docker development.

bash
docker compose up --build --wait
npm run seed

The seed bootstraps demo users and creates representative Mercado Pago and Stripe data through the real HTTP surfaces. For the Compose development defaults, sign in to the console with:

text
email:    operadora@example.test
password: emulator-demo-password

These are local demo credentials only. If the stack is reachable by anyone else, copy .env.example to .env and replace every secret before starting it. When ADMIN_TOKEN is overridden, expose the same value to npm run seed.

URL Surface
http://localhost:3000 Operator console
http://localhost:3001 Buyer checkout
http://localhost:8080/health API health check

Verify the complete running stack with:

bash
npm run smoke:stack

For installation without Docker, migrations and the four development processes, follow Getting started.

#Repository map

text
apps/
├── api/                 # NestJS API and webhook/timer worker
├── console/             # Nuxt operator application (PrimeVue)
└── checkout/            # Nuxt buyer application and provider visual kits
contracts/               # Imported provider specifications and checksums
scenarios/               # Canonical JSON scenario sources
scripts/                 # Whole-stack acceptance smoke
docs/                    # Maintainer and onboarding documentation
.agents/                 # Binding architecture rules; read before code changes
graphify-out/             # Generated discovery graph (historical snapshot)

The Graphify snapshot was generated before the current apps/ workspace layout. It remains useful for finding the original hubs (gateway, ledger, scenarios and webhooks), but current paths and relationships must be checked against source. See Project structure for where new code belongs.

#Common commands

Run all commands from the repository root.

bash
npm ci
npm run typecheck
npm run lint
npm test
npm run build
npm run smoke

Integration tests run against PostgreSQL only when DATABASE_URL is set; without it, Vitest reports them as skipped. The whole-stack smoke requires a running Compose stack.

Useful development commands:

bash
npm run dev:api
npm run dev:console
npm run dev:checkout
npm run start:worker --workspace @payment-emulator/api
npm run migrate
npm run admin:create
npm run scenario:compile

#Your first change

Before changing code, read .agents/README.md. The short version is:

  1. Find the owning application and module.
  2. Keep controllers/pages thin; put behaviour in services or the app's gateway.
  3. Put provider-specific behaviour only in apps/api/src/providers/<id>/ and provider visual identity only in apps/checkout/providers/<id>/.
  4. Never add a provider branch to the core, write ledger rows directly, store a mutable balance or let a browser call the NestJS API directly.
  5. Add or update the narrowest test, then run typecheck, lint, tests and build.

Detailed paths and examples are in the Development guide.

#Documentation

Start at the documentation index:

#Safety

  • Runtime code never calls a real payment provider.
  • Webhooks are sent only when outbound delivery is enabled and the destination host is allow-listed.
  • Application and user-session tokens are stored as digests; application access tokens are shown only when issued or rotated.
  • Trace payloads redact known credential fields before persistence.
  • Full bank account numbers, PAN and CVV are not stored.
  • Scenario definitions are data; arbitrary JavaScript is not allowed.

This repository is an integration-development tool, not a production payment processor or treasury ledger.

Payment Emulator Lab · RonuSoftwareMIT