@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,164 +1,164 @@
|
|
|
1
|
-
// Tests for the ECR17 response parsers. Payloads are synthesized field-by-field
|
|
2
|
-
// at the exact 1-based offsets from the spec response tables in docs/. Helpers
|
|
3
|
-
// guarantee each field's width so offsets can't drift from a miscounted space.
|
|
4
|
-
|
|
5
|
-
#include <gtest/gtest.h>
|
|
6
|
-
|
|
7
|
-
#include <string>
|
|
8
|
-
|
|
9
|
-
#include "Ecr17Response/Ecr17Response.hpp"
|
|
10
|
-
|
|
11
|
-
using namespace margelo::nitro::ecr17;
|
|
12
|
-
|
|
13
|
-
namespace {
|
|
14
|
-
|
|
15
|
-
// Left-justified field, right-padded with spaces to `width` (alpha fields).
|
|
16
|
-
std::string a(const std::string& value, size_t width) {
|
|
17
|
-
std::string s = value;
|
|
18
|
-
s.resize(width, ' ');
|
|
19
|
-
return s;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Right-justified numeric field, left-padded with '0' to `width`.
|
|
23
|
-
std::string n(const std::string& value, size_t width) {
|
|
24
|
-
return std::string(width - value.size(), '0') + value;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
} // namespace
|
|
28
|
-
|
|
29
|
-
TEST(Response, PaymentPositive) {
|
|
30
|
-
std::string p = a("12345678", 8) + "0" + "E" + "00" + // header + result
|
|
31
|
-
n("4111111111", 19) + // PAN(19)
|
|
32
|
-
a("ICC", 3) + a("ABC123", 6) + "2111520" + // txType authCode dateTime
|
|
33
|
-
"2" + // cardType
|
|
34
|
-
a("ACQ", 11) + n("42", 6) + n("99", 6); // acquirer STAN idOnline
|
|
35
|
-
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
36
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
37
|
-
EXPECT_EQ(r.resultCode, "00");
|
|
38
|
-
EXPECT_EQ(r.pan, n("4111111111", 19));
|
|
39
|
-
EXPECT_EQ(r.transactionType, "ICC");
|
|
40
|
-
EXPECT_EQ(r.authCode, "ABC123");
|
|
41
|
-
EXPECT_EQ(r.hostDateTime, "2111520");
|
|
42
|
-
EXPECT_EQ(r.cardType, "2");
|
|
43
|
-
EXPECT_EQ(r.acquirerId, "ACQ");
|
|
44
|
-
EXPECT_EQ(r.stan, "000042");
|
|
45
|
-
EXPECT_EQ(r.onlineId, "000099");
|
|
46
|
-
EXPECT_FALSE(r.currency.applied);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
TEST(Response, PaymentNegative) {
|
|
50
|
-
std::string p = a("12345678", 8) + "0" + "E" + "01" + a("CARTA RIFIUTATA", 24) +
|
|
51
|
-
n("", 11) + // reserved 37-47
|
|
52
|
-
"3" + a("AC2", 11) + n("7", 6) + n("3", 6);
|
|
53
|
-
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
54
|
-
EXPECT_EQ(r.outcome, Outcome::Ko);
|
|
55
|
-
EXPECT_EQ(r.resultCode, "01");
|
|
56
|
-
EXPECT_EQ(r.errorDescription, "CARTA RIFIUTATA");
|
|
57
|
-
EXPECT_EQ(r.cardType, "3");
|
|
58
|
-
EXPECT_EQ(r.stan, "000007");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
TEST(Response, PaymentWithCurrencyExchange) {
|
|
62
|
-
std::string base = a("12345678", 8) + "0" + "V" + "00" + n("4111111111", 19) + a("ICC", 3) +
|
|
63
|
-
a("ABC123", 6) + "2111520" + "2" + a("ACQ", 11) + n("42", 6) + n("99", 6);
|
|
64
|
-
// actionCode(3) origAmount(8) flag(1) rate(8) ccy(3) amount(12) precision(1)
|
|
65
|
-
std::string p = base + "000" + n("650", 8) + "1" + n("12345", 8) + "USD" + n("650", 12) + "2";
|
|
66
|
-
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
67
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
68
|
-
EXPECT_TRUE(r.currency.applied);
|
|
69
|
-
EXPECT_EQ(r.currency.rate, "00012345");
|
|
70
|
-
EXPECT_EQ(r.currency.currencyCode, "USD");
|
|
71
|
-
EXPECT_EQ(r.currency.amount, "000000000650");
|
|
72
|
-
EXPECT_EQ(r.currency.precision, "2");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
TEST(Response, Status) {
|
|
76
|
-
std::string p = a("12345678", 8) + "0" + "s" + n("", 10) + // reserved 11-20
|
|
77
|
-
"0102251530" + "2" + "V1.2.3"; // dateTime status sw
|
|
78
|
-
StatusResponse r = Ecr17Response::parseStatus(p);
|
|
79
|
-
EXPECT_EQ(r.terminalId, "12345678");
|
|
80
|
-
EXPECT_EQ(r.dateTimeRaw, "0102251530");
|
|
81
|
-
EXPECT_EQ(r.status, 2);
|
|
82
|
-
EXPECT_EQ(r.softwareRelease, "V1.2.3");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
TEST(Response, Totals) {
|
|
86
|
-
std::string p = a("12345678", 8) + "0" + "T" + "00" + n("123456", 16) + n("", 6);
|
|
87
|
-
TotalsResponse r = Ecr17Response::parseTotals(p);
|
|
88
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
89
|
-
EXPECT_EQ(r.posTotal, n("123456", 16));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
TEST(Response, ClosePositive) {
|
|
93
|
-
std::string p = a("12345678", 8) + "0" + "C" + "00" + n("1000", 16) + n("1000", 16);
|
|
94
|
-
CloseResponse r = Ecr17Response::parseClose(p);
|
|
95
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
96
|
-
EXPECT_EQ(r.posTotal, n("1000", 16));
|
|
97
|
-
EXPECT_EQ(r.hostTotal, n("1000", 16));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
TEST(Response, CloseNegative) {
|
|
101
|
-
std::string p = a("12345678", 8) + "0" + "C" + "01" + a("SBILANCIO", 19) + "100";
|
|
102
|
-
CloseResponse r = Ecr17Response::parseClose(p);
|
|
103
|
-
EXPECT_EQ(r.outcome, Outcome::Ko);
|
|
104
|
-
EXPECT_EQ(r.errorDescription, "SBILANCIO");
|
|
105
|
-
EXPECT_EQ(r.actionCode, "100");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
TEST(Response, PreAuthPositive) {
|
|
109
|
-
std::string p = a("12345678", 8) + "0" + "e" + "00" + n("4111111111", 19) + a("CLI", 3) +
|
|
110
|
-
a("AUTH01", 6) + n("50000", 8) + n("123", 9) + "000" + "2111520";
|
|
111
|
-
PreAuthResponse r = Ecr17Response::parsePreAuth(p);
|
|
112
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
113
|
-
EXPECT_EQ(r.transactionType, "CLI");
|
|
114
|
-
EXPECT_EQ(r.authCode, "AUTH01");
|
|
115
|
-
EXPECT_EQ(r.preAuthorizedAmount, "00050000");
|
|
116
|
-
EXPECT_EQ(r.preAuthCode, "000000123");
|
|
117
|
-
EXPECT_EQ(r.hostDateTime, "2111520");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Regression: on an approved pre-auth the amount field occupies positions 41-48,
|
|
121
|
-
// so its last digit sits exactly where cardType would be read. An amount ending
|
|
122
|
-
// in 1/2/3 must NOT be surfaced as debit/credit/other. cardType is only
|
|
123
|
-
// meaningful for the KO layout.
|
|
124
|
-
TEST(Response, PreAuthPositiveDoesNotLeakAmountDigitAsCardType) {
|
|
125
|
-
std::string p = a("12345678", 8) + "0" + "e" + "00" + n("4111111111", 19) + a("CLI", 3) +
|
|
126
|
-
a("AUTH01", 6) + n("50001", 8) + n("123", 9) + "000" + "2111520";
|
|
127
|
-
PreAuthResponse r = Ecr17Response::parsePreAuth(p);
|
|
128
|
-
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
129
|
-
EXPECT_EQ(r.preAuthorizedAmount, "00050001"); // ends in '1'
|
|
130
|
-
EXPECT_EQ(r.cardType, ""); // must stay empty, not "1"
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
TEST(Response, Vas) {
|
|
134
|
-
std::string xml =
|
|
135
|
-
"<ecrres><p k=\"RESPID\">0</p><p k=\"RESPMSG\">OK-APPROVED</p>"
|
|
136
|
-
"<p k=\"ORDER_ID\">ABC123</p></ecrres>";
|
|
137
|
-
// header(10) reserved(4) concatFlag(1) idMessage(3) filler-to-pos27(8) xml
|
|
138
|
-
std::string p = a("12345678", 8) + "0" + "K" + n("", 4) + "0" + "001" + n("", 8) + xml;
|
|
139
|
-
VasResponse r = Ecr17Response::parseVas(p);
|
|
140
|
-
EXPECT_FALSE(r.moreMessages);
|
|
141
|
-
EXPECT_EQ(r.idMessage, "001");
|
|
142
|
-
EXPECT_EQ(r.responseId, "0");
|
|
143
|
-
EXPECT_EQ(r.responseMessage, "OK-APPROVED");
|
|
144
|
-
EXPECT_EQ(r.orderId, "ABC123");
|
|
145
|
-
EXPECT_EQ(r.rawXml, xml);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
TEST(Response, DefensiveOnShortOrEmptyPayload) {
|
|
149
|
-
PaymentResponse r = Ecr17Response::parsePayment("");
|
|
150
|
-
EXPECT_EQ(r.outcome, Outcome::Unknown);
|
|
151
|
-
EXPECT_EQ(r.resultCode, "");
|
|
152
|
-
EXPECT_EQ(r.pan, "");
|
|
153
|
-
|
|
154
|
-
StatusResponse s = Ecr17Response::parseStatus("123"); // truncated, must not crash
|
|
155
|
-
EXPECT_EQ(s.status, -1);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
TEST(Response, OutcomeMapping) {
|
|
159
|
-
EXPECT_EQ(outcomeFromCode("00"), Outcome::Ok);
|
|
160
|
-
EXPECT_EQ(outcomeFromCode("01"), Outcome::Ko);
|
|
161
|
-
EXPECT_EQ(outcomeFromCode("05"), Outcome::CardNotPresent);
|
|
162
|
-
EXPECT_EQ(outcomeFromCode("09"), Outcome::UnknownTag);
|
|
163
|
-
EXPECT_EQ(outcomeFromCode("zz"), Outcome::Unknown);
|
|
164
|
-
}
|
|
1
|
+
// Tests for the ECR17 response parsers. Payloads are synthesized field-by-field
|
|
2
|
+
// at the exact 1-based offsets from the spec response tables in docs/. Helpers
|
|
3
|
+
// guarantee each field's width so offsets can't drift from a miscounted space.
|
|
4
|
+
|
|
5
|
+
#include <gtest/gtest.h>
|
|
6
|
+
|
|
7
|
+
#include <string>
|
|
8
|
+
|
|
9
|
+
#include "Ecr17Response/Ecr17Response.hpp"
|
|
10
|
+
|
|
11
|
+
using namespace margelo::nitro::ecr17;
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
|
|
15
|
+
// Left-justified field, right-padded with spaces to `width` (alpha fields).
|
|
16
|
+
std::string a(const std::string& value, size_t width) {
|
|
17
|
+
std::string s = value;
|
|
18
|
+
s.resize(width, ' ');
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Right-justified numeric field, left-padded with '0' to `width`.
|
|
23
|
+
std::string n(const std::string& value, size_t width) {
|
|
24
|
+
return std::string(width - value.size(), '0') + value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace
|
|
28
|
+
|
|
29
|
+
TEST(Response, PaymentPositive) {
|
|
30
|
+
std::string p = a("12345678", 8) + "0" + "E" + "00" + // header + result
|
|
31
|
+
n("4111111111", 19) + // PAN(19)
|
|
32
|
+
a("ICC", 3) + a("ABC123", 6) + "2111520" + // txType authCode dateTime
|
|
33
|
+
"2" + // cardType
|
|
34
|
+
a("ACQ", 11) + n("42", 6) + n("99", 6); // acquirer STAN idOnline
|
|
35
|
+
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
36
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
37
|
+
EXPECT_EQ(r.resultCode, "00");
|
|
38
|
+
EXPECT_EQ(r.pan, n("4111111111", 19));
|
|
39
|
+
EXPECT_EQ(r.transactionType, "ICC");
|
|
40
|
+
EXPECT_EQ(r.authCode, "ABC123");
|
|
41
|
+
EXPECT_EQ(r.hostDateTime, "2111520");
|
|
42
|
+
EXPECT_EQ(r.cardType, "2");
|
|
43
|
+
EXPECT_EQ(r.acquirerId, "ACQ");
|
|
44
|
+
EXPECT_EQ(r.stan, "000042");
|
|
45
|
+
EXPECT_EQ(r.onlineId, "000099");
|
|
46
|
+
EXPECT_FALSE(r.currency.applied);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
TEST(Response, PaymentNegative) {
|
|
50
|
+
std::string p = a("12345678", 8) + "0" + "E" + "01" + a("CARTA RIFIUTATA", 24) +
|
|
51
|
+
n("", 11) + // reserved 37-47
|
|
52
|
+
"3" + a("AC2", 11) + n("7", 6) + n("3", 6);
|
|
53
|
+
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
54
|
+
EXPECT_EQ(r.outcome, Outcome::Ko);
|
|
55
|
+
EXPECT_EQ(r.resultCode, "01");
|
|
56
|
+
EXPECT_EQ(r.errorDescription, "CARTA RIFIUTATA");
|
|
57
|
+
EXPECT_EQ(r.cardType, "3");
|
|
58
|
+
EXPECT_EQ(r.stan, "000007");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
TEST(Response, PaymentWithCurrencyExchange) {
|
|
62
|
+
std::string base = a("12345678", 8) + "0" + "V" + "00" + n("4111111111", 19) + a("ICC", 3) +
|
|
63
|
+
a("ABC123", 6) + "2111520" + "2" + a("ACQ", 11) + n("42", 6) + n("99", 6);
|
|
64
|
+
// actionCode(3) origAmount(8) flag(1) rate(8) ccy(3) amount(12) precision(1)
|
|
65
|
+
std::string p = base + "000" + n("650", 8) + "1" + n("12345", 8) + "USD" + n("650", 12) + "2";
|
|
66
|
+
PaymentResponse r = Ecr17Response::parsePayment(p);
|
|
67
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
68
|
+
EXPECT_TRUE(r.currency.applied);
|
|
69
|
+
EXPECT_EQ(r.currency.rate, "00012345");
|
|
70
|
+
EXPECT_EQ(r.currency.currencyCode, "USD");
|
|
71
|
+
EXPECT_EQ(r.currency.amount, "000000000650");
|
|
72
|
+
EXPECT_EQ(r.currency.precision, "2");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
TEST(Response, Status) {
|
|
76
|
+
std::string p = a("12345678", 8) + "0" + "s" + n("", 10) + // reserved 11-20
|
|
77
|
+
"0102251530" + "2" + "V1.2.3"; // dateTime status sw
|
|
78
|
+
StatusResponse r = Ecr17Response::parseStatus(p);
|
|
79
|
+
EXPECT_EQ(r.terminalId, "12345678");
|
|
80
|
+
EXPECT_EQ(r.dateTimeRaw, "0102251530");
|
|
81
|
+
EXPECT_EQ(r.status, 2);
|
|
82
|
+
EXPECT_EQ(r.softwareRelease, "V1.2.3");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
TEST(Response, Totals) {
|
|
86
|
+
std::string p = a("12345678", 8) + "0" + "T" + "00" + n("123456", 16) + n("", 6);
|
|
87
|
+
TotalsResponse r = Ecr17Response::parseTotals(p);
|
|
88
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
89
|
+
EXPECT_EQ(r.posTotal, n("123456", 16));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
TEST(Response, ClosePositive) {
|
|
93
|
+
std::string p = a("12345678", 8) + "0" + "C" + "00" + n("1000", 16) + n("1000", 16);
|
|
94
|
+
CloseResponse r = Ecr17Response::parseClose(p);
|
|
95
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
96
|
+
EXPECT_EQ(r.posTotal, n("1000", 16));
|
|
97
|
+
EXPECT_EQ(r.hostTotal, n("1000", 16));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
TEST(Response, CloseNegative) {
|
|
101
|
+
std::string p = a("12345678", 8) + "0" + "C" + "01" + a("SBILANCIO", 19) + "100";
|
|
102
|
+
CloseResponse r = Ecr17Response::parseClose(p);
|
|
103
|
+
EXPECT_EQ(r.outcome, Outcome::Ko);
|
|
104
|
+
EXPECT_EQ(r.errorDescription, "SBILANCIO");
|
|
105
|
+
EXPECT_EQ(r.actionCode, "100");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
TEST(Response, PreAuthPositive) {
|
|
109
|
+
std::string p = a("12345678", 8) + "0" + "e" + "00" + n("4111111111", 19) + a("CLI", 3) +
|
|
110
|
+
a("AUTH01", 6) + n("50000", 8) + n("123", 9) + "000" + "2111520";
|
|
111
|
+
PreAuthResponse r = Ecr17Response::parsePreAuth(p);
|
|
112
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
113
|
+
EXPECT_EQ(r.transactionType, "CLI");
|
|
114
|
+
EXPECT_EQ(r.authCode, "AUTH01");
|
|
115
|
+
EXPECT_EQ(r.preAuthorizedAmount, "00050000");
|
|
116
|
+
EXPECT_EQ(r.preAuthCode, "000000123");
|
|
117
|
+
EXPECT_EQ(r.hostDateTime, "2111520");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Regression: on an approved pre-auth the amount field occupies positions 41-48,
|
|
121
|
+
// so its last digit sits exactly where cardType would be read. An amount ending
|
|
122
|
+
// in 1/2/3 must NOT be surfaced as debit/credit/other. cardType is only
|
|
123
|
+
// meaningful for the KO layout.
|
|
124
|
+
TEST(Response, PreAuthPositiveDoesNotLeakAmountDigitAsCardType) {
|
|
125
|
+
std::string p = a("12345678", 8) + "0" + "e" + "00" + n("4111111111", 19) + a("CLI", 3) +
|
|
126
|
+
a("AUTH01", 6) + n("50001", 8) + n("123", 9) + "000" + "2111520";
|
|
127
|
+
PreAuthResponse r = Ecr17Response::parsePreAuth(p);
|
|
128
|
+
EXPECT_EQ(r.outcome, Outcome::Ok);
|
|
129
|
+
EXPECT_EQ(r.preAuthorizedAmount, "00050001"); // ends in '1'
|
|
130
|
+
EXPECT_EQ(r.cardType, ""); // must stay empty, not "1"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
TEST(Response, Vas) {
|
|
134
|
+
std::string xml =
|
|
135
|
+
"<ecrres><p k=\"RESPID\">0</p><p k=\"RESPMSG\">OK-APPROVED</p>"
|
|
136
|
+
"<p k=\"ORDER_ID\">ABC123</p></ecrres>";
|
|
137
|
+
// header(10) reserved(4) concatFlag(1) idMessage(3) filler-to-pos27(8) xml
|
|
138
|
+
std::string p = a("12345678", 8) + "0" + "K" + n("", 4) + "0" + "001" + n("", 8) + xml;
|
|
139
|
+
VasResponse r = Ecr17Response::parseVas(p);
|
|
140
|
+
EXPECT_FALSE(r.moreMessages);
|
|
141
|
+
EXPECT_EQ(r.idMessage, "001");
|
|
142
|
+
EXPECT_EQ(r.responseId, "0");
|
|
143
|
+
EXPECT_EQ(r.responseMessage, "OK-APPROVED");
|
|
144
|
+
EXPECT_EQ(r.orderId, "ABC123");
|
|
145
|
+
EXPECT_EQ(r.rawXml, xml);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
TEST(Response, DefensiveOnShortOrEmptyPayload) {
|
|
149
|
+
PaymentResponse r = Ecr17Response::parsePayment("");
|
|
150
|
+
EXPECT_EQ(r.outcome, Outcome::Unknown);
|
|
151
|
+
EXPECT_EQ(r.resultCode, "");
|
|
152
|
+
EXPECT_EQ(r.pan, "");
|
|
153
|
+
|
|
154
|
+
StatusResponse s = Ecr17Response::parseStatus("123"); // truncated, must not crash
|
|
155
|
+
EXPECT_EQ(s.status, -1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
TEST(Response, OutcomeMapping) {
|
|
159
|
+
EXPECT_EQ(outcomeFromCode("00"), Outcome::Ok);
|
|
160
|
+
EXPECT_EQ(outcomeFromCode("01"), Outcome::Ko);
|
|
161
|
+
EXPECT_EQ(outcomeFromCode("05"), Outcome::CardNotPresent);
|
|
162
|
+
EXPECT_EQ(outcomeFromCode("09"), Outcome::UnknownTag);
|
|
163
|
+
EXPECT_EQ(outcomeFromCode("zz"), Outcome::Unknown);
|
|
164
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
// Money-critical safety tests for the auto-reconnect retry decision.
|
|
2
|
-
// The terminal handles real payments, so a financial command must NEVER be
|
|
3
|
-
// blindly re-sent after a connection drop (double-charge risk).
|
|
4
|
-
|
|
5
|
-
#include <gtest/gtest.h>
|
|
6
|
-
|
|
7
|
-
#include "Session/RetryPolicy.hpp"
|
|
8
|
-
|
|
9
|
-
using margelo::nitro::ecr17::shouldRetryAfterReconnect;
|
|
10
|
-
|
|
11
|
-
// A financial command (safeToRetry == false) must never be retried, regardless
|
|
12
|
-
// of autoReconnect / drop state. This is the invariant that prevents double
|
|
13
|
-
// charging; recovery is via sendLastResult ('G'), not a re-send.
|
|
14
|
-
TEST(RetryPolicy, FinancialCommandIsNeverRetried) {
|
|
15
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/true, /*safe=*/false));
|
|
16
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/true, /*safe=*/false));
|
|
17
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/false, /*safe=*/false));
|
|
18
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/false, /*safe=*/false));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// A safe/idempotent command is retried ONLY when autoReconnect is on AND the
|
|
22
|
-
// transport actually dropped.
|
|
23
|
-
TEST(RetryPolicy, SafeCommandRetriedOnlyOnReconnectAfterDrop) {
|
|
24
|
-
EXPECT_TRUE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/true, /*safe=*/true));
|
|
25
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/true, /*safe=*/true));
|
|
26
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/false, /*safe=*/true));
|
|
27
|
-
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/false, /*safe=*/true));
|
|
28
|
-
}
|
|
1
|
+
// Money-critical safety tests for the auto-reconnect retry decision.
|
|
2
|
+
// The terminal handles real payments, so a financial command must NEVER be
|
|
3
|
+
// blindly re-sent after a connection drop (double-charge risk).
|
|
4
|
+
|
|
5
|
+
#include <gtest/gtest.h>
|
|
6
|
+
|
|
7
|
+
#include "Session/RetryPolicy.hpp"
|
|
8
|
+
|
|
9
|
+
using margelo::nitro::ecr17::shouldRetryAfterReconnect;
|
|
10
|
+
|
|
11
|
+
// A financial command (safeToRetry == false) must never be retried, regardless
|
|
12
|
+
// of autoReconnect / drop state. This is the invariant that prevents double
|
|
13
|
+
// charging; recovery is via sendLastResult ('G'), not a re-send.
|
|
14
|
+
TEST(RetryPolicy, FinancialCommandIsNeverRetried) {
|
|
15
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/true, /*safe=*/false));
|
|
16
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/true, /*safe=*/false));
|
|
17
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/false, /*safe=*/false));
|
|
18
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/false, /*safe=*/false));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A safe/idempotent command is retried ONLY when autoReconnect is on AND the
|
|
22
|
+
// transport actually dropped.
|
|
23
|
+
TEST(RetryPolicy, SafeCommandRetriedOnlyOnReconnectAfterDrop) {
|
|
24
|
+
EXPECT_TRUE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/true, /*safe=*/true));
|
|
25
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/true, /*safe=*/true));
|
|
26
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/true, /*dropped=*/false, /*safe=*/true));
|
|
27
|
+
EXPECT_FALSE(shouldRetryAfterReconnect(/*autoReconnect=*/false, /*dropped=*/false, /*safe=*/true));
|
|
28
|
+
}
|