@padosoft/react-native-ecr17 0.0.0 → 2.0.1
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
|
@@ -1,103 +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
|
|
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
|
|
@@ -1,155 +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
|
|
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
|