nets-service-sdk 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +78 -2
  2. package/dist/index.js +397 -1
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # NETS Service SDK
2
2
 
3
- A utility library for integrating with NETS payment terminals and handling related operations. This SDK provides tools for serial communication, message parsing, configuration management, and common utility functions.
3
+ A utility library for integrating with NETS payment terminals and handling related operations. This SDK provides tools for serial communication, message parsing, configuration management, transaction storage, and common utility functions.
4
+
5
+ ## Features
6
+
7
+ - 🔌 Serial communication with NETS payment terminals
8
+ - 💾 SQLite-based transaction storage with automatic purging
9
+ - 📝 Comprehensive logging with Winston
10
+ - 🔧 Configuration management
11
+ - 🛠️ Utility functions for payment processing
4
12
 
5
13
  ## Installation
6
14
 
@@ -53,10 +61,67 @@ communication.sentToTerminal('STATUS_CHECK', {});
53
61
  // For Payment
54
62
  communication.sentToTerminal('PAYMENT', {
55
63
  amount: 1000, // Amount in cents
56
- reference: 'ORDER123'
64
+ reference: 'ORDER123',
65
+ ecn: '040126003000'
57
66
  });
58
67
  ```
59
68
 
69
+ ### Transaction Storage (New in v1.1.0)
70
+
71
+ All terminal requests and responses are automatically stored in a SQLite database.
72
+
73
+ ```javascript
74
+ const sdk = require('nets-service-sdk');
75
+
76
+ // Query specific transaction by ECN
77
+ const transaction = sdk.getTransactionByEcn('040126003000');
78
+ console.log(transaction);
79
+ // {
80
+ // ecn: '040126003000',
81
+ // request_type: 'PAYMENT',
82
+ // request_payload: { amount: 10.50, reference: 'ORDER123' },
83
+ // response_data: { status: 'APPROVED', ... },
84
+ // status: 'APPROVED',
85
+ // ...
86
+ // }
87
+
88
+ // Get recent transactions
89
+ const recent = sdk.getRecentTransactions(50);
90
+
91
+ // Query by status
92
+ const approved = sdk.getTransactionsByStatus('APPROVED', 100);
93
+ const pending = sdk.getTransactionsByStatus('PENDING', 100);
94
+
95
+ // Manual cleanup (delete old transactions)
96
+ const deleted = sdk.cleanupOldTransactions(90); // Keep last 90 days
97
+ ```
98
+
99
+ ### Automatic Data Purging (New in v1.1.0)
100
+
101
+ The SDK automatically purges old transaction data weekly.
102
+
103
+ ```javascript
104
+ const sdk = require('nets-service-sdk');
105
+
106
+ // Auto-purge is enabled by default
107
+ // Default: Every Sunday at 2 AM, keeps last 7 days
108
+
109
+ // Start with custom settings
110
+ sdk.startAutoPurge(14, '0 3 * * *'); // Keep 14 days, run daily at 3 AM
111
+
112
+ // Stop auto-purge
113
+ sdk.stopAutoPurge();
114
+
115
+ // Check if auto-purge is running
116
+ const isRunning = sdk.isAutoPurgeRunning();
117
+ ```
118
+
119
+ **Cron Schedule Examples:**
120
+ - `'0 2 * * 0'` - Every Sunday at 2 AM (default)
121
+ - `'0 3 * * *'` - Every day at 3 AM
122
+ - `'0 */6 * * *'` - Every 6 hours
123
+ - `'0 0 1 * *'` - First day of every month
124
+
60
125
  ### Utility Functions
61
126
 
62
127
  The SDK exports various utility functions directly.
@@ -96,6 +161,17 @@ myLogger.error('This is an error message');
96
161
  - **sendMessage**: Helper for sending messages (e.g., to socket rooms).
97
162
  - **hexRequest**: Generates HEX requests for the terminal.
98
163
  - **parser**: Parses responses from the terminal.
164
+ - **Transaction Storage**: SQLite-based storage for request/response tracking.
165
+ - **Auto-Purge**: Automatic cleanup of old transaction data.
166
+
167
+ ## Database
168
+
169
+ The SDK creates a `transactions.db` SQLite database in your project root to store all terminal transactions. This provides:
170
+
171
+ - Complete audit trail of all requests and responses
172
+ - Debugging capabilities with full hex data
173
+ - Transaction reconciliation
174
+ - Automatic weekly cleanup (configurable)
99
175
 
100
176
  ## License
101
177
 
package/dist/index.js CHANGED
@@ -631,6 +631,303 @@ var require_parser = __commonJS({
631
631
  }
632
632
  });
633
633
 
634
+ // src/db/autoPurge.js
635
+ var require_autoPurge = __commonJS({
636
+ "src/db/autoPurge.js"(exports2, module2) {
637
+ var cron = require("node-cron");
638
+ var logger = require_winston()(module2);
639
+ var { cleanupOldTransactions } = require_transactionStore();
640
+ var scheduledTask = null;
641
+ function startAutoPurge(daysToKeep = 7, cronSchedule = "0 2 * * 0") {
642
+ if (scheduledTask) {
643
+ logger.log({
644
+ level: "warn",
645
+ message: "Auto-purge already running. Stopping existing task first."
646
+ });
647
+ stopAutoPurge();
648
+ }
649
+ scheduledTask = cron.schedule(cronSchedule, () => {
650
+ logger.log({
651
+ level: "info",
652
+ message: `Running scheduled transaction cleanup (keeping last ${daysToKeep} days)`
653
+ });
654
+ try {
655
+ const deletedCount = cleanupOldTransactions(daysToKeep);
656
+ logger.log({
657
+ level: "info",
658
+ message: `Auto-purge completed: Deleted ${deletedCount} old transactions`
659
+ });
660
+ } catch (error) {
661
+ logger.log({
662
+ level: "error",
663
+ message: `Auto-purge failed: ${error.message}`
664
+ });
665
+ }
666
+ });
667
+ logger.log({
668
+ level: "info",
669
+ message: `Auto-purge scheduled: ${cronSchedule} (keeping last ${daysToKeep} days)`
670
+ });
671
+ return scheduledTask;
672
+ }
673
+ function stopAutoPurge() {
674
+ if (scheduledTask) {
675
+ scheduledTask.stop();
676
+ scheduledTask = null;
677
+ logger.log({
678
+ level: "info",
679
+ message: "Auto-purge stopped"
680
+ });
681
+ }
682
+ }
683
+ function isAutoPurgeRunning() {
684
+ return scheduledTask !== null;
685
+ }
686
+ module2.exports = {
687
+ startAutoPurge,
688
+ stopAutoPurge,
689
+ isAutoPurgeRunning
690
+ };
691
+ }
692
+ });
693
+
694
+ // src/db/transactionStore.js
695
+ var require_transactionStore = __commonJS({
696
+ "src/db/transactionStore.js"(exports2, module2) {
697
+ var Database = require("better-sqlite3");
698
+ var path = require("path");
699
+ var fs = require("fs");
700
+ var logger = require_winston()(module2);
701
+ var db = null;
702
+ function initDatabase(dbPath, autoPurgeConfig = {}) {
703
+ try {
704
+ const defaultPath = path.join(__dirname, "../../transactions.db");
705
+ const finalPath = dbPath || defaultPath;
706
+ const dir = path.dirname(finalPath);
707
+ if (!fs.existsSync(dir)) {
708
+ fs.mkdirSync(dir, { recursive: true });
709
+ }
710
+ db = new Database(finalPath);
711
+ db.pragma("journal_mode = WAL");
712
+ const createTableSQL = `
713
+ CREATE TABLE IF NOT EXISTS transactions (
714
+ ecn TEXT PRIMARY KEY,
715
+ request_type TEXT NOT NULL,
716
+ request_payload TEXT,
717
+ request_hex TEXT,
718
+ request_timestamp INTEGER NOT NULL,
719
+ response_hex TEXT,
720
+ response_data TEXT,
721
+ response_timestamp INTEGER,
722
+ status TEXT DEFAULT 'PENDING',
723
+ created_at INTEGER NOT NULL,
724
+ updated_at INTEGER NOT NULL
725
+ )
726
+ `;
727
+ db.exec(createTableSQL);
728
+ db.exec("CREATE INDEX IF NOT EXISTS idx_request_timestamp ON transactions(request_timestamp DESC)");
729
+ db.exec("CREATE INDEX IF NOT EXISTS idx_status ON transactions(status)");
730
+ logger.log({
731
+ level: "info",
732
+ message: `Transaction database initialized at ${finalPath}`
733
+ });
734
+ const { enabled = true, daysToKeep = 7, schedule = "0 2 * * 0" } = autoPurgeConfig;
735
+ if (enabled) {
736
+ const autoPurge = require_autoPurge();
737
+ autoPurge.startAutoPurge(daysToKeep, schedule);
738
+ }
739
+ return db;
740
+ } catch (error) {
741
+ logger.log({
742
+ level: "error",
743
+ message: `Failed to initialize database: ${error.message}`
744
+ });
745
+ throw error;
746
+ }
747
+ }
748
+ function getDatabase() {
749
+ if (!db) {
750
+ initDatabase();
751
+ }
752
+ return db;
753
+ }
754
+ function createTransaction(data) {
755
+ try {
756
+ const database = getDatabase();
757
+ const now = Date.now();
758
+ const stmt = database.prepare(`
759
+ INSERT INTO transactions (
760
+ ecn, request_type, request_payload, request_hex,
761
+ request_timestamp, status, created_at, updated_at
762
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
763
+ `);
764
+ const result = stmt.run(
765
+ data.ecn,
766
+ data.requestType,
767
+ JSON.stringify(data.requestPayload || {}),
768
+ data.requestHex,
769
+ now,
770
+ "PENDING",
771
+ now,
772
+ now
773
+ );
774
+ logger.log({
775
+ level: "info",
776
+ message: `Transaction created: ECN=${data.ecn}, Type=${data.requestType}`
777
+ });
778
+ return result;
779
+ } catch (error) {
780
+ logger.log({
781
+ level: "error",
782
+ message: `Failed to create transaction: ${error.message}`
783
+ });
784
+ throw error;
785
+ }
786
+ }
787
+ function updateTransactionResponse(ecn, data) {
788
+ try {
789
+ const database = getDatabase();
790
+ const now = Date.now();
791
+ const stmt = database.prepare(`
792
+ UPDATE transactions
793
+ SET response_hex = ?,
794
+ response_data = ?,
795
+ response_timestamp = ?,
796
+ status = ?,
797
+ updated_at = ?
798
+ WHERE ecn = ?
799
+ `);
800
+ const result = stmt.run(
801
+ data.responseHex,
802
+ JSON.stringify(data.responseData || {}),
803
+ now,
804
+ data.status || "COMPLETED",
805
+ now,
806
+ ecn
807
+ );
808
+ logger.log({
809
+ level: "info",
810
+ message: `Transaction updated: ECN=${ecn}, Status=${data.status || "COMPLETED"}`
811
+ });
812
+ return result;
813
+ } catch (error) {
814
+ logger.log({
815
+ level: "error",
816
+ message: `Failed to update transaction: ${error.message}`
817
+ });
818
+ throw error;
819
+ }
820
+ }
821
+ function getTransactionByEcn(ecn) {
822
+ try {
823
+ const database = getDatabase();
824
+ const stmt = database.prepare("SELECT * FROM transactions WHERE ecn = ?");
825
+ const row = stmt.get(ecn);
826
+ if (row) {
827
+ row.request_payload = JSON.parse(row.request_payload || "{}");
828
+ row.response_data = JSON.parse(row.response_data || "{}");
829
+ }
830
+ return row;
831
+ } catch (error) {
832
+ logger.log({
833
+ level: "error",
834
+ message: `Failed to get transaction: ${error.message}`
835
+ });
836
+ return null;
837
+ }
838
+ }
839
+ function getRecentTransactions(limit = 100) {
840
+ try {
841
+ const database = getDatabase();
842
+ const stmt = database.prepare(`
843
+ SELECT * FROM transactions
844
+ ORDER BY request_timestamp DESC
845
+ LIMIT ?
846
+ `);
847
+ const rows = stmt.all(limit);
848
+ return rows.map((row) => ({
849
+ ...row,
850
+ request_payload: JSON.parse(row.request_payload || "{}"),
851
+ response_data: JSON.parse(row.response_data || "{}")
852
+ }));
853
+ } catch (error) {
854
+ logger.log({
855
+ level: "error",
856
+ message: `Failed to get recent transactions: ${error.message}`
857
+ });
858
+ return [];
859
+ }
860
+ }
861
+ function getTransactionsByStatus(status, limit = 100) {
862
+ try {
863
+ const database = getDatabase();
864
+ const stmt = database.prepare(`
865
+ SELECT * FROM transactions
866
+ WHERE status = ?
867
+ ORDER BY request_timestamp DESC
868
+ LIMIT ?
869
+ `);
870
+ const rows = stmt.all(status, 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 transactions by status: ${error.message}`
880
+ });
881
+ return [];
882
+ }
883
+ }
884
+ function cleanupOldTransactions(daysToKeep = 90) {
885
+ try {
886
+ const database = getDatabase();
887
+ const cutoffTime = Date.now() - daysToKeep * 24 * 60 * 60 * 1e3;
888
+ const stmt = database.prepare("DELETE FROM transactions WHERE request_timestamp < ?");
889
+ const result = stmt.run(cutoffTime);
890
+ logger.log({
891
+ level: "info",
892
+ message: `Cleaned up ${result.changes} old transactions (older than ${daysToKeep} days)`
893
+ });
894
+ return result.changes;
895
+ } catch (error) {
896
+ logger.log({
897
+ level: "error",
898
+ message: `Failed to cleanup old transactions: ${error.message}`
899
+ });
900
+ return 0;
901
+ }
902
+ }
903
+ function closeDatabase() {
904
+ if (db) {
905
+ db.close();
906
+ db = null;
907
+ logger.log({
908
+ level: "info",
909
+ message: "Database connection closed"
910
+ });
911
+ }
912
+ }
913
+ module2.exports = {
914
+ initDatabase,
915
+ getDatabase,
916
+ createTransaction,
917
+ updateTransactionResponse,
918
+ getTransactionByEcn,
919
+ getRecentTransactions,
920
+ getTransactionsByStatus,
921
+ cleanupOldTransactions,
922
+ closeDatabase,
923
+ // Auto-purge control functions
924
+ startAutoPurge: require_autoPurge().startAutoPurge,
925
+ stopAutoPurge: require_autoPurge().stopAutoPurge,
926
+ isAutoPurgeRunning: require_autoPurge().isAutoPurgeRunning
927
+ };
928
+ }
929
+ });
930
+
634
931
  // src/payment/responseHandler.js
635
932
  var require_responseHandler = __commonJS({
636
933
  "src/payment/responseHandler.js"(exports2, module2) {
@@ -648,6 +945,7 @@ var require_responseHandler = __commonJS({
648
945
  } = require_parser();
649
946
  var fw = require_manageConfig();
650
947
  var { xorCalculation } = require_utils_helper();
948
+ var { updateTransactionResponse } = require_transactionStore();
651
949
  var queue2 = new Queue();
652
950
  module2.exports.terminalStatus = (ecn) => {
653
951
  const length = queue2.size();
@@ -662,6 +960,18 @@ var require_responseHandler = __commonJS({
662
960
  level: "info",
663
961
  message: status
664
962
  });
963
+ try {
964
+ updateTransactionResponse(ecn, {
965
+ responseHex: fullHex,
966
+ responseData: { status },
967
+ status: status ? "COMPLETED" : "FAILED"
968
+ });
969
+ } catch (error) {
970
+ logger.log({
971
+ level: "error",
972
+ message: `Failed to update transaction: ${error.message}`
973
+ });
974
+ }
665
975
  if (status) {
666
976
  fw.updateConfig({ terminal_status: true });
667
977
  sendMessage2("clientRoom", "STATUS_MESSAGE", {
@@ -687,6 +997,18 @@ var require_responseHandler = __commonJS({
687
997
  }
688
998
  console.log(fullHex);
689
999
  const response = logonParser(fullHex, ecn);
1000
+ try {
1001
+ updateTransactionResponse(ecn, {
1002
+ responseHex: fullHex,
1003
+ responseData: response,
1004
+ status: response.status ? "COMPLETED" : "FAILED"
1005
+ });
1006
+ } catch (error) {
1007
+ logger.log({
1008
+ level: "error",
1009
+ message: `Failed to update transaction: ${error.message}`
1010
+ });
1011
+ }
690
1012
  if (response.status) {
691
1013
  fw.updateConfig({ last_logon: /* @__PURE__ */ new Date() });
692
1014
  sendMessage2("clientRoom", "LOGON_MESSAGE", {
@@ -714,6 +1036,18 @@ var require_responseHandler = __commonJS({
714
1036
  module2.exports.terminalPayment = (hexValue, ecn) => {
715
1037
  const parsedValue = netsPaymentParser(hexValue, ecn);
716
1038
  const response = jsonProcessor(parsedValue);
1039
+ try {
1040
+ updateTransactionResponse(ecn, {
1041
+ responseHex: hexValue,
1042
+ responseData: response,
1043
+ status: response.translated?.status || "UNKNOWN"
1044
+ });
1045
+ } catch (error) {
1046
+ logger.log({
1047
+ level: "error",
1048
+ message: `Failed to update transaction: ${error.message}`
1049
+ });
1050
+ }
717
1051
  if (response.translated) {
718
1052
  if (response.translated.status == "APPROVED") {
719
1053
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
@@ -741,6 +1075,18 @@ var require_responseHandler = __commonJS({
741
1075
  module2.exports.terminalCreditPayment = (hexValue, ecn) => {
742
1076
  const parsedValue = creditPaymentParser(hexValue, ecn);
743
1077
  const response = jsonProcessor(parsedValue);
1078
+ try {
1079
+ updateTransactionResponse(ecn, {
1080
+ responseHex: hexValue,
1081
+ responseData: response,
1082
+ status: response.translated?.status || "UNKNOWN"
1083
+ });
1084
+ } catch (error) {
1085
+ logger.log({
1086
+ level: "error",
1087
+ message: `Failed to update transaction: ${error.message}`
1088
+ });
1089
+ }
744
1090
  if (response.translated) {
745
1091
  if (response.translated.status == "APPROVED") {
746
1092
  sendMessage2("clientRoom", "PAYMENT_MESSAGE", {
@@ -817,6 +1163,7 @@ var require_communication = __commonJS({
817
1163
  } = require_responseHandler();
818
1164
  var { sendMessage: sendMessage2 } = require_sendMessage();
819
1165
  var fw = require_manageConfig();
1166
+ var { createTransaction } = require_transactionStore();
820
1167
  var { initialiseRequest } = require_httpRequest();
821
1168
  var requestType;
822
1169
  var calculated;
@@ -968,16 +1315,55 @@ var require_communication = __commonJS({
968
1315
  requestPayload = body;
969
1316
  if (requestType == "STATUS_CHECK") {
970
1317
  calculated = generateStatusReq();
1318
+ try {
1319
+ createTransaction({
1320
+ ecn: calculated.ecn,
1321
+ requestType,
1322
+ requestPayload: null,
1323
+ requestHex: calculated.buffer.toString("hex")
1324
+ });
1325
+ } catch (error) {
1326
+ logger.log({
1327
+ level: "error",
1328
+ message: `Failed to store transaction: ${error.message}`
1329
+ });
1330
+ }
971
1331
  global.port.write(calculated.buffer);
972
1332
  }
973
1333
  if (requestType == "LOGON") {
974
1334
  calculated = generateLogonRequest();
1335
+ try {
1336
+ createTransaction({
1337
+ ecn: calculated.ecn,
1338
+ requestType,
1339
+ requestPayload: null,
1340
+ requestHex: calculated.buffer.toString("hex")
1341
+ });
1342
+ } catch (error) {
1343
+ logger.log({
1344
+ level: "error",
1345
+ message: `Failed to store transaction: ${error.message}`
1346
+ });
1347
+ }
975
1348
  global.port.write(calculated.buffer);
976
1349
  }
977
1350
  if (requestType == "PAYMENT" || requestType == "CREDIT_PAYMENT") {
978
1351
  exports2.reset();
979
1352
  calculated = generatePaymentRequest(body);
980
1353
  console.log(calculated);
1354
+ try {
1355
+ createTransaction({
1356
+ ecn: calculated.ecn,
1357
+ requestType,
1358
+ requestPayload: body,
1359
+ requestHex: calculated.hexString
1360
+ });
1361
+ } catch (error) {
1362
+ logger.log({
1363
+ level: "error",
1364
+ message: `Failed to store transaction: ${error.message}`
1365
+ });
1366
+ }
981
1367
  global.port.write(calculated.buffer);
982
1368
  exports2.checkACKorNACK();
983
1369
  }
@@ -996,6 +1382,7 @@ var hexRequest = require_hexRequest();
996
1382
  var httpRequest = require_httpRequest();
997
1383
  var parser = require_parser();
998
1384
  var responseHandler = require_responseHandler();
1385
+ var transactionStore = require_transactionStore();
999
1386
  module.exports = {
1000
1387
  ...utilsHelper,
1001
1388
  logger: winston,
@@ -1006,5 +1393,14 @@ module.exports = {
1006
1393
  hexRequest,
1007
1394
  httpRequest,
1008
1395
  parser,
1009
- responseHandler
1396
+ responseHandler,
1397
+ // Transaction store utilities
1398
+ getTransactionByEcn: transactionStore.getTransactionByEcn,
1399
+ getRecentTransactions: transactionStore.getRecentTransactions,
1400
+ getTransactionsByStatus: transactionStore.getTransactionsByStatus,
1401
+ cleanupOldTransactions: transactionStore.cleanupOldTransactions,
1402
+ // Auto-purge control
1403
+ startAutoPurge: transactionStore.startAutoPurge,
1404
+ stopAutoPurge: transactionStore.stopAutoPurge,
1405
+ isAutoPurgeRunning: transactionStore.isAutoPurgeRunning
1010
1406
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nets-service-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Utility functions for Nets Service",
5
5
  "source": "src/index.js",
6
6
  "main": "dist/index.js",
@@ -11,16 +11,18 @@
11
11
  "watch": "microbundle watch",
12
12
  "test": "echo \"Error: no test specified\" && exit 1"
13
13
  },
14
- "keywords": [],
14
+ "keywords": ["nets", "payment", "terminal", "pos", "sqlite", "transaction", "serial", "payment-gateway"],
15
15
  "author": "dineshhv",
16
16
  "license": "ISC",
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
+ "better-sqlite3": "^12.5.0",
21
22
  "dollars-to-cents": "^1.0.3",
22
23
  "lodash": "^4.17.21",
23
24
  "moment": "^2.29.4",
25
+ "node-cron": "^4.2.1",
24
26
  "request": "^2.88.2",
25
27
  "serialport": "^10.5.0",
26
28
  "socket.io-client": "^4.7.2",