@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.
Files changed (80) hide show
  1. package/Ecr17.podspec +39 -39
  2. package/README.md +348 -348
  3. package/android/CMakeLists.txt +41 -41
  4. package/android/build.gradle +148 -148
  5. package/android/fix-prefab.gradle +50 -50
  6. package/android/gradle.properties +5 -5
  7. package/android/src/main/AndroidManifest.xml +2 -2
  8. package/android/src/main/cpp/cpp-adapter.cpp +8 -8
  9. package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -233
  10. package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -30
  11. package/cpp/Ecr17.hpp +1 -1
  12. package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -598
  13. package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -85
  14. package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -277
  15. package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -103
  16. package/cpp/Ecr17Response/Ecr17Response.cpp +155 -155
  17. package/cpp/Ecr17Response/Ecr17Response.hpp +113 -113
  18. package/cpp/Lcr/Lcr.cpp +42 -42
  19. package/cpp/Lcr/Lcr.hpp +21 -21
  20. package/cpp/PacketCodec/PacketCodec.cpp +145 -145
  21. package/cpp/PacketCodec/PacketCodec.hpp +47 -47
  22. package/cpp/Session/Ecr17Session.cpp +260 -260
  23. package/cpp/Session/Ecr17Session.hpp +97 -97
  24. package/cpp/Session/RetryPolicy.hpp +23 -23
  25. package/cpp/Transport/FakeTransport.hpp +95 -95
  26. package/cpp/Transport/NativeTransportAdapter.cpp +42 -42
  27. package/cpp/Transport/NativeTransportAdapter.hpp +32 -32
  28. package/cpp/Transport/Transport.hpp +30 -30
  29. package/cpp/tests/CMakeLists.txt +55 -55
  30. package/cpp/tests/PosixTcpTransport.hpp +105 -105
  31. package/cpp/tests/stubs/LrcMode.hpp +25 -25
  32. package/cpp/tests/test_flows.cpp +148 -148
  33. package/cpp/tests/test_integration_terminal.cpp +72 -72
  34. package/cpp/tests/test_lrc.cpp +66 -66
  35. package/cpp/tests/test_packet_codec.cpp +164 -164
  36. package/cpp/tests/test_protocol.cpp +102 -102
  37. package/cpp/tests/test_protocol_commands.cpp +190 -190
  38. package/cpp/tests/test_response.cpp +164 -164
  39. package/cpp/tests/test_retry_policy.cpp +28 -28
  40. package/cpp/tests/test_session.cpp +262 -262
  41. package/ios/HybridEcr17Transport.swift +103 -103
  42. package/lib/commonjs/index.js +50 -0
  43. package/lib/commonjs/index.js.map +1 -0
  44. package/lib/commonjs/package.json +1 -0
  45. package/lib/commonjs/specs/client.nitro.js +17 -0
  46. package/lib/commonjs/specs/client.nitro.js.map +1 -0
  47. package/lib/commonjs/specs/transport.nitro.js +6 -0
  48. package/lib/commonjs/specs/transport.nitro.js.map +1 -0
  49. package/lib/commonjs/types/client.js +2 -0
  50. package/lib/commonjs/types/client.js.map +1 -0
  51. package/lib/commonjs/utils/client.js +13 -0
  52. package/lib/commonjs/utils/client.js.map +1 -0
  53. package/lib/module/index.js +7 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/specs/client.nitro.js +13 -0
  56. package/lib/module/specs/client.nitro.js.map +1 -0
  57. package/lib/module/specs/transport.nitro.js +4 -0
  58. package/lib/module/specs/transport.nitro.js.map +1 -0
  59. package/lib/module/types/client.js +2 -0
  60. package/lib/module/types/client.js.map +1 -0
  61. package/lib/module/utils/client.js +9 -0
  62. package/lib/module/utils/client.js.map +1 -0
  63. package/lib/typescript/src/index.d.ts +5 -0
  64. package/lib/typescript/src/index.d.ts.map +1 -0
  65. package/lib/typescript/src/specs/client.nitro.d.ts +63 -0
  66. package/lib/typescript/src/specs/client.nitro.d.ts.map +1 -0
  67. package/lib/typescript/src/specs/transport.nitro.d.ts +13 -0
  68. package/lib/typescript/src/specs/transport.nitro.d.ts.map +1 -0
  69. package/lib/typescript/src/types/client.d.ts +138 -0
  70. package/lib/typescript/src/types/client.d.ts.map +1 -0
  71. package/lib/typescript/src/utils/client.d.ts +3 -0
  72. package/lib/typescript/src/utils/client.d.ts.map +1 -0
  73. package/nitro.json +30 -30
  74. package/package.json +4 -4
  75. package/react-native.config.js +18 -18
  76. package/src/index.ts +4 -4
  77. package/src/specs/client.nitro.ts +102 -102
  78. package/src/specs/transport.nitro.ts +25 -25
  79. package/src/types/client.ts +196 -196
  80. package/src/utils/client.ts +10 -10
@@ -1,55 +1,55 @@
1
- cmake_minimum_required(VERSION 3.16)
2
- project(ecr17_cpp_tests CXX)
3
-
4
- set(CMAKE_CXX_STANDARD 20)
5
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
- set(CMAKE_CXX_EXTENSIONS OFF)
7
-
8
- # GoogleTest via FetchContent (CI has network access).
9
- include(FetchContent)
10
- FetchContent_Declare(
11
- googletest
12
- URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
13
- )
14
- # Keep CRT linkage consistent on Windows runners.
15
- set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
16
- FetchContent_MakeAvailable(googletest)
17
-
18
- enable_testing()
19
-
20
- # Production C++ sources under test (../ is package/cpp).
21
- set(ECR17_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
22
-
23
- add_executable(ecr17_cpp_tests
24
- test_lrc.cpp
25
- test_packet_codec.cpp
26
- test_protocol.cpp
27
- test_protocol_commands.cpp
28
- test_response.cpp
29
- test_session.cpp
30
- test_retry_policy.cpp
31
- test_integration_terminal.cpp
32
- test_flows.cpp
33
- ${ECR17_CPP_DIR}/Lcr/Lcr.cpp
34
- ${ECR17_CPP_DIR}/PacketCodec/PacketCodec.cpp
35
- ${ECR17_CPP_DIR}/Ecr17Protocol/Ecr17Protocol.cpp
36
- ${ECR17_CPP_DIR}/Ecr17Response/Ecr17Response.cpp
37
- ${ECR17_CPP_DIR}/Session/Ecr17Session.cpp
38
- )
39
-
40
- target_include_directories(ecr17_cpp_tests PRIVATE
41
- ${ECR17_CPP_DIR} # resolves "Lcr/Lcr.hpp", "PacketCodec/..."
42
- ${CMAKE_CURRENT_SOURCE_DIR}/stubs # test-only LrcMode.hpp (Nitrogen-generated in real builds)
43
- )
44
-
45
- if(MSVC)
46
- target_compile_options(ecr17_cpp_tests PRIVATE /W4)
47
- else()
48
- target_compile_options(ecr17_cpp_tests PRIVATE -Wall -Wextra)
49
- endif()
50
-
51
- find_package(Threads REQUIRED)
52
- target_link_libraries(ecr17_cpp_tests PRIVATE GTest::gtest_main Threads::Threads)
53
-
54
- include(GoogleTest)
55
- gtest_discover_tests(ecr17_cpp_tests)
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project(ecr17_cpp_tests CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ # GoogleTest via FetchContent (CI has network access).
9
+ include(FetchContent)
10
+ FetchContent_Declare(
11
+ googletest
12
+ URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz
13
+ )
14
+ # Keep CRT linkage consistent on Windows runners.
15
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
16
+ FetchContent_MakeAvailable(googletest)
17
+
18
+ enable_testing()
19
+
20
+ # Production C++ sources under test (../ is package/cpp).
21
+ set(ECR17_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..)
22
+
23
+ add_executable(ecr17_cpp_tests
24
+ test_lrc.cpp
25
+ test_packet_codec.cpp
26
+ test_protocol.cpp
27
+ test_protocol_commands.cpp
28
+ test_response.cpp
29
+ test_session.cpp
30
+ test_retry_policy.cpp
31
+ test_integration_terminal.cpp
32
+ test_flows.cpp
33
+ ${ECR17_CPP_DIR}/Lcr/Lcr.cpp
34
+ ${ECR17_CPP_DIR}/PacketCodec/PacketCodec.cpp
35
+ ${ECR17_CPP_DIR}/Ecr17Protocol/Ecr17Protocol.cpp
36
+ ${ECR17_CPP_DIR}/Ecr17Response/Ecr17Response.cpp
37
+ ${ECR17_CPP_DIR}/Session/Ecr17Session.cpp
38
+ )
39
+
40
+ target_include_directories(ecr17_cpp_tests PRIVATE
41
+ ${ECR17_CPP_DIR} # resolves "Lcr/Lcr.hpp", "PacketCodec/..."
42
+ ${CMAKE_CURRENT_SOURCE_DIR}/stubs # test-only LrcMode.hpp (Nitrogen-generated in real builds)
43
+ )
44
+
45
+ if(MSVC)
46
+ target_compile_options(ecr17_cpp_tests PRIVATE /W4)
47
+ else()
48
+ target_compile_options(ecr17_cpp_tests PRIVATE -Wall -Wextra)
49
+ endif()
50
+
51
+ find_package(Threads REQUIRED)
52
+ target_link_libraries(ecr17_cpp_tests PRIVATE GTest::gtest_main Threads::Threads)
53
+
54
+ include(GoogleTest)
55
+ gtest_discover_tests(ecr17_cpp_tests)
@@ -1,105 +1,105 @@
1
- #pragma once
2
-
3
- // Test-only POSIX TCP transport, used by the opt-in real-terminal integration
4
- // test. Lets a developer run the C++ protocol core (builders + session + parsers)
5
- // against an actual terminal over the LAN, without the native (Kotlin/Swift)
6
- // transport. POSIX-only (Linux/macOS); excluded on Windows.
7
- #if !defined(_WIN32)
8
-
9
- #include <arpa/inet.h>
10
- #include <netdb.h>
11
- #include <sys/socket.h>
12
- #include <unistd.h>
13
-
14
- #include <atomic>
15
- #include <cstdint>
16
- #include <stdexcept>
17
- #include <string>
18
- #include <thread>
19
- #include <vector>
20
-
21
- #include "Transport/Transport.hpp"
22
-
23
- namespace margelo::nitro::ecr17 {
24
-
25
- class PosixTcpTransport : public Transport {
26
- public:
27
- PosixTcpTransport(std::string host, int port) : host_(std::move(host)), port_(port) {}
28
- ~PosixTcpTransport() override { disconnect(); }
29
-
30
- void connect() override {
31
- addrinfo hints{};
32
- hints.ai_family = AF_UNSPEC;
33
- hints.ai_socktype = SOCK_STREAM;
34
- addrinfo* res = nullptr;
35
- if (getaddrinfo(host_.c_str(), std::to_string(port_).c_str(), &hints, &res) != 0) {
36
- throw std::runtime_error("PosixTcpTransport: host resolution failed");
37
- }
38
- fd_ = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
39
- if (fd_ < 0) {
40
- freeaddrinfo(res);
41
- throw std::runtime_error("PosixTcpTransport: socket() failed");
42
- }
43
- if (::connect(fd_, res->ai_addr, res->ai_addrlen) != 0) {
44
- freeaddrinfo(res);
45
- ::close(fd_);
46
- fd_ = -1;
47
- throw std::runtime_error("PosixTcpTransport: connect() failed");
48
- }
49
- freeaddrinfo(res);
50
- running_ = true;
51
- reader_ = std::thread([this]() { readLoop(); });
52
- }
53
-
54
- void disconnect() override {
55
- running_ = false;
56
- if (fd_ >= 0) {
57
- ::shutdown(fd_, SHUT_RDWR);
58
- ::close(fd_);
59
- fd_ = -1;
60
- }
61
- if (reader_.joinable()) {
62
- reader_.join();
63
- }
64
- }
65
-
66
- bool isConnected() const override { return fd_ >= 0; }
67
-
68
- void send(const std::vector<uint8_t>& bytes) override {
69
- if (fd_ >= 0) {
70
- ::send(fd_, bytes.data(), bytes.size(), 0);
71
- }
72
- }
73
-
74
- void setDataCallback(DataCallback cb) override { onData_ = std::move(cb); }
75
- void setDisconnectCallback(DisconnectCallback cb) override { onDisconnect_ = std::move(cb); }
76
-
77
- private:
78
- void readLoop() {
79
- uint8_t buffer[4096];
80
- while (running_) {
81
- ssize_t n = ::recv(fd_, buffer, sizeof(buffer), 0);
82
- if (n <= 0) {
83
- break;
84
- }
85
- if (onData_) {
86
- onData_(std::vector<uint8_t>(buffer, buffer + n));
87
- }
88
- }
89
- if (onDisconnect_) {
90
- onDisconnect_();
91
- }
92
- }
93
-
94
- std::string host_;
95
- int port_;
96
- int fd_ = -1;
97
- std::atomic<bool> running_{false};
98
- std::thread reader_;
99
- DataCallback onData_{};
100
- DisconnectCallback onDisconnect_{};
101
- };
102
-
103
- } // namespace margelo::nitro::ecr17
104
-
105
- #endif // !_WIN32
1
+ #pragma once
2
+
3
+ // Test-only POSIX TCP transport, used by the opt-in real-terminal integration
4
+ // test. Lets a developer run the C++ protocol core (builders + session + parsers)
5
+ // against an actual terminal over the LAN, without the native (Kotlin/Swift)
6
+ // transport. POSIX-only (Linux/macOS); excluded on Windows.
7
+ #if !defined(_WIN32)
8
+
9
+ #include <arpa/inet.h>
10
+ #include <netdb.h>
11
+ #include <sys/socket.h>
12
+ #include <unistd.h>
13
+
14
+ #include <atomic>
15
+ #include <cstdint>
16
+ #include <stdexcept>
17
+ #include <string>
18
+ #include <thread>
19
+ #include <vector>
20
+
21
+ #include "Transport/Transport.hpp"
22
+
23
+ namespace margelo::nitro::ecr17 {
24
+
25
+ class PosixTcpTransport : public Transport {
26
+ public:
27
+ PosixTcpTransport(std::string host, int port) : host_(std::move(host)), port_(port) {}
28
+ ~PosixTcpTransport() override { disconnect(); }
29
+
30
+ void connect() override {
31
+ addrinfo hints{};
32
+ hints.ai_family = AF_UNSPEC;
33
+ hints.ai_socktype = SOCK_STREAM;
34
+ addrinfo* res = nullptr;
35
+ if (getaddrinfo(host_.c_str(), std::to_string(port_).c_str(), &hints, &res) != 0) {
36
+ throw std::runtime_error("PosixTcpTransport: host resolution failed");
37
+ }
38
+ fd_ = ::socket(res->ai_family, res->ai_socktype, res->ai_protocol);
39
+ if (fd_ < 0) {
40
+ freeaddrinfo(res);
41
+ throw std::runtime_error("PosixTcpTransport: socket() failed");
42
+ }
43
+ if (::connect(fd_, res->ai_addr, res->ai_addrlen) != 0) {
44
+ freeaddrinfo(res);
45
+ ::close(fd_);
46
+ fd_ = -1;
47
+ throw std::runtime_error("PosixTcpTransport: connect() failed");
48
+ }
49
+ freeaddrinfo(res);
50
+ running_ = true;
51
+ reader_ = std::thread([this]() { readLoop(); });
52
+ }
53
+
54
+ void disconnect() override {
55
+ running_ = false;
56
+ if (fd_ >= 0) {
57
+ ::shutdown(fd_, SHUT_RDWR);
58
+ ::close(fd_);
59
+ fd_ = -1;
60
+ }
61
+ if (reader_.joinable()) {
62
+ reader_.join();
63
+ }
64
+ }
65
+
66
+ bool isConnected() const override { return fd_ >= 0; }
67
+
68
+ void send(const std::vector<uint8_t>& bytes) override {
69
+ if (fd_ >= 0) {
70
+ ::send(fd_, bytes.data(), bytes.size(), 0);
71
+ }
72
+ }
73
+
74
+ void setDataCallback(DataCallback cb) override { onData_ = std::move(cb); }
75
+ void setDisconnectCallback(DisconnectCallback cb) override { onDisconnect_ = std::move(cb); }
76
+
77
+ private:
78
+ void readLoop() {
79
+ uint8_t buffer[4096];
80
+ while (running_) {
81
+ ssize_t n = ::recv(fd_, buffer, sizeof(buffer), 0);
82
+ if (n <= 0) {
83
+ break;
84
+ }
85
+ if (onData_) {
86
+ onData_(std::vector<uint8_t>(buffer, buffer + n));
87
+ }
88
+ }
89
+ if (onDisconnect_) {
90
+ onDisconnect_();
91
+ }
92
+ }
93
+
94
+ std::string host_;
95
+ int port_;
96
+ int fd_ = -1;
97
+ std::atomic<bool> running_{false};
98
+ std::thread reader_;
99
+ DataCallback onData_{};
100
+ DisconnectCallback onDisconnect_{};
101
+ };
102
+
103
+ } // namespace margelo::nitro::ecr17
104
+
105
+ #endif // !_WIN32
@@ -1,25 +1,25 @@
1
- #pragma once
2
-
3
- // TEST-ONLY STUB.
4
- //
5
- // In a real build this header is generated by Nitrogen from the TypeScript
6
- // union `type LrcMode = "stx" | "std" | "noext" | "stx_noext"` (see
7
- // package/src/types/client.ts). The unit tests compile the pure-logic C++
8
- // units (Lrc, PacketCodec, Ecr17Protocol) in isolation, without running
9
- // Nitrogen, so we provide a minimal enum that mirrors the generated one.
10
- //
11
- // The member names MUST match what the production code references
12
- // (Lrc.cpp uses LrcMode::STX, LrcMode::STX_NOEXT, LrcMode::NOEXT). If Nitrogen
13
- // turns out to emit different casing, that is a real bug to reconcile when the
14
- // codegen is run — this stub documents the contract the code assumes today.
15
-
16
- namespace margelo::nitro::ecr17 {
17
-
18
- enum class LrcMode {
19
- STX,
20
- STD,
21
- NOEXT,
22
- STX_NOEXT,
23
- };
24
-
25
- } // namespace margelo::nitro::ecr17
1
+ #pragma once
2
+
3
+ // TEST-ONLY STUB.
4
+ //
5
+ // In a real build this header is generated by Nitrogen from the TypeScript
6
+ // union `type LrcMode = "stx" | "std" | "noext" | "stx_noext"` (see
7
+ // package/src/types/client.ts). The unit tests compile the pure-logic C++
8
+ // units (Lrc, PacketCodec, Ecr17Protocol) in isolation, without running
9
+ // Nitrogen, so we provide a minimal enum that mirrors the generated one.
10
+ //
11
+ // The member names MUST match what the production code references
12
+ // (Lrc.cpp uses LrcMode::STX, LrcMode::STX_NOEXT, LrcMode::NOEXT). If Nitrogen
13
+ // turns out to emit different casing, that is a real bug to reconcile when the
14
+ // codegen is run — this stub documents the contract the code assumes today.
15
+
16
+ namespace margelo::nitro::ecr17 {
17
+
18
+ enum class LrcMode {
19
+ STX,
20
+ STD,
21
+ NOEXT,
22
+ STX_NOEXT,
23
+ };
24
+
25
+ } // namespace margelo::nitro::ecr17
@@ -1,148 +1,148 @@
1
- // End-to-end protocol *flow* tests, modelled on the documented Nexi ECR17
2
- // sequences (basic payment, reversal/"annullamento", re-payment, status,
3
- // NAK retransmission).
4
- //
5
- // These exercise the layers that exist today: request building
6
- // (Ecr17Protocol) + physical framing (PacketCodec). Response *field* parsing
7
- // is not implemented yet, so terminal responses are synthesized as spec-shaped
8
- // payloads and asserted at the framing/classification level (packet type, LRC
9
- // validity, message code position), not by parsed fields.
10
-
11
- #include <gtest/gtest.h>
12
-
13
- #include <cstdint>
14
- #include <string>
15
- #include <vector>
16
-
17
- #include "Ecr17Protocol/Ecr17Protocol.hpp"
18
- #include "PacketCodec/PacketCodec.hpp"
19
-
20
- using namespace margelo::nitro::ecr17;
21
-
22
- namespace {
23
-
24
- constexpr uint8_t kStx = 0x02;
25
- constexpr uint8_t kEtx = 0x03;
26
- constexpr uint8_t kSoh = 0x01;
27
- constexpr uint8_t kEot = 0x04;
28
- constexpr uint8_t kAck = 0x06;
29
- constexpr uint8_t kNak = 0x15;
30
-
31
- const std::string kTerminal = "12345678";
32
- const std::string kCashReg = "00000001";
33
-
34
- // Terminal-side framing of a progress-update packet: SOH + 20-char message + EOT.
35
- std::vector<uint8_t> progressFrame(const std::string& msg20) {
36
- std::vector<uint8_t> f{kSoh};
37
- f.insert(f.end(), msg20.begin(), msg20.end());
38
- f.push_back(kEot);
39
- return f;
40
- }
41
-
42
- // Terminal-side framing of a spec-shaped result message ('E', result "00" = OK).
43
- std::vector<uint8_t> okResultFrame(PacketCodec& codec) {
44
- std::string e = kTerminal + "0" + "E" + "00" + std::string(60, '0');
45
- return codec.encodeApplication(e);
46
- }
47
-
48
- } // namespace
49
-
50
- // Documented "Basic Payment Flow": request -> ACK -> progress -> result -> ACK.
51
- TEST(Flow, BasicPayment) {
52
- PacketCodec codec(LrcMode::STD);
53
-
54
- // 1. ECR -> POS: payment request on the wire (STX .. ETX .. LRC).
55
- std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650);
56
- auto reqFrame = codec.encodeApplication(req);
57
- EXPECT_EQ(reqFrame.front(), kStx);
58
- EXPECT_EQ(reqFrame[reqFrame.size() - 2], kEtx);
59
-
60
- // POS validates and recovers the exact request payload.
61
- DecodedPacket atPos = codec.decode(reqFrame);
62
- EXPECT_EQ(atPos.type, PacketType::APPLICATION);
63
- EXPECT_TRUE(atPos.validLrc);
64
- EXPECT_EQ(atPos.payload, req);
65
- EXPECT_EQ(atPos.payload[9], 'P');
66
-
67
- // 2. POS -> ECR: physical ACK.
68
- EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
69
-
70
- // 3. POS -> ECR: progress update while contacting the host (no LRC).
71
- DecodedPacket prog = codec.decode(progressFrame("ATTENDERE PREGO "));
72
- EXPECT_EQ(prog.type, PacketType::PROGRESS);
73
- EXPECT_EQ(prog.payload, "ATTENDERE PREGO ");
74
-
75
- // 4. POS -> ECR: positive result.
76
- DecodedPacket result = codec.decode(okResultFrame(codec));
77
- EXPECT_EQ(result.type, PacketType::APPLICATION);
78
- EXPECT_TRUE(result.validLrc);
79
- EXPECT_EQ(result.payload[9], 'E');
80
- EXPECT_EQ(result.payload.substr(10, 2), "00");
81
-
82
- // 5. ECR -> POS: ACK confirming receipt of the result.
83
- EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
84
- }
85
-
86
- // Documented NAK handling: a NAK triggers retransmission of the same message
87
- // (up to 3 times). Framing is deterministic, so the retransmitted bytes match.
88
- TEST(Flow, NakTriggersIdenticalRetransmit) {
89
- PacketCodec codec(LrcMode::STD);
90
- std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 1999);
91
- auto first = codec.encodeApplication(req);
92
-
93
- EXPECT_EQ(codec.decode(codec.encodeControl(kNak)).type, PacketType::NAK);
94
-
95
- auto retransmit = codec.encodeApplication(req);
96
- EXPECT_EQ(first, retransmit);
97
- }
98
-
99
- // Documented "Reversal Last Transaction" (annullamento), command 'S'.
100
- TEST(Flow, ReversalAnnullamento) {
101
- PacketCodec codec(LrcMode::STD);
102
-
103
- std::string req = Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg, "000123");
104
- ASSERT_EQ(req.size(), 26u);
105
- EXPECT_EQ(req[9], 'S');
106
- EXPECT_EQ(req.substr(18, 6), "000123");
107
-
108
- DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
109
- EXPECT_EQ(atPos.type, PacketType::APPLICATION);
110
- EXPECT_TRUE(atPos.validLrc);
111
- EXPECT_EQ(atPos.payload, req);
112
-
113
- EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
114
- DecodedPacket result = codec.decode(okResultFrame(codec));
115
- EXPECT_TRUE(result.validLrc);
116
- EXPECT_EQ(result.payload[9], 'E');
117
- }
118
-
119
- // Pay -> annullamento -> ripaga (re-payment with the corrected amount).
120
- TEST(Flow, PayReverseRepay) {
121
- PacketCodec codec(LrcMode::STD);
122
-
123
- auto pay1 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650));
124
- auto rev = codec.encodeApplication(Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg));
125
- auto pay2 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 720));
126
-
127
- for (const auto* frame : {&pay1, &rev, &pay2}) {
128
- DecodedPacket d = codec.decode(*frame);
129
- EXPECT_EQ(d.type, PacketType::APPLICATION);
130
- EXPECT_TRUE(d.validLrc);
131
- }
132
-
133
- EXPECT_EQ(codec.decode(pay1).payload.substr(23, 8), "00000650");
134
- EXPECT_EQ(codec.decode(pay2).payload.substr(23, 8), "00000720");
135
- EXPECT_NE(pay1, pay2); // the corrected re-payment differs on the wire
136
- }
137
-
138
- // Terminal status request/round-trip ('s').
139
- TEST(Flow, StatusRequest) {
140
- PacketCodec codec(LrcMode::STD);
141
- std::string req = Ecr17Protocol::buildStatusMessage(kTerminal);
142
- EXPECT_EQ(req, "123456780s");
143
-
144
- DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
145
- EXPECT_EQ(atPos.type, PacketType::APPLICATION);
146
- EXPECT_TRUE(atPos.validLrc);
147
- EXPECT_EQ(atPos.payload, req);
148
- }
1
+ // End-to-end protocol *flow* tests, modelled on the documented Nexi ECR17
2
+ // sequences (basic payment, reversal/"annullamento", re-payment, status,
3
+ // NAK retransmission).
4
+ //
5
+ // These exercise the layers that exist today: request building
6
+ // (Ecr17Protocol) + physical framing (PacketCodec). Response *field* parsing
7
+ // is not implemented yet, so terminal responses are synthesized as spec-shaped
8
+ // payloads and asserted at the framing/classification level (packet type, LRC
9
+ // validity, message code position), not by parsed fields.
10
+
11
+ #include <gtest/gtest.h>
12
+
13
+ #include <cstdint>
14
+ #include <string>
15
+ #include <vector>
16
+
17
+ #include "Ecr17Protocol/Ecr17Protocol.hpp"
18
+ #include "PacketCodec/PacketCodec.hpp"
19
+
20
+ using namespace margelo::nitro::ecr17;
21
+
22
+ namespace {
23
+
24
+ constexpr uint8_t kStx = 0x02;
25
+ constexpr uint8_t kEtx = 0x03;
26
+ constexpr uint8_t kSoh = 0x01;
27
+ constexpr uint8_t kEot = 0x04;
28
+ constexpr uint8_t kAck = 0x06;
29
+ constexpr uint8_t kNak = 0x15;
30
+
31
+ const std::string kTerminal = "12345678";
32
+ const std::string kCashReg = "00000001";
33
+
34
+ // Terminal-side framing of a progress-update packet: SOH + 20-char message + EOT.
35
+ std::vector<uint8_t> progressFrame(const std::string& msg20) {
36
+ std::vector<uint8_t> f{kSoh};
37
+ f.insert(f.end(), msg20.begin(), msg20.end());
38
+ f.push_back(kEot);
39
+ return f;
40
+ }
41
+
42
+ // Terminal-side framing of a spec-shaped result message ('E', result "00" = OK).
43
+ std::vector<uint8_t> okResultFrame(PacketCodec& codec) {
44
+ std::string e = kTerminal + "0" + "E" + "00" + std::string(60, '0');
45
+ return codec.encodeApplication(e);
46
+ }
47
+
48
+ } // namespace
49
+
50
+ // Documented "Basic Payment Flow": request -> ACK -> progress -> result -> ACK.
51
+ TEST(Flow, BasicPayment) {
52
+ PacketCodec codec(LrcMode::STD);
53
+
54
+ // 1. ECR -> POS: payment request on the wire (STX .. ETX .. LRC).
55
+ std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650);
56
+ auto reqFrame = codec.encodeApplication(req);
57
+ EXPECT_EQ(reqFrame.front(), kStx);
58
+ EXPECT_EQ(reqFrame[reqFrame.size() - 2], kEtx);
59
+
60
+ // POS validates and recovers the exact request payload.
61
+ DecodedPacket atPos = codec.decode(reqFrame);
62
+ EXPECT_EQ(atPos.type, PacketType::APPLICATION);
63
+ EXPECT_TRUE(atPos.validLrc);
64
+ EXPECT_EQ(atPos.payload, req);
65
+ EXPECT_EQ(atPos.payload[9], 'P');
66
+
67
+ // 2. POS -> ECR: physical ACK.
68
+ EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
69
+
70
+ // 3. POS -> ECR: progress update while contacting the host (no LRC).
71
+ DecodedPacket prog = codec.decode(progressFrame("ATTENDERE PREGO "));
72
+ EXPECT_EQ(prog.type, PacketType::PROGRESS);
73
+ EXPECT_EQ(prog.payload, "ATTENDERE PREGO ");
74
+
75
+ // 4. POS -> ECR: positive result.
76
+ DecodedPacket result = codec.decode(okResultFrame(codec));
77
+ EXPECT_EQ(result.type, PacketType::APPLICATION);
78
+ EXPECT_TRUE(result.validLrc);
79
+ EXPECT_EQ(result.payload[9], 'E');
80
+ EXPECT_EQ(result.payload.substr(10, 2), "00");
81
+
82
+ // 5. ECR -> POS: ACK confirming receipt of the result.
83
+ EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
84
+ }
85
+
86
+ // Documented NAK handling: a NAK triggers retransmission of the same message
87
+ // (up to 3 times). Framing is deterministic, so the retransmitted bytes match.
88
+ TEST(Flow, NakTriggersIdenticalRetransmit) {
89
+ PacketCodec codec(LrcMode::STD);
90
+ std::string req = Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 1999);
91
+ auto first = codec.encodeApplication(req);
92
+
93
+ EXPECT_EQ(codec.decode(codec.encodeControl(kNak)).type, PacketType::NAK);
94
+
95
+ auto retransmit = codec.encodeApplication(req);
96
+ EXPECT_EQ(first, retransmit);
97
+ }
98
+
99
+ // Documented "Reversal Last Transaction" (annullamento), command 'S'.
100
+ TEST(Flow, ReversalAnnullamento) {
101
+ PacketCodec codec(LrcMode::STD);
102
+
103
+ std::string req = Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg, "000123");
104
+ ASSERT_EQ(req.size(), 26u);
105
+ EXPECT_EQ(req[9], 'S');
106
+ EXPECT_EQ(req.substr(18, 6), "000123");
107
+
108
+ DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
109
+ EXPECT_EQ(atPos.type, PacketType::APPLICATION);
110
+ EXPECT_TRUE(atPos.validLrc);
111
+ EXPECT_EQ(atPos.payload, req);
112
+
113
+ EXPECT_EQ(codec.decode(codec.encodeControl(kAck)).type, PacketType::ACK);
114
+ DecodedPacket result = codec.decode(okResultFrame(codec));
115
+ EXPECT_TRUE(result.validLrc);
116
+ EXPECT_EQ(result.payload[9], 'E');
117
+ }
118
+
119
+ // Pay -> annullamento -> ripaga (re-payment with the corrected amount).
120
+ TEST(Flow, PayReverseRepay) {
121
+ PacketCodec codec(LrcMode::STD);
122
+
123
+ auto pay1 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 650));
124
+ auto rev = codec.encodeApplication(Ecr17Protocol::buildReversalMessage(kTerminal, kCashReg));
125
+ auto pay2 = codec.encodeApplication(Ecr17Protocol::buildPaymentMessage(kTerminal, kCashReg, 720));
126
+
127
+ for (const auto* frame : {&pay1, &rev, &pay2}) {
128
+ DecodedPacket d = codec.decode(*frame);
129
+ EXPECT_EQ(d.type, PacketType::APPLICATION);
130
+ EXPECT_TRUE(d.validLrc);
131
+ }
132
+
133
+ EXPECT_EQ(codec.decode(pay1).payload.substr(23, 8), "00000650");
134
+ EXPECT_EQ(codec.decode(pay2).payload.substr(23, 8), "00000720");
135
+ EXPECT_NE(pay1, pay2); // the corrected re-payment differs on the wire
136
+ }
137
+
138
+ // Terminal status request/round-trip ('s').
139
+ TEST(Flow, StatusRequest) {
140
+ PacketCodec codec(LrcMode::STD);
141
+ std::string req = Ecr17Protocol::buildStatusMessage(kTerminal);
142
+ EXPECT_EQ(req, "123456780s");
143
+
144
+ DecodedPacket atPos = codec.decode(codec.encodeApplication(req));
145
+ EXPECT_EQ(atPos.type, PacketType::APPLICATION);
146
+ EXPECT_TRUE(atPos.validLrc);
147
+ EXPECT_EQ(atPos.payload, req);
148
+ }