Skip to content
Payment Emulator LabDocumentationOpen console

Getting started

Install Payment Emulator Lab

A clean installation is one command. Everything below it is that command takenapart, for when a step has to be run on its own.

11 min read

Índice / Index · Español

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

bash
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
bash
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:

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:

bash
npm run admin:create

It asks for an email, a display name and a password, without echoing the password. Non-interactively:

bash
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:

bash
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:

powershell
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.

bash
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:

text
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

bash
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:

bash
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:

bash
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.

bash
docker compose up --build --wait
curl http://localhost:8080/health

Expected health response:

json
{"status":"ok","service":"payment-emulator-lab"}

On Windows, run.bat wraps Compose with the commands that are otherwise several flags long:

bat
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:

bash
docker compose up -d postgres redis
npm run migrate

Then start four long-running processes in separate terminals:

bash
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:

powershell
$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.

bat
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:

text
  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

bash
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:

bash
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

bash
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:

bash
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:

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:

bash
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.

Payment Emulator Lab · RonuSoftwareMIT