Multi-terminal Node.js + TypeScript app to pair with PayTec POS terminals via the cloud, send pairing codes, run transactions, and manage multiple terminals with individual loop configurations.
Uses the official PayTec ECR interface: PayTecAG/ecritf.
- Install dependencies
npm install- Start the app
npm run dev- Open the GUI
- Multi-terminal setup
- Terminal-ID wählen: Enter a terminal ID (e.g.,
default,99,1) and click "Laden" - Pairing: Enter the pairing code shown on the terminal (e.g., 6017087) and submit
- Automatic loading: Pairing info is persisted to
.data/pairings/{terminalId}.jsonand loaded on startup - Multiple terminals: Each terminal ID has its own pairing, loop settings, and transaction history
- Healthcheck
curl http://localhost:3000/healthz- Terminal selection: Choose terminal ID and load its configuration
- Pairing form: Send pairing code to specific terminal
- Status card: Current pairing info, terminal status, connection diagnostics
- Actions (per terminal):
- Terminal aktivieren →
/activate/:id - ACCOUNT_VERIFICATION →
/transaction/:id/account-verification - PURCHASE →
/transaction/:id/purchase(enter amount in minor units, e.g., 1575 = CHF 15.75)
- Terminal aktivieren →
- Loop modes (per terminal):
- ACCOUNT_VERIFICATION loop: auto-repeat after successful receipt/approval/decline/abort/timeout
- PURCHASE loop: configure amount/currency/delay for automatic PURCHASE retriggering
- Live logs: Streamed via Server-Sent Events from
/logs/:id(filtered by terminal) - Connection diagnostics: Test cloud connectivity and terminal readiness
- Results log: Transaction outcomes persisted to
.data/transactions.ndjsonwith terminal ID
GET /healthz→{ ok: true }GET /terminals→{ ids: string[] }- list all known terminal IDs
GET /pairing/:id→ pairing JSON for specific terminalPOST /pair/:id→{ code: string }pair specific terminalPOST /activate/:id→ activate specific terminalPOST /transaction/:id/account-verification→ start ACCOUNT_VERIFICATIONPOST /transaction/:id/purchase→{ AmtAuth: number, TrxCurrC?: number, RecOrderRef?: object }GET /logs/:id→ Server-Sent Events for specific terminal
GET /loop/:id→{ enabled: boolean, delayMs: number }- ACCOUNT_VERIFICATION loopPOST /loop/:id→{ enabled?: boolean, delayMs?: number }- configure ACCOUNT_VERIFICATION loopGET /loop/:id/purchase→{ enabled: boolean, amount: number, currency: number, delayMs: number }- PURCHASE loopPOST /loop/:id/purchase→{ enabled?: boolean, amount?: number, currency?: number, delayMs?: number }- configure PURCHASE loop
GET /diagnostics→{ ok: boolean, serverTime: string, uptimeSec: number, cloud: object }- global connectivity testGET /diagnostics/terminal/:id→{ ok: boolean, id: string, paired: boolean, status: number, ready: boolean }- terminal-specific status
- File:
.data/transactions.ndjson - One JSON object per line with fields:
ts: ISO timestampterminalId: terminal ID that generated the eventtype:transactionApproved | transactionDeclined | transactionAborted | transactionTimedOut | transactionConfirmationSucceeded | transactionConfirmationFailedstatus: last knownTrmStatusfor the terminalpayload: full PayTec event payload (includes amounts, AID, IIN, refs if provided)
- Pairing data:
.data/pairings/{terminalId}.json- one file per terminal ID - Transaction outcomes:
.data/transactions.ndjson- all outcomes with terminal ID - Terminal state: In-memory per-terminal loop configurations and status tracking
- Dev:
npm run dev- Build & start:
npm run build
npm startEnvironment variables:
PORT(default:3000)DATA_DIR(default:.data) directory for persisted pairing and logs
npm run dev→ start in watch mode with ts-node + nodemonnpm run build→ compile TypeScript todist/npm start→ run compiled app fromdist/
- Uses the official PayTec library from GitHub:
github:PayTecAG/ecritf - Cloud transport endpoint is managed internally by the PayTec library (
wss://ecritf.paytec.ch/smq.lsp) - Each terminal ID maintains its own pairing channel and connection state
- Pairing and transaction logs are visible both in the terminal output and in the GUI log panel
- Loop modes automatically retrigger transactions after completion (approved/declined/aborted/timeout)
- Connection diagnostics help troubleshoot cloud connectivity issues
- All endpoints are now ID-based for multi-terminal support
flowchart LR
subgraph Client
GUI["Browser GUI\n(public/index.html)"]
end
subgraph Server[Node.js/Express]
API["Express API\n/src/server.ts"]
TM["Terminal Manager\n/src/terminalManager.ts"]
LOGS["SSE /logs/:id\nsubscribeAll"]
LOOP["Per-terminal Loops\nAV + Purchase auto-retrigger"]
PERSIST["Persistence\n.data/pairings/{id}.json\n.data/transactions.ndjson"]
DIAG["Diagnostics\nCloud connectivity tests"]
end
subgraph PayTec
ECRITF["github:PayTecAG/ecritf\nPOSTerminal instances"]
CLOUD["SMQ Cloud\nwss://ecritf.paytec.ch/smq.lsp"]
TERM1["POS Terminal 1"]
TERM2["POS Terminal 2"]
TERMN["POS Terminal N"]
end
GUI -- "POST /pair/:id, /activate/:id, /transaction/:id/*, /loop/:id" --> API
GUI -- "EventSource /logs/:id" --> LOGS
GUI -- "GET /diagnostics" --> DIAG
API --> TM
TM --> ECRITF
ECRITF <--> CLOUD <--> TERM1
ECRITF <--> CLOUD <--> TERM2
ECRITF <--> CLOUD <--> TERMN
TM --> PERSIST
API --> PERSIST
LOOP --> API
LOGS --> GUI
sequenceDiagram
participant User as User
participant GUI as Browser GUI
participant API as eftkey API
participant TM as Terminal Manager
participant Lib as PayTec Library
participant Cloud as PayTec Cloud (SMQ)
participant Term1 as Terminal 1
participant Term2 as Terminal 2
User->>GUI: Select terminal ID "99"
GUI->>API: GET /pairing/99
API->>TM: getPairingById("99")
TM-->>API: Pairing info
API-->>GUI: Display pairing status
User->>GUI: Enter pairing code for terminal "99"
GUI->>API: POST /pair/99 {code}
API->>TM: pairTerminal("99", code)
TM->>Lib: Create/get terminal instance
Lib->>Cloud: Secure WebSocket (SMQ)
Cloud->>Term1: Forward pairing command
Term1-->>Cloud: Pairing success
Cloud-->>Lib: Pairing response
Lib-->>TM: Pairing callback
TM-->>API: Pairing complete
API-->>GUI: SSE /logs/99 (live updates)
API-->>API: Persist pairing (.data/pairings/99.json)
User->>GUI: Start transaction on terminal "99"
GUI->>API: POST /transaction/99/account-verification
API->>TM: getOrCreateTerminal("99")
TM->>Lib: trm.startTransaction
Lib->>Cloud: Transaction command
Cloud->>Term1: Forward transaction
Term1-->>Cloud: Transaction outcome
Cloud-->>Lib: Transaction response
Lib-->>TM: Transaction callback
TM-->>API: Transaction complete
API-->>GUI: SSE /logs/99 (transaction result)
API-->>API: Persist outcome (.data/transactions.ndjson)
Initialize and push to your repository:
git init
git add .
git commit -m "feat: multi-terminal eftkey with pairing, GUI, loops, and diagnostics"
git branch -M main
git remote add origin <YOUR_GIT_REMOTE_URL>
git push -u origin mainUse feature branches for changes:
git checkout -b feat/transaction-endpoints
# ...changes...
git commit -m "feat: add purchase endpoint"
git push -u origin feat/transaction-endpointsSee TECHNICAL.md for a deeper technical overview.