Skip to content

padosoft/react-native-ecr17-protocol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

110 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’³ react-native-ecr17

A React Native / Nitro module for the Italian ECR17 payment protocol β€” drive Nexi Group POS terminals over LAN, straight from your cash-register app.

The most complete open-source ECR17 toolkit for React Native & native mobile (iOS/Android).

C++ tests License: MIT Built with Nitro Platforms

react-native-ecr17 banner

🐘 Using PHP / Laravel? There's a sibling port: padosoft/laravel-ecr17 β€” the same ECR17 protocol as a Laravel package + debug console.


πŸ“š Table of contents

🧭 What is ECR17?

ECR17 is the Italian standard protocol β€” supported by Nexi Group terminals β€” that integrates an Electronic Cash Register (ECR) with an EFT-POS payment terminal over a local LAN connection. The cash register sends a request (payment, reversal, status…), the terminal talks to the acquiring host, and replies synchronously.

This library speaks that protocol from React Native, with the protocol engine written in C++ and bridged via Nitro Modules.

πŸ“š Official protocol reference (public): https://developer.nexigroup.com/traditionalpos/en-EU/docs/ β€” the authoritative source. Field positions, message codes and lrcMode may vary by terminal/firmware; always check against the official docs.

🎯 Why this exists

Integrating Italian POS terminals has long been needlessly painful. The ECR17 protocol is not publicly documented β€” the specifications are shared under NDA, mostly with established point-of-sale software vendors β€” so everyone else reverse-engineers it by trial and error across terminals and firmware versions. (The classic trap that blocks almost everyone: the LRC is computed over a base of 0x7F, not 0x00 β€” handled here, and configurable per terminal.)

A few community efforts exist for server-side languages, but there was nothing for React Native or native mobile (iOS/Android). To our knowledge this is the most complete open-source ECR17 toolkit for React Native and native mobile: the full command set, response parsing, the ACK/NAK + retransmit orchestration, configurable LRC modes, and payment-safety β€” all tested.

The goal is simple: low-level, Android and iOS developers should no longer struggle to talk to Italian POS terminals. No NDA hunting, no guesswork β€” just await client.pay({ amountCents }). These protocols should be this approachable for everyone, and now, for mobile, they are.

🀝 Compatibility notes (lrcMode, field quirks per terminal/firmware) are welcome as issues, so we can build, together, the reference the ecosystem never had.

✨ Highlights

  • ⚑️ C++ protocol core, Nitro-bridged β€” framing/LRC/orchestration run natively on iOS & Android.
  • πŸ”„ Async, Promise-based API β€” await client.pay({ amountCents }).
  • 🧱 Full command set β€” payment, extended payment, reversal, pre-auth (request/incremental/closure), card verification, close session, totals, last result, ECR printing, reprint, VAS.
  • πŸ›‘οΈ Robust by design β€” fixed-width field validation, defensive response parsing, ACK/NAK handshake with retransmit-up-to-3 and timeouts.
  • πŸ“‘ Live events β€” progress messages, streamed receipt lines, connection state.
  • 🧩 Shared C++ ↔ native bridge β€” one C++ protocol engine talks to the native TCP socket (Kotlin/Swift) through Nitro's auto-generated C++↔Kotlin JNI bridge β€” a notoriously fiddly piece on Android, here done cleanly with no hand-written JNI.
  • βœ… Heavily tested β€” 83 C++ unit/flow/safety tests (LRC, codec, every builder, every parser, full session orchestration) run in CI.
  • πŸ€– Vibe-coding batteries included β€” ships first-class AI-agent context (AGENTS.md, CLAUDE.md, docs/LESSON.md, PROGRESS.md) so contributors using AI assistants get accurate, instant project context. See below.

πŸ“± Screenshots

The repo ships an example Debug Console app (iOS & Android) that exercises every ECR17 command against a real terminal and streams the behind-the-scenes log (sent / progress / receipt / result / error) live.

Debug Console on Android β€” commands & configuration
Android β€” commands & configuration
Debug Console on iOS β€” commands & configuration
iOS β€” commands & configuration
Debug Console on iOS β€” live logs tab
iOS β€” live logs tab

πŸ›‘οΈ Enterprise robustness & payment safety

This module handles real money, so correctness and failure handling are first-class:

  • Physical handshake β€” every application frame is confirmed with ACK/NAK and retransmitted up to 3 times (per spec) on NAK or timeout, with separate ACK and response timeouts.
  • Integrity β€” LRC validated on every received frame; invalid frames are NAKed to request retransmission. Outgoing fixed-width fields are validated, so a malformed frame is never sent to the terminal.
  • No double charge β€” on a connection drop, autoReconnect restores the socket but a financial command is never blindly re-sent (a re-send could charge the cardholder twice). Read-only/idempotent commands (status, totals, sendLastResult, enable-printing) are retried; payments/reversals/pre-auths reconnect and surface the error so you recover the outcome via sendLastResult() (the spec's G command). This invariant is unit-tested.
  • Defensive parsing β€” response parsers never read out of bounds on short or malformed payloads.
  • One transaction at a time β€” matches the protocol's request/response model.
  • Tested β€” 83 C++ unit/flow/safety tests in CI, plus an opt-in real-terminal integration test.

πŸ“Š Feature status

Area Status
Packet framing + LRC (4 modes) βœ…
All request builders (P X p i c H U C T G E R K s S) βœ…
Response parsing (E/V/s/T/C/e/K, incl. DCC) βœ…
Session orchestration (ACK/NAK, retransmit, timeout, progress/receipt) βœ…
Async client API + events βœ…
Auto-connect, tokenization (U) flow, receipt streaming βœ…
Android native transport (Kotlin TCP) βœ… (CI-built)
iOS native transport (Swift / Network.framework) βœ… (verified on device)

Requirements

  • React Native 0.76+ (new architecture) β€” the example uses Expo SDK 56 / RN 0.85
  • react-native-nitro-modules (peer dependency)
  • A Nexi Group ECR17-compatible terminal configured for LAN integration

πŸ“¦ Installation

bun add react-native-ecr17 react-native-nitro-modules
# or: npm install react-native-ecr17 react-native-nitro-modules
cd ios && pod install   # iOS

Nitro module: requires the RN new architecture (default on 0.76+).

πŸš€ Quick start

import { createEcr17Client } from 'react-native-ecr17';

const client = createEcr17Client({
  host: '192.168.1.50',     // terminal IP on the LAN
  port: 10000,               // configured ECR port
  terminalId: '12345678',
  cashRegisterId: '00000001',
  lrcMode: 'std',
  responseTimeoutMs: 60000,
});

await client.connect();

const result = await client.pay({ amountCents: 650 });
if (result.outcome === 'ok') {
  console.log('Approved', result.authCode, 'PAN', result.pan);
} else {
  console.warn('Declined:', result.errorDescription);
}

// Reversal ("annullamento") of the last transaction:
await client.reverse({});

const status = await client.status();   // PosStatusResponse
await client.disconnect();

βš›οΈ React hook example

import { useEffect, useMemo, useState } from 'react';
import { createEcr17Client, type Ecr17Config, type ProgressEvent } from 'react-native-ecr17';

export function useEcr17(config: Ecr17Config) {
  const client = useMemo(() => createEcr17Client(config), [config]);
  const [progress, setProgress] = useState<string>('');

  useEffect(() => {
    client.setOnProgress((e: ProgressEvent) => setProgress(e.message));
    client.connect();
    return () => client.disconnect();
  }, [client]);

  return {
    progress,
    pay: (amountCents: number) => client.pay({ amountCents }),
    reverse: () => client.reverse({}),
    status: () => client.status(),
  };
}

βš™οΈ Configuration

Ecr17Config: host (required), port?, terminalId (required), cashRegisterId (required), lrcMode?, keepAlive?, autoReconnect?, connectionTimeoutMs?, responseTimeoutMs?, ackTimeoutMs?, retryCount?, retryDelayMs?, debug?.

πŸ“– API reference

All commands are async (Promise) and perform a full request/response exchange. configure/configuration are synchronous.

Method Command Returns
connect() / disconnect() / isConnected() β€” Promise<void> / void / bool
status() s PosStatusResponse
pay(req) / payExtended(req) P / X PaymentResult
reverse(req) S ReversalResult
preAuth(req) / incrementalAuth(req) / preAuthClosure(req) p / i / c PreAuthResult / PaymentResult
verifyCard(req) H CardVerificationResult
closeSession() / totals() C / T CloseSessionResult / TotalsResult
sendLastResult() G PaymentResult
enableEcrPrinting(bool) / reprint(bool) E / R Promise<void>
vas(xml) K VasResult

Commands require an open connection (connect() first) and reject on timeout / retransmission exhaustion / disconnect.

πŸ“‘ Events

client.setOnProgress((e) => {/* e.message β€” display text during a procedure */});
client.setOnReceiptLine((l) => {/* l.text β€” a receipt line when ECR printing is on */});
client.setOnConnectionStateChange((s) => {/* 'disconnected' | 'connecting' | 'connected' */});

πŸ” Protocol cheat-sheet

App frame: STX(0x02) Β· payload Β· ETX(0x03) Β· LRC. Progress: SOH(0x01) Β· 20 chars Β· EOT(0x04). Confirmation: ACK(0x06) / NAK(0x15) Β· ETX Β· LRC. LRC = 0x7F XOR-folded; framing bytes folded in are selectable via lrcMode (stx / std / noext / stx_noext).

πŸ—οΈ Architecture

package/cpp/
β”œβ”€β”€ Lcr/            # LRC (4 modes, base 0x7F)
β”œβ”€β”€ PacketCodec/    # framing: STXΒ·ETXΒ·SOHΒ·EOTΒ·ACKΒ·NAK + LRC
β”œβ”€β”€ Ecr17Protocol/  # request builders (all commands), fixed-width + validated
β”œβ”€β”€ Ecr17Response/  # response field parsers -> plain structs
β”œβ”€β”€ Session/        # ACK/NAK + retransmit + timeout orchestration
β”œβ”€β”€ Transport/      # abstract Transport + NativeTransportAdapter + FakeTransport (tests)
└── Ecr17Client/    # HybridEcr17Client (Nitro async API)
package/android/.../HybridEcr17Transport.kt   # Kotlin TCP transport
package/ios/HybridEcr17Transport.swift        # Swift (Network.framework) transport

πŸ§ͺ Testing

cmake -S package/cpp/tests -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build && ctest --test-dir build --output-on-failure

83 tests cover LRC, packet (de)framing edge cases, every builder's byte layout, every response parser, and the documented payment / reversal / re-pay / progress / receipt / NAK-retransmit / timeout flows (against an in-memory FakeTransport).

🧾 Tokenization & receipts

// Tokenization: attach a contract to a payment/preAuth/verifyCard. The 'U'
// additional-data message is sent automatically (P -> ACK -> U -> ACK -> result).
await client.pay({
  amountCents: 1000,
  tokenization: { service: 'recurring', contractCode: '1666354841608' },
});

// Receipts printed by the ECR: enable printing, set receiptDrainMs in the config,
// and receive lines via the event.
await client.enableEcrPrinting(true);
client.setOnReceiptLine((l) => appendToReceipt(l.text));

πŸ”Œ Testing against a real terminal (opt-in)

An opt-in C++ integration test runs the full core over a real TCP socket. It is skipped unless ECR17_TERMINAL_HOST is set:

cmake -S package/cpp/tests -B build && cmake --build build
ECR17_TERMINAL_HOST=192.168.1.50 ECR17_TERMINAL_PORT=10000 \
ECR17_TERMINAL_ID=00000000 ECR17_LRC_MODE=std \
ctest --test-dir build -R Integration --output-on-failure

πŸ€– Vibe-coding batteries included

Building on an undocumented payment protocol is exactly where AI assistants get things subtly wrong. This repo ships the context to prevent that, so an agent (or a new contributor) is productive and safe from minute one:

  • AGENTS.md / CLAUDE.md β€” project guide, the mandatory per-phase workflow, CI strategy, and the money-critical rules (e.g. never blindly retry a payment).
  • docs/LESSON.md β€” accumulated, verified engineering lessons (Nitro APIs, C++↔Kotlin JNI, build traps, payment-safety) β€” the gotchas already solved.
  • PROGRESS.md β€” crash-safe resume state across sessions.

The result: less hallucination, fewer footguns, and changes that respect the payment-safety invariants by default.

πŸ“„ License

MIT Β© padosoft

Disclaimer: independent integration library. "ECR17", "Nexi" and related marks belong to their respective owners and are referenced for interoperability only.

Releases

No releases published

Contributors