Skip to content
Payment Emulator LabDocumentationOpen console

Operations and validation

Configure Payment Emulator Lab

Configuration comes from process environment variables. docker-compose.ymlsupplies safe local defaults and can read a root .env; direct development mustset the variables in each pr

7 min read

Índice / Index · Español

Configuration comes from process environment variables. docker-compose.yml supplies safe local defaults and can read a root .env; direct development must set the variables in each process environment.

Which file a process reads is the part that catches people out, because the root .env is the one npm run setup creates and asks you to edit — and it is not read by the servers:

Reader Reads
docker-compose.yml The root .env, and only for the three names it substitutes (ADMIN_TOKEN, SESSION_PASSWORD, CHECKOUT_SESSION_PASSWORD)
setup, migrate, admin:create, run-local.bat The root .env, then .env.local. Neither overrides a variable already exported in the shell
seed, smoke, smoke:stack, smoke:landing, docs:check Only the process environment
API and worker <working directory>/.env, which under the workspace scripts is apps/api/.envnever the root .env
Console and checkout apps/<app>/.env in nuxt dev only. The built server (node .output/server/index.mjs, and therefore Compose) reads only the process environment

So npm run dev:api with a root .env full of values fails with ADMIN_TOKEN is required: export the variables in that shell, or put them in apps/api/.env. And a NUXT_* value that works in nuxt dev because it sits in apps/console/.env will be silently absent once the same application is built.

Never copy values from a real .env into documentation or commits. The examples below are placeholders or local-only defaults.

#Compose-facing variables

These are the variables a root .env should normally override:

Variable Required Purpose Safe example
ADMIN_TOKEN Yes outside Compose defaults Root bootstrap/CI credential; minimum 16 characters replace-with-random-hex
SESSION_PASSWORD Recommended Seals the console cookie in Compose replace-with-32-plus-random-characters
CHECKOUT_SESSION_PASSWORD Recommended Seals the separate checkout cookie different-32-plus-random-characters

Compose injects internal service URLs itself. Its readable default credentials are for a private development machine only.

#API process

Variable Required Default Purpose
ADMIN_TOKEN Yes none Bootstrap credential; API startup fails if absent/short
PORT No 8080 HTTP port
DATABASE_URL No local PostgreSQL on 54329 MikroORM connection
MIKRO_ORM_DEBUG No false Enables ORM debug output when exactly true
DEFAULT_SITE_ID No MLA Mercado Pago default site/currency mapping
API_PUBLIC_URL No http://localhost:8080 Public API origin for PayPal resource links and webhook certificate URL
CHECKOUT_PUBLIC_URL No http://localhost:3001 Origin used in Stripe redirects and PayPal approval links
ALLOW_CONTRACT_DOWNLOAD No false Opt-in guard for the contract importer
MERCADOPAGO_CONTRACT_URL No official allow-listed GitHub raw URL Import source override within importer rules

The HTTP API needs no queue at all. Webhook events are committed to PostgreSQL and the worker dispatches them later, through whichever transport WEBHOOK_QUEUE_DRIVER names.

#Worker process

The worker needs the same DATABASE_URL and ADMIN_TOKEN because it boots the shared Nest context for timed transitions.

It also chooses a webhook transport. WEBHOOK_QUEUE_DRIVER takes postgres (the default — the outbox carries its own deliveries and nothing else has to be running), mongo (MONGO_URL, MONGO_DB; a standalone mongod is enough, no replica set) or bullmq (REDIS_URL), which is what docker-compose names.

Variable Required Default Purpose
WEBHOOK_QUEUE_DRIVER No postgres postgres, mongo or bullmq
REDIS_URL For bullmq redis://localhost:6389 BullMQ connection. run-local.bat derives it from REDIS_PORT, so the port it checks and the port the worker dials cannot disagree
MONGO_URL For mongo mongodb://localhost:27017 Standalone mongod is enough
MONGO_DB No payment_emulator Database holding webhook_jobs
RUNTIME_OUTBOUND_ENABLED For delivery false Must equal true before webhook HTTP is allowed
WEBHOOK_ALLOWED_HOSTS No localhost,127.0.0.1,host.docker.internal Exceptions: hosts allowed in spite of resolving to an internal address. A public domain needs no entry
OUTBOX_INTERVAL_MS No 1000 Outbox polling interval
TIMED_INTERVAL_MS No 1000 Timed payment transition interval
SETTLEMENT_INTERVAL_MS No 60000 Reserve release sweep interval

Allowed hosts are comma-separated hostnames, not URL patterns. The worker also requires http: or https:.

#Console process

Nuxt runtime overrides must use Nuxt's NUXT_ names:

Variable Required Default Exposure
NUXT_API_URL No http://localhost:8080 Server only; NestJS origin
NUXT_ADMIN_TOKEN Needed for bootstrap fallback empty Server only; root operator token
NUXT_SESSION_PASSWORD Yes empty Server only; console cookie secret, 32+ chars
NUXT_PUBLIC_CHECKOUT_URL No http://localhost:3001 Browser-visible address
NUXT_PUBLIC_API_PUBLIC_URL No http://localhost:8080 Browser-visible address used for integration instructions
NUXT_PUBLIC_SITE_URL No empty Public origin of the deployment. Sets the canonical URL, the hreflang alternates, the Open Graph image and the addresses in robots.txt and sitemap.xml. Left empty, each request answers for its own host, which is what a local install needs

Normal proxied requests use the signed-in person's API session. The admin token remains only as a server-side bootstrap fallback and must never be put under runtimeConfig.public.

Documentation is built from local Markdown and served at /docs/es and /docs/en. It requires no GitHub credentials, public repository URL or published branch. See the documentation portal for publishing and validation.

#Checkout process

Variable Required Default Exposure
NUXT_API_URL No http://localhost:8080 Server only
NUXT_SESSION_PASSWORD Yes empty Server only; checkout cookie secret, 32+ chars

Use a different NUXT_SESSION_PASSWORD from the console. The cookies also have different names (emulator_console and emulator_checkout).

#Windows without Docker (run-local.bat)

The launcher reads the root .env and .env.local, then fills in what is missing. Its defaults are not always the code's defaults, because it runs against a PostgreSQL you installed rather than the one Compose publishes:

Variable Default in run-local.bat Purpose
PG_PORT discovered, then written to .env.local The port the local PostgreSQL answers on
PG_CANDIDATES 5432 5433 5434 5435 54329 The ports setup probes, newest instance wins
PGSUPERUSER / PGSUPERPASSWORD postgres / postgres Only used by setup, to create the payment role and the database
DATABASE_URL postgresql://payment:payment@localhost:5432/payment_emulator The local port, not the 54329 Compose publishes. A port inside this URL wins over PG_PORT
API_PORT 8080 Also sets API_URL; a PORT in the environment is read into it
CONSOLE_PORT / CHECKOUT_PORT 3000 / 3001 Also set CONSOLE_URL and CHECKOUT_URL
REDIS_PORT 6379 The locally installed default, not Compose's published 6389. REDIS_URL is derived from it
MONGO_PORT 27017 MONGO_URL is derived from it
WEBHOOK_ALLOWED_HOSTS localhost,127.0.0.1 Without host.docker.internal: there is no container to reach back into
ADMIN_TOKEN the Compose development token The API refuses to start without one, so the launcher supplies it
NUXT_SESSION_PASSWORD / CHECKOUT_SESSION_PASSWORD the Compose development secrets Different values for the two surfaces

run-local.bat doctor prints the ports and the chosen transport without changing anything, which is the fastest way to see what these resolved to.

#Script-only variables

Variable Script Default
API_URL setup, admin creation, seed and stack smoke http://localhost:8080
ADMIN_EMAIL admin creation asked for
ADMIN_PASSWORD admin creation asked for
ADMIN_NAME admin creation the part of the address before @
CONSOLE_URL stack smoke and landing smoke http://localhost:3000
CHECKOUT_URL stack smoke http://localhost:3001
SMOKE_EMAIL stack smoke smoke-operator@example.test
SMOKE_PASSWORD stack smoke smoke-operator-password
SEED_ADMIN_EMAIL seed operadora@example.test
SEED_PASSWORD seed emulator-demo-password

All of these except the landing smoke also read ADMIN_TOKEN. The seed and the stack smoke default it to the Compose local default; if Compose is overridden, pass the same value to the script process. setup and admin:create have no such default — they read .env and .env.local, and refuse rather than guess.

#Root commands

Command Does
npm run setup The clean installation: dependencies, build, database, migrations and the administrator. --docker/--local, --seed, --reset, --no-admin, --yes
npm run admin:create One administrator, through POST /auth/bootstrap, against a running API
npm run migrate Pending migrations. -- --down for one step back, -- --fresh for all of them and back again
npm run seed The demo data, through the HTTP surfaces
npm run smoke / npm run smoke:stack The offline smoke, and the one against a running stack
npm run smoke:landing The public page against a running console: canonical, hreflang, structured data, robots.txt, sitemap.xml, the 404 and the noindex on everything behind the login. CONSOLE_URL points it
npm run docs:check Local Markdown links and English/Spanish counterparts

setup, admin:create and migrate read .env and then .env.local, and neither file overrides a variable already exported in the shell. The seed, the smokes and the documentation check read only the process environment — see the table at the top.

#Execution modes

Mode Processes Intended use
Full Compose PostgreSQL, Redis, API, worker, console, checkout Recommended onboarding and acceptance; the worker is pinned to bullmq there
run-local.bat The four processes against a locally installed PostgreSQL Windows without Docker; reads .env.local for machine settings
run.bat Compose, wrapped Windows with Docker; up, dev, infra, setup, admin, reset and the rest
Direct development Dependencies in Compose; API/Nuxt development servers and compiled worker Fast code iteration
API-only PostgreSQL + API Provider/control-plane work without webhook delivery
Checks Typecheck, lint, Vitest, build, pure smoke Local/CI verification

#Contract download

Contract import is intentionally separate from runtime:

bash
ALLOW_CONTRACT_DOWNLOAD=true npm run contract:import:mercadopago --workspace @payment-emulator/api

It uses an allow-listed official URL and records a checksum. Do not enable downloads on the API or worker runtime merely to run the emulator. Official snapshots for all three providers are present; see the contract inventory. Their presence does not imply complete runtime schema validation.

Payment Emulator Lab · RonuSoftwareMIT