@padosoft/react-native-ecr17 0.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 -0
- package/README.md +348 -0
- package/android/CMakeLists.txt +41 -0
- package/android/build.gradle +149 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/margelo/nitro/ecr17/HybridEcr17Transport.kt +233 -0
- package/android/src/main/java/com/padosoft/ecr17/Ecr17Package.kt +30 -0
- package/cpp/Ecr17.cpp +1 -0
- package/cpp/Ecr17.hpp +2 -0
- package/cpp/Ecr17Client/HybridEcr17Client.cpp +598 -0
- package/cpp/Ecr17Client/HybridEcr17Client.hpp +85 -0
- package/cpp/Ecr17Protocol/Ecr17Protocol.cpp +277 -0
- package/cpp/Ecr17Protocol/Ecr17Protocol.hpp +103 -0
- package/cpp/Ecr17Response/Ecr17Response.cpp +155 -0
- package/cpp/Ecr17Response/Ecr17Response.hpp +113 -0
- package/cpp/Lcr/Lcr.cpp +42 -0
- package/cpp/Lcr/Lcr.hpp +22 -0
- package/cpp/PacketCodec/PacketCodec.cpp +146 -0
- package/cpp/PacketCodec/PacketCodec.hpp +48 -0
- package/cpp/Session/Ecr17Session.cpp +260 -0
- package/cpp/Session/Ecr17Session.hpp +97 -0
- package/cpp/Session/RetryPolicy.hpp +23 -0
- package/cpp/Transport/FakeTransport.hpp +95 -0
- package/cpp/Transport/NativeTransportAdapter.cpp +42 -0
- package/cpp/Transport/NativeTransportAdapter.hpp +32 -0
- package/cpp/Transport/Transport.hpp +31 -0
- package/cpp/tests/CMakeLists.txt +55 -0
- package/cpp/tests/PosixTcpTransport.hpp +105 -0
- package/cpp/tests/stubs/LrcMode.hpp +25 -0
- package/cpp/tests/test_flows.cpp +148 -0
- package/cpp/tests/test_integration_terminal.cpp +72 -0
- package/cpp/tests/test_lrc.cpp +66 -0
- package/cpp/tests/test_packet_codec.cpp +164 -0
- package/cpp/tests/test_protocol.cpp +102 -0
- package/cpp/tests/test_protocol_commands.cpp +190 -0
- package/cpp/tests/test_response.cpp +164 -0
- package/cpp/tests/test_retry_policy.cpp +28 -0
- package/cpp/tests/test_session.cpp +262 -0
- package/ios/Bridge.h +1 -0
- package/ios/HybridEcr17Transport.swift +103 -0
- package/nitro.json +30 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/Ecr17+autolinking.cmake +82 -0
- package/nitrogen/generated/android/Ecr17+autolinking.gradle +27 -0
- package/nitrogen/generated/android/Ecr17OnLoad.cpp +68 -0
- package/nitrogen/generated/android/Ecr17OnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__shared_ptr_ArrayBuffer_.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.cpp +93 -0
- package/nitrogen/generated/android/c++/JHybridEcr17TransportSpec.hpp +68 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Ecr17OnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/Func_void_std__shared_ptr_ArrayBuffer_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/ecr17/HybridEcr17TransportSpec.kt +86 -0
- package/nitrogen/generated/ios/Ecr17+autolinking.rb +62 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.cpp +57 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Bridge.hpp +154 -0
- package/nitrogen/generated/ios/Ecr17-Swift-Cxx-Umbrella.hpp +47 -0
- package/nitrogen/generated/ios/Ecr17Autolinking.mm +43 -0
- package/nitrogen/generated/ios/Ecr17Autolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridEcr17TransportSpecSwift.hpp +119 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_ArrayBuffer_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridEcr17TransportSpec_cxx.swift +211 -0
- package/nitrogen/generated/shared/c++/CardType.hpp +84 -0
- package/nitrogen/generated/shared/c++/CardVerificationRequest.hpp +97 -0
- package/nitrogen/generated/shared/c++/CardVerificationResult.hpp +136 -0
- package/nitrogen/generated/shared/c++/CloseSessionResult.hpp +106 -0
- package/nitrogen/generated/shared/c++/ConnectionState.hpp +80 -0
- package/nitrogen/generated/shared/c++/CurrencyExchange.hpp +100 -0
- package/nitrogen/generated/shared/c++/Ecr17Config.hpp +138 -0
- package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.cpp +42 -0
- package/nitrogen/generated/shared/c++/HybridEcr17ClientSpec.hpp +138 -0
- package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridEcr17TransportSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/IncrementalAuthRequest.hpp +96 -0
- package/nitrogen/generated/shared/c++/LrcMode.hpp +84 -0
- package/nitrogen/generated/shared/c++/PaymentCardType.hpp +84 -0
- package/nitrogen/generated/shared/c++/PaymentRequest.hpp +109 -0
- package/nitrogen/generated/shared/c++/PaymentResult.hpp +139 -0
- package/nitrogen/generated/shared/c++/PosStatusResponse.hpp +96 -0
- package/nitrogen/generated/shared/c++/PreAuthClosureRequest.hpp +96 -0
- package/nitrogen/generated/shared/c++/PreAuthRequest.hpp +109 -0
- package/nitrogen/generated/shared/c++/PreAuthResult.hpp +144 -0
- package/nitrogen/generated/shared/c++/ProgressEvent.hpp +83 -0
- package/nitrogen/generated/shared/c++/ReceiptLine.hpp +83 -0
- package/nitrogen/generated/shared/c++/ReversalRequest.hpp +88 -0
- package/nitrogen/generated/shared/c++/ReversalResult.hpp +132 -0
- package/nitrogen/generated/shared/c++/TokenizationRequest.hpp +89 -0
- package/nitrogen/generated/shared/c++/TokenizationService.hpp +76 -0
- package/nitrogen/generated/shared/c++/TotalsResult.hpp +93 -0
- package/nitrogen/generated/shared/c++/TransactionEntryMode.hpp +92 -0
- package/nitrogen/generated/shared/c++/TransactionOutcome.hpp +88 -0
- package/nitrogen/generated/shared/c++/VasResult.hpp +96 -0
- package/package.json +102 -0
- package/react-native.config.js +18 -0
- package/src/index.ts +4 -0
- package/src/specs/client.nitro.ts +102 -0
- package/src/specs/transport.nitro.ts +25 -0
- package/src/types/client.ts +196 -0
- package/src/utils/client.ts +10 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#include "Ecr17Protocol.hpp"
|
|
2
|
+
|
|
3
|
+
#include <stdexcept>
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::ecr17 {
|
|
6
|
+
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
constexpr char kReserved = '0'; // '0' (0x30) filler for reserved numeric fields
|
|
10
|
+
constexpr char kFieldSep = 0x1B; // end-of-field for the privative TAG content
|
|
11
|
+
|
|
12
|
+
// Right-aligns `value` into a fixed-width field of `size` bytes, padding on the
|
|
13
|
+
// left with `ch`. ECR17 fields have a fixed length, so a value longer than the
|
|
14
|
+
// field would shift every following field and corrupt the frame: reject it.
|
|
15
|
+
std::string leftPad(const std::string& value, size_t size, char ch = kReserved) {
|
|
16
|
+
if (value.size() > size) {
|
|
17
|
+
throw std::invalid_argument("ECR17: value '" + value + "' exceeds fixed field width of " +
|
|
18
|
+
std::to_string(size) + " bytes");
|
|
19
|
+
}
|
|
20
|
+
if (value.size() == size) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return std::string(size - value.size(), ch) + value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Left-aligns `value` into a fixed-width field, padding on the right with `ch`.
|
|
27
|
+
std::string rightPad(const std::string& value, size_t size, char ch = ' ') {
|
|
28
|
+
if (value.size() > size) {
|
|
29
|
+
throw std::invalid_argument("ECR17: value '" + value + "' exceeds fixed field width of " +
|
|
30
|
+
std::to_string(size) + " bytes");
|
|
31
|
+
}
|
|
32
|
+
return value + std::string(size - value.size(), ch);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
std::string amountField(int amountCents) {
|
|
36
|
+
if (amountCents < 0) {
|
|
37
|
+
throw std::invalid_argument("ECR17: amount must be non-negative");
|
|
38
|
+
}
|
|
39
|
+
return leftPad(std::to_string(amountCents), 8);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
char flag(bool on) { return on ? '1' : '0'; }
|
|
43
|
+
|
|
44
|
+
// Shared 167-byte payment-family layout (codes 'P', 'X', 'p').
|
|
45
|
+
std::string buildPaymentLike(char code, const std::string& terminalId,
|
|
46
|
+
const std::string& cashRegisterId, int amountCents, char paymentType,
|
|
47
|
+
bool cardAlreadyPresent, bool withAdditionalData,
|
|
48
|
+
const std::string& receiptText) {
|
|
49
|
+
std::string m;
|
|
50
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
51
|
+
m += kReserved; // 9 : reserved
|
|
52
|
+
m += code; // 10 : message code
|
|
53
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
54
|
+
m += flag(withAdditionalData); // 19 : presence of additional GT data
|
|
55
|
+
m += "00"; // 20 : reserved
|
|
56
|
+
m += flag(cardAlreadyPresent); // 22 : start-with-card-present
|
|
57
|
+
m += paymentType; // 23 : payment type
|
|
58
|
+
m += amountField(amountCents); // 24 : amount (8)
|
|
59
|
+
m += leftPad(receiptText, 128, ' '); // 32 : receipt text (128)
|
|
60
|
+
m += "00000000"; // 160: reserved (8)
|
|
61
|
+
return m; // 167
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Shared 176-byte pre-auth integration/closure layout (codes 'i', 'c').
|
|
65
|
+
std::string buildPreAuthFollowUp(char code, const std::string& terminalId,
|
|
66
|
+
const std::string& cashRegisterId, int amountCents,
|
|
67
|
+
const std::string& originalPreAuthCode, bool withAdditionalData,
|
|
68
|
+
const std::string& receiptText) {
|
|
69
|
+
std::string m;
|
|
70
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
71
|
+
m += kReserved; // 9 : reserved
|
|
72
|
+
m += code; // 10 : message code
|
|
73
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
74
|
+
m += flag(withAdditionalData); // 19 : presence of additional GT data
|
|
75
|
+
m += "0000"; // 20 : reserved (4)
|
|
76
|
+
m += amountField(amountCents); // 24 : amount (8)
|
|
77
|
+
m += leftPad(receiptText, 128, ' '); // 32 : receipt text (128)
|
|
78
|
+
m += leftPad(originalPreAuthCode, 9); // 160: original pre-auth code (9)
|
|
79
|
+
m += "00000000"; // 169: reserved (8)
|
|
80
|
+
return m; // 176
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Shared 26-byte session command layout (codes 'C', 'T').
|
|
84
|
+
std::string buildSessionCommand(char code, const std::string& terminalId,
|
|
85
|
+
const std::string& cashRegisterId, bool withAdditionalData) {
|
|
86
|
+
std::string m;
|
|
87
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
88
|
+
m += kReserved; // 9 : reserved
|
|
89
|
+
m += code; // 10 : message code
|
|
90
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
91
|
+
m += flag(withAdditionalData); // 19 : presence of additional GT data
|
|
92
|
+
m += "0000000"; // 20 : reserved (7)
|
|
93
|
+
return m; // 26
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
} // namespace
|
|
97
|
+
|
|
98
|
+
std::string Ecr17Protocol::buildPaymentMessage(const std::string& terminalId,
|
|
99
|
+
const std::string& cashRegisterId, int amountCents,
|
|
100
|
+
char paymentType, bool cardAlreadyPresent,
|
|
101
|
+
bool withAdditionalData,
|
|
102
|
+
const std::string& receiptText) {
|
|
103
|
+
return buildPaymentLike('P', terminalId, cashRegisterId, amountCents, paymentType,
|
|
104
|
+
cardAlreadyPresent, withAdditionalData, receiptText);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
std::string Ecr17Protocol::buildExtendedPaymentMessage(const std::string& terminalId,
|
|
108
|
+
const std::string& cashRegisterId,
|
|
109
|
+
int amountCents, char paymentType,
|
|
110
|
+
bool cardAlreadyPresent,
|
|
111
|
+
bool withAdditionalData,
|
|
112
|
+
const std::string& receiptText) {
|
|
113
|
+
return buildPaymentLike('X', terminalId, cashRegisterId, amountCents, paymentType,
|
|
114
|
+
cardAlreadyPresent, withAdditionalData, receiptText);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
std::string Ecr17Protocol::buildPreAuthMessage(const std::string& terminalId,
|
|
118
|
+
const std::string& cashRegisterId, int amountCents,
|
|
119
|
+
char paymentType, bool cardAlreadyPresent,
|
|
120
|
+
bool withAdditionalData,
|
|
121
|
+
const std::string& receiptText) {
|
|
122
|
+
return buildPaymentLike('p', terminalId, cashRegisterId, amountCents, paymentType,
|
|
123
|
+
cardAlreadyPresent, withAdditionalData, receiptText);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
std::string Ecr17Protocol::buildIncrementalMessage(const std::string& terminalId,
|
|
127
|
+
const std::string& cashRegisterId,
|
|
128
|
+
int amountCents,
|
|
129
|
+
const std::string& originalPreAuthCode,
|
|
130
|
+
bool withAdditionalData,
|
|
131
|
+
const std::string& receiptText) {
|
|
132
|
+
return buildPreAuthFollowUp('i', terminalId, cashRegisterId, amountCents, originalPreAuthCode,
|
|
133
|
+
withAdditionalData, receiptText);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
std::string Ecr17Protocol::buildPreAuthClosureMessage(const std::string& terminalId,
|
|
137
|
+
const std::string& cashRegisterId,
|
|
138
|
+
int amountCents,
|
|
139
|
+
const std::string& originalPreAuthCode,
|
|
140
|
+
bool withAdditionalData,
|
|
141
|
+
const std::string& receiptText) {
|
|
142
|
+
return buildPreAuthFollowUp('c', terminalId, cashRegisterId, amountCents, originalPreAuthCode,
|
|
143
|
+
withAdditionalData, receiptText);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
std::string Ecr17Protocol::buildCardVerificationMessage(const std::string& terminalId,
|
|
147
|
+
const std::string& cashRegisterId,
|
|
148
|
+
char paymentType, bool withAdditionalData) {
|
|
149
|
+
std::string m;
|
|
150
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
151
|
+
m += kReserved; // 9 : reserved
|
|
152
|
+
m += 'H'; // 10 : message code
|
|
153
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
154
|
+
m += flag(withAdditionalData); // 19 : presence of additional GT data
|
|
155
|
+
m += "00"; // 20 : reserved (2)
|
|
156
|
+
m += '0'; // 22 : standard card verification
|
|
157
|
+
m += paymentType; // 23 : payment type
|
|
158
|
+
m += "0000000000000000"; // 24 : reserved (16)
|
|
159
|
+
return m; // 39
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
std::string Ecr17Protocol::buildCloseSessionMessage(const std::string& terminalId,
|
|
163
|
+
const std::string& cashRegisterId,
|
|
164
|
+
bool withAdditionalData) {
|
|
165
|
+
return buildSessionCommand('C', terminalId, cashRegisterId, withAdditionalData);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
std::string Ecr17Protocol::buildTotalsMessage(const std::string& terminalId,
|
|
169
|
+
const std::string& cashRegisterId,
|
|
170
|
+
bool withAdditionalData) {
|
|
171
|
+
return buildSessionCommand('T', terminalId, cashRegisterId, withAdditionalData);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
std::string Ecr17Protocol::buildSendLastResultMessage(const std::string& terminalId,
|
|
175
|
+
const std::string& cashRegisterId,
|
|
176
|
+
bool withAdditionalData) {
|
|
177
|
+
std::string m;
|
|
178
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
179
|
+
m += kReserved; // 9 : reserved
|
|
180
|
+
m += 'G'; // 10 : message code
|
|
181
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
182
|
+
m += flag(withAdditionalData); // 19 : presence of additional GT data
|
|
183
|
+
m += "000"; // 20 : reserved (3)
|
|
184
|
+
return m; // 22
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
std::string Ecr17Protocol::buildEnableEcrPrintMessage(const std::string& terminalId, bool enabled) {
|
|
188
|
+
std::string m;
|
|
189
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
190
|
+
m += kReserved; // 9 : reserved
|
|
191
|
+
m += 'E'; // 10 : message code
|
|
192
|
+
m += flag(enabled); // 11 : enable(1)/disable(0) printing on ECR
|
|
193
|
+
return m; // 11
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
std::string Ecr17Protocol::buildReprintMessage(const std::string& terminalId, bool toEcr,
|
|
197
|
+
char ticketType) {
|
|
198
|
+
std::string m;
|
|
199
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
200
|
+
m += kReserved; // 9 : reserved
|
|
201
|
+
m += 'R'; // 10 : message code
|
|
202
|
+
m += flag(toEcr); // 11 : 1 = send receipt to ECR, 0 = print on terminal
|
|
203
|
+
m += ticketType; // 12 : ticket type flag
|
|
204
|
+
m += "0000000000"; // 13 : reserved (10)
|
|
205
|
+
return m; // 22
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
std::string Ecr17Protocol::buildStatusMessage(const std::string& terminalId) {
|
|
209
|
+
std::string m;
|
|
210
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
211
|
+
m += kReserved; // 9 : reserved
|
|
212
|
+
m += 's'; // 10 : message code (lowercase per spec)
|
|
213
|
+
return m; // 10
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
std::string Ecr17Protocol::buildReversalMessage(const std::string& terminalId,
|
|
217
|
+
const std::string& cashRegisterId,
|
|
218
|
+
const std::string& stan) {
|
|
219
|
+
std::string m;
|
|
220
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
221
|
+
m += kReserved; // 9 : reserved
|
|
222
|
+
m += 'S'; // 10 : message code
|
|
223
|
+
m += leftPad(cashRegisterId, 8); // 11 : cash register id
|
|
224
|
+
m += leftPad(stan, 6); // 19 : STAN ("000000" = no check)
|
|
225
|
+
m += kReserved; // 25 : presence of additional GT data
|
|
226
|
+
m += kReserved; // 26 : reserved
|
|
227
|
+
return m; // 26
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
std::string Ecr17Protocol::buildVasMessage(const std::string& terminalId, const std::string& ecrId,
|
|
231
|
+
const std::string& xmlRequest) {
|
|
232
|
+
if (xmlRequest.size() > 1024) {
|
|
233
|
+
throw std::invalid_argument("ECR17: VAS request exceeds 1024 bytes");
|
|
234
|
+
}
|
|
235
|
+
std::string m;
|
|
236
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
237
|
+
m += kReserved; // 9 : reserved
|
|
238
|
+
m += 'K'; // 10 : message code
|
|
239
|
+
m += leftPad(ecrId, 8); // 11 : ECR identifier
|
|
240
|
+
m += "000"; // 19 : reserved (3)
|
|
241
|
+
m += kReserved; // 22 : reserved (1)
|
|
242
|
+
m += leftPad(std::to_string(xmlRequest.size()), 4);// 23 : VAS request length (4)
|
|
243
|
+
m += xmlRequest; // 27 : VAS request (XML)
|
|
244
|
+
return m;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
std::string Ecr17Protocol::buildAdditionalTagsMessage(const std::string& terminalId,
|
|
248
|
+
const std::string& tagContent,
|
|
249
|
+
const std::string& isoField,
|
|
250
|
+
const std::string& tagNumber) {
|
|
251
|
+
if (tagContent.empty() || tagContent.size() > 100) {
|
|
252
|
+
throw std::invalid_argument("ECR17: additional TAG content must be 1..100 chars");
|
|
253
|
+
}
|
|
254
|
+
std::string m;
|
|
255
|
+
m += leftPad(terminalId, 8); // 1 : terminal id
|
|
256
|
+
m += kReserved; // 9 : reserved
|
|
257
|
+
m += 'U'; // 10 : message code
|
|
258
|
+
m += "000000"; // 11 : payment type (6) -> standard payment
|
|
259
|
+
m += leftPad(isoField, 2); // 17 : ISO field number (e.g. "62")
|
|
260
|
+
m += rightPad(tagNumber, 8); // 19 : TAG number, left-justified, blank-filled
|
|
261
|
+
m += kReserved; // 27 : reserved (1)
|
|
262
|
+
m += "0000"; // 28 : exclusive TAG index bytemap (none to send to GT)
|
|
263
|
+
m += "00000"; // 32 : reserved (5)
|
|
264
|
+
m += tagContent; // 37 : privative TAG content (1..100)
|
|
265
|
+
m += kFieldSep; // end-of-field (0x1B)
|
|
266
|
+
return m;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
std::string Ecr17Protocol::formatTokenizationTag(bool recurring, const std::string& contractCode) {
|
|
270
|
+
if (contractCode.empty() || contractCode.size() > 18) {
|
|
271
|
+
throw std::invalid_argument("ECR17: tokenization contract code must be 1..18 chars");
|
|
272
|
+
}
|
|
273
|
+
const std::string service = recurring ? "0REC" : "0COF";
|
|
274
|
+
return service + "0TRK" + contractCode + "|0FNZ03";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::ecr17 {
|
|
6
|
+
|
|
7
|
+
// Builds ECR17 application-message payloads (the bytes that go between STX and
|
|
8
|
+
// ETX). All fields are fixed-width and validated; values that overflow their
|
|
9
|
+
// field throw std::invalid_argument so a malformed frame is never produced.
|
|
10
|
+
//
|
|
11
|
+
// `paymentType` is the single request digit: '0' auto, '1' debit, '2' credit,
|
|
12
|
+
// '3' other. `cardAlreadyPresent` maps to field "start with card already
|
|
13
|
+
// inserted". `receiptText` is the contract/print text (max 128 chars).
|
|
14
|
+
class Ecr17Protocol {
|
|
15
|
+
public:
|
|
16
|
+
// --- Payment family (167 bytes): 'P' payment, 'X' extended, 'p' pre-auth ---
|
|
17
|
+
static std::string buildPaymentMessage(const std::string& terminalId,
|
|
18
|
+
const std::string& cashRegisterId, int amountCents,
|
|
19
|
+
char paymentType = '0', bool cardAlreadyPresent = false,
|
|
20
|
+
bool withAdditionalData = false,
|
|
21
|
+
const std::string& receiptText = "");
|
|
22
|
+
|
|
23
|
+
static std::string buildExtendedPaymentMessage(const std::string& terminalId,
|
|
24
|
+
const std::string& cashRegisterId, int amountCents,
|
|
25
|
+
char paymentType = '0',
|
|
26
|
+
bool cardAlreadyPresent = false,
|
|
27
|
+
bool withAdditionalData = false,
|
|
28
|
+
const std::string& receiptText = "");
|
|
29
|
+
|
|
30
|
+
static std::string buildPreAuthMessage(const std::string& terminalId,
|
|
31
|
+
const std::string& cashRegisterId, int amountCents,
|
|
32
|
+
char paymentType = '0', bool cardAlreadyPresent = false,
|
|
33
|
+
bool withAdditionalData = false,
|
|
34
|
+
const std::string& receiptText = "");
|
|
35
|
+
|
|
36
|
+
// --- Pre-auth integration/closure (176 bytes): 'i' incremental, 'c' closure ---
|
|
37
|
+
static std::string buildIncrementalMessage(const std::string& terminalId,
|
|
38
|
+
const std::string& cashRegisterId, int amountCents,
|
|
39
|
+
const std::string& originalPreAuthCode,
|
|
40
|
+
bool withAdditionalData = false,
|
|
41
|
+
const std::string& receiptText = "");
|
|
42
|
+
|
|
43
|
+
static std::string buildPreAuthClosureMessage(const std::string& terminalId,
|
|
44
|
+
const std::string& cashRegisterId, int amountCents,
|
|
45
|
+
const std::string& originalPreAuthCode,
|
|
46
|
+
bool withAdditionalData = false,
|
|
47
|
+
const std::string& receiptText = "");
|
|
48
|
+
|
|
49
|
+
// --- Card verification 'H' (39 bytes) ---
|
|
50
|
+
static std::string buildCardVerificationMessage(const std::string& terminalId,
|
|
51
|
+
const std::string& cashRegisterId,
|
|
52
|
+
char paymentType = '0',
|
|
53
|
+
bool withAdditionalData = false);
|
|
54
|
+
|
|
55
|
+
// --- Session commands (26 bytes): 'C' close, 'T' totals ---
|
|
56
|
+
static std::string buildCloseSessionMessage(const std::string& terminalId,
|
|
57
|
+
const std::string& cashRegisterId,
|
|
58
|
+
bool withAdditionalData = false);
|
|
59
|
+
|
|
60
|
+
static std::string buildTotalsMessage(const std::string& terminalId,
|
|
61
|
+
const std::string& cashRegisterId,
|
|
62
|
+
bool withAdditionalData = false);
|
|
63
|
+
|
|
64
|
+
// --- Send last result 'G' (22 bytes) ---
|
|
65
|
+
static std::string buildSendLastResultMessage(const std::string& terminalId,
|
|
66
|
+
const std::string& cashRegisterId,
|
|
67
|
+
bool withAdditionalData = false);
|
|
68
|
+
|
|
69
|
+
// --- Enable/disable ECR printing 'E' (11 bytes) ---
|
|
70
|
+
static std::string buildEnableEcrPrintMessage(const std::string& terminalId, bool enabled);
|
|
71
|
+
|
|
72
|
+
// --- Reprint ticket 'R' (22 bytes) ---
|
|
73
|
+
static std::string buildReprintMessage(const std::string& terminalId, bool toEcr,
|
|
74
|
+
char ticketType = '0');
|
|
75
|
+
|
|
76
|
+
// --- Status 's' (10 bytes) ---
|
|
77
|
+
static std::string buildStatusMessage(const std::string& terminalId);
|
|
78
|
+
|
|
79
|
+
// --- Reversal 'S' (26 bytes); stan "000000" = reverse last, no STAN check ---
|
|
80
|
+
static std::string buildReversalMessage(const std::string& terminalId,
|
|
81
|
+
const std::string& cashRegisterId,
|
|
82
|
+
const std::string& stan = "000000");
|
|
83
|
+
|
|
84
|
+
// --- VAS 'K' (variable, length-prefixed XML, max 1024) ---
|
|
85
|
+
static std::string buildVasMessage(const std::string& terminalId, const std::string& ecrId,
|
|
86
|
+
const std::string& xmlRequest);
|
|
87
|
+
|
|
88
|
+
// --- Additional data for GT / tokenization 'U' (variable) ---
|
|
89
|
+
// `tagContent` is the privative TAG content (1..100 chars), terminated with
|
|
90
|
+
// 0x1B by this builder. Use formatTokenizationTag() to produce it.
|
|
91
|
+
static std::string buildAdditionalTagsMessage(const std::string& terminalId,
|
|
92
|
+
const std::string& tagContent,
|
|
93
|
+
const std::string& isoField = "62",
|
|
94
|
+
const std::string& tagNumber = "DF8D01");
|
|
95
|
+
|
|
96
|
+
// Formats the TAG 5 content for tokenization (Intesa-style mapping):
|
|
97
|
+
// "0COF0TRK<contract>|0FNZ03" (unscheduled/one-click)
|
|
98
|
+
// "0REC0TRK<contract>|0FNZ03" (recurring)
|
|
99
|
+
// `recurring` selects 0REC vs 0COF.
|
|
100
|
+
static std::string formatTokenizationTag(bool recurring, const std::string& contractCode);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#include "Ecr17Response.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cctype>
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::ecr17 {
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
// 1-based field extractor. Returns "" if the field starts beyond the payload;
|
|
11
|
+
// clamps the length to whatever bytes are actually present (defensive parsing).
|
|
12
|
+
std::string at(const std::string& p, size_t pos1, size_t len) {
|
|
13
|
+
if (pos1 == 0 || pos1 > p.size()) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const size_t i = pos1 - 1;
|
|
17
|
+
return p.substr(i, std::min(len, p.size() - i));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
std::string trimRight(const std::string& s) {
|
|
21
|
+
const size_t end = s.find_last_not_of(' ');
|
|
22
|
+
return end == std::string::npos ? std::string() : s.substr(0, end + 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Extracts the value of a Nexi VAS XML param: <p k="KEY">value</p>.
|
|
26
|
+
std::string xmlValue(const std::string& xml, const std::string& key) {
|
|
27
|
+
const std::string needle = "\"" + key + "\">";
|
|
28
|
+
const size_t start = xml.find(needle);
|
|
29
|
+
if (start == std::string::npos) {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
const size_t from = start + needle.size();
|
|
33
|
+
const size_t end = xml.find('<', from);
|
|
34
|
+
std::string value = xml.substr(from, end == std::string::npos ? std::string::npos : end - from);
|
|
35
|
+
// trim surrounding whitespace
|
|
36
|
+
const size_t a = value.find_first_not_of(" \t\r\n");
|
|
37
|
+
const size_t b = value.find_last_not_of(" \t\r\n");
|
|
38
|
+
return a == std::string::npos ? std::string() : value.substr(a, b - a + 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
} // namespace
|
|
42
|
+
|
|
43
|
+
Outcome outcomeFromCode(const std::string& code) {
|
|
44
|
+
if (code == "00") return Outcome::Ok;
|
|
45
|
+
if (code == "01") return Outcome::Ko;
|
|
46
|
+
if (code == "05") return Outcome::CardNotPresent;
|
|
47
|
+
if (code == "09") return Outcome::UnknownTag;
|
|
48
|
+
return Outcome::Unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
PaymentResponse Ecr17Response::parsePayment(const std::string& p) {
|
|
52
|
+
PaymentResponse r;
|
|
53
|
+
const std::string code = at(p, 10, 1); // message code 'E' (plain) or 'V' (DCC)
|
|
54
|
+
const bool dcc = code == "V";
|
|
55
|
+
|
|
56
|
+
r.resultCode = at(p, 11, 2);
|
|
57
|
+
r.outcome = outcomeFromCode(r.resultCode);
|
|
58
|
+
|
|
59
|
+
if (r.outcome == Outcome::Ko) {
|
|
60
|
+
r.errorDescription = trimRight(at(p, 13, 24));
|
|
61
|
+
} else {
|
|
62
|
+
r.pan = at(p, 13, 19);
|
|
63
|
+
r.transactionType = trimRight(at(p, 32, 3));
|
|
64
|
+
r.authCode = trimRight(at(p, 35, 6));
|
|
65
|
+
r.hostDateTime = at(p, 41, 7);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Common to any response.
|
|
69
|
+
r.cardType = at(p, 48, 1);
|
|
70
|
+
r.acquirerId = trimRight(at(p, 49, 11));
|
|
71
|
+
r.stan = at(p, 60, 6);
|
|
72
|
+
r.onlineId = at(p, 66, 6);
|
|
73
|
+
|
|
74
|
+
if (dcc) {
|
|
75
|
+
r.currency.applied = at(p, 83, 1) == "1";
|
|
76
|
+
r.currency.rate = at(p, 84, 8);
|
|
77
|
+
r.currency.currencyCode = trimRight(at(p, 92, 3));
|
|
78
|
+
r.currency.amount = at(p, 95, 12);
|
|
79
|
+
r.currency.precision = at(p, 107, 1);
|
|
80
|
+
}
|
|
81
|
+
return r;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
StatusResponse Ecr17Response::parseStatus(const std::string& p) {
|
|
85
|
+
StatusResponse r;
|
|
86
|
+
r.terminalId = at(p, 1, 8);
|
|
87
|
+
r.dateTimeRaw = at(p, 21, 10);
|
|
88
|
+
const std::string s = at(p, 31, 1);
|
|
89
|
+
r.status = (!s.empty() && std::isdigit(static_cast<unsigned char>(s[0]))) ? s[0] - '0' : -1;
|
|
90
|
+
r.softwareRelease = trimRight(at(p, 32, p.size()));
|
|
91
|
+
return r;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
TotalsResponse Ecr17Response::parseTotals(const std::string& p) {
|
|
95
|
+
TotalsResponse r;
|
|
96
|
+
r.resultCode = at(p, 11, 2);
|
|
97
|
+
r.outcome = outcomeFromCode(r.resultCode);
|
|
98
|
+
r.posTotal = at(p, 13, 16);
|
|
99
|
+
return r;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
CloseResponse Ecr17Response::parseClose(const std::string& p) {
|
|
103
|
+
CloseResponse r;
|
|
104
|
+
r.resultCode = at(p, 11, 2);
|
|
105
|
+
r.outcome = outcomeFromCode(r.resultCode);
|
|
106
|
+
if (r.outcome == Outcome::Ok) {
|
|
107
|
+
r.posTotal = at(p, 13, 16);
|
|
108
|
+
r.hostTotal = at(p, 29, 16);
|
|
109
|
+
} else {
|
|
110
|
+
r.errorDescription = trimRight(at(p, 13, 19));
|
|
111
|
+
r.actionCode = at(p, 32, 3);
|
|
112
|
+
}
|
|
113
|
+
return r;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
PreAuthResponse Ecr17Response::parsePreAuth(const std::string& p) {
|
|
117
|
+
PreAuthResponse r;
|
|
118
|
+
r.resultCode = at(p, 11, 2);
|
|
119
|
+
r.outcome = outcomeFromCode(r.resultCode);
|
|
120
|
+
if (r.outcome == Outcome::Ko) {
|
|
121
|
+
r.errorDescription = trimRight(at(p, 13, 24));
|
|
122
|
+
r.actionCode = at(p, 37, 3);
|
|
123
|
+
} else {
|
|
124
|
+
r.pan = at(p, 13, 19);
|
|
125
|
+
r.transactionType = trimRight(at(p, 32, 3));
|
|
126
|
+
r.authCode = trimRight(at(p, 35, 6));
|
|
127
|
+
r.preAuthorizedAmount = at(p, 41, 8);
|
|
128
|
+
r.preAuthCode = at(p, 49, 9);
|
|
129
|
+
r.actionCode = at(p, 58, 3);
|
|
130
|
+
r.hostDateTime = at(p, 61, 7);
|
|
131
|
+
}
|
|
132
|
+
// In the OK layout preAuthorizedAmount occupies positions 41-48, so position
|
|
133
|
+
// 48 is the amount's last digit, NOT a card type. Only read cardType for the
|
|
134
|
+
// KO layout, where that byte is not part of an amount field.
|
|
135
|
+
if (r.outcome == Outcome::Ko) {
|
|
136
|
+
r.cardType = at(p, 48, 1);
|
|
137
|
+
}
|
|
138
|
+
r.acquirerId = trimRight(at(p, 72, 11));
|
|
139
|
+
r.stan = at(p, 83, 6);
|
|
140
|
+
r.onlineId = at(p, 89, 6);
|
|
141
|
+
return r;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
VasResponse Ecr17Response::parseVas(const std::string& p) {
|
|
145
|
+
VasResponse r;
|
|
146
|
+
r.moreMessages = at(p, 15, 1) == "1";
|
|
147
|
+
r.idMessage = at(p, 16, 3);
|
|
148
|
+
r.rawXml = at(p, 27, p.size());
|
|
149
|
+
r.responseId = xmlValue(r.rawXml, "RESPID");
|
|
150
|
+
r.responseMessage = xmlValue(r.rawXml, "RESPMSG");
|
|
151
|
+
r.orderId = xmlValue(r.rawXml, "ORDER_ID");
|
|
152
|
+
return r;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
} // namespace margelo::nitro::ecr17
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
// Parsers for ECR17 terminal *response* application messages. They take the
|
|
7
|
+
// application payload (the bytes between STX and ETX, i.e. DecodedPacket.payload)
|
|
8
|
+
// and return plain C++ structs — intentionally independent of Nitro so they can
|
|
9
|
+
// be unit-tested standalone. HybridEcr17Client maps these to the generated Nitro
|
|
10
|
+
// result types. Field offsets follow the spec response tables in docs/.
|
|
11
|
+
//
|
|
12
|
+
// Parsing is defensive: fields beyond the payload length come back empty rather
|
|
13
|
+
// than throwing, so a short/truncated response degrades gracefully.
|
|
14
|
+
|
|
15
|
+
namespace margelo::nitro::ecr17 {
|
|
16
|
+
|
|
17
|
+
enum class Outcome {
|
|
18
|
+
Ok, // "00"
|
|
19
|
+
Ko, // "01"
|
|
20
|
+
CardNotPresent, // "05"
|
|
21
|
+
UnknownTag, // "09"
|
|
22
|
+
Unknown, // anything else / missing
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
Outcome outcomeFromCode(const std::string& code);
|
|
26
|
+
|
|
27
|
+
// Optional DCC / currency-exchange block (parsed from a 'V'/'v' response).
|
|
28
|
+
// Named DccInfo (not CurrencyExchange) to avoid clashing with the Nitro-generated
|
|
29
|
+
// CurrencyExchange struct in the same namespace.
|
|
30
|
+
struct DccInfo {
|
|
31
|
+
bool applied = false;
|
|
32
|
+
std::string rate; // 8 digits, 4 decimals
|
|
33
|
+
std::string currencyCode; // alpha-3
|
|
34
|
+
std::string amount; // 12 digits, in transaction currency
|
|
35
|
+
std::string precision; // decimals
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Result of a payment-family response ('E' without DCC, 'V' with DCC). Reused
|
|
39
|
+
// for reversal / card verification / pre-auth closure which share the layout.
|
|
40
|
+
struct PaymentResponse {
|
|
41
|
+
Outcome outcome = Outcome::Unknown;
|
|
42
|
+
std::string resultCode; // raw "00"/"01"/"05"/"09"
|
|
43
|
+
std::string pan; // positive
|
|
44
|
+
std::string transactionType; // positive, raw "ICC"/"MAG"/...
|
|
45
|
+
std::string authCode; // positive
|
|
46
|
+
std::string hostDateTime; // positive, raw DDDHHMM
|
|
47
|
+
std::string errorDescription; // negative
|
|
48
|
+
std::string cardType; // common, raw "1"/"2"/"3"
|
|
49
|
+
std::string acquirerId; // common
|
|
50
|
+
std::string stan; // common
|
|
51
|
+
std::string onlineId; // common
|
|
52
|
+
DccInfo currency; // only when DCC present
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
struct StatusResponse {
|
|
56
|
+
std::string terminalId;
|
|
57
|
+
std::string dateTimeRaw; // "DDMMYYhhmm"
|
|
58
|
+
int status = -1; // 0..6, -1 = unknown/missing
|
|
59
|
+
std::string softwareRelease;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
struct TotalsResponse {
|
|
63
|
+
Outcome outcome = Outcome::Unknown;
|
|
64
|
+
std::string resultCode;
|
|
65
|
+
std::string posTotal; // 16 digits, cents
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
struct CloseResponse {
|
|
69
|
+
Outcome outcome = Outcome::Unknown;
|
|
70
|
+
std::string resultCode;
|
|
71
|
+
std::string posTotal; // positive, 16 digits
|
|
72
|
+
std::string hostTotal; // positive, 16 digits
|
|
73
|
+
std::string errorDescription; // negative
|
|
74
|
+
std::string actionCode; // negative
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
struct PreAuthResponse {
|
|
78
|
+
Outcome outcome = Outcome::Unknown;
|
|
79
|
+
std::string resultCode;
|
|
80
|
+
std::string pan;
|
|
81
|
+
std::string transactionType;
|
|
82
|
+
std::string authCode;
|
|
83
|
+
std::string preAuthorizedAmount; // 8 digits, cents
|
|
84
|
+
std::string preAuthCode; // 9 digits, unique pre-auth id
|
|
85
|
+
std::string actionCode;
|
|
86
|
+
std::string hostDateTime;
|
|
87
|
+
std::string errorDescription;
|
|
88
|
+
std::string cardType;
|
|
89
|
+
std::string acquirerId;
|
|
90
|
+
std::string stan;
|
|
91
|
+
std::string onlineId;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
struct VasResponse {
|
|
95
|
+
std::string responseId; // RESPID parsed from XML ("0" = OK), "" if absent
|
|
96
|
+
std::string responseMessage; // RESPMSG
|
|
97
|
+
std::string orderId; // ORDER_ID
|
|
98
|
+
bool moreMessages = false; // concatenation flag "1"
|
|
99
|
+
std::string idMessage; // 3-digit sequence
|
|
100
|
+
std::string rawXml; // the XML body of this message
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
class Ecr17Response {
|
|
104
|
+
public:
|
|
105
|
+
static PaymentResponse parsePayment(const std::string& payload);
|
|
106
|
+
static StatusResponse parseStatus(const std::string& payload);
|
|
107
|
+
static TotalsResponse parseTotals(const std::string& payload);
|
|
108
|
+
static CloseResponse parseClose(const std::string& payload);
|
|
109
|
+
static PreAuthResponse parsePreAuth(const std::string& payload);
|
|
110
|
+
static VasResponse parseVas(const std::string& payload);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
} // namespace margelo::nitro::ecr17
|