@padosoft/react-native-ecr17 0.0.0 β 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Ecr17.podspec +39 -39
- package/README.md +348 -348
- package/android/CMakeLists.txt +41 -41
- package/android/build.gradle +148 -148
- package/android/fix-prefab.gradle +50 -50
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +2 -2
- package/android/src/main/cpp/cpp-adapter.cpp +8 -8
- package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -233
- package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -30
- package/cpp/Ecr17.hpp +1 -1
- package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -598
- package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -85
- package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -277
- package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -103
- package/cpp/Ecr17Response/Ecr17Response.cpp +155 -155
- package/cpp/Ecr17Response/Ecr17Response.hpp +113 -113
- package/cpp/Lcr/Lcr.cpp +42 -42
- package/cpp/Lcr/Lcr.hpp +21 -21
- package/cpp/PacketCodec/PacketCodec.cpp +145 -145
- package/cpp/PacketCodec/PacketCodec.hpp +47 -47
- package/cpp/Session/Ecr17Session.cpp +260 -260
- package/cpp/Session/Ecr17Session.hpp +97 -97
- package/cpp/Session/RetryPolicy.hpp +23 -23
- package/cpp/Transport/FakeTransport.hpp +95 -95
- package/cpp/Transport/NativeTransportAdapter.cpp +42 -42
- package/cpp/Transport/NativeTransportAdapter.hpp +32 -32
- package/cpp/Transport/Transport.hpp +30 -30
- package/cpp/tests/CMakeLists.txt +55 -55
- package/cpp/tests/PosixTcpTransport.hpp +105 -105
- package/cpp/tests/stubs/LrcMode.hpp +25 -25
- package/cpp/tests/test_flows.cpp +148 -148
- package/cpp/tests/test_integration_terminal.cpp +72 -72
- package/cpp/tests/test_lrc.cpp +66 -66
- package/cpp/tests/test_packet_codec.cpp +164 -164
- package/cpp/tests/test_protocol.cpp +102 -102
- package/cpp/tests/test_protocol_commands.cpp +190 -190
- package/cpp/tests/test_response.cpp +164 -164
- package/cpp/tests/test_retry_policy.cpp +28 -28
- package/cpp/tests/test_session.cpp +262 -262
- package/ios/HybridEcr17Transport.swift +103 -103
- package/lib/commonjs/index.js +50 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/client.nitro.js +17 -0
- package/lib/commonjs/specs/client.nitro.js.map +1 -0
- package/lib/commonjs/specs/transport.nitro.js +6 -0
- package/lib/commonjs/specs/transport.nitro.js.map +1 -0
- package/lib/commonjs/types/client.js +2 -0
- package/lib/commonjs/types/client.js.map +1 -0
- package/lib/commonjs/utils/client.js +13 -0
- package/lib/commonjs/utils/client.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/client.nitro.js +13 -0
- package/lib/module/specs/client.nitro.js.map +1 -0
- package/lib/module/specs/transport.nitro.js +4 -0
- package/lib/module/specs/transport.nitro.js.map +1 -0
- package/lib/module/types/client.js +2 -0
- package/lib/module/types/client.js.map +1 -0
- package/lib/module/utils/client.js +9 -0
- package/lib/module/utils/client.js.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/client.nitro.d.ts +63 -0
- package/lib/typescript/src/specs/client.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/transport.nitro.d.ts +13 -0
- package/lib/typescript/src/specs/transport.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types/client.d.ts +138 -0
- package/lib/typescript/src/types/client.d.ts.map +1 -0
- package/lib/typescript/src/utils/client.d.ts +3 -0
- package/lib/typescript/src/utils/client.d.ts.map +1 -0
- package/nitro.json +30 -30
- package/package.json +4 -4
- package/react-native.config.js +18 -18
- package/src/index.ts +4 -4
- package/src/specs/client.nitro.ts +102 -102
- package/src/specs/transport.nitro.ts +25 -25
- package/src/types/client.ts +196 -196
- package/src/utils/client.ts +10 -10
package/README.md
CHANGED
|
@@ -1,348 +1,348 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
# π³ @padosoft/react-native-ecr17
|
|
4
|
-
|
|
5
|
-
**A React Native / Nitro module for the Italian ECR17 payment protocol β drive Nexi Group POS terminals over LAN, straight from your cash-register app.**
|
|
6
|
-
|
|
7
|
-
**The most complete open-source ECR17 toolkit for React Native & native mobile (iOS/Android).**
|
|
8
|
-
|
|
9
|
-
[](https://github.com/padosoft/react-native-ecr17-protocol/actions/workflows/cpp-tests.yml)
|
|
10
|
-
[](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/LICENSE)
|
|
11
|
-
[](https://nitro.margelo.com)
|
|
12
|
-
[](#requirements)
|
|
13
|
-
|
|
14
|
-
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/banner.png" alt="@padosoft/react-native-ecr17 banner" width="100%" />
|
|
15
|
-
|
|
16
|
-
</div>
|
|
17
|
-
|
|
18
|
-
> π **Using PHP / Laravel?** There's a sibling port:
|
|
19
|
-
> **[padosoft/laravel-ecr17](https://github.com/padosoft/laravel-ecr17)** β
|
|
20
|
-
> the same ECR17 protocol as a Laravel package + debug console.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## π Table of contents
|
|
25
|
-
|
|
26
|
-
- [What is ECR17?](#-what-is-ecr17)
|
|
27
|
-
- [Why this exists](#-why-this-exists)
|
|
28
|
-
- [Highlights](#-highlights)
|
|
29
|
-
- [Screenshots](#-screenshots)
|
|
30
|
-
- [Feature status](#-feature-status)
|
|
31
|
-
- [Requirements](#requirements)
|
|
32
|
-
- [Installation](#-installation)
|
|
33
|
-
- [Quick start](#-quick-start)
|
|
34
|
-
- [React hook example](#-react-hook-example)
|
|
35
|
-
- [Configuration](#%EF%B8%8F-configuration)
|
|
36
|
-
- [API reference](#-api-reference)
|
|
37
|
-
- [Events](#-events)
|
|
38
|
-
- [Protocol cheat-sheet](#-protocol-cheat-sheet)
|
|
39
|
-
- [Architecture](#%EF%B8%8F-architecture)
|
|
40
|
-
- [Testing](#-testing)
|
|
41
|
-
- [Vibe-coding batteries included](#-vibe-coding-batteries-included)
|
|
42
|
-
- [License](#-license)
|
|
43
|
-
|
|
44
|
-
## π§ What is ECR17?
|
|
45
|
-
|
|
46
|
-
**ECR17** is the Italian standard protocol β supported by **Nexi Group** terminals β
|
|
47
|
-
that integrates an *Electronic Cash Register* (ECR) with an *EFT-POS* payment
|
|
48
|
-
terminal over a local LAN connection. The cash register sends a request
|
|
49
|
-
(payment, reversal, statusβ¦), the terminal talks to the acquiring host, and
|
|
50
|
-
replies synchronously.
|
|
51
|
-
|
|
52
|
-
This library speaks that protocol from React Native, with the protocol engine
|
|
53
|
-
written in C++ and bridged via [Nitro Modules](https://nitro.margelo.com).
|
|
54
|
-
|
|
55
|
-
> π **Official protocol reference (public):**
|
|
56
|
-
> <https://developer.nexigroup.com/traditionalpos/en-EU/docs/> β the
|
|
57
|
-
> authoritative source. Field positions, message codes and `lrcMode` may vary by
|
|
58
|
-
> terminal/firmware; always check against the official docs.
|
|
59
|
-
|
|
60
|
-
## π― Why this exists
|
|
61
|
-
|
|
62
|
-
Integrating Italian POS terminals has long been needlessly painful. The ECR17
|
|
63
|
-
protocol is **not publicly documented** β the specifications are shared under NDA,
|
|
64
|
-
mostly with established point-of-sale software vendors β so everyone else
|
|
65
|
-
reverse-engineers it by trial and error across terminals and firmware versions.
|
|
66
|
-
(The classic trap that blocks almost everyone: the LRC is computed over a base of
|
|
67
|
-
`0x7F`, not `0x00` β handled here, and configurable per terminal.)
|
|
68
|
-
|
|
69
|
-
A few community efforts exist for server-side languages, but there was **nothing
|
|
70
|
-
for React Native or native mobile (iOS/Android)**. To our knowledge this is the
|
|
71
|
-
**most complete open-source ECR17 toolkit for React Native and native mobile**:
|
|
72
|
-
the full command set, response parsing, the ACK/NAK + retransmit orchestration,
|
|
73
|
-
configurable LRC modes, and payment-safety β all tested.
|
|
74
|
-
|
|
75
|
-
The goal is simple: **low-level, Android and iOS developers should no longer
|
|
76
|
-
struggle to talk to Italian POS terminals.** No NDA hunting, no guesswork β just
|
|
77
|
-
`await client.pay({ amountCents })`. These protocols should be this approachable
|
|
78
|
-
for everyone, and now, for mobile, they are.
|
|
79
|
-
|
|
80
|
-
> π€ Compatibility notes (lrcMode, field quirks per terminal/firmware) are
|
|
81
|
-
> welcome as issues, so we can build, together, the reference the ecosystem
|
|
82
|
-
> never had.
|
|
83
|
-
|
|
84
|
-
## β¨ Highlights
|
|
85
|
-
|
|
86
|
-
- β‘οΈ **C++ protocol core, Nitro-bridged** β framing/LRC/orchestration run natively on iOS & Android.
|
|
87
|
-
- π **Async, Promise-based API** β `await client.pay({ amountCents })`.
|
|
88
|
-
- π§± **Full command set** β payment, extended payment, reversal, pre-auth (request/incremental/closure), card verification, close session, totals, last result, ECR printing, reprint, VAS.
|
|
89
|
-
- π‘οΈ **Robust by design** β fixed-width field validation, defensive response parsing, ACK/NAK handshake with **retransmit-up-to-3** and timeouts.
|
|
90
|
-
- π‘ **Live events** β progress messages, streamed receipt lines, connection state.
|
|
91
|
-
- π§© **Shared C++ β native bridge** β one C++ protocol engine talks to the native
|
|
92
|
-
TCP socket (Kotlin/Swift) through Nitro's auto-generated **C++βKotlin JNI**
|
|
93
|
-
bridge β a notoriously fiddly piece on Android, here done cleanly with no
|
|
94
|
-
hand-written JNI.
|
|
95
|
-
- β
**Heavily tested** β 83 C++ unit/flow/safety tests (LRC, codec, every builder, every parser, full session orchestration) run in CI.
|
|
96
|
-
- π€ **Vibe-coding batteries included** β ships first-class AI-agent context
|
|
97
|
-
(`AGENTS.md`, `CLAUDE.md`, `docs/LESSON.md`, `PROGRESS.md`) so contributors
|
|
98
|
-
using AI assistants get accurate, instant project context. See [below](#-vibe-coding-batteries-included).
|
|
99
|
-
|
|
100
|
-
## π± Screenshots
|
|
101
|
-
|
|
102
|
-
The repo ships an example **Debug Console** app (iOS & Android) that exercises every
|
|
103
|
-
ECR17 command against a real terminal and streams the behind-the-scenes log
|
|
104
|
-
(sent / progress / receipt / result / error) live.
|
|
105
|
-
|
|
106
|
-
<table>
|
|
107
|
-
<tr>
|
|
108
|
-
<td align="center" width="33%">
|
|
109
|
-
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-android.jpeg" alt="Debug Console on Android β commands & configuration" width="240" /><br/>
|
|
110
|
-
<sub>Android β commands & configuration</sub>
|
|
111
|
-
</td>
|
|
112
|
-
<td align="center" width="33%">
|
|
113
|
-
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-iOS.jpeg" alt="Debug Console on iOS β commands & configuration" width="240" /><br/>
|
|
114
|
-
<sub>iOS β commands & configuration</sub>
|
|
115
|
-
</td>
|
|
116
|
-
<td align="center" width="33%">
|
|
117
|
-
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-iOS-logs.jpeg" alt="Debug Console on iOS β live logs tab" width="240" /><br/>
|
|
118
|
-
<sub>iOS β live logs tab</sub>
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
</table>
|
|
122
|
-
|
|
123
|
-
## π‘οΈ Enterprise robustness & payment safety
|
|
124
|
-
|
|
125
|
-
This module handles real money, so correctness and failure handling are
|
|
126
|
-
first-class:
|
|
127
|
-
|
|
128
|
-
- **Physical handshake** β every application frame is confirmed with ACK/NAK and
|
|
129
|
-
**retransmitted up to 3 times** (per spec) on NAK or timeout, with separate
|
|
130
|
-
ACK and response timeouts.
|
|
131
|
-
- **Integrity** β LRC validated on every received frame; invalid frames are
|
|
132
|
-
NAKed to request retransmission. Outgoing fixed-width fields are validated, so
|
|
133
|
-
a malformed frame is never sent to the terminal.
|
|
134
|
-
- **No double charge** β on a connection drop, `autoReconnect` restores the
|
|
135
|
-
socket but a **financial command is never blindly re-sent** (a re-send could
|
|
136
|
-
charge the cardholder twice). Read-only/idempotent commands (status, totals,
|
|
137
|
-
`sendLastResult`, enable-printing) are retried; payments/reversals/pre-auths
|
|
138
|
-
reconnect and surface the error so you recover the outcome via
|
|
139
|
-
`sendLastResult()` (the spec's `G` command). This invariant is unit-tested.
|
|
140
|
-
- **Defensive parsing** β response parsers never read out of bounds on short or
|
|
141
|
-
malformed payloads.
|
|
142
|
-
- **One transaction at a time** β matches the protocol's request/response model.
|
|
143
|
-
- **Tested** β 83 C++ unit/flow/safety tests in CI, plus an opt-in real-terminal
|
|
144
|
-
integration test.
|
|
145
|
-
|
|
146
|
-
## π Feature status
|
|
147
|
-
|
|
148
|
-
| Area | Status |
|
|
149
|
-
|------|:------:|
|
|
150
|
-
| Packet framing + LRC (4 modes) | β
|
|
|
151
|
-
| All request builders (`P X p i c H U C T G E R K s S`) | β
|
|
|
152
|
-
| Response parsing (`E/V/s/T/C/e/K`, incl. DCC) | β
|
|
|
153
|
-
| Session orchestration (ACK/NAK, retransmit, timeout, progress/receipt) | β
|
|
|
154
|
-
| Async client API + events | β
|
|
|
155
|
-
| Auto-connect, tokenization (`U`) flow, receipt streaming | β
|
|
|
156
|
-
| Android native transport (Kotlin TCP) | β
*(CI-built)* |
|
|
157
|
-
| iOS native transport (Swift / Network.framework) | β
*(verified on device)* |
|
|
158
|
-
|
|
159
|
-
## Requirements
|
|
160
|
-
|
|
161
|
-
- **React Native** 0.76+ (new architecture) β the example uses Expo SDK 56 / RN 0.85
|
|
162
|
-
- **react-native-nitro-modules** (peer dependency)
|
|
163
|
-
- A Nexi Group ECR17-compatible terminal configured for **LAN integration**
|
|
164
|
-
|
|
165
|
-
## π¦ Installation
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
bun add @padosoft/react-native-ecr17 react-native-nitro-modules
|
|
169
|
-
# or: npm install react-native-ecr17 react-native-nitro-modules
|
|
170
|
-
cd ios && pod install # iOS
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
> Nitro module: requires the RN **new architecture** (default on 0.76+).
|
|
174
|
-
|
|
175
|
-
## π Quick start
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
import { createEcr17Client } from '@padosoft/react-native-ecr17';
|
|
179
|
-
|
|
180
|
-
const client = createEcr17Client({
|
|
181
|
-
host: '192.168.1.50', // terminal IP on the LAN
|
|
182
|
-
port: 10000, // configured ECR port
|
|
183
|
-
terminalId: '12345678',
|
|
184
|
-
cashRegisterId: '00000001',
|
|
185
|
-
lrcMode: 'std',
|
|
186
|
-
responseTimeoutMs: 60000,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
await client.connect();
|
|
190
|
-
|
|
191
|
-
const result = await client.pay({ amountCents: 650 });
|
|
192
|
-
if (result.outcome === 'ok') {
|
|
193
|
-
console.log('Approved', result.authCode, 'PAN', result.pan);
|
|
194
|
-
} else {
|
|
195
|
-
console.warn('Declined:', result.errorDescription);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Reversal ("annullamento") of the last transaction:
|
|
199
|
-
await client.reverse({});
|
|
200
|
-
|
|
201
|
-
const status = await client.status(); // PosStatusResponse
|
|
202
|
-
await client.disconnect();
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## βοΈ React hook example
|
|
206
|
-
|
|
207
|
-
```tsx
|
|
208
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
209
|
-
import { createEcr17Client, type Ecr17Config, type ProgressEvent } from '@padosoft/react-native-ecr17';
|
|
210
|
-
|
|
211
|
-
export function useEcr17(config: Ecr17Config) {
|
|
212
|
-
const client = useMemo(() => createEcr17Client(config), [config]);
|
|
213
|
-
const [progress, setProgress] = useState<string>('');
|
|
214
|
-
|
|
215
|
-
useEffect(() => {
|
|
216
|
-
client.setOnProgress((e: ProgressEvent) => setProgress(e.message));
|
|
217
|
-
client.connect();
|
|
218
|
-
return () => client.disconnect();
|
|
219
|
-
}, [client]);
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
progress,
|
|
223
|
-
pay: (amountCents: number) => client.pay({ amountCents }),
|
|
224
|
-
reverse: () => client.reverse({}),
|
|
225
|
-
status: () => client.status(),
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## βοΈ Configuration
|
|
231
|
-
|
|
232
|
-
`Ecr17Config`: `host` (required), `port?`, `terminalId` (required), `cashRegisterId`
|
|
233
|
-
(required), `lrcMode?`, `keepAlive?`, `autoReconnect?`, `connectionTimeoutMs?`,
|
|
234
|
-
`responseTimeoutMs?`, `ackTimeoutMs?`, `retryCount?`, `retryDelayMs?`, `debug?`.
|
|
235
|
-
|
|
236
|
-
## π API reference
|
|
237
|
-
|
|
238
|
-
All commands are **async** (`Promise`) and perform a full request/response
|
|
239
|
-
exchange. `configure`/`configuration` are synchronous.
|
|
240
|
-
|
|
241
|
-
| Method | Command | Returns |
|
|
242
|
-
|--------|:------:|---------|
|
|
243
|
-
| `connect()` / `disconnect()` / `isConnected()` | β | `Promise<void>` / `void` / `bool` |
|
|
244
|
-
| `status()` | `s` | `PosStatusResponse` |
|
|
245
|
-
| `pay(req)` / `payExtended(req)` | `P` / `X` | `PaymentResult` |
|
|
246
|
-
| `reverse(req)` | `S` | `ReversalResult` |
|
|
247
|
-
| `preAuth(req)` / `incrementalAuth(req)` / `preAuthClosure(req)` | `p` / `i` / `c` | `PreAuthResult` / `PaymentResult` |
|
|
248
|
-
| `verifyCard(req)` | `H` | `CardVerificationResult` |
|
|
249
|
-
| `closeSession()` / `totals()` | `C` / `T` | `CloseSessionResult` / `TotalsResult` |
|
|
250
|
-
| `sendLastResult()` | `G` | `PaymentResult` |
|
|
251
|
-
| `enableEcrPrinting(bool)` / `reprint(bool)` | `E` / `R` | `Promise<void>` |
|
|
252
|
-
| `vas(xml)` | `K` | `VasResult` |
|
|
253
|
-
|
|
254
|
-
Commands require an open connection (`connect()` first) and reject on
|
|
255
|
-
timeout / retransmission exhaustion / disconnect.
|
|
256
|
-
|
|
257
|
-
## π‘ Events
|
|
258
|
-
|
|
259
|
-
```ts
|
|
260
|
-
client.setOnProgress((e) => {/* e.message β display text during a procedure */});
|
|
261
|
-
client.setOnReceiptLine((l) => {/* l.text β a receipt line when ECR printing is on */});
|
|
262
|
-
client.setOnConnectionStateChange((s) => {/* 'disconnected' | 'connecting' | 'connected' */});
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## π Protocol cheat-sheet
|
|
266
|
-
|
|
267
|
-
App frame: `STX(0x02)` Β· payload Β· `ETX(0x03)` Β· `LRC`. Progress: `SOH(0x01)` Β·
|
|
268
|
-
20 chars Β· `EOT(0x04)`. Confirmation: `ACK(0x06)` / `NAK(0x15)` Β· `ETX` Β· `LRC`.
|
|
269
|
-
LRC = `0x7F` XOR-folded; framing bytes folded in are selectable via `lrcMode`
|
|
270
|
-
(`stx` / `std` / `noext` / `stx_noext`).
|
|
271
|
-
|
|
272
|
-
## ποΈ Architecture
|
|
273
|
-
|
|
274
|
-
```
|
|
275
|
-
package/cpp/
|
|
276
|
-
βββ Lcr/ # LRC (4 modes, base 0x7F)
|
|
277
|
-
βββ PacketCodec/ # framing: STXΒ·ETXΒ·SOHΒ·EOTΒ·ACKΒ·NAK + LRC
|
|
278
|
-
βββ Ecr17Protocol/ # request builders (all commands), fixed-width + validated
|
|
279
|
-
βββ Ecr17Response/ # response field parsers -> plain structs
|
|
280
|
-
βββ Session/ # ACK/NAK + retransmit + timeout orchestration
|
|
281
|
-
βββ Transport/ # abstract Transport + NativeTransportAdapter + FakeTransport (tests)
|
|
282
|
-
βββ Ecr17Client/ # HybridEcr17Client (Nitro async API)
|
|
283
|
-
package/android/.../HybridEcr17Transport.kt # Kotlin TCP transport
|
|
284
|
-
package/ios/HybridEcr17Transport.swift # Swift (Network.framework) transport
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
## π§ͺ Testing
|
|
288
|
-
|
|
289
|
-
```bash
|
|
290
|
-
cmake -S package/cpp/tests -B build -DCMAKE_BUILD_TYPE=Release
|
|
291
|
-
cmake --build build && ctest --test-dir build --output-on-failure
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
83 tests cover LRC, packet (de)framing edge cases, every builder's byte layout,
|
|
295
|
-
every response parser, and the documented payment / reversal / re-pay / progress
|
|
296
|
-
/ receipt / NAK-retransmit / timeout flows (against an in-memory `FakeTransport`).
|
|
297
|
-
|
|
298
|
-
## π§Ύ Tokenization & receipts
|
|
299
|
-
|
|
300
|
-
```ts
|
|
301
|
-
// Tokenization: attach a contract to a payment/preAuth/verifyCard. The 'U'
|
|
302
|
-
// additional-data message is sent automatically (P -> ACK -> U -> ACK -> result).
|
|
303
|
-
await client.pay({
|
|
304
|
-
amountCents: 1000,
|
|
305
|
-
tokenization: { service: 'recurring', contractCode: '1666354841608' },
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Receipts printed by the ECR: enable printing, set receiptDrainMs in the config,
|
|
309
|
-
// and receive lines via the event.
|
|
310
|
-
await client.enableEcrPrinting(true);
|
|
311
|
-
client.setOnReceiptLine((l) => appendToReceipt(l.text));
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
## π Testing against a real terminal (opt-in)
|
|
315
|
-
|
|
316
|
-
An opt-in C++ integration test runs the full core over a real TCP socket. It is
|
|
317
|
-
**skipped** unless `ECR17_TERMINAL_HOST` is set:
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
cmake -S package/cpp/tests -B build && cmake --build build
|
|
321
|
-
ECR17_TERMINAL_HOST=192.168.1.50 ECR17_TERMINAL_PORT=10000 \
|
|
322
|
-
ECR17_TERMINAL_ID=00000000 ECR17_LRC_MODE=std \
|
|
323
|
-
ctest --test-dir build -R Integration --output-on-failure
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## π€ Vibe-coding batteries included
|
|
327
|
-
|
|
328
|
-
Building on an undocumented payment protocol is exactly where AI assistants get
|
|
329
|
-
things subtly wrong. This repo ships the context to prevent that, so an agent (or
|
|
330
|
-
a new contributor) is productive and *safe* from minute one:
|
|
331
|
-
|
|
332
|
-
- **[`AGENTS.md`](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/AGENTS.md)** /
|
|
333
|
-
**[`CLAUDE.md`](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/CLAUDE.md)** β project guide, the mandatory
|
|
334
|
-
per-phase workflow, CI strategy, and the **money-critical** rules (e.g. never
|
|
335
|
-
blindly retry a payment).
|
|
336
|
-
- **`docs/LESSON.md`** β accumulated, verified engineering lessons (Nitro APIs,
|
|
337
|
-
C++βKotlin JNI, build traps, payment-safety) β the gotchas already solved.
|
|
338
|
-
- **`PROGRESS.md`** β crash-safe resume state across sessions.
|
|
339
|
-
|
|
340
|
-
The result: less hallucination, fewer footguns, and changes that respect the
|
|
341
|
-
payment-safety invariants by default.
|
|
342
|
-
|
|
343
|
-
## π License
|
|
344
|
-
|
|
345
|
-
[MIT](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/LICENSE) Β© [padosoft](https://github.com/padosoft)
|
|
346
|
-
|
|
347
|
-
> **Disclaimer:** independent integration library. "ECR17", "Nexi" and related
|
|
348
|
-
> marks belong to their respective owners and are referenced for interoperability only.
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# π³ @padosoft/react-native-ecr17
|
|
4
|
+
|
|
5
|
+
**A React Native / Nitro module for the Italian ECR17 payment protocol β drive Nexi Group POS terminals over LAN, straight from your cash-register app.**
|
|
6
|
+
|
|
7
|
+
**The most complete open-source ECR17 toolkit for React Native & native mobile (iOS/Android).**
|
|
8
|
+
|
|
9
|
+
[](https://github.com/padosoft/react-native-ecr17-protocol/actions/workflows/cpp-tests.yml)
|
|
10
|
+
[](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/LICENSE)
|
|
11
|
+
[](https://nitro.margelo.com)
|
|
12
|
+
[](#requirements)
|
|
13
|
+
|
|
14
|
+
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/banner.png" alt="@padosoft/react-native-ecr17 banner" width="100%" />
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
> π **Using PHP / Laravel?** There's a sibling port:
|
|
19
|
+
> **[padosoft/laravel-ecr17](https://github.com/padosoft/laravel-ecr17)** β
|
|
20
|
+
> the same ECR17 protocol as a Laravel package + debug console.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π Table of contents
|
|
25
|
+
|
|
26
|
+
- [What is ECR17?](#-what-is-ecr17)
|
|
27
|
+
- [Why this exists](#-why-this-exists)
|
|
28
|
+
- [Highlights](#-highlights)
|
|
29
|
+
- [Screenshots](#-screenshots)
|
|
30
|
+
- [Feature status](#-feature-status)
|
|
31
|
+
- [Requirements](#requirements)
|
|
32
|
+
- [Installation](#-installation)
|
|
33
|
+
- [Quick start](#-quick-start)
|
|
34
|
+
- [React hook example](#-react-hook-example)
|
|
35
|
+
- [Configuration](#%EF%B8%8F-configuration)
|
|
36
|
+
- [API reference](#-api-reference)
|
|
37
|
+
- [Events](#-events)
|
|
38
|
+
- [Protocol cheat-sheet](#-protocol-cheat-sheet)
|
|
39
|
+
- [Architecture](#%EF%B8%8F-architecture)
|
|
40
|
+
- [Testing](#-testing)
|
|
41
|
+
- [Vibe-coding batteries included](#-vibe-coding-batteries-included)
|
|
42
|
+
- [License](#-license)
|
|
43
|
+
|
|
44
|
+
## π§ What is ECR17?
|
|
45
|
+
|
|
46
|
+
**ECR17** is the Italian standard protocol β supported by **Nexi Group** terminals β
|
|
47
|
+
that integrates an *Electronic Cash Register* (ECR) with an *EFT-POS* payment
|
|
48
|
+
terminal over a local LAN connection. The cash register sends a request
|
|
49
|
+
(payment, reversal, statusβ¦), the terminal talks to the acquiring host, and
|
|
50
|
+
replies synchronously.
|
|
51
|
+
|
|
52
|
+
This library speaks that protocol from React Native, with the protocol engine
|
|
53
|
+
written in C++ and bridged via [Nitro Modules](https://nitro.margelo.com).
|
|
54
|
+
|
|
55
|
+
> π **Official protocol reference (public):**
|
|
56
|
+
> <https://developer.nexigroup.com/traditionalpos/en-EU/docs/> β the
|
|
57
|
+
> authoritative source. Field positions, message codes and `lrcMode` may vary by
|
|
58
|
+
> terminal/firmware; always check against the official docs.
|
|
59
|
+
|
|
60
|
+
## π― Why this exists
|
|
61
|
+
|
|
62
|
+
Integrating Italian POS terminals has long been needlessly painful. The ECR17
|
|
63
|
+
protocol is **not publicly documented** β the specifications are shared under NDA,
|
|
64
|
+
mostly with established point-of-sale software vendors β so everyone else
|
|
65
|
+
reverse-engineers it by trial and error across terminals and firmware versions.
|
|
66
|
+
(The classic trap that blocks almost everyone: the LRC is computed over a base of
|
|
67
|
+
`0x7F`, not `0x00` β handled here, and configurable per terminal.)
|
|
68
|
+
|
|
69
|
+
A few community efforts exist for server-side languages, but there was **nothing
|
|
70
|
+
for React Native or native mobile (iOS/Android)**. To our knowledge this is the
|
|
71
|
+
**most complete open-source ECR17 toolkit for React Native and native mobile**:
|
|
72
|
+
the full command set, response parsing, the ACK/NAK + retransmit orchestration,
|
|
73
|
+
configurable LRC modes, and payment-safety β all tested.
|
|
74
|
+
|
|
75
|
+
The goal is simple: **low-level, Android and iOS developers should no longer
|
|
76
|
+
struggle to talk to Italian POS terminals.** No NDA hunting, no guesswork β just
|
|
77
|
+
`await client.pay({ amountCents })`. These protocols should be this approachable
|
|
78
|
+
for everyone, and now, for mobile, they are.
|
|
79
|
+
|
|
80
|
+
> π€ Compatibility notes (lrcMode, field quirks per terminal/firmware) are
|
|
81
|
+
> welcome as issues, so we can build, together, the reference the ecosystem
|
|
82
|
+
> never had.
|
|
83
|
+
|
|
84
|
+
## β¨ Highlights
|
|
85
|
+
|
|
86
|
+
- β‘οΈ **C++ protocol core, Nitro-bridged** β framing/LRC/orchestration run natively on iOS & Android.
|
|
87
|
+
- π **Async, Promise-based API** β `await client.pay({ amountCents })`.
|
|
88
|
+
- π§± **Full command set** β payment, extended payment, reversal, pre-auth (request/incremental/closure), card verification, close session, totals, last result, ECR printing, reprint, VAS.
|
|
89
|
+
- π‘οΈ **Robust by design** β fixed-width field validation, defensive response parsing, ACK/NAK handshake with **retransmit-up-to-3** and timeouts.
|
|
90
|
+
- π‘ **Live events** β progress messages, streamed receipt lines, connection state.
|
|
91
|
+
- π§© **Shared C++ β native bridge** β one C++ protocol engine talks to the native
|
|
92
|
+
TCP socket (Kotlin/Swift) through Nitro's auto-generated **C++βKotlin JNI**
|
|
93
|
+
bridge β a notoriously fiddly piece on Android, here done cleanly with no
|
|
94
|
+
hand-written JNI.
|
|
95
|
+
- β
**Heavily tested** β 83 C++ unit/flow/safety tests (LRC, codec, every builder, every parser, full session orchestration) run in CI.
|
|
96
|
+
- π€ **Vibe-coding batteries included** β ships first-class AI-agent context
|
|
97
|
+
(`AGENTS.md`, `CLAUDE.md`, `docs/LESSON.md`, `PROGRESS.md`) so contributors
|
|
98
|
+
using AI assistants get accurate, instant project context. See [below](#-vibe-coding-batteries-included).
|
|
99
|
+
|
|
100
|
+
## π± Screenshots
|
|
101
|
+
|
|
102
|
+
The repo ships an example **Debug Console** app (iOS & Android) that exercises every
|
|
103
|
+
ECR17 command against a real terminal and streams the behind-the-scenes log
|
|
104
|
+
(sent / progress / receipt / result / error) live.
|
|
105
|
+
|
|
106
|
+
<table>
|
|
107
|
+
<tr>
|
|
108
|
+
<td align="center" width="33%">
|
|
109
|
+
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-android.jpeg" alt="Debug Console on Android β commands & configuration" width="240" /><br/>
|
|
110
|
+
<sub>Android β commands & configuration</sub>
|
|
111
|
+
</td>
|
|
112
|
+
<td align="center" width="33%">
|
|
113
|
+
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-iOS.jpeg" alt="Debug Console on iOS β commands & configuration" width="240" /><br/>
|
|
114
|
+
<sub>iOS β commands & configuration</sub>
|
|
115
|
+
</td>
|
|
116
|
+
<td align="center" width="33%">
|
|
117
|
+
<img src="https://raw.githubusercontent.com/padosoft/react-native-ecr17-protocol/main/resources/screenshoots/demo-app-iOS-logs.jpeg" alt="Debug Console on iOS β live logs tab" width="240" /><br/>
|
|
118
|
+
<sub>iOS β live logs tab</sub>
|
|
119
|
+
</td>
|
|
120
|
+
</tr>
|
|
121
|
+
</table>
|
|
122
|
+
|
|
123
|
+
## π‘οΈ Enterprise robustness & payment safety
|
|
124
|
+
|
|
125
|
+
This module handles real money, so correctness and failure handling are
|
|
126
|
+
first-class:
|
|
127
|
+
|
|
128
|
+
- **Physical handshake** β every application frame is confirmed with ACK/NAK and
|
|
129
|
+
**retransmitted up to 3 times** (per spec) on NAK or timeout, with separate
|
|
130
|
+
ACK and response timeouts.
|
|
131
|
+
- **Integrity** β LRC validated on every received frame; invalid frames are
|
|
132
|
+
NAKed to request retransmission. Outgoing fixed-width fields are validated, so
|
|
133
|
+
a malformed frame is never sent to the terminal.
|
|
134
|
+
- **No double charge** β on a connection drop, `autoReconnect` restores the
|
|
135
|
+
socket but a **financial command is never blindly re-sent** (a re-send could
|
|
136
|
+
charge the cardholder twice). Read-only/idempotent commands (status, totals,
|
|
137
|
+
`sendLastResult`, enable-printing) are retried; payments/reversals/pre-auths
|
|
138
|
+
reconnect and surface the error so you recover the outcome via
|
|
139
|
+
`sendLastResult()` (the spec's `G` command). This invariant is unit-tested.
|
|
140
|
+
- **Defensive parsing** β response parsers never read out of bounds on short or
|
|
141
|
+
malformed payloads.
|
|
142
|
+
- **One transaction at a time** β matches the protocol's request/response model.
|
|
143
|
+
- **Tested** β 83 C++ unit/flow/safety tests in CI, plus an opt-in real-terminal
|
|
144
|
+
integration test.
|
|
145
|
+
|
|
146
|
+
## π Feature status
|
|
147
|
+
|
|
148
|
+
| Area | Status |
|
|
149
|
+
|------|:------:|
|
|
150
|
+
| Packet framing + LRC (4 modes) | β
|
|
|
151
|
+
| All request builders (`P X p i c H U C T G E R K s S`) | β
|
|
|
152
|
+
| Response parsing (`E/V/s/T/C/e/K`, incl. DCC) | β
|
|
|
153
|
+
| Session orchestration (ACK/NAK, retransmit, timeout, progress/receipt) | β
|
|
|
154
|
+
| Async client API + events | β
|
|
|
155
|
+
| Auto-connect, tokenization (`U`) flow, receipt streaming | β
|
|
|
156
|
+
| Android native transport (Kotlin TCP) | β
*(CI-built)* |
|
|
157
|
+
| iOS native transport (Swift / Network.framework) | β
*(verified on device)* |
|
|
158
|
+
|
|
159
|
+
## Requirements
|
|
160
|
+
|
|
161
|
+
- **React Native** 0.76+ (new architecture) β the example uses Expo SDK 56 / RN 0.85
|
|
162
|
+
- **react-native-nitro-modules** (peer dependency)
|
|
163
|
+
- A Nexi Group ECR17-compatible terminal configured for **LAN integration**
|
|
164
|
+
|
|
165
|
+
## π¦ Installation
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bun add @padosoft/react-native-ecr17 react-native-nitro-modules
|
|
169
|
+
# or: npm install react-native-ecr17 react-native-nitro-modules
|
|
170
|
+
cd ios && pod install # iOS
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
> Nitro module: requires the RN **new architecture** (default on 0.76+).
|
|
174
|
+
|
|
175
|
+
## π Quick start
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { createEcr17Client } from '@padosoft/react-native-ecr17';
|
|
179
|
+
|
|
180
|
+
const client = createEcr17Client({
|
|
181
|
+
host: '192.168.1.50', // terminal IP on the LAN
|
|
182
|
+
port: 10000, // configured ECR port
|
|
183
|
+
terminalId: '12345678',
|
|
184
|
+
cashRegisterId: '00000001',
|
|
185
|
+
lrcMode: 'std',
|
|
186
|
+
responseTimeoutMs: 60000,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await client.connect();
|
|
190
|
+
|
|
191
|
+
const result = await client.pay({ amountCents: 650 });
|
|
192
|
+
if (result.outcome === 'ok') {
|
|
193
|
+
console.log('Approved', result.authCode, 'PAN', result.pan);
|
|
194
|
+
} else {
|
|
195
|
+
console.warn('Declined:', result.errorDescription);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Reversal ("annullamento") of the last transaction:
|
|
199
|
+
await client.reverse({});
|
|
200
|
+
|
|
201
|
+
const status = await client.status(); // PosStatusResponse
|
|
202
|
+
await client.disconnect();
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## βοΈ React hook example
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
209
|
+
import { createEcr17Client, type Ecr17Config, type ProgressEvent } from '@padosoft/react-native-ecr17';
|
|
210
|
+
|
|
211
|
+
export function useEcr17(config: Ecr17Config) {
|
|
212
|
+
const client = useMemo(() => createEcr17Client(config), [config]);
|
|
213
|
+
const [progress, setProgress] = useState<string>('');
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
client.setOnProgress((e: ProgressEvent) => setProgress(e.message));
|
|
217
|
+
client.connect();
|
|
218
|
+
return () => client.disconnect();
|
|
219
|
+
}, [client]);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
progress,
|
|
223
|
+
pay: (amountCents: number) => client.pay({ amountCents }),
|
|
224
|
+
reverse: () => client.reverse({}),
|
|
225
|
+
status: () => client.status(),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## βοΈ Configuration
|
|
231
|
+
|
|
232
|
+
`Ecr17Config`: `host` (required), `port?`, `terminalId` (required), `cashRegisterId`
|
|
233
|
+
(required), `lrcMode?`, `keepAlive?`, `autoReconnect?`, `connectionTimeoutMs?`,
|
|
234
|
+
`responseTimeoutMs?`, `ackTimeoutMs?`, `retryCount?`, `retryDelayMs?`, `debug?`.
|
|
235
|
+
|
|
236
|
+
## π API reference
|
|
237
|
+
|
|
238
|
+
All commands are **async** (`Promise`) and perform a full request/response
|
|
239
|
+
exchange. `configure`/`configuration` are synchronous.
|
|
240
|
+
|
|
241
|
+
| Method | Command | Returns |
|
|
242
|
+
|--------|:------:|---------|
|
|
243
|
+
| `connect()` / `disconnect()` / `isConnected()` | β | `Promise<void>` / `void` / `bool` |
|
|
244
|
+
| `status()` | `s` | `PosStatusResponse` |
|
|
245
|
+
| `pay(req)` / `payExtended(req)` | `P` / `X` | `PaymentResult` |
|
|
246
|
+
| `reverse(req)` | `S` | `ReversalResult` |
|
|
247
|
+
| `preAuth(req)` / `incrementalAuth(req)` / `preAuthClosure(req)` | `p` / `i` / `c` | `PreAuthResult` / `PaymentResult` |
|
|
248
|
+
| `verifyCard(req)` | `H` | `CardVerificationResult` |
|
|
249
|
+
| `closeSession()` / `totals()` | `C` / `T` | `CloseSessionResult` / `TotalsResult` |
|
|
250
|
+
| `sendLastResult()` | `G` | `PaymentResult` |
|
|
251
|
+
| `enableEcrPrinting(bool)` / `reprint(bool)` | `E` / `R` | `Promise<void>` |
|
|
252
|
+
| `vas(xml)` | `K` | `VasResult` |
|
|
253
|
+
|
|
254
|
+
Commands require an open connection (`connect()` first) and reject on
|
|
255
|
+
timeout / retransmission exhaustion / disconnect.
|
|
256
|
+
|
|
257
|
+
## π‘ Events
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
client.setOnProgress((e) => {/* e.message β display text during a procedure */});
|
|
261
|
+
client.setOnReceiptLine((l) => {/* l.text β a receipt line when ECR printing is on */});
|
|
262
|
+
client.setOnConnectionStateChange((s) => {/* 'disconnected' | 'connecting' | 'connected' */});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## π Protocol cheat-sheet
|
|
266
|
+
|
|
267
|
+
App frame: `STX(0x02)` Β· payload Β· `ETX(0x03)` Β· `LRC`. Progress: `SOH(0x01)` Β·
|
|
268
|
+
20 chars Β· `EOT(0x04)`. Confirmation: `ACK(0x06)` / `NAK(0x15)` Β· `ETX` Β· `LRC`.
|
|
269
|
+
LRC = `0x7F` XOR-folded; framing bytes folded in are selectable via `lrcMode`
|
|
270
|
+
(`stx` / `std` / `noext` / `stx_noext`).
|
|
271
|
+
|
|
272
|
+
## ποΈ Architecture
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
package/cpp/
|
|
276
|
+
βββ Lcr/ # LRC (4 modes, base 0x7F)
|
|
277
|
+
βββ PacketCodec/ # framing: STXΒ·ETXΒ·SOHΒ·EOTΒ·ACKΒ·NAK + LRC
|
|
278
|
+
βββ Ecr17Protocol/ # request builders (all commands), fixed-width + validated
|
|
279
|
+
βββ Ecr17Response/ # response field parsers -> plain structs
|
|
280
|
+
βββ Session/ # ACK/NAK + retransmit + timeout orchestration
|
|
281
|
+
βββ Transport/ # abstract Transport + NativeTransportAdapter + FakeTransport (tests)
|
|
282
|
+
βββ Ecr17Client/ # HybridEcr17Client (Nitro async API)
|
|
283
|
+
package/android/.../HybridEcr17Transport.kt # Kotlin TCP transport
|
|
284
|
+
package/ios/HybridEcr17Transport.swift # Swift (Network.framework) transport
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## π§ͺ Testing
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
cmake -S package/cpp/tests -B build -DCMAKE_BUILD_TYPE=Release
|
|
291
|
+
cmake --build build && ctest --test-dir build --output-on-failure
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
83 tests cover LRC, packet (de)framing edge cases, every builder's byte layout,
|
|
295
|
+
every response parser, and the documented payment / reversal / re-pay / progress
|
|
296
|
+
/ receipt / NAK-retransmit / timeout flows (against an in-memory `FakeTransport`).
|
|
297
|
+
|
|
298
|
+
## π§Ύ Tokenization & receipts
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// Tokenization: attach a contract to a payment/preAuth/verifyCard. The 'U'
|
|
302
|
+
// additional-data message is sent automatically (P -> ACK -> U -> ACK -> result).
|
|
303
|
+
await client.pay({
|
|
304
|
+
amountCents: 1000,
|
|
305
|
+
tokenization: { service: 'recurring', contractCode: '1666354841608' },
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Receipts printed by the ECR: enable printing, set receiptDrainMs in the config,
|
|
309
|
+
// and receive lines via the event.
|
|
310
|
+
await client.enableEcrPrinting(true);
|
|
311
|
+
client.setOnReceiptLine((l) => appendToReceipt(l.text));
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## π Testing against a real terminal (opt-in)
|
|
315
|
+
|
|
316
|
+
An opt-in C++ integration test runs the full core over a real TCP socket. It is
|
|
317
|
+
**skipped** unless `ECR17_TERMINAL_HOST` is set:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
cmake -S package/cpp/tests -B build && cmake --build build
|
|
321
|
+
ECR17_TERMINAL_HOST=192.168.1.50 ECR17_TERMINAL_PORT=10000 \
|
|
322
|
+
ECR17_TERMINAL_ID=00000000 ECR17_LRC_MODE=std \
|
|
323
|
+
ctest --test-dir build -R Integration --output-on-failure
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## π€ Vibe-coding batteries included
|
|
327
|
+
|
|
328
|
+
Building on an undocumented payment protocol is exactly where AI assistants get
|
|
329
|
+
things subtly wrong. This repo ships the context to prevent that, so an agent (or
|
|
330
|
+
a new contributor) is productive and *safe* from minute one:
|
|
331
|
+
|
|
332
|
+
- **[`AGENTS.md`](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/AGENTS.md)** /
|
|
333
|
+
**[`CLAUDE.md`](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/CLAUDE.md)** β project guide, the mandatory
|
|
334
|
+
per-phase workflow, CI strategy, and the **money-critical** rules (e.g. never
|
|
335
|
+
blindly retry a payment).
|
|
336
|
+
- **`docs/LESSON.md`** β accumulated, verified engineering lessons (Nitro APIs,
|
|
337
|
+
C++βKotlin JNI, build traps, payment-safety) β the gotchas already solved.
|
|
338
|
+
- **`PROGRESS.md`** β crash-safe resume state across sessions.
|
|
339
|
+
|
|
340
|
+
The result: less hallucination, fewer footguns, and changes that respect the
|
|
341
|
+
payment-safety invariants by default.
|
|
342
|
+
|
|
343
|
+
## π License
|
|
344
|
+
|
|
345
|
+
[MIT](https://github.com/padosoft/react-native-ecr17-protocol/blob/main/LICENSE) Β© [padosoft](https://github.com/padosoft)
|
|
346
|
+
|
|
347
|
+
> **Disclaimer:** independent integration library. "ECR17", "Nexi" and related
|
|
348
|
+
> marks belong to their respective owners and are referenced for interoperability only.
|