nets-service-sdk 1.1.9 → 1.1.10

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 +227 -698
  2. package/package.json +12 -3
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var require_utils_helper = __commonJS({
22
22
  response = xor(response, value2);
23
23
  }
24
24
  });
25
- return response;
25
+ return response.toUpperCase();
26
26
  };
27
27
  String.prototype.toHexString = function() {
28
28
  var res = [];
@@ -376,7 +376,7 @@ var require_parser = __commonJS({
376
376
  "src/payment/parser.js"(exports2, module2) {
377
377
  var logger = require_winston()(module2);
378
378
  module2.exports.statusParser = (hexCode, ecn) => {
379
- const splitted = hexCode.split("1c");
379
+ const splitted = hexCode.split("1C");
380
380
  for (let i = 0; i < splitted.length; i++) {
381
381
  if (splitted[i]?.toAsciiString()?.indexOf(ecn) > -1) {
382
382
  logger.log({ level: "info", message: `statusParser: ECN ${ecn} found in response` });
@@ -388,7 +388,7 @@ var require_parser = __commonJS({
388
388
  };
389
389
  module2.exports.logonParser = (hexCode, ecn) => {
390
390
  try {
391
- const splitted = hexCode.split("1c");
391
+ const splitted = hexCode.split("1C");
392
392
  let status = false;
393
393
  const json = {};
394
394
  for (let i = 0; i < splitted.length; i++) {
@@ -419,13 +419,16 @@ var require_parser = __commonJS({
419
419
  };
420
420
  module2.exports.netsPaymentParser = (hexCode, ecn) => {
421
421
  try {
422
- const splitted = hexCode.split("1c");
422
+ const splitted = hexCode.split("1C");
423
423
  let status = false;
424
424
  const json = {};
425
425
  for (let i = 0; i < splitted.length; i++) {
426
426
  const ascii = splitted[i].toAsciiString();
427
- if (ascii?.indexOf(ecn) > -1) {
427
+ if (ascii?.indexOf(ecn) > -1 || i === 0 && ascii.length >= 12) {
428
428
  json["ECN"] = ascii.cleanUp();
429
+ if (ascii?.indexOf(ecn) === -1) {
430
+ logger.warn({ level: "info", message: `ECN match mismatch but segment identified: expected ${ecn} in ${ascii}` });
431
+ }
429
432
  } else {
430
433
  const subStringArray = ascii.splitAtIndex(2);
431
434
  if (subStringArray.length > 1) {
@@ -446,12 +449,12 @@ var require_parser = __commonJS({
446
449
  };
447
450
  module2.exports.creditPaymentParser = (hexCode, ecn) => {
448
451
  try {
449
- const splitted = hexCode.split("1c");
452
+ const splitted = hexCode.split("1C");
450
453
  let status = false;
451
454
  const json = {};
452
455
  for (let i = 0; i < splitted.length; i++) {
453
456
  const ascii = splitted[i].toAsciiString();
454
- if (ascii?.indexOf(ecn) > -1) {
457
+ if (ascii?.indexOf(ecn) > -1 || i === 0 && ascii.length >= 12) {
455
458
  json["ECN"] = ascii.cleanUp();
456
459
  } else {
457
460
  const subStringArray = ascii.splitAtIndex(2);
@@ -654,303 +657,6 @@ var require_parser = __commonJS({
654
657
  }
655
658
  });
656
659
 
657
- // src/db/autoPurge.js
658
- var require_autoPurge = __commonJS({
659
- "src/db/autoPurge.js"(exports2, module2) {
660
- var cron = require("node-cron");
661
- var logger = require_winston()(module2);
662
- var { cleanupOldTransactions } = require_transactionStore();
663
- var scheduledTask = null;
664
- function startAutoPurge(daysToKeep = 7, cronSchedule = "0 2 * * 0") {
665
- if (scheduledTask) {
666
- logger.log({
667
- level: "warn",
668
- message: "Auto-purge already running. Stopping existing task first."
669
- });
670
- stopAutoPurge();
671
- }
672
- scheduledTask = cron.schedule(cronSchedule, () => {
673
- logger.log({
674
- level: "info",
675
- message: `Running scheduled transaction cleanup (keeping last ${daysToKeep} days)`
676
- });
677
- try {
678
- const deletedCount = cleanupOldTransactions(daysToKeep);
679
- logger.log({
680
- level: "info",
681
- message: `Auto-purge completed: Deleted ${deletedCount} old transactions`
682
- });
683
- } catch (error) {
684
- logger.log({
685
- level: "error",
686
- message: `Auto-purge failed: ${error.message}`
687
- });
688
- }
689
- });
690
- logger.log({
691
- level: "info",
692
- message: `Auto-purge scheduled: ${cronSchedule} (keeping last ${daysToKeep} days)`
693
- });
694
- return scheduledTask;
695
- }
696
- function stopAutoPurge() {
697
- if (scheduledTask) {
698
- scheduledTask.stop();
699
- scheduledTask = null;
700
- logger.log({
701
- level: "info",
702
- message: "Auto-purge stopped"
703
- });
704
- }
705
- }
706
- function isAutoPurgeRunning() {
707
- return scheduledTask !== null;
708
- }
709
- module2.exports = {
710
- startAutoPurge,
711
- stopAutoPurge,
712
- isAutoPurgeRunning
713
- };
714
- }
715
- });
716
-
717
- // src/db/transactionStore.js
718
- var require_transactionStore = __commonJS({
719
- "src/db/transactionStore.js"(exports2, module2) {
720
- var Database = require("better-sqlite3");
721
- var path = require("path");
722
- var fs = require("fs");
723
- var logger = require_winston()(module2);
724
- var db = null;
725
- function initDatabase(dbPath, autoPurgeConfig = {}) {
726
- try {
727
- const defaultPath = path.join(__dirname, "../../transactions.db");
728
- const finalPath = dbPath || defaultPath;
729
- const dir = path.dirname(finalPath);
730
- if (!fs.existsSync(dir)) {
731
- fs.mkdirSync(dir, { recursive: true });
732
- }
733
- db = new Database(finalPath);
734
- db.pragma("journal_mode = WAL");
735
- const createTableSQL = `
736
- CREATE TABLE IF NOT EXISTS transactions (
737
- ecn TEXT PRIMARY KEY,
738
- request_type TEXT NOT NULL,
739
- request_payload TEXT,
740
- request_hex TEXT,
741
- request_timestamp INTEGER NOT NULL,
742
- response_hex TEXT,
743
- response_data TEXT,
744
- response_timestamp INTEGER,
745
- status TEXT DEFAULT 'PENDING',
746
- created_at INTEGER NOT NULL,
747
- updated_at INTEGER NOT NULL
748
- )
749
- `;
750
- db.exec(createTableSQL);
751
- db.exec("CREATE INDEX IF NOT EXISTS idx_request_timestamp ON transactions(request_timestamp DESC)");
752
- db.exec("CREATE INDEX IF NOT EXISTS idx_status ON transactions(status)");
753
- logger.log({
754
- level: "info",
755
- message: `Transaction database initialized at ${finalPath}`
756
- });
757
- const { enabled = true, daysToKeep = 7, schedule = "0 2 * * 0" } = autoPurgeConfig;
758
- if (enabled) {
759
- const autoPurge = require_autoPurge();
760
- autoPurge.startAutoPurge(daysToKeep, schedule);
761
- }
762
- return db;
763
- } catch (error) {
764
- logger.log({
765
- level: "error",
766
- message: `Failed to initialize database: ${error.message}`
767
- });
768
- throw error;
769
- }
770
- }
771
- function getDatabase() {
772
- if (!db) {
773
- initDatabase();
774
- }
775
- return db;
776
- }
777
- function createTransaction(data) {
778
- try {
779
- const database = getDatabase();
780
- const now = Date.now();
781
- const stmt = database.prepare(`
782
- INSERT INTO transactions (
783
- ecn, request_type, request_payload, request_hex,
784
- request_timestamp, status, created_at, updated_at
785
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
786
- `);
787
- const result = stmt.run(
788
- data.ecn,
789
- data.requestType,
790
- JSON.stringify(data.requestPayload || {}),
791
- data.requestHex,
792
- now,
793
- "PENDING",
794
- now,
795
- now
796
- );
797
- logger.log({
798
- level: "info",
799
- message: `Transaction created: ECN=${data.ecn}, Type=${data.requestType}`
800
- });
801
- return result;
802
- } catch (error) {
803
- logger.log({
804
- level: "error",
805
- message: `Failed to create transaction: ${error.message}`
806
- });
807
- throw error;
808
- }
809
- }
810
- function updateTransactionResponse(ecn, data) {
811
- try {
812
- const database = getDatabase();
813
- const now = Date.now();
814
- const stmt = database.prepare(`
815
- UPDATE transactions
816
- SET response_hex = ?,
817
- response_data = ?,
818
- response_timestamp = ?,
819
- status = ?,
820
- updated_at = ?
821
- WHERE ecn = ?
822
- `);
823
- const result = stmt.run(
824
- data.responseHex,
825
- JSON.stringify(data.responseData || {}),
826
- now,
827
- data.status || "COMPLETED",
828
- now,
829
- ecn
830
- );
831
- logger.log({
832
- level: "info",
833
- message: `Transaction updated: ECN=${ecn}, Status=${data.status || "COMPLETED"}`
834
- });
835
- return result;
836
- } catch (error) {
837
- logger.log({
838
- level: "error",
839
- message: `Failed to update transaction: ${error.message}`
840
- });
841
- throw error;
842
- }
843
- }
844
- function getTransactionByEcn(ecn) {
845
- try {
846
- const database = getDatabase();
847
- const stmt = database.prepare("SELECT * FROM transactions WHERE ecn = ?");
848
- const row = stmt.get(ecn);
849
- if (row) {
850
- row.request_payload = JSON.parse(row.request_payload || "{}");
851
- row.response_data = JSON.parse(row.response_data || "{}");
852
- }
853
- return row;
854
- } catch (error) {
855
- logger.log({
856
- level: "error",
857
- message: `Failed to get transaction: ${error.message}`
858
- });
859
- return null;
860
- }
861
- }
862
- function getRecentTransactions(limit = 100) {
863
- try {
864
- const database = getDatabase();
865
- const stmt = database.prepare(`
866
- SELECT * FROM transactions
867
- ORDER BY request_timestamp DESC
868
- LIMIT ?
869
- `);
870
- const rows = stmt.all(limit);
871
- return rows.map((row) => ({
872
- ...row,
873
- request_payload: JSON.parse(row.request_payload || "{}"),
874
- response_data: JSON.parse(row.response_data || "{}")
875
- }));
876
- } catch (error) {
877
- logger.log({
878
- level: "error",
879
- message: `Failed to get recent transactions: ${error.message}`
880
- });
881
- return [];
882
- }
883
- }
884
- function getTransactionsByStatus(status, limit = 100) {
885
- try {
886
- const database = getDatabase();
887
- const stmt = database.prepare(`
888
- SELECT * FROM transactions
889
- WHERE status = ?
890
- ORDER BY request_timestamp DESC
891
- LIMIT ?
892
- `);
893
- const rows = stmt.all(status, limit);
894
- return rows.map((row) => ({
895
- ...row,
896
- request_payload: JSON.parse(row.request_payload || "{}"),
897
- response_data: JSON.parse(row.response_data || "{}")
898
- }));
899
- } catch (error) {
900
- logger.log({
901
- level: "error",
902
- message: `Failed to get transactions by status: ${error.message}`
903
- });
904
- return [];
905
- }
906
- }
907
- function cleanupOldTransactions(daysToKeep = 90) {
908
- try {
909
- const database = getDatabase();
910
- const cutoffTime = Date.now() - daysToKeep * 24 * 60 * 60 * 1e3;
911
- const stmt = database.prepare("DELETE FROM transactions WHERE request_timestamp < ?");
912
- const result = stmt.run(cutoffTime);
913
- logger.log({
914
- level: "info",
915
- message: `Cleaned up ${result.changes} old transactions (older than ${daysToKeep} days)`
916
- });
917
- return result.changes;
918
- } catch (error) {
919
- logger.log({
920
- level: "error",
921
- message: `Failed to cleanup old transactions: ${error.message}`
922
- });
923
- return 0;
924
- }
925
- }
926
- function closeDatabase() {
927
- if (db) {
928
- db.close();
929
- db = null;
930
- logger.log({
931
- level: "info",
932
- message: "Database connection closed"
933
- });
934
- }
935
- }
936
- module2.exports = {
937
- initDatabase,
938
- getDatabase,
939
- createTransaction,
940
- updateTransactionResponse,
941
- getTransactionByEcn,
942
- getRecentTransactions,
943
- getTransactionsByStatus,
944
- cleanupOldTransactions,
945
- closeDatabase,
946
- // Auto-purge control functions
947
- startAutoPurge: require_autoPurge().startAutoPurge,
948
- stopAutoPurge: require_autoPurge().stopAutoPurge,
949
- isAutoPurgeRunning: require_autoPurge().isAutoPurgeRunning
950
- };
951
- }
952
- });
953
-
954
660
  // src/payment/responseHandler.js
955
661
  var require_responseHandler = __commonJS({
956
662
  "src/payment/responseHandler.js"(exports2, module2) {
@@ -969,47 +675,49 @@ var require_responseHandler = __commonJS({
969
675
  } = require_parser();
970
676
  var fw = require_manageConfig();
971
677
  var { xorCalculation } = require_utils_helper();
972
- var { updateTransactionResponse } = require_transactionStore();
973
678
  var queue2 = new Queue();
974
679
  var paymentCloudUrl = null;
975
680
  var paymentOrderId = null;
681
+ var activePaymentEcn = null;
976
682
  module2.exports.setPaymentCloudUrl = (url) => {
977
683
  paymentCloudUrl = url;
978
684
  };
979
685
  module2.exports.setPaymentOrderId = (orderId) => {
980
686
  paymentOrderId = orderId;
981
687
  };
688
+ module2.exports.setPaymentEcn = (ecn) => {
689
+ activePaymentEcn = ecn;
690
+ };
982
691
  function sendPaymentResponseToCloud(translatedResponse) {
983
692
  const cloudUrl = paymentCloudUrl || fw.getConfigByKey("cloud_url");
984
- const isSimulation = fw.getConfigByKey("simulation");
985
- if (isSimulation && paymentOrderId && translatedResponse.retrievalReferenceNumber) {
986
- translatedResponse.retrievalReferenceNumber = paymentOrderId;
693
+ if (paymentOrderId && translatedResponse) {
694
+ translatedResponse.enhancedECRReferenceNumber = paymentOrderId;
987
695
  }
988
696
  paymentCloudUrl = null;
989
697
  paymentOrderId = null;
990
698
  if (!cloudUrl) {
991
- logger.log({ level: "warn", message: "?? cloud_url not configured, skipping payment response POST" });
699
+ logger.log({ level: "warn", message: "\u26A0\uFE0F cloud_url not configured, skipping payment response POST" });
992
700
  return;
993
701
  }
994
- const URl = `${cloudUrl}/payresponse/api/v1/nets/response`;
995
- console.log(JSON.stringify({
996
- netsRes: translatedResponse
997
- }));
998
- console.log(URl);
999
- axios.post(URl, {
702
+ const targetUrl = `${cloudUrl}/payresponse/api/v1/nets/response`;
703
+ logger.log({ level: "info", message: `\u{1F4E1} POSTing payment response to: ${targetUrl}` });
704
+ axios.post(targetUrl, {
1000
705
  netsRes: translatedResponse
1001
706
  }).then((res) => {
1002
- logger.log({ level: "info", message: `?? Payment response sent to cloud: ${res.status}` });
707
+ logger.log({ level: "info", message: `\u2705 Payment response sent to cloud: ${res.status}` });
1003
708
  }).catch((err) => {
1004
- logger.log({ level: "error", message: `? Failed to send payment response to cloud: ${err.message}` });
709
+ logger.log({ level: "error", message: `\u274C Failed to send payment response to cloud: ${err.message}` });
1005
710
  });
1006
711
  }
1007
- module2.exports.terminalStatus = (ecn) => {
1008
- const length = queue2.size();
1009
- let fullHex = "";
1010
- for (let i = 0; i < length; i++) {
1011
- const node = queue2.dequeue();
1012
- fullHex += node.data.hex;
712
+ module2.exports.terminalStatus = (ecn, hexValue = null) => {
713
+ let fullHex = hexValue;
714
+ if (!fullHex) {
715
+ const length = queue2.size();
716
+ fullHex = "";
717
+ for (let i = 0; i < length; i++) {
718
+ const node = queue2.dequeue();
719
+ fullHex += node.data.hex;
720
+ }
1013
721
  }
1014
722
  const status = statusParser(fullHex, ecn);
1015
723
  console.log(fullHex);
@@ -1017,18 +725,6 @@ var require_responseHandler = __commonJS({
1017
725
  level: "info",
1018
726
  message: status
1019
727
  });
1020
- try {
1021
- updateTransactionResponse(ecn, {
1022
- responseHex: fullHex,
1023
- responseData: { status },
1024
- status: status ? "COMPLETED" : "FAILED"
1025
- });
1026
- } catch (error) {
1027
- logger.log({
1028
- level: "error",
1029
- message: `Failed to update transaction: ${error.message}`
1030
- });
1031
- }
1032
728
  if (status) {
1033
729
  fw.updateConfig({ terminal_status: true });
1034
730
  sendMessage2("clientRoom", "STATUS_MESSAGE", {
@@ -1045,26 +741,17 @@ var require_responseHandler = __commonJS({
1045
741
  });
1046
742
  }
1047
743
  };
1048
- module2.exports.terminalLogon = (ecn) => {
1049
- const length = queue2.size();
1050
- let fullHex = "";
1051
- for (let i = 0; i < length; i++) {
1052
- const node = queue2.dequeue();
1053
- fullHex += node.data.hex;
744
+ module2.exports.terminalLogon = (ecn, hexValue = null) => {
745
+ let fullHex = hexValue;
746
+ if (!fullHex) {
747
+ const length = queue2.size();
748
+ fullHex = "";
749
+ for (let i = 0; i < length; i++) {
750
+ const node = queue2.dequeue();
751
+ fullHex += node.data.hex;
752
+ }
1054
753
  }
1055
754
  const response = logonParser(fullHex, ecn);
1056
- try {
1057
- updateTransactionResponse(ecn, {
1058
- responseHex: fullHex,
1059
- responseData: response,
1060
- status: response.status ? "COMPLETED" : "FAILED"
1061
- });
1062
- } catch (error) {
1063
- logger.log({
1064
- level: "error",
1065
- message: `Failed to update transaction: ${error.message}`
1066
- });
1067
- }
1068
755
  if (response.status) {
1069
756
  fw.updateConfig({ last_logon: /* @__PURE__ */ new Date() });
1070
757
  sendMessage2("clientRoom", "LOGON_MESSAGE", {
@@ -1089,25 +776,18 @@ var require_responseHandler = __commonJS({
1089
776
  }
1090
777
  return fullHex;
1091
778
  };
1092
- module2.exports.terminalPayment = (hexValue, ecn) => {
779
+ module2.exports.terminalPayment = (hexValue, ecnArg) => {
780
+ const ecn = activePaymentEcn || ecnArg;
1093
781
  const parsedValue = netsPaymentParser(hexValue, ecn);
1094
782
  const response = jsonProcessor(parsedValue);
1095
- try {
1096
- updateTransactionResponse(ecn, {
1097
- responseHex: hexValue,
1098
- responseData: response,
1099
- status: response.translated?.status || "UNKNOWN"
1100
- });
1101
- } catch (error) {
1102
- logger.log({
1103
- level: "error",
1104
- message: `Failed to update transaction: ${error.message}`
1105
- });
783
+ if (response.translated) {
784
+ activePaymentEcn = null;
1106
785
  }
1107
786
  if (response.translated) {
1108
787
  if (response.translated.status == "APPROVED") {
1109
788
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
1110
789
  success: true,
790
+ status: "SUCCESS",
1111
791
  response,
1112
792
  action: "COMPLETED"
1113
793
  });
@@ -1129,24 +809,17 @@ var require_responseHandler = __commonJS({
1129
809
  }
1130
810
  }
1131
811
  };
1132
- module2.exports.terminalCreditPayment = (hexValue, ecn) => {
812
+ module2.exports.terminalCreditPayment = (hexValue, ecnArg) => {
813
+ const ecn = activePaymentEcn || ecnArg;
1133
814
  const parsedValue = creditPaymentParser(hexValue, ecn);
1134
815
  const response = jsonProcessor(parsedValue);
1135
- try {
1136
- updateTransactionResponse(ecn, {
1137
- responseHex: hexValue,
1138
- responseData: response,
1139
- status: response.translated?.status || "UNKNOWN"
1140
- });
1141
- } catch (error) {
1142
- logger.log({
1143
- level: "error",
1144
- message: `Failed to update transaction: ${error.message}`
1145
- });
816
+ if (response.translated) {
817
+ activePaymentEcn = null;
1146
818
  }
1147
819
  if (response.translated) {
1148
820
  if (response.translated.status == "APPROVED") {
1149
821
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
822
+ success: true,
1150
823
  status: "SUCCESS",
1151
824
  response,
1152
825
  action: "COMPLETED"
@@ -1190,11 +863,12 @@ var require_responseHandler = __commonJS({
1190
863
  }
1191
864
  };
1192
865
  module2.exports.checkLrc = (hexValue) => {
866
+ if (!hexValue || hexValue.length < 4) return false;
1193
867
  const value = hexValue.substring(2, hexValue.length - 2);
1194
- const xor = xorCalculation(value);
1195
- const lrcCheck = hexValue.substring(hexValue.length - 2, hexValue.length);
1196
- console.log(`LRC Check: ${xor} === ${lrcCheck}`);
1197
- return xor == lrcCheck;
868
+ const xor = xorCalculation(value).toUpperCase();
869
+ const lrcCheck = hexValue.substring(hexValue.length - 2, hexValue.length).toUpperCase();
870
+ logger.info({ level: "info", message: `LRC Check: Calculated ${xor} vs Received ${lrcCheck}` });
871
+ return xor === lrcCheck;
1198
872
  };
1199
873
  }
1200
874
  });
@@ -1217,93 +891,47 @@ var require_communication = __commonJS({
1217
891
  terminalStatus,
1218
892
  fetchFullDetails,
1219
893
  checkLrc,
1220
- verifyParser
894
+ verifyParser,
895
+ setPaymentEcn
1221
896
  } = require_responseHandler();
1222
897
  var { sendMessage: sendMessage2 } = require_sendMessage();
1223
898
  var fw = require_manageConfig();
1224
- var { createTransaction } = require_transactionStore();
1225
899
  var { initialiseRequest } = require_httpRequest();
1226
900
  var requestType;
1227
901
  var calculated;
1228
- var isTimeOutset = false;
1229
902
  var MAX_COUNT = 3;
1230
903
  var WRONG_LRC_MAX_COUNT = 3;
1231
904
  var count;
1232
905
  var lrcCount;
1233
906
  var ackOrNack;
1234
907
  var requestPayload;
908
+ var ackTimeout = null;
1235
909
  var ACK = Buffer.alloc(1, 6, "hex");
1236
910
  var NACK = Buffer.alloc(1, 21, "hex");
911
+ var dataBuffer = Buffer.alloc(0);
1237
912
  var config = fw.getConfig();
1238
- var triggerTimer = () => {
1239
- if (requestType == "STATUS_CHECK") {
1240
- setTimeout(() => {
1241
- isTimeOutset = false;
1242
- terminalStatus(calculated.ecn);
1243
- }, 3e3);
1244
- }
1245
- if (requestType == "LOGON") {
1246
- setTimeout(() => {
1247
- isTimeOutset = false;
1248
- terminalLogon(calculated.ecn);
1249
- }, 5e3);
1250
- }
1251
- if (requestType == "PAYMENT") {
1252
- setTimeout(() => {
1253
- isTimeOutset = false;
1254
- lrcCount--;
1255
- if (lrcCount >= 0) {
1256
- const hexValue = fetchFullDetails();
1257
- const lrcResponse = checkLrc(hexValue);
1258
- if (!lrcResponse) {
1259
- logger.log({
1260
- level: "info",
1261
- message: `Terminal send response data with wrong LRC, POS expected to send NACK`
1262
- });
1263
- global.port.write(NACK);
1264
- } else {
1265
- logger.log({
1266
- level: "info",
1267
- message: `Terminal resend response data with correct LRC, POS expected to send ACK`
1268
- });
1269
- global.port.write(ACK);
1270
- terminalPayment(hexValue, calculated.ecn);
1271
- }
1272
- } else {
1273
- sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
1274
- success: true,
1275
- action: "APPROVED_FLAGGED"
1276
- });
1277
- }
1278
- }, 8e3);
1279
- }
1280
- if (requestType === "CREDIT_PAYMENT") {
1281
- setTimeout(() => {
1282
- isTimeOutset = false;
1283
- const hexValue = fetchFullDetails();
1284
- const lrcResponse = checkLrc(hexValue);
1285
- if (!lrcResponse) {
1286
- global.port.write(NACK);
1287
- } else {
1288
- global.port.write(ACK);
1289
- terminalCreditPayment(hexValue, calculated.ecn);
1290
- }
1291
- }, 8e3);
1292
- }
1293
- };
913
+ var lastPaymentReference = null;
914
+ var lastPaymentTime = 0;
1294
915
  module2.exports.reset = () => {
1295
916
  count = MAX_COUNT;
1296
917
  lrcCount = WRONG_LRC_MAX_COUNT;
1297
918
  ackOrNack = null;
919
+ dataBuffer = Buffer.alloc(0);
920
+ if (ackTimeout) {
921
+ clearTimeout(ackTimeout);
922
+ ackTimeout = null;
923
+ }
1298
924
  };
1299
925
  module2.exports.checkACKorNACK = () => {
1300
- setTimeout(() => {
926
+ if (ackTimeout) clearTimeout(ackTimeout);
927
+ ackTimeout = setTimeout(() => {
928
+ ackTimeout = null;
1301
929
  if (ackOrNack == null) {
1302
930
  count--;
1303
931
  if (count >= 0) {
1304
932
  logger.log({
1305
933
  level: "info",
1306
- message: `POS send Purchase command, Terminal doesn't send any reply`
934
+ message: `POS send Purchase command, Terminal doesn't send any reply. Retrying...`
1307
935
  });
1308
936
  resendData();
1309
937
  } else {
@@ -1313,7 +941,7 @@ var require_communication = __commonJS({
1313
941
  });
1314
942
  }
1315
943
  }
1316
- }, 2e3);
944
+ }, 4e3);
1317
945
  };
1318
946
  var resendData = () => {
1319
947
  if (requestType == "STATUS_CHECK") {
@@ -1329,64 +957,149 @@ var require_communication = __commonJS({
1329
957
  exports2.checkACKorNACK();
1330
958
  };
1331
959
  module2.exports.initialize = async (socketConnection = null) => {
1332
- if (socketConnection) {
1333
- global.io = socketConnection;
1334
- logger.log({
1335
- level: "info",
1336
- message: "Socket connection initialized"
1337
- });
1338
- } else {
1339
- logger.log({
1340
- level: "info",
1341
- message: "No socket connection provided, messages will be logged only"
1342
- });
1343
- }
1344
- if (!config.terminal || config.terminal === "enable") {
1345
- global.port = new SerialPort({
1346
- path: config.simulation ? config.simulationPort : config.com,
1347
- baudRate: 9600,
1348
- dataBits: 8,
1349
- stopBits: 1,
1350
- parity: "none"
1351
- });
1352
- global.port.on("open", () => {
960
+ try {
961
+ if (socketConnection) {
962
+ global.io = socketConnection;
963
+ }
964
+ if (global.io) {
1353
965
  logger.log({
1354
966
  level: "info",
1355
- message: `Port connected successfully on ${config.simulation ? config.simulationPort : config.com}`
967
+ message: "Socket connection available for terminal communication"
1356
968
  });
1357
- });
1358
- global.port.on("data", (data) => {
969
+ } else {
1359
970
  logger.log({
1360
- level: "info",
1361
- message: `Data received from terminal: ${data.toString("hex")}`
971
+ level: "warn",
972
+ message: "No socket connection (global.io) found. Messages will be logged only."
1362
973
  });
1363
- if (data.toString("hex").length == 2 && data.toString("hex") == "15") {
1364
- count--;
1365
- ackOrNack = data;
1366
- if (count >= 0) {
1367
- logger.log({
1368
- level: "info",
1369
- message: `POS send Purchase command, Terminal reply NACK`
1370
- });
1371
- resendData();
1372
- } else {
1373
- sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
1374
- success: true,
1375
- action: "TERMINAL_ERROR"
1376
- });
1377
- return;
1378
- }
1379
- } else if (data.toString("hex").length == 2 && data.toString("hex") == "06") {
1380
- ackOrNack = data;
1381
- } else if (data.length > 2) {
1382
- global.port.write(ACK);
1383
- if (!isTimeOutset) {
1384
- isTimeOutset = true;
1385
- triggerTimer();
974
+ }
975
+ if (!config.terminal || config.terminal === "enable") {
976
+ if (global.port) {
977
+ try {
978
+ if (global.port.isOpen) {
979
+ logger.log({ level: "info", message: "Closing existing serial port..." });
980
+ global.port.close();
981
+ }
982
+ global.port.removeAllListeners();
983
+ if (global.port.destroy) global.port.destroy();
984
+ } catch (e) {
985
+ logger.log({ level: "debug", message: `Error during port cleanup: ${e.message}` });
1386
986
  }
1387
- requestParser(data, requestType, calculated?.ecn);
987
+ global.port = null;
1388
988
  }
1389
- });
989
+ const portPath = config.simulation ? config.simulationPort : config.com;
990
+ const openPort = () => {
991
+ logger.log({ level: "info", message: `Initializing Serial Port on ${portPath}...` });
992
+ global.port = new SerialPort({
993
+ path: portPath,
994
+ baudRate: 9600,
995
+ dataBits: 8,
996
+ stopBits: 1,
997
+ parity: "none",
998
+ autoOpen: false,
999
+ lock: false,
1000
+ // Disable locking to prevent "Access Denied" or "Error 31" stuck files
1001
+ rtscts: false
1002
+ // Explicitly disable flow control
1003
+ });
1004
+ global.port.on("data", (data) => {
1005
+ logger.log({ level: "info", message: `Raw data: ${data.toString("hex")}` });
1006
+ dataBuffer = Buffer.concat([dataBuffer, data]);
1007
+ exports2.processIncomingData();
1008
+ });
1009
+ global.port.on("error", (err) => {
1010
+ logger.error({ level: "error", message: `SerialPort Error Event: ${err.message}` });
1011
+ });
1012
+ setTimeout(() => {
1013
+ global.port.open((err) => {
1014
+ if (err) {
1015
+ logger.error({ level: "error", message: `Error opening serial port ${portPath}: ${err.message}` });
1016
+ if (err.message.includes("31")) {
1017
+ logger.log({ level: "warn", message: "Error 31 detected. Attempting hardware reset retry in 2s..." });
1018
+ setTimeout(openPort, 2e3);
1019
+ }
1020
+ } else {
1021
+ logger.log({ level: "info", message: `Serial port ${portPath} opened successfully` });
1022
+ setTimeout(() => exports2.sentToTerminal("STATUS_CHECK"), 1e3);
1023
+ }
1024
+ });
1025
+ }, 1500);
1026
+ };
1027
+ openPort();
1028
+ }
1029
+ } catch (err) {
1030
+ console.error("Serial initialization error:", err.message);
1031
+ }
1032
+ };
1033
+ module2.exports.processIncomingData = () => {
1034
+ while (dataBuffer.length >= 3) {
1035
+ if (dataBuffer[0] === 6 || dataBuffer[0] === 21) {
1036
+ const byte = dataBuffer[0];
1037
+ ackOrNack = Buffer.from([byte]);
1038
+ dataBuffer = dataBuffer.slice(1);
1039
+ logger.log({ level: "info", message: `Terminal sent ${byte === 6 ? "ACK" : "NACK"} (0x${byte.toString(16)})` });
1040
+ if (byte === 21) {
1041
+ count--;
1042
+ if (count >= 0) resendData();
1043
+ else sendMessage2("clientRoom", "PAYMENT_MESSAGE", { success: true, action: "TERMINAL_ERROR" });
1044
+ }
1045
+ continue;
1046
+ }
1047
+ const stxIndex = dataBuffer.indexOf(2);
1048
+ if (stxIndex === -1) {
1049
+ dataBuffer = Buffer.alloc(0);
1050
+ break;
1051
+ }
1052
+ if (stxIndex > 0) {
1053
+ dataBuffer = dataBuffer.slice(stxIndex);
1054
+ }
1055
+ if (dataBuffer.length < 3) break;
1056
+ if (dataBuffer.length < 3) break;
1057
+ const lenBytes = dataBuffer.slice(1, 3).toString("hex");
1058
+ const bodyLen = parseInt(lenBytes, 10);
1059
+ const expectedFrameSize = 1 + 2 + bodyLen + 2;
1060
+ if (dataBuffer.length < expectedFrameSize) break;
1061
+ if (dataBuffer[3 + bodyLen] === 3) {
1062
+ const frame = dataBuffer.slice(0, expectedFrameSize);
1063
+ dataBuffer = dataBuffer.slice(expectedFrameSize);
1064
+ exports2.handleFullMessage(frame);
1065
+ } else {
1066
+ const etxIndex = dataBuffer.indexOf(3, 1);
1067
+ if (etxIndex !== -1 && dataBuffer.length > etxIndex + 1) {
1068
+ const frame = dataBuffer.slice(0, etxIndex + 2);
1069
+ dataBuffer = dataBuffer.slice(etxIndex + 2);
1070
+ exports2.handleFullMessage(frame);
1071
+ } else {
1072
+ if (dataBuffer.length > 1024) dataBuffer = Buffer.alloc(0);
1073
+ break;
1074
+ }
1075
+ }
1076
+ }
1077
+ };
1078
+ module2.exports.handleFullMessage = (fullFrame) => {
1079
+ const { checkLrc: checkLrc2, terminalStatus: terminalStatus2, terminalLogon: terminalLogon2, terminalPayment: terminalPayment2, terminalCreditPayment: terminalCreditPayment2 } = require_responseHandler();
1080
+ if (ackTimeout) {
1081
+ clearTimeout(ackTimeout);
1082
+ ackTimeout = null;
1083
+ }
1084
+ ackOrNack = Buffer.from([6]);
1085
+ const hexFrame = fullFrame.toString("hex").toUpperCase();
1086
+ logger.log({ level: "info", message: `Full frame processed: ${hexFrame}` });
1087
+ if (checkLrc2(hexFrame)) {
1088
+ logger.log({ level: "info", message: `LRC Correct. Sending ACK (0x06).` });
1089
+ global.port.write(ACK);
1090
+ logger.log({ level: "info", message: `requestType: ${requestType}` });
1091
+ if (requestType === "STATUS_CHECK") {
1092
+ terminalStatus2(calculated?.ecn, hexFrame);
1093
+ } else if (requestType === "LOGON") {
1094
+ terminalLogon2(calculated?.ecn, hexFrame);
1095
+ } else if (requestType === "PAYMENT") {
1096
+ terminalPayment2(hexFrame, calculated?.ecn);
1097
+ } else if (requestType === "CREDIT_PAYMENT") {
1098
+ terminalCreditPayment2(hexFrame, calculated?.ecn);
1099
+ }
1100
+ } else {
1101
+ logger.log({ level: "error", message: `LRC Incorrect. Sending NACK (0x15).` });
1102
+ global.port.write(NACK);
1390
1103
  }
1391
1104
  };
1392
1105
  module2.exports.sentToTerminal = (type, body) => {
@@ -1394,23 +1107,6 @@ var require_communication = __commonJS({
1394
1107
  requestPayload = body;
1395
1108
  if (requestType == "STATUS_CHECK") {
1396
1109
  calculated = generateStatusReq();
1397
- logger.log({
1398
- level: "info",
1399
- message: `Calculated STATUS_CHECK request: ${calculated.buffer.toString("hex")}`
1400
- });
1401
- try {
1402
- createTransaction({
1403
- ecn: calculated.ecn,
1404
- requestType,
1405
- requestPayload: null,
1406
- requestHex: calculated.buffer.toString("hex")
1407
- });
1408
- } catch (error) {
1409
- logger.log({
1410
- level: "error",
1411
- message: `Failed to store transaction: ${error.message}`
1412
- });
1413
- }
1414
1110
  sendMessage2("clientRoom", "STATUS_MESSAGE", {
1415
1111
  status: "INITIATED",
1416
1112
  action: "STATUS_CHECK_INITIATED"
@@ -1419,23 +1115,6 @@ var require_communication = __commonJS({
1419
1115
  }
1420
1116
  if (requestType == "LOGON") {
1421
1117
  calculated = generateLogonRequest();
1422
- logger.log({
1423
- level: "info",
1424
- message: `Calculated LOGON request: ${calculated.buffer.toString("hex")}`
1425
- });
1426
- try {
1427
- createTransaction({
1428
- ecn: calculated.ecn,
1429
- requestType,
1430
- requestPayload: null,
1431
- requestHex: calculated.buffer.toString("hex")
1432
- });
1433
- } catch (error) {
1434
- logger.log({
1435
- level: "error",
1436
- message: `Failed to store transaction: ${error.message}`
1437
- });
1438
- }
1439
1118
  sendMessage2("clientRoom", "LOGON_MESSAGE", {
1440
1119
  status: "INITIATED",
1441
1120
  action: "LOGON_INITIATED"
@@ -1443,29 +1122,30 @@ var require_communication = __commonJS({
1443
1122
  global.port.write(calculated.buffer);
1444
1123
  }
1445
1124
  if (requestType == "PAYMENT" || requestType == "CREDIT_PAYMENT") {
1125
+ if (body && body.reference) {
1126
+ const now = Date.now();
1127
+ if (body.reference === lastPaymentReference && now - lastPaymentTime < 1e4) {
1128
+ logger.log({
1129
+ level: "warn",
1130
+ message: `Duplicate payment request received for reference ${body.reference} within 10s. Ignored.`
1131
+ });
1132
+ return;
1133
+ }
1134
+ lastPaymentReference = body.reference;
1135
+ lastPaymentTime = now;
1136
+ }
1446
1137
  exports2.reset();
1138
+ const { setPaymentCloudUrl, setPaymentOrderId } = require_responseHandler();
1139
+ if (body && body.cloud_url) setPaymentCloudUrl(body.cloud_url);
1140
+ if (body && (body.reference || body.orderId)) setPaymentOrderId(body.reference || body.orderId);
1447
1141
  calculated = generatePaymentRequest(body);
1448
- logger.log({
1449
- level: "info",
1450
- message: `Calculated payment request: ${calculated.hexString || calculated.buffer && calculated.buffer.toString("hex")}`
1451
- });
1452
- try {
1453
- createTransaction({
1454
- ecn: calculated.ecn,
1455
- requestType,
1456
- requestPayload: body,
1457
- requestHex: calculated.hexString
1458
- });
1459
- } catch (error) {
1460
- logger.log({
1461
- level: "error",
1462
- message: `Failed to store transaction: ${error.message}`
1463
- });
1464
- }
1142
+ console.log(calculated);
1465
1143
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
1466
1144
  status: "INITIATED",
1467
- action: "PAYMENT_INITIATED"
1145
+ action: "PAYMENT_INITIATED",
1146
+ orderId: body.reference || body.orderId
1468
1147
  });
1148
+ if (calculated && calculated.ecn) setPaymentEcn(calculated.ecn);
1469
1149
  global.port.write(calculated.buffer);
1470
1150
  exports2.checkACKorNACK();
1471
1151
  }
@@ -1473,142 +1153,6 @@ var require_communication = __commonJS({
1473
1153
  }
1474
1154
  });
1475
1155
 
1476
- // src/db/listTransactions.js
1477
- var require_listTransactions = __commonJS({
1478
- "src/db/listTransactions.js"(exports2, module2) {
1479
- var { getRecentTransactions, getTransactionByEcn, getTransactionsByStatus } = require_transactionStore();
1480
- var logger = require_winston()(module2);
1481
- function listRecentTransactions(limit = 10) {
1482
- try {
1483
- const transactions = getRecentTransactions(limit);
1484
- if (transactions.length === 0) {
1485
- console.log("No transactions found in database.");
1486
- return [];
1487
- }
1488
- console.log(`
1489
- \u{1F4CA} Recent ${transactions.length} Transaction(s)
1490
- `);
1491
- console.log("=".repeat(80));
1492
- transactions.forEach((txn, index) => {
1493
- const time = new Date(txn.request_timestamp).toLocaleString();
1494
- const duration = txn.response_timestamp ? `${txn.response_timestamp - txn.request_timestamp}ms` : "Pending";
1495
- console.log(`
1496
- ${index + 1}. ECN: ${txn.ecn}`);
1497
- console.log(` Type: ${txn.request_type}`);
1498
- console.log(` Status: ${txn.status}`);
1499
- console.log(` Time: ${time}`);
1500
- console.log(` Duration: ${duration}`);
1501
- if (txn.request_type === "PAYMENT" || txn.request_type === "CREDIT_PAYMENT") {
1502
- const amount = txn.request_payload.amount ? `$${(txn.request_payload.amount / 100).toFixed(2)}` : "N/A";
1503
- console.log(` Amount: ${amount}`);
1504
- console.log(` Reference: ${txn.request_payload.reference || "N/A"}`);
1505
- }
1506
- });
1507
- console.log("\n" + "=".repeat(80) + "\n");
1508
- return transactions;
1509
- } catch (error) {
1510
- if (error.code === "SQLITE_CANTOPEN") {
1511
- console.log("\u2139\uFE0F Database not found. No transactions have been created yet.");
1512
- console.log(" Transactions will be automatically stored when you send requests to the terminal.\n");
1513
- return [];
1514
- }
1515
- logger.log({
1516
- level: "error",
1517
- message: `Failed to list transactions: ${error.message}`
1518
- });
1519
- throw error;
1520
- }
1521
- }
1522
- function listTransactionsByStatus(status, limit = 50) {
1523
- try {
1524
- const transactions = getTransactionsByStatus(status, limit);
1525
- if (transactions.length === 0) {
1526
- console.log(`No ${status} transactions found.`);
1527
- return [];
1528
- }
1529
- console.log(`
1530
- \u{1F4CA} ${status} Transactions (${transactions.length})
1531
- `);
1532
- console.log("=".repeat(80));
1533
- transactions.forEach((txn, index) => {
1534
- const time = new Date(txn.request_timestamp).toLocaleString();
1535
- console.log(`
1536
- ${index + 1}. ECN: ${txn.ecn} | ${txn.request_type} | ${time}`);
1537
- });
1538
- console.log("\n" + "=".repeat(80) + "\n");
1539
- return transactions;
1540
- } catch (error) {
1541
- if (error.code === "SQLITE_CANTOPEN") {
1542
- console.log("\u2139\uFE0F Database not found. No transactions have been created yet.\n");
1543
- return [];
1544
- }
1545
- logger.log({
1546
- level: "error",
1547
- message: `Failed to list transactions: ${error.message}`
1548
- });
1549
- throw error;
1550
- }
1551
- }
1552
- function getTransactionDetails(ecn) {
1553
- try {
1554
- const transaction = getTransactionByEcn(ecn);
1555
- if (!transaction) {
1556
- console.log(`Transaction with ECN ${ecn} not found.`);
1557
- return null;
1558
- }
1559
- console.log(`
1560
- \u{1F4C4} Transaction Details: ${ecn}
1561
- `);
1562
- console.log("=".repeat(80));
1563
- console.log(`Type: ${transaction.request_type}`);
1564
- console.log(`Status: ${transaction.status}`);
1565
- console.log(`Request Time: ${new Date(transaction.request_timestamp).toLocaleString()}`);
1566
- if (transaction.response_timestamp) {
1567
- console.log(`Response Time: ${new Date(transaction.response_timestamp).toLocaleString()}`);
1568
- console.log(`Duration: ${transaction.response_timestamp - transaction.request_timestamp}ms`);
1569
- }
1570
- console.log(`
1571
- Request Payload:`);
1572
- console.log(JSON.stringify(transaction.request_payload, null, 2));
1573
- if (transaction.response_data && Object.keys(transaction.response_data).length > 0) {
1574
- console.log(`
1575
- Response Data:`);
1576
- console.log(JSON.stringify(transaction.response_data, null, 2));
1577
- }
1578
- console.log("\n" + "=".repeat(80) + "\n");
1579
- return transaction;
1580
- } catch (error) {
1581
- if (error.code === "SQLITE_CANTOPEN") {
1582
- console.log("\u2139\uFE0F Database not found. No transactions have been created yet.\n");
1583
- return null;
1584
- }
1585
- logger.log({
1586
- level: "error",
1587
- message: `Failed to get transaction: ${error.message}`
1588
- });
1589
- throw error;
1590
- }
1591
- }
1592
- module2.exports = {
1593
- listRecentTransactions,
1594
- listTransactionsByStatus,
1595
- getTransactionDetails
1596
- };
1597
- if (require.main === module2) {
1598
- const args = process.argv.slice(2);
1599
- if (args[0] === "--status" && args[1]) {
1600
- const limit = args[2] ? parseInt(args[2]) : 50;
1601
- listTransactionsByStatus(args[1], limit);
1602
- } else if (args[0] === "--ecn" && args[1]) {
1603
- getTransactionDetails(args[1]);
1604
- } else {
1605
- const limit = args[0] ? parseInt(args[0]) : 10;
1606
- listRecentTransactions(limit);
1607
- }
1608
- }
1609
- }
1610
- });
1611
-
1612
1156
  // src/index.js
1613
1157
  var utilsHelper = require_utils_helper();
1614
1158
  var winston = require_winston();
@@ -1620,8 +1164,6 @@ var hexRequest = require_hexRequest();
1620
1164
  var httpRequest = require_httpRequest();
1621
1165
  var parser = require_parser();
1622
1166
  var responseHandler = require_responseHandler();
1623
- var transactionStore = require_transactionStore();
1624
- var listTransactions = require_listTransactions();
1625
1167
  module.exports = {
1626
1168
  ...utilsHelper,
1627
1169
  logger: winston,
@@ -1632,18 +1174,5 @@ module.exports = {
1632
1174
  hexRequest,
1633
1175
  httpRequest,
1634
1176
  parser,
1635
- responseHandler,
1636
- // Transaction store utilities
1637
- getTransactionByEcn: transactionStore.getTransactionByEcn,
1638
- getRecentTransactions: transactionStore.getRecentTransactions,
1639
- getTransactionsByStatus: transactionStore.getTransactionsByStatus,
1640
- cleanupOldTransactions: transactionStore.cleanupOldTransactions,
1641
- // Auto-purge control
1642
- startAutoPurge: transactionStore.startAutoPurge,
1643
- stopAutoPurge: transactionStore.stopAutoPurge,
1644
- isAutoPurgeRunning: transactionStore.isAutoPurgeRunning,
1645
- // Transaction listing functions
1646
- listRecentTransactions: listTransactions.listRecentTransactions,
1647
- listTransactionsByStatus: listTransactions.listTransactionsByStatus,
1648
- getTransactionDetails: listTransactions.getTransactionDetails
1177
+ responseHandler
1649
1178
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nets-service-sdk",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Utility functions for Nets Service",
5
5
  "source": "src/index.js",
6
6
  "main": "dist/index.js",
@@ -11,7 +11,16 @@
11
11
  "watch": "microbundle watch",
12
12
  "test": "echo \"Error: no test specified\" && exit 1"
13
13
  },
14
- "keywords": ["nets", "payment", "terminal", "pos", "sqlite", "transaction", "serial", "payment-gateway"],
14
+ "keywords": [
15
+ "nets",
16
+ "payment",
17
+ "terminal",
18
+ "pos",
19
+ "sqlite",
20
+ "transaction",
21
+ "serial",
22
+ "payment-gateway"
23
+ ],
15
24
  "author": "dineshhv",
16
25
  "license": "ISC",
17
26
  "files": [
@@ -32,4 +41,4 @@
32
41
  "esbuild": "^0.27.0",
33
42
  "microbundle": "^0.15.1"
34
43
  }
35
- }
44
+ }