A clean installation is one command. Everything below it is that command taken apart, for when a step has to be run on its own.
#Requirements
| Requirement | Confirmed version or constraint |
|---|---|
| Git | Any current version |
| Node.js | >=22.17.0 |
| npm | Lockfile generated for npm; use npm ci |
| Docker Engine + Compose | Optional. Without it, a locally installed PostgreSQL |
The current CI uses Node 24. Node 22 is the minimum declared by the root and API manifests.
#Install
git clone <repository-url>
cd payment-emulator-lab
npm run setup
npm run setup does the whole installation, in order: checks Node, creates
.env from .env.example if it is missing, installs from the lockfile with
npm ci, builds the three workspaces, brings up the database, applies the
migrations, and creates the administrator you will sign in as. It asks for that
administrator's email and password, and nothing else.
It uses Docker when the engine answers and a locally installed PostgreSQL when it
does not. Say which explicitly with --docker or --local.
| Flag | Effect |
|---|---|
--docker / --local |
Choose the mode instead of detecting it |
--seed |
Also load the demo data |
--reset |
Destroy the existing data first. Asks for confirmation |
--no-admin |
Stop after the migrations, with an empty database |
--yes |
Never ask. For continuous integration |
--admin-email --admin-password --admin-name |
Supply the administrator instead of being asked |
npm run setup -- --local --seed
npm run setup -- --reset
npm run setup -- --yes --admin-email admin@example.test --admin-password "..." --admin-name Admin
In Docker mode the stack is left running:
- Console: http://localhost:3000
- Checkout: http://localhost:3001
- API: http://localhost:8080
In local mode nothing is started; the database is ready and the four processes are yours to start (see Run in development mode).
On Windows, run.bat setup reaches this same script. Without Docker there is a
step first that no portable script can take: configure-local.bat — the same
wizard as run-local.bat setup — asks about your PostgreSQL, creates the
payment role and the payment_emulator database if they are missing, picks
ports, mode and webhook transport, generates any missing secrets and writes all
of it into .env.local before calling this installer.
#The administrator
There is no default account and no console password. There are people, with
roles, and the first one is created with the operator token — ADMIN_TOKEN,
which acts as an administrator but is nobody. See
Identity, roles and scope.
npm run setup creates that first person. To create one at any other time,
against a running API:
npm run admin:create
It asks for an email, a display name and a password, without echoing the password. Non-interactively:
npm run admin:create -- --email admin@example.test --password "..." --name Admin
The command is idempotent by email: an address that already exists is left exactly as it is, password included. After creating the account it signs in with it and signs out again, so a bootstrap that succeeded but left an account nobody can use fails here rather than at the console's login form.
It reads ADMIN_TOKEN and API_URL from the environment, then from .env and
.env.local. Underneath it is one call, POST /auth/bootstrap, which you can
also make by hand:
curl -X POST http://localhost:8080/auth/bootstrap \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.test","displayName":"Local admin","role":"admin","password":"change-this-password"}'
In PowerShell, curl is an alias for Invoke-WebRequest and neither -X nor
single-quoted JSON survives it. Use npm run admin:create, or:
Invoke-RestMethod -Method Post http://localhost:8080/auth/bootstrap `
-Headers @{ Authorization = "Bearer $env:ADMIN_TOKEN" } -ContentType 'application/json' `
-Body '{"email":"admin@example.test","displayName":"Local admin","role":"admin","password":"change-this-password"}'
Once an administrator exists, sign in through the console or with
POST /auth/login. Normal control-plane calls should use that person's session,
not pretend the operator token is a person.
#Demo data
The seed is separate from the installation on purpose: an installation you are going to work in should not start out holding somebody else's payments.
npm run seed
npm run seed -- --keep # add to what is already there
It calls the real HTTP surfaces — nothing in it reaches past the API — and creates people, applications, payments in every canonical state, accounts, settlements, a withdrawal, a chargeback and a hand-written scenario. It also creates its own administrator:
operadora@example.test / emulator-demo-password
Those are deliberately visible demo credentials. Before exposing the stack to
another machine, replace ADMIN_TOKEN, SESSION_PASSWORD and
CHECKOUT_SESSION_PASSWORD in .env, and do not reuse those values outside this
emulator. If you override ADMIN_TOKEN, set that same variable in the shell that
runs the seed.
#Migrations
npm run migrate # apply what is pending
npm run migrate -- --down # undo the last one
npm run migrate -- --fresh # undo everything, then apply everything
The wrapper reads .env and .env.local, builds the API if dist/ is not there
and waits for PostgreSQL to accept connections, so it is safe to run against a
database that is still starting. Under Compose it is rarely needed: the api
service's own command migrates before it serves.
--fresh is the portable empty database. Dropping and recreating needs a
superuser and a client this project does not require, so it walks every migration
back and applies them again — which also means the down half is exercised
rather than assumed.
Migrations and the worker run from dist/, never through tsx: the entities
carry their property types in emitDecoratorMetadata, and esbuild — which is
what tsx runs on — does not emit it. Inside the API workspace the same three
commands are npm run migrate, migrate:down and migrate:fresh.
#Verify it works
The stack smoke uses the console and checkout through their Nitro routes, calls the provider APIs as an integration, and checks that all three providers and the ledger behave coherently:
npm run smoke:stack
The smaller smoke does not need a running server or database. It validates money round trips, scenario matching and static conformance for every plugin:
npm run smoke
#Run the stack by hand
The Compose path runs the same six pieces exercised by CI: PostgreSQL, Redis, API, worker, console and checkout.
docker compose up --build --wait
curl http://localhost:8080/health
Expected health response:
{"status":"ok","service":"payment-emulator-lab"}
On Windows, run.bat wraps Compose with the commands that are otherwise several
flags long:
run.bat :: the whole stack in Docker
run.bat dev :: Postgres and Redis in containers, the four processes local
run.bat infra :: only Postgres and Redis
run.bat setup :: the clean installation above
run.bat admin :: create an administrator
run.bat migrate :: apply pending migrations
run.bat seed :: demo data
run.bat smoke :: smoke test against the running stack
run.bat logs [svc] :: follow a service's logs
run.bat status :: what is running
run.bat down :: stop the containers, keep the data
run.bat reset :: stop and delete the volumes
run.bat env :: create .env from .env.example
#Run in development mode
Use Compose for the dependencies only:
docker compose up -d postgres redis
npm run migrate
Then start four long-running processes in separate terminals:
npm run dev:api
npm run start:worker --workspace @payment-emulator/api
npm run dev:console
npm run dev:checkout
Before starting each process, provide its environment as described in
Configuration. The important direct-development
distinction is that both Nuxt applications read NUXT_SESSION_PASSWORD, but they
must receive different values in their separate processes.
The root .env is not read here. It is the file npm run setup created, and
Compose and the Node scripts read it, but a directly started API reads the .env
of its own working directory — apps/api/.env under these commands — so a full
root .env still ends in ADMIN_TOKEN is required. Export the variables in each
terminal, as below, or write them into apps/api/.env. The two Nuxt applications
behave the same way with apps/console/.env and apps/checkout/.env, with one
trap of their own: nuxt dev reads those files and the built server does not, so
a value that worked all through development disappears the moment it is built.
For PowerShell, a minimal API terminal looks like:
$env:ADMIN_TOKEN = 'replace-with-at-least-16-characters'
$env:DATABASE_URL = 'postgresql://payment:payment@localhost:54329/payment_emulator'
npm run dev:api
The API refuses to start if ADMIN_TOKEN is absent or shorter than 16
characters. The worker needs the database URL and, unless it is using the default
postgres transport, whatever WEBHOOK_QUEUE_DRIVER points it at; set
RUNTIME_OUTBOUND_ENABLED=true only when webhook callbacks should actually be
sent. The start:worker:dev script uses tsx and cannot supply the decorator
metadata the entities need; rebuild the API and use start:worker.
#Run on Windows without Docker
run-local.bat runs the same four processes against a PostgreSQL already
installed on the machine. It exists because a local PostgreSQL is rarely on the
port Compose publishes, and because the webhook transport is a choice.
configure-local.bat :: asks for the configuration, prepares the install and starts the four processes
run-local.bat setup :: the same wizard
run-local.bat doctor :: reports configuration, services and ports, changing nothing
run-local.bat :: starts API, worker, console and checkout, one window each
run-local.bat admin :: create an administrator
run-local.bat stop :: lists this project's windows and offers to close them
The wizard writes its answers into .env.local, the machine-local settings file
the launcher reads back on every run: the connection, the three ports, the mode,
WEBHOOK_QUEUE_DRIVER and the secrets all live there. It is gitignored, and the
wizard replaces only its own block — anything you wrote by hand stays. Everything
after configuring is the portable installer, so what happens here and what
happens on macOS is the same code.
The worker only starts when its transport is reachable, and doctor says which
one that is:
Transporte de webhooks
driver postgres
necesita nada mas: entrega desde el outbox
With the default postgres transport nothing else has to be running, which is
the point of it on a machine where Redis would mean WSL. See
Configuration and
Webhooks.
#Start over
npm run setup -- --reset
Under Docker that deletes the PostgreSQL and Redis volumes; locally it walks
every migration back and applies them again. Either way it asks first, and
--yes is how a pipeline says it meant it.
To stop without losing anything:
docker compose down
That keeps the named volumes. docker compose down --volumes also deletes their
local data.
#Change a password by email
For an existing account, you need the API and PostgreSQL running, migrations
applied and the ADMIN_TOKEN used to start the API. The console, checkout and
worker are not required. Run the following commands from the repository root,
after preparing the installation.
#1. Build the NestJS server
npm run build --workspace @payment-emulator/api
This compiles scenarios first, then the API. Build output goes to apps/api/dist.
#2. Start the API and leave that terminal open
To load the root configuration explicitly, including .env.local when present:
node --env-file=.env --env-file-if-exists=.env.local apps/api/dist/main.js
Configure DATABASE_URL and ADMIN_TOKEN in those files before starting.
ADMIN_TOKEN must contain at least 16 characters and match the CLI's token.
PostgreSQL must be reachable at the address configured in DATABASE_URL.
You can also use npm run start --workspace @payment-emulator/api if variables
are already configured in the terminal or in apps/api/.env. That script does
not automatically load the root .env. If it reports
ADMIN_TOKEN is required and must be at least 16 characters, check that the
token is configured and use the explicit start command above. An existing valid
token does not need to be regenerated, and loading it does not require a rebuild.
Alternatively, configure the API terminal in PowerShell:
$env:ADMIN_TOKEN = 'the-same-token-configured-in-your-env'
$env:DATABASE_URL = 'postgresql://payment:payment@localhost:54329/payment_emulator'
npm run start --workspace @payment-emulator/api
Replace the token with your installation's value and adjust the PostgreSQL user,
password, port and database. The CLI does read the root .env and .env.local;
the effective ADMIN_TOKEN must match in both processes. Variables exported in
one terminal do not propagate to another terminal that is already open.
#3. Reset the password from another terminal
With the API running, open another terminal at the repository root:
npm run password:reset -- --email user@example.test
Replace the address with your account's email. If the API was already running,
you can go straight to this step. To be prompted for the email too, run
npm run password:reset without arguments.
The command prompts for a hidden password and asks you to repeat it. Automation
can use USER_PASSWORD or --password; --api selects the API. It reads
ADMIN_TOKEN from .env and .env.local, like admin:create. Use --help for
options. It finds the exact normalized address across all required pages and
updates it through PATCH /users/:id. The API validates 8–200 characters, hashes
the password and revokes prior sessions. The CLI verifies login and closes its
verification session. A disabled account gets a new password without being
activated. Passwords are never printed or stored by the command. There is no
universal administrator password: use the one chosen at account creation or set
a new one here.