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
bigintminor 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,merchantandcustomerroles, 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
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.
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:
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:
npm run smoke:stack
For installation without Docker, migrations and the four development processes, follow Getting started.
#Repository map
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.
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:
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:
- Find the owning application and module.
- Keep controllers/pages thin; put behaviour in services or the app's gateway.
- Put provider-specific behaviour only in
apps/api/src/providers/<id>/and provider visual identity only inapps/checkout/providers/<id>/. - 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.
- 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:
- Getting started
- Architecture
- Business domain
- Critical request flows
- Testing
- Configuration
- Troubleshooting
- Adding a provider
#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.