nets-service-sdk 1.1.14 → 1.1.16

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 (2) hide show
  1. package/dist/index.js +161 -80
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ var __commonJS = (cb, mod) => function __require() {
6
6
  // src/utils.helper.js
7
7
  var require_utils_helper = __commonJS({
8
8
  "src/utils.helper.js"(exports2, module2) {
9
- var { SerialPort } = require("serialport");
10
9
  var crypto = require("crypto");
11
10
  var _ = require("lodash");
12
11
  module2.exports.padWithLeadingZeros = (num, totalLength) => {
@@ -53,34 +52,6 @@ var require_utils_helper = __commonJS({
53
52
  ""
54
53
  ).trim();
55
54
  };
56
- SerialPort.prototype._disconnected = function(err) {
57
- this.paused = true;
58
- this.emit("disconnect", err);
59
- if (this.closing) {
60
- return;
61
- }
62
- if (this.fd === null) {
63
- return;
64
- }
65
- this.closing = true;
66
- if (process.platform !== "win32") {
67
- this.readable = false;
68
- if (this.serialPoller) {
69
- this.serialPoller.close();
70
- }
71
- }
72
- SerialPortBinding.close(
73
- this.fd,
74
- function(err2) {
75
- this.closing = false;
76
- if (err2) {
77
- debug("Disconnect close completed with error: ", err2);
78
- }
79
- this.fd = null;
80
- this.emit("close");
81
- }.bind(this)
82
- );
83
- };
84
55
  module2.exports.generateServiceKey = (payload) => {
85
56
  const key = `${process.env.user}:${process.env.password}`;
86
57
  const algorithm = "aes256";
@@ -382,6 +353,40 @@ var require_httpRequest = __commonJS({
382
353
  var require_parser = __commonJS({
383
354
  "src/payment/parser.js"(exports2, module2) {
384
355
  var logger = require_winston()(module2);
356
+ var RESPONSE_CODES = {
357
+ "00": { status: "APPROVED", detail: "SUCCESS", category: "SUCCESS" },
358
+ "04": { status: "DECLINED", detail: "PICKUP_CARD", category: "HARD_FAIL" },
359
+ "05": { status: "DECLINED", detail: "DO_NOT_HONOUR", category: "HARD_FAIL" },
360
+ "07": { status: "DECLINED", detail: "PICKUP_CARD_SPECIAL", category: "HARD_FAIL" },
361
+ "12": { status: "DECLINED", detail: "INVALID_TRANSACTION", category: "HARD_FAIL" },
362
+ "13": { status: "DECLINED", detail: "INVALID_AMOUNT", category: "HARD_FAIL" },
363
+ "14": { status: "DECLINED", detail: "INVALID_CARD", category: "HARD_FAIL" },
364
+ "30": { status: "DECLINED", detail: "FORMAT_ERROR", category: "HARD_FAIL" },
365
+ "41": { status: "DECLINED", detail: "LOST_CARD", category: "HARD_FAIL" },
366
+ "43": { status: "DECLINED", detail: "STOLEN_CARD", category: "HARD_FAIL" },
367
+ "51": { status: "DECLINED", detail: "INSUFFICIENT_FUNDS", category: "HARD_FAIL" },
368
+ "54": { status: "DECLINED", detail: "EXPIRED_CARD", category: "HARD_FAIL" },
369
+ "55": { status: "DECLINED", detail: "WRONG_PIN", category: "HARD_FAIL" },
370
+ "57": { status: "DECLINED", detail: "NOT_PERMITTED", category: "HARD_FAIL" },
371
+ "58": { status: "DECLINED", detail: "TERMINAL_NOT_ALLOWED", category: "HARD_FAIL" },
372
+ "61": { status: "DECLINED", detail: "LIMIT_EXCEEDED", category: "HARD_FAIL" },
373
+ "62": { status: "DECLINED", detail: "RESTRICTED_CARD", category: "HARD_FAIL" },
374
+ "63": { status: "DECLINED", detail: "SECURITY_ISSUE", category: "HARD_FAIL" },
375
+ "65": { status: "DECLINED", detail: "FREQUENCY_LIMIT", category: "HARD_FAIL" },
376
+ "68": { status: "RETRY", detail: "RESPONSE_RECEIVED_TOO_LATE", category: "RETRY" },
377
+ "75": { status: "DECLINED", detail: "PIN_LOCKED", category: "HARD_FAIL" },
378
+ "91": { status: "RETRY", detail: "ISSUER_UNAVAILABLE", category: "RETRY" },
379
+ "96": { status: "DECLINED", detail: "SYSTEM_ERROR", category: "HARD_FAIL" },
380
+ "TO": { status: "TIME_OUT", detail: "TIMEOUT", category: "RETRY" },
381
+ "CE": { status: "RETRY", detail: "COMMUNICATION_ERROR", category: "RETRY" },
382
+ "PE": { status: "DECLINED", detail: "PROTOCOL_ERROR", category: "HARD_FAIL" },
383
+ "NA": { status: "RETRY", detail: "NO_ACKNOWLEDGEMENT", category: "RETRY" },
384
+ "US": { status: "USER_CANCELLED", detail: "USER_CANCELLED", category: "HARD_FAIL" },
385
+ "GX": { status: "OUT_OF_PAPER", detail: "OUT_OF_PAPER", category: "HARD_FAIL" }
386
+ };
387
+ function getResponseFromCode(code) {
388
+ return RESPONSE_CODES[code] || null;
389
+ }
385
390
  module2.exports.statusParser = (hexCode, ecn) => {
386
391
  const splitted = hexCode.split("1C");
387
392
  for (let i = 0; i < splitted.length; i++) {
@@ -406,11 +411,11 @@ var require_parser = __commonJS({
406
411
  } else {
407
412
  const subStringArray = ascii.splitAtIndex(2);
408
413
  if (subStringArray.length > 0) {
409
- if (subStringArray.length > 2 && subStringArray[0] == "02" && subStringArray[0]?.indexOf("APPROVED") > -1) {
414
+ if (subStringArray.length > 1 && subStringArray[0].cleanUp() == "02" && subStringArray[1].cleanUp()?.indexOf("APPROVED") > -1) {
410
415
  status = true;
411
416
  }
412
417
  const identifier = subStringArray[0].cleanUp();
413
- const value = subStringArray[1].cleanUp();
418
+ const value = subStringArray[1]?.cleanUp();
414
419
  if (identifier && value) {
415
420
  json[identifier] = value;
416
421
  }
@@ -494,6 +499,7 @@ var require_parser = __commonJS({
494
499
  if (statusResponse) {
495
500
  translatedJson.status = statusResponse.status;
496
501
  translatedJson.description = statusResponse.detail;
502
+ translatedJson.category = statusResponse.category;
497
503
  if (statusResponse.balance) {
498
504
  translatedJson["balance"] = statusResponse.balance;
499
505
  }
@@ -502,6 +508,7 @@ var require_parser = __commonJS({
502
508
  if (ecnnError) {
503
509
  translatedJson.status = ecnnError.status;
504
510
  translatedJson.description = ecnnError.detail;
511
+ translatedJson.category = ecnnError.category;
505
512
  }
506
513
  translatedJson["responsetext"] = obj[key];
507
514
  }
@@ -582,6 +589,12 @@ var require_parser = __commonJS({
582
589
  }
583
590
  if (key == "HC") {
584
591
  translatedJson["hostResponseCode"] = obj[key];
592
+ const mapped = getResponseFromCode(obj[key]);
593
+ if (mapped) {
594
+ translatedJson.status = mapped.status;
595
+ translatedJson.description = mapped.detail;
596
+ translatedJson.category = mapped.category;
597
+ }
585
598
  }
586
599
  if (key == "HD") {
587
600
  translatedJson["enhancedECRReferenceNumber"] = obj[key];
@@ -627,8 +640,19 @@ var require_parser = __commonJS({
627
640
  return { translated: translatedJson, raw: obj };
628
641
  };
629
642
  function statusCheck(data) {
643
+ if (!data) return null;
644
+ const code = data.substring(0, 2);
645
+ const mapped = getResponseFromCode(code);
646
+ if (mapped) {
647
+ const result = { ...mapped };
648
+ if (code === "00" && data.indexOf("BAL:") > -1) {
649
+ const balance = data.split("BAL:");
650
+ result.balance = balance[1].trim();
651
+ }
652
+ return result;
653
+ }
630
654
  if (data?.indexOf("APPROVED") > -1) {
631
- const response = { status: "APPROVED", detail: "Payment Succeed" };
655
+ const response = { status: "APPROVED", detail: "Payment Succeed", category: "SUCCESS" };
632
656
  if (data?.indexOf("BAL:") > -1) {
633
657
  const balance = data.split("BAL:");
634
658
  response.balance = balance[1].trim();
@@ -636,28 +660,25 @@ var require_parser = __commonJS({
636
660
  return response;
637
661
  }
638
662
  if (data?.indexOf("INVALID CARD") > -1) {
639
- return { status: "INVALID_CARD", detail: "Invalid Card" };
663
+ return { status: "INVALID_CARD", detail: "Invalid Card", category: "HARD_FAIL" };
640
664
  }
641
665
  if (data?.indexOf("F3905-Parameter") > -1) {
642
- return { status: "INVALID_CARD", detail: "Invalid Card" };
666
+ return { status: "INVALID_CARD", detail: "Invalid Card", category: "HARD_FAIL" };
643
667
  }
644
668
  if (data?.indexOf("DECLINED") > -1) {
645
- return { status: "DECLINED", detail: "Transaction Declined" };
669
+ return { status: "DECLINED", detail: "Transaction Declined", category: "HARD_FAIL" };
646
670
  }
647
671
  if (data?.indexOf("CARD NOT SUPPORTED") > -1) {
648
- return { status: "CARD_NOT_SUPPORTED", detail: "Card not supported" };
672
+ return { status: "CARD_NOT_SUPPORTED", detail: "Card not supported", category: "HARD_FAIL" };
649
673
  }
650
674
  return null;
651
675
  }
652
676
  function CheckECNERROR(data) {
653
- if (data?.indexOf("US") > -1) {
654
- return { status: "USER_CANCELLED", detail: "User Cancelled" };
655
- }
656
- if (data?.indexOf("GX") > -1) {
657
- return { status: "OUT_OF_PAPER", detail: "Out of Paper" };
658
- }
659
- if (data?.indexOf("TO") > -1) {
660
- return { status: "TIME_OUT", detail: "Time Out" };
677
+ if (!data) return null;
678
+ for (const code of ["US", "GX", "TO"]) {
679
+ if (data.indexOf(code) > -1) {
680
+ return getResponseFromCode(code);
681
+ }
661
682
  }
662
683
  return null;
663
684
  }
@@ -799,7 +820,7 @@ var require_responseHandler = __commonJS({
799
820
  activePaymentEcn = null;
800
821
  }
801
822
  if (response.translated) {
802
- if (response.translated.status == "APPROVED") {
823
+ if (response.translated.category == "SUCCESS" || response.translated.status == "APPROVED") {
803
824
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
804
825
  success: true,
805
826
  status: "SUCCESS",
@@ -807,15 +828,13 @@ var require_responseHandler = __commonJS({
807
828
  action: "COMPLETED"
808
829
  });
809
830
  sendPaymentResponseToCloud(response.translated);
810
- }
811
- if (response.translated.status == "TIME_OUT") {
831
+ } else if (response.translated.category == "RETRY" || response.translated.status == "TIME_OUT") {
812
832
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
813
833
  status: "RETRY",
814
834
  response,
815
835
  action: "RETRY"
816
836
  });
817
- }
818
- if (response.translated.status == "USER_CANCELLED" || response.translated.status == "OUT_OF_PAPER" || response.translated.status == "INVALID_CARD" || response.translated.status == "DECLINED" || response.translated.status == "CARD_NOT_SUPPORTED") {
837
+ } else {
819
838
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
820
839
  status: "CANCELLED",
821
840
  response,
@@ -832,7 +851,7 @@ var require_responseHandler = __commonJS({
832
851
  activePaymentEcn = null;
833
852
  }
834
853
  if (response.translated) {
835
- if (response.translated.status == "APPROVED") {
854
+ if (response.translated.category == "SUCCESS" || response.translated.status == "APPROVED") {
836
855
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
837
856
  success: true,
838
857
  status: "SUCCESS",
@@ -840,15 +859,13 @@ var require_responseHandler = __commonJS({
840
859
  action: "COMPLETED"
841
860
  });
842
861
  sendPaymentResponseToCloud(response.translated);
843
- }
844
- if (response.translated.status == "TIME_OUT") {
862
+ } else if (response.translated.category == "RETRY" || response.translated.status == "TIME_OUT") {
845
863
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
846
864
  status: "RETRY",
847
865
  response,
848
866
  action: "RETRY"
849
867
  });
850
- }
851
- if (response.translated.status == "USER_CANCELLED" || response.translated.status == "OUT_OF_PAPER" || response.translated.status == "INVALID_CARD" || response.translated.status == "DECLINED" || response.translated.status == "CARD_NOT_SUPPORTED") {
868
+ } else {
852
869
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
853
870
  status: "CANCELLED",
854
871
  response,
@@ -950,6 +967,10 @@ var require_communication = __commonJS({
950
967
  });
951
968
  resendData();
952
969
  } else {
970
+ logger.log({
971
+ level: "warn",
972
+ message: `On ACK/NACK Listener: Terminal did not respond with ACK/NACK after max ${count} retries. Sending TERMINAL_ERROR to client.`
973
+ });
953
974
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
954
975
  success: true,
955
976
  action: "TERMINAL_ERROR"
@@ -1018,20 +1039,6 @@ var require_communication = __commonJS({
1018
1039
  });
1019
1040
  global.port.on("data", (data) => {
1020
1041
  logger.log({ level: "info", message: `Raw data: ${data.toString("hex")}` });
1021
- if (data.length == 1) {
1022
- console.log(data);
1023
- if (data[0] === 6 || data[0] === 6 || data[0] === 21 || data[0] === 21) {
1024
- const byte = data[0];
1025
- ackOrNack = Buffer.from([byte]);
1026
- data = data.slice(1);
1027
- logger.log({ level: "info", message: `Terminal sent ${byte === 6 ? "ACK" : "NACK"} (0x${byte.toString(16)})` });
1028
- if (byte === 21 || byte === 21) {
1029
- count--;
1030
- if (count >= 0) resendData();
1031
- else sendMessage2("clientRoom", "PAYMENT_MESSAGE", { success: true, action: "TERMINAL_ERROR" });
1032
- }
1033
- }
1034
- }
1035
1042
  dataBuffer = Buffer.concat([dataBuffer, data]);
1036
1043
  exports2.processIncomingData();
1037
1044
  });
@@ -1060,17 +1067,42 @@ var require_communication = __commonJS({
1060
1067
  }
1061
1068
  };
1062
1069
  module2.exports.processIncomingData = () => {
1063
- while (dataBuffer.length >= 3) {
1064
- const stxIndex = dataBuffer.indexOf(2);
1065
- if (stxIndex === -1) {
1066
- dataBuffer = Buffer.alloc(0);
1067
- break;
1070
+ while (dataBuffer.length > 0) {
1071
+ if (dataBuffer[0] === 6 || dataBuffer[0] === 21) {
1072
+ const byte = dataBuffer[0];
1073
+ ackOrNack = Buffer.from([byte]);
1074
+ dataBuffer = dataBuffer.slice(1);
1075
+ logger.log({ level: "info", message: `Terminal sent ${byte === 6 ? "ACK" : "NACK"} (0x${byte.toString(16).padStart(2, "0")})` });
1076
+ if (ackTimeout) {
1077
+ clearTimeout(ackTimeout);
1078
+ ackTimeout = null;
1079
+ }
1080
+ if (byte === 21) {
1081
+ count--;
1082
+ if (count >= 0) {
1083
+ logger.log({ level: "info", message: `Terminal NACK received, retrying ${requestType}...` });
1084
+ resendData();
1085
+ } else {
1086
+ logger.log({
1087
+ level: "error",
1088
+ message: `Terminal responded with NACK multiple times. Max retries reached. Sending TERMINAL_ERROR.`
1089
+ });
1090
+ sendMessage2("clientRoom", "PAYMENT_MESSAGE", { success: true, action: "TERMINAL_ERROR" });
1091
+ }
1092
+ }
1093
+ continue;
1068
1094
  }
1069
- if (stxIndex > 0) {
1070
- dataBuffer = dataBuffer.slice(stxIndex);
1095
+ if (dataBuffer[0] !== 2) {
1096
+ const nextStart = dataBuffer.slice(1).findIndex((b) => b === 2 || b === 6 || b === 21);
1097
+ if (nextStart === -1) {
1098
+ dataBuffer = Buffer.alloc(0);
1099
+ break;
1100
+ } else {
1101
+ dataBuffer = dataBuffer.slice(nextStart + 1);
1102
+ continue;
1103
+ }
1071
1104
  }
1072
1105
  if (dataBuffer.length < 3) break;
1073
- if (dataBuffer.length < 3) break;
1074
1106
  const lenBytes = dataBuffer.slice(1, 3).toString("hex");
1075
1107
  const bodyLen = parseInt(lenBytes, 10);
1076
1108
  const expectedFrameSize = 1 + 2 + bodyLen + 2;
@@ -1086,7 +1118,10 @@ var require_communication = __commonJS({
1086
1118
  dataBuffer = dataBuffer.slice(etxIndex + 2);
1087
1119
  exports2.handleFullMessage(frame);
1088
1120
  } else {
1089
- if (dataBuffer.length > 1024) dataBuffer = Buffer.alloc(0);
1121
+ if (dataBuffer.length > 2048) {
1122
+ logger.log({ level: "warn", message: "Stale data in buffer exceeds limit, clearing." });
1123
+ dataBuffer = Buffer.alloc(0);
1124
+ }
1090
1125
  break;
1091
1126
  }
1092
1127
  }
@@ -1119,9 +1154,51 @@ var require_communication = __commonJS({
1119
1154
  global.port.write(NACK);
1120
1155
  }
1121
1156
  };
1122
- module2.exports.sentToTerminal = (type, body) => {
1157
+ module2.exports.checkPortConnection = async () => {
1158
+ const portPath = config.simulation ? config.simulationPort : config.com;
1159
+ try {
1160
+ const ports = await SerialPort.list();
1161
+ const availablePorts = ports.map((p) => p.path);
1162
+ if (!availablePorts.includes(portPath)) {
1163
+ logger.error({
1164
+ level: "error",
1165
+ message: `\u274C Port ${portPath} is NOT connected to the system.`
1166
+ });
1167
+ return { success: false, error: "PORT_DISCONNECTED" };
1168
+ }
1169
+ if (!global.port || !global.port.isOpen) {
1170
+ logger.error({
1171
+ level: "error",
1172
+ message: `\u274C Port ${portPath} is connected but NOT open.`
1173
+ });
1174
+ if (config.terminal === "enable" || !config.terminal) {
1175
+ logger.log({ level: "info", message: "Attempting to re-initialize port..." });
1176
+ exports2.initialize();
1177
+ }
1178
+ return { success: false, error: "PORT_NOT_OPEN" };
1179
+ }
1180
+ return { success: true };
1181
+ } catch (e) {
1182
+ logger.error({
1183
+ level: "error",
1184
+ message: `Error checking port connection: ${e.message}`
1185
+ });
1186
+ return { success: false, error: e.message };
1187
+ }
1188
+ };
1189
+ module2.exports.sentToTerminal = async (type, body) => {
1123
1190
  requestType = type;
1124
1191
  requestPayload = body;
1192
+ const portStatus = await exports2.checkPortConnection();
1193
+ if (!portStatus.success) {
1194
+ const msgType = requestType === "STATUS_CHECK" ? "STATUS_MESSAGE" : requestType === "LOGON" ? "LOGON_MESSAGE" : "PAYMENT_MESSAGE";
1195
+ sendMessage2("clientRoom", msgType, {
1196
+ status: "FAILED",
1197
+ action: "TERMINAL_ERROR",
1198
+ error: portStatus.error,
1199
+ message: "Port is not connected or not responding."
1200
+ });
1201
+ }
1125
1202
  if (requestType == "STATUS_CHECK") {
1126
1203
  calculated = generateStatusReq();
1127
1204
  sendMessage2("clientRoom", "STATUS_MESSAGE", {
@@ -1152,9 +1229,13 @@ var require_communication = __commonJS({
1152
1229
  lastPaymentTime = now;
1153
1230
  }
1154
1231
  exports2.reset();
1155
- const { setPaymentCloudUrl, setPaymentOrderId } = require_responseHandler();
1232
+ const {
1233
+ setPaymentCloudUrl,
1234
+ setPaymentOrderId
1235
+ } = require_responseHandler();
1156
1236
  if (body && body.cloud_url) setPaymentCloudUrl(body.cloud_url);
1157
- if (body && (body.reference || body.orderId)) setPaymentOrderId(body.reference || body.orderId);
1237
+ if (body && (body.reference || body.orderId))
1238
+ setPaymentOrderId(body.reference || body.orderId);
1158
1239
  calculated = generatePaymentRequest(body);
1159
1240
  console.log(calculated);
1160
1241
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nets-service-sdk",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Utility functions for Nets Service",
5
5
  "source": "src/index.js",
6
6
  "main": "dist/index.js",