@danidoble/webserial-pinpad
v1.0.0
Published
A strongly-typed, event-driven USB pinpad driver for the Web Serial API, built on top of webserial-core.
Readme
@danidoble/webserial-pinpad
A strongly-typed, event-driven pinpad driver built on top of webserial-core.
Handles the serial connection, binary handshake, auto-reconnect, DUKPT key injection, EMV card reading, and communication with the MIT payment gateway — so you only deal with clean, typed events and simple async methods.
Compatible with Verifone and Ingenico terminals. 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 browser that exposes one of the following transport APIs:
- 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
- A MIT payment gateway account (
username+password) for gateway operations
Installation
# npm
npm install @danidoble/webserial-pinpad webserial-core
# pnpm
pnpm add @danidoble/webserial-pinpad webserial-core
# yarn
yarn add @danidoble/webserial-pinpad webserial-core
# bun
bun add @danidoble/webserial-pinpad webserial-core
webserial-coreis a peer dependency — it must be installed alongside this package.
Quick start
import { PinPad } from '@danidoble/webserial-pinpad';
const pinpad = new PinPad({
username: 'YOUR_MIT_USER',
password: 'YOUR_MIT_PASSWORD',
environment: 'production',
filters: [{ usbVendorId: 0x0801 }], // optional USB filter
});
// Serial lifecycle events
pinpad.on('serial:connecting', () => console.log('Opening port…'));
pinpad.on('serial:connected', () => console.log('Port open'));
pinpad.on('serial:disconnected', () => console.log('Disconnected'));
// PinPad events
pinpad.on('pinpad:connected', () => console.log('PinPad ready'));
pinpad.on('pinpad:processing-card', ({ waiting }) => console.log('Processing…', waiting));
pinpad.on('pinpad:read-card', (data) => console.log('Card read:', data.maskPan));
pinpad.on('pinpad:error', (err) => console.error(`[${err.error}] ${err.message}`));
pinpad.on('pinpad:print', (evt) => console.log('Print event:', evt.type));
pinpad.on('pinpad:dukpt', (evt) => console.log('DUKPT:', evt.status));
// Opens a port picker dialog (requires a user gesture)
await pinpad.connect();
// ── Sale ────────────────────────────────────────────────────────
const result = await pinpad.sendSale({ amount: 150.00, reference: 'ORDER001' });
if (result.approved) {
console.log('Approved:', result.object);
} else {
console.error('Declined:', result.message);
}
// ── Cancel / void ───────────────────────────────────────────────
await pinpad.cancelPurchase({
amount: 150.00,
authorization: 'ABC123', // 6-char alphanumeric
folio: '123456789', // 9-digit operation number
});
// ── Re-print voucher ────────────────────────────────────────────
await pinpad.rePrint({ folio: '123456789' });
// ── Consult transaction ─────────────────────────────────────────
const tx = await pinpad.consult({ reference: 'ORDER001' });
console.log(tx);Serial settings
The constructor pre-configures the following defaults — no extra setup needed:
| Setting | Value | | ------------------- | ------------------------- | | Baud rate | 19 200 | | Data bits | 8 | | Stop bits | 1 | | Parity | none | | Flow control | none | | Buffer size | 32 768 B | | Parser | interByteTimeout (50 ms) | | Command timeout | 30 000 ms | | Auto-reconnect | enabled (1 500 ms) | | Handshake timeout | 5 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 { PinPad } from '@danidoble/webserial-pinpad';
const pinpad = new PinPad({ username: 'USER', password: 'PASS' });
await pinpad.connect();WebUSB (WebUsbProvider)
import { PinPad, WebUsbProvider } from '@danidoble/webserial-pinpad';
const pinpad = new PinPad({
username: 'USER',
password: 'PASS',
provider: new WebUsbProvider(),
});
await pinpad.connect();Web Bluetooth (createBluetoothProvider)
import { PinPad, createBluetoothProvider } from '@danidoble/webserial-pinpad';
const pinpad = new PinPad({
username: 'USER',
password: 'PASS',
provider: createBluetoothProvider(),
});
await pinpad.connect();WebSocket (createWebSocketProvider)
import { PinPad, createWebSocketProvider } from '@danidoble/webserial-pinpad';
const pinpad = new PinPad({
username: 'USER',
password: 'PASS',
provider: createWebSocketProvider('ws://localhost:8080'),
});
await pinpad.connect();API
new PinPad(options?)
| Option | Type | Default | Description |
| ----------------- | --------------------- | -------------- | ------------------------------------------------------------ |
| username | string \| null | null | MIT gateway username. |
| password | string \| null | null | MIT gateway password (stored as uppercase). |
| environment | PinPadEnvironment | 'production' | Target environment for API calls. |
| filters | SerialPortFilter[] | [] | USB vendor/product filters for port matching. |
| provider | SerialProvider | — | Per-instance transport provider. |
| polyfillOptions | SerialDeviceOptions | — | Extra options forwarded to the underlying AbstractSerialDevice. |
pinpad.connect()
Opens the serial port and performs the device handshake (about command). Shows a browser port-picker dialog on first use.
await pinpad.connect();pinpad.disconnect()
Gracefully closes the port and stops auto-reconnect.
await pinpad.disconnect();pinpad.login({ force? })
Authenticates against the MIT gateway. Caches credentials in localStorage for 24 hours. Pass force: true to bypass the cache.
const info = await pinpad.login(); // cached
const info = await pinpad.login({ force: true }); // force refreshpinpad.sendSale({ amount, reference })
Full sale flow: login → device init → read card → process payment. Returns a SaleResult.
const result = await pinpad.sendSale({ amount: 250.00, reference: 'ORDER123' });
// result: { error: boolean, message: string | null, approved: boolean, object: Record<string, unknown> }| Option | Type | Description |
| ----------- | ------------------ | --------------------------------------- |
| amount | number | Amount in currency units (e.g. 150.00). Must be > 0. |
| reference | string \| null | Alphanumeric transaction reference. |
pinpad.cancelPurchase({ amount, authorization, folio })
Voids a previously approved transaction. Returns the raw gateway JSON string.
await pinpad.cancelPurchase({
amount: 250.00,
authorization: 'ABC123', // exactly 6 alphanumeric characters
folio: '123456789', // exactly 9-digit operation number
});pinpad.rePrint({ folio? })
Re-prints the last voucher or a specific one by folio. Stores the decoded commerce and client vouchers internally.
await pinpad.rePrint({ folio: '123456789' });
// then print:
await pinpad.sendPrint('client');
await pinpad.sendPrint('commerce');pinpad.consult({ reference? })
Queries the status of a transaction by reference.
const tx = await pinpad.consult({ reference: 'ORDER123' });pinpad.sendAbout()
Queries the terminal for device information (model, serial, capabilities). Called automatically during handshake.
await pinpad.sendAbout();pinpad.sendReadCard()
Initiates the card-reading process. Requires amount to be set beforehand. Resolves when a card is read or rejects on timeout / error.
pinpad.amount = 150.00;
await pinpad.sendReadCard();pinpad.sendCancelReadCard()
Cancels an in-progress card read.
await pinpad.sendCancelReadCard();pinpad.sendPrint(voucherType?)
Prints the stored voucher. Pass 'client' (default) or 'commerce'.
await pinpad.sendPrint('client');
await pinpad.sendPrint('commerce');pinpad.sendFinishEMV()
Sends the finish-EMV command after a successful sale. Called automatically inside sendSale.
await pinpad.sendFinishEMV();pinpad.sendCustomCode(code)
Sends a raw string command directly to the device. Useful for debugging or unsupported commands.
await pinpad.sendCustomCode('\x02012VXVCANCEL\x03l');pinpad.getPosition()
Requests the device geolocation via navigator.geolocation. Caches the result until clearSession() is called.
const { latitude, longitude } = await pinpad.getPosition();pinpad.checkPositionPermission()
Returns true if the geolocation permission is already granted.
const granted = await pinpad.checkPositionPermission();pinpad.getClientVoucher() / pinpad.getCommerceVoucher()
Returns the last decoded client or commerce voucher string (empty string if unavailable).
const clientVoucher = pinpad.getClientVoucher();
const commerceVoucher = pinpad.getCommerceVoucher();pinpad.clearSession()
Removes cached login response, RSA key, and public IP from localStorage.
pinpad.clearSession();pinpad.isConnected()
Returns true when the port is open and the handshake has succeeded.
Getters and setters
| Property | Type | Description |
| --------------- | ------------------- | ------------------------------------------------------------------------------- |
| username | string \| null | MIT gateway username. |
| password | string \| null | MIT gateway password (read as uppercase). |
| amount | number | Transaction amount. Must be > 0. |
| reference | string | Transaction reference. Alphanumeric, no special characters. |
| environment | PinPadEnvironment | Active environment. One of development, qa, production, productionAlternative. |
| timeoutPinPad | number | Card-read timeout in seconds (11–299). Default 100. |
| url | string (readonly) | Base URL for the current environment. |
| version | object (readonly) | { name, version, environment } — library name, version, and current env. |
Events
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: Uint8Array, instance | Raw binary frame received from the device. |
| serial:sent | data: Uint8Array, instance | Raw bytes 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: Uint8Array, instance | A queued command timed out. |
PinPad events
| Event | Payload | Description |
| -------------------------- | -------------------------------- | ------------------------------------------------------------------ |
| pinpad:connected | — | Handshake succeeded; terminal is ready for commands. |
| pinpad:processing-card | { waiting: boolean } | Terminal is processing the card (e.g. chip being read). |
| pinpad:read-card | PinPadReadCardEvent | Card read successfully. Contains masked PAN, name, expiry. |
| pinpad:error | PinPadErrorEvent | A terminal or gateway error occurred. |
| pinpad:print | PinPadPrintEvent | Print operation result (success, warning, or error). |
| pinpad:dukpt | PinPadDukptEvent | DUKPT key status (unsupported or charged). |
| pinpad:finish-emv | Record<string, unknown> | EMV finish response from the gateway. |
| pinpad:response | { raw: string } | Raw string response from the terminal (emitted for every frame). |
PinPadReadCardEvent
interface PinPadReadCardEvent {
ERROR: string;
maskPan: string; // masked card number, e.g. '411111******1111'
name: string; // cardholder name
month: string; // expiry month (MM)
year: string; // expiry year (YY)
}PinPadErrorEvent
interface PinPadErrorEvent {
error: string; // error code, e.g. 'A10'
message: string; // human-readable message
}PinPadPrintEvent
interface PinPadPrintEvent {
type?: string; // 'success' | 'warning' | 'error'
error?: boolean;
code?: string;
message?: string;
}Environments
| Value | URL |
| ------------------------ | --------------------------------- |
| development | https://fcdev.mitec.com.mx |
| qa | https://fcqa.mitec.com.mx |
| production | https://m.mit.com.mx |
| productionAlternative | https://m2.mit.com.mx |
The gateway automatically falls back to productionAlternative on the second retry of a sale.
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 {
PinPad,
WebUsbProvider,
createBluetoothProvider,
createWebSocketProvider,
} from '@danidoble/webserial-pinpad';
import type {
PinPadOptions,
PinPadAbout,
PinPadAboutPP,
PinPadConfig,
PinPadReadConfig,
PinPadOperation,
PinPadEnvironment,
PinPadReadCardEvent,
PinPadErrorEvent,
PinPadPrintEvent,
PinPadDukptEvent,
SaleResult,
WaitingStatus,
PinPadDukptStatus,
SerialPortFilter,
SerialDeviceOptions,
SerialEventMap,
SerialProvider,
SerialPolyfillOptions,
} from '@danidoble/webserial-pinpad';