Skip to content

padosoft/react-native-ecr17-protocol

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

122 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’³ @padosoft/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

@padosoft/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 @padosoft/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 '@padosoft/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 '@padosoft/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.

About

RN package built with Nitro for the ECR17 protocol, allowing integration with POS terminals.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors