@danidoble/webserial-pax
v1.0.1
Published
A strongly-typed, event-driven USB pax driver for the Web Serial API, built on top of webserial-core.
Downloads
174
Readme
@danidoble/webserial-pax
A strongly-typed, event-driven PAX payment terminal driver built on top of webserial-core.
Handles the serial connection, JSON handshake, auto-reconnect, and message routing — so you only deal with clean, typed events.
Not tied to a single transport: swap in the WebUSB, Web Bluetooth, or WebSocket provider from webserial-core, or implement your own SerialProvider for any platform.
Requirements
webserial-core^2.1.0(peer dependency)- A compatible transport (see Providers):
- Web Serial API — Chrome / Edge 89+ (default, no extra setup)
- WebUSB — Chrome / Edge (via
WebUsbProvider) - Web Bluetooth — Chrome / Edge (via
createBluetoothProvider, Nordic UART Service) - WebSocket — any environment (via
createWebSocketProvider+ a bridge server) - Custom — any platform via your own
SerialProviderimplementation
Installation
# npm
npm install @danidoble/webserial-pax webserial-core
# pnpm
pnpm add @danidoble/webserial-pax webserial-core
# yarn
yarn add @danidoble/webserial-pax webserial-core
# bun
bun add @danidoble/webserial-pax webserial-core
webserial-coreis a peer dependency — it must be installed alongside this package.
Quick start
import { Pax } from '@danidoble/webserial-pax';
import type { SerialPortFilter } from '@danidoble/webserial-pax';
const filters: SerialPortFilter[] = [{ usbVendorId: 0x0ed3 }];
const pax = new Pax({ filters });
// Serial lifecycle
pax.on('serial:connecting', () => console.log('Opening port…'));
pax.on('serial:connected', () => console.log('Port open'));
pax.on('serial:disconnected', () => console.log('Disconnected'));
// PAX events
pax.on('pax:connected', ({ name }) => console.log('PAX ready:', name));
pax.on('pax:payment', data => console.log('Payment event:', data));
pax.on('pax:error', data => console.error('PAX error:', data));
// Opens a port picker dialog (requires a user gesture)
await pax.connect();
// Set credentials for MIT login
pax.server = 'PROD';
pax.businessId = 'your-business-id';
pax.encryptionKey = 'your-encryption-key';
pax.apiKey = 'your-api-key';
// Perform a sale (blocks until terminal responds)
const approved = await pax.sendSale({ amount: 100.0, reference: 'ORDER-001' });
console.log('Approved:', approved);
// Retrieve a voucher
const voucher = await pax.sendVoucher({ folio: 12345 });
console.log('Voucher:', voucher);
// Refund
await pax.sendRefund({ amount: 100.0, folio: '12345', auth: '654321' });Serial settings
The constructor pre-configures the following defaults — no extra setup needed:
| Setting | Value |
| ------------------ | ------------------ |
| Baud rate | 115 200 |
| Data bits | 8 |
| Stop bits | 1 |
| Parity | none |
| Flow control | none |
| Buffer size | 32 768 B |
| Parser | delimiter (\r\n) |
| Command timeout | 5 000 ms |
| Auto-reconnect | ✓ |
| Reconnect interval | 1 500 ms |
| Handshake timeout | 4 000 ms |
Providers
By default the library uses the browser's native Web Serial API (navigator.serial). You can replace this with any of the built-in providers from webserial-core, or write your own.
Web Serial API (default)
No setup required — works out of the box in Chrome / Edge 89+.
import { Pax } from '@danidoble/webserial-pax';
const pax = new Pax({ filters: [{ usbVendorId: 0x0ed3 }] });
await pax.connect();WebUSB (WebUsbProvider)
import { Pax, WebUsbProvider } from '@danidoble/webserial-pax';
const pax = new Pax({
filters: [{ usbVendorId: 0x0ed3 }],
provider: new WebUsbProvider()
});
await pax.connect();Web Bluetooth (createBluetoothProvider)
import { Pax, createBluetoothProvider } from '@danidoble/webserial-pax';
const pax = new Pax({ provider: createBluetoothProvider() });
await pax.connect();WebSocket (createWebSocketProvider)
import { Pax, createWebSocketProvider } from '@danidoble/webserial-pax';
const pax = new Pax({
provider: createWebSocketProvider('ws://localhost:8080')
});
await pax.connect();Global provider (AbstractSerialDevice.setProvider)
Set a provider once for all device instances instead of per-instance. Import AbstractSerialDevice directly from webserial-core:
import { AbstractSerialDevice, WebUsbProvider } from 'webserial-core';
import { Pax } from '@danidoble/webserial-pax';
AbstractSerialDevice.setProvider(new WebUsbProvider());
const pax = new Pax({ filters: [{ usbVendorId: 0x0ed3 }] });
await pax.connect();Custom provider
Implement the SerialProvider interface to target any platform:
import type { SerialProvider, SerialPortFilter } from '@danidoble/webserial-pax';
import { Pax } from '@danidoble/webserial-pax';
const myProvider: SerialProvider = {
async requestPort(options?: { filters?: SerialPortFilter[] }): Promise<SerialPort> {
// return a SerialPort-compatible object
},
async getPorts(): Promise<SerialPort[]> {
// return previously authorised ports
}
};
const pax = new Pax({ provider: myProvider });
await pax.connect();API
Constructor
new Pax(options?: PaxOptions)| Option | Type | Default | Description |
| ----------------- | --------------------- | ------- | ------------------------------------------------- |
| filters | SerialPortFilter[] | [] | USB filters shown in the port picker dialog. |
| provider | SerialProvider | — | Custom serial transport (WebUSB, WebSocket, etc). |
| polyfillOptions | SerialDeviceOptions | — | Low-level serial settings override. |
Properties
pax.server; // get / set — 'PROD' | 'QA' | 'DEV'
pax.businessId; // get / set — string | null
pax.encryptionKey; // get / set — string | null
pax.apiKey; // get / set — string | nullMethods
pax.connect()
Opens the port picker and performs the handshake. Inherited from AbstractSerialDevice.
pax.sendConnectMessage()
Sends a CONNECT command manually.
pax.sendSale(options)
Performs a full sale flow: login → init → payment. Resolves to true if approved, false otherwise. Throws if a sale is already in progress.
| Option | Type | Default | Description |
| ----------- | ---------------- | ------- | -------------------------------------------- |
| amount | number | 0 | Sale amount. Must be greater than 0. |
| reference | string \| null | null | Alphanumeric reference (allows - and _). |
const approved = await pax.sendSale({ amount: 150.0, reference: 'ORDER-42' });pax.sendVoucher(options)
Requests the last voucher by folio. Resolves to the voucher data or rejects on timeout (10 s).
| Option | Type | Description |
| ------- | -------- | ---------------------------------------------- |
| folio | number | Folio number returned after a successful sale. |
const voucher = await pax.sendVoucher({ folio: 12345 });pax.sendRefund(options?)
| Option | Type | Default | Description |
| -------- | -------------------------- | ------- | --------------------------- |
| amount | number | 0 | Refund amount (> 0). |
| folio | number \| string \| null | null | Original transaction folio. |
| auth | number \| string \| null | null | Original transaction auth. |
await pax.sendRefund({ amount: 150.0, folio: '12345', auth: '654321' });pax.sendInfo()
Requests device information. Emits pax:info.
pax.sendKeepAlive()
Sends a keep-alive ping. Emits pax:keep-alive.
pax.sendRestartApp()
Requests the PAX app to restart. Emits pax:reset-app.
pax.sendGetConfig()
Requests the device configuration. Emits pax:get-config.
pax.sendHideButtons()
Hides the terminal's on-screen buttons.
pax.sendShowButtons()
Shows the terminal's on-screen buttons.
pax.sendDemo()
Triggers demo mode on the terminal.
pax.sendProductionQR()
Reads a QR code in production mode.
pax.sendQualityAssuranceQR()
Reads a QR code in QA mode.
pax.sendExit()
Sends an exit command to the terminal.
pax.stop()
Sends an stop request to cancel sale if is posible
pax.verifyNetwork
Check if pax device has connection to internet
pax.sendCustomCode(obj)
Sends a raw JSON command. The object must have an action key.
await pax.sendCustomCode({ action: 'CUSTOM_CMD', extra: 'value' });pax.softReload()
Resets in-progress sale / voucher state without reconnecting. Useful for recovering from stale operations.
pax.cancelSaleRequestInProcess()
Cancels an in-progress sale that is still awaiting a response.
pax.isConnected()
Returns true when the port is open and the handshake has completed.
Commands (static helpers)
All commands are available as standalone static methods for building raw payloads:
import { Commands } from '@danidoble/webserial-pax';
Commands.connection(); // CONNECT handshake
Commands.expectedResponseConnection(); // expected CONNECT response
Commands.makeSale({ amount: 100 }); // PAYMENT
Commands.makeSale({ amount: 100, reference: 'X' }); // PAYMENT with reference
Commands.getVoucher({ folio: 12345 }); // GETVOUCHER
Commands.refund({ amount: 100, folio: '1', auth: '2' }); // REFUND
Commands.info(); // DEVICEINFO
Commands.keepAlive(); // KEEPALIVE
Commands.restartApp(); // RESETAPP
Commands.getConfig(); // GETCONFIG
Commands.hideButtons(); // HIDEBUTTONS
Commands.showButtons(); // SHOWBUTTONS
Commands.forceHide(); // FORCEHIDE
Commands.forceShow(); // FORCESHOW
Commands.demo(); // DEMO
Commands.exit(); // EXIT
Commands.init(); // INIT
Commands.readQR(); // READQR (production)
Commands.readQR({ type: 'QA' }); // READQR (QA)
Commands.loginMit({ server, business_id, encryption_key, api_key });
Commands.stop(); // STOP
Commands.verifyNetwork(); // CHECKINTERNET
Commands.custom({ action: 'MY_ACTION' }); // custom payloadEvents
Core events (from webserial-core)
| Event | Payload | Description |
| ------------------------ | ----------------------------- | ------------------------------------------------- |
| serial:connecting | instance | Port is being opened. |
| serial:connected | instance | Port opened successfully. |
| serial:disconnected | instance | Port closed or device unplugged. |
| serial:reconnecting | instance | Auto-reconnect attempt in progress. |
| serial:data | data: string, instance | Parsed string frame received from the device. |
| serial:sent | data: string, instance | Data written to the port. |
| serial:error | error: Error, instance | An error occurred during communication. |
| serial:need-permission | instance | No authorised port found; user must grant access. |
| serial:timeout | command: string, instance | A queued command timed out. |
PAX events
| Event | Payload | Description |
| -------------------- | ----------------------------------------- | ---------------------------------------------------------- |
| pax:connected | { name, request, status } | Handshake succeeded; PAX is ready. |
| pax:init | { name, request, status } | INIT response received. |
| pax:init-app | { name, request, status } | INITAPP response received. |
| pax:login | Record<string, unknown> | LOGIN response received. |
| pax:voucher | Record<string, unknown> | LASTVOUCHER response received. |
| pax:info | Record<string, unknown> | DEVICEINFO response received. |
| pax:keep-alive | { name, request, status } | KEEPALIVE response received. |
| pax:reset-app | { name, request, status } | RESETAPP response received. |
| pax:get-config | Record<string, unknown> | GETCONFIG response received. |
| pax:buttons-status | { name, request, hidden: boolean } | Button visibility changed. |
| pax:payment | Record<string, unknown> | Any payment-related response (process, card data, result). |
| pax:error | Record<string, unknown> | An ERROR response was received. |
| pax:refund | Record<string, unknown> | REFUND response received. |
| pax:message | Record<string, unknown> \| string | Emitted for every incoming message (parsed or raw string). |
| pax:network | { name, request, hasInternet, quality } | Response to check CHECKINTERNET |
TypeScript
All events and method signatures are fully typed. The package ships with .d.mts / .d.cts declaration files — no extra @types package required.
Commonly used types and all built-in providers are re-exported so you do not need to import directly from webserial-core:
import {
Pax,
Commands,
WebUsbProvider,
createBluetoothProvider,
createWebSocketProvider
} from '@danidoble/webserial-pax';
import type {
PaxOptions,
MakeSaleOptions,
GetVoucherOptions,
RefundOptions,
LoginMitOptions,
ServerType,
SerialPortFilter,
SerialDeviceOptions,
SerialEventMap,
SerialProvider,
SerialPolyfillOptions
} from '@danidoble/webserial-pax';