From a9aef2698af6df3b6134632af50b65f7d08afe62 Mon Sep 17 00:00:00 2001
From: Adhidarma Hadiwinoto <me@adhisimon.org>
Date: Tue, 6 Aug 2024 14:08:06 +0700
Subject: [PATCH] Separate webhook sender

---
 config.sample.json                    |  6 ++-
 lib/core-callback/sender.js           | 44 ++---------------
 lib/partner-listener/routers/topup.js | 38 ++-------------
 lib/webhook-sender.js                 | 89 +++++++++++++++++++++++++++++++++++
 package-lock.json                     | 25 +++++++---
 package.json                          |  1 +
 6 files changed, 123 insertions(+), 80 deletions(-)
 create mode 100644 lib/webhook-sender.js

diff --git a/config.sample.json b/config.sample.json
index 56d3d9e..cb2cce5 100644
--- a/config.sample.json
+++ b/config.sample.json
@@ -8,7 +8,6 @@
         "partner": {
             "trust_proxy": ["loopback", "linklocal"],
             "port": 25614,
-            "# webhook": "http://PLEASE_CHANGE_ME/PLEASE_CHANGE_ME",
             "dump": false
         },
         "core": {
@@ -27,6 +26,11 @@
         "max_retry": 10
     },
 
+    "webhook": {
+        "url": null,
+        "dump": false
+    },
+
     "terminals_with_location": [],
 
     "# cluster": "Isi dengan boolean atau angka jumlah anak yang akan dibuat. Jika diisi boolean true, jumlah anak akan dihitung otomatis",
diff --git a/lib/core-callback/sender.js b/lib/core-callback/sender.js
index 49b52be..0687151 100644
--- a/lib/core-callback/sender.js
+++ b/lib/core-callback/sender.js
@@ -7,6 +7,8 @@ const logger = require('tektrans-logger');
 const dumper = require('./dumper/sender');
 const matrix = require('../matrix');
 
+const webhookSender = require('../webhook-sender');
+
 const HTTP_TIMEOUT = Number(
     config.callback_sender && config.callback_sender.http_timeout_ms,
 ) || 30 * 1000;
@@ -180,45 +182,6 @@ const sender = async (data, xid, retry) => {
             delete matrix.callback_sender.active_sending[xid];
         }
 
-        if (config.listener.partner.webhook) {
-            try {
-                const webhookType = 'KOMODO-CENTER-HTTPGETX.CORE-CALLBACK';
-
-                logger.verbose(`${MODULE_NAME} E21E3DC6: Sending webhook`, {
-                    xid,
-                    webhookType,
-                    partner: config.listener.partner.webhook,
-                    trxId: data.transaction_id && data.transaction_id.toString(),
-                    request_id: data.request_id && data.request_id.toString(),
-                    product_name: data.product_name,
-                    destination: data.destination,
-                    rc: data.rc,
-                });
-
-                axios.post(config.listener.partner.webhook, {
-                    webhookType: 'KOMODO-CENTER-HTTPGETX.CORE-CALLBACK',
-                    body: params,
-                });
-
-                logger.verbose(`${MODULE_NAME} 26BCA07F: Webhook sent`, {
-                    xid,
-                    webhookType,
-                    partner: config.listener.partner.webhook,
-                    trxId: data.transaction_id && data.transaction_id.toString(),
-                    request_id: data.request_id && data.request_id.toString(),
-                    product_name: data.product_name,
-                    destination: data.destination,
-                    rc: data.rc,
-                });
-            } catch (e) {
-                logger.warn(`${MODULE_NAME} F722520A: Exception on calling webhook`, {
-                    xid,
-                    eCode: e.code,
-                    eMessage: e.message || e.toString(),
-                });
-            }
-        }
-
         dumper(
             xid,
             isHttpPost ? 'POST' : 'GET',
@@ -227,6 +190,9 @@ const sender = async (data, xid, retry) => {
             responseToDump,
             errorResponseToDump,
         );
+
+        const webhookType = 'KOMODO-CENTER-HTTPGETX.CORE-CALLBACK';
+        webhookSender(xid, webhookType, params);
     }
 };
 
diff --git a/lib/partner-listener/routers/topup.js b/lib/partner-listener/routers/topup.js
index 470e152..1c94ab0 100644
--- a/lib/partner-listener/routers/topup.js
+++ b/lib/partner-listener/routers/topup.js
@@ -1,7 +1,6 @@
 const MODULE_NAME = 'PARTNER-LISTENER.ROUTER.TOPUP';
 
 const express = require('express');
-const axios = require('axios');
 
 const config = require('komodo-sdk/config');
 const logger = require('tektrans-logger');
@@ -9,6 +8,7 @@ const coreapi = require('komodo-sdk/coreapi');
 // const coreapi = require('../../coreapi');
 const matrix = require('../../matrix');
 const dumper = require('../dumper');
+const webhookSender = require('../../webhook-sender');
 
 const router = express.Router();
 module.exports = router;
@@ -151,40 +151,10 @@ async function pageIndex(req, res) {
 
     res.json(responseToPartner);
 
-    if (config.listener.partner.webhook) {
-        try {
-            const webhookType = 'KOMODO-CENTER-HTTPGETX.PARTNER-LISTENER.DIRECT-RESPONSE';
-
-            logger.verbose(`${MODULE_NAME} 2CA59ED3: Sending webhook`, {
-                xid,
-                webhookType,
-                partner: config.listener.partner.webhook,
-                trxId: coreResponse.transaction_id,
-                request_id: req.body.request_id || req.query.request_id,
-            });
-
-            axios.post(config.listener.partner.webhook, {
-                webhookType,
-                body: responseToPartner,
-            });
-
-            logger.verbose(`${MODULE_NAME} 50BE8D98: Webhook sent`, {
-                xid,
-                webhookType,
-                partner: config.listener.partner.webhook,
-                trxId: coreResponse.transaction_id,
-                request_id: req.body.request_id || req.query.request_id,
-            });
-        } catch (e) {
-            logger.warn(`${MODULE_NAME} ECC37ECA: Exception on calling webhook`, {
-                xid,
-                eCode: e.code,
-                eMessage: e.message || e.toString(),
-            });
-        }
-    }
-
     dumper(xid, req, responseToPartner);
+
+    const webhookType = 'KOMODO-CENTER-HTTPGETX.PARTNER-LISTENER.DIRECT-RESPONSE';
+    webhookSender(xid, webhookType, responseToPartner);
 }
 
 // router.all('/', (req, res) => { res.status(404).end('404: Not implemented yet'); });
diff --git a/lib/webhook-sender.js b/lib/webhook-sender.js
new file mode 100644
index 0000000..ae4eb08
--- /dev/null
+++ b/lib/webhook-sender.js
@@ -0,0 +1,89 @@
+const MODULE_NAME = 'WEBHOOK-SENDER';
+
+const axios = require('axios');
+const moment = require('moment');
+const fs = require('fs');
+const path = require('path');
+const stringify = require('json-stringify-pretty-compact');
+const config = require('komodo-sdk/config');
+const logger = require('tektrans-logger');
+
+const DEFAULT_MAX_RETRY = 10;
+const DEFAULT_SLEEP_BEFORE_RETRY_MS = 10 * 1000;
+
+const maxRetry = Number(config.webhook && config.webhook.max_retry)
+    || DEFAULT_MAX_RETRY;
+const sleepBeforeRetryMs = Number(config.webhook && config.webhook.sleep_before_retry_ms)
+    || DEFAULT_SLEEP_BEFORE_RETRY_MS;
+
+const baseDumpDir = path.join('dump', 'webhook-sender');
+if (!fs.existsSync(baseDumpDir)) {
+    fs.mkdirSync(baseDumpDir, { recursive: true });
+}
+const lastDumpFileName = path.join(baseDumpDir, 'last');
+
+const dumper = async (xid, webhookType, body) => {
+    if (!config.webhook || !config.webhook.dump) {
+        return;
+    }
+
+    await fs.promises.writeFile(
+        path.join(baseDumpDir, [moment().format('YYYYMMDD-HHmmssSSS'), xid].join('_')),
+        stringify({ webhookType, body }),
+    );
+
+    await fs.promises.writeFile(
+        lastDumpFileName,
+        stringify({ webhookType, body }),
+    );
+};
+
+const sender = async (xid, webhookType, body, retry) => {
+    if (!config.webhook || !config.webhook.url) {
+        return;
+    }
+
+    try {
+        logger.verbose(`${MODULE_NAME} 2CA59ED3: Sending webhook`, {
+            xid,
+            webhookType,
+            partner: config.webhook.url,
+            trxId: body.transaction_id,
+            request_id: body.request_id,
+        });
+
+        axios.post(config.listener.partner.webhook, {
+            webhookType,
+            body,
+        });
+
+        await dumper(xid, webhookType, body);
+
+        logger.verbose(`${MODULE_NAME} 50BE8D98: Webhook sent`, {
+            xid,
+            webhookType,
+            partner: config.listener.partner.webhook,
+        });
+    } catch (e) {
+        logger.warn(`${MODULE_NAME} ECC37ECA: Exception on calling webhook`, {
+            xid,
+            eCode: e.code,
+            eMessage: e.message || e.toString(),
+            retried: retry || 0,
+            maxRetry,
+        });
+
+        if ((retry || 0) >= maxRetry) {
+            logger.warn(`${MODULE_NAME} 4A60B406: Max retry exceeded`, {
+                xid,
+            });
+
+            return;
+        }
+
+        setTimeout(() => {
+            sender(xid, webhookType, body, (retry || 0) + 1);
+        }, sleepBeforeRetryMs);
+    }
+};
+module.exports = sender;
diff --git a/package-lock.json b/package-lock.json
index 7d84eee..4effd40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
         "express": "^4.17.1",
         "express-rate-limit": "^6.6.0",
         "join-path": "^1.1.1",
+        "json-stringify-pretty-compact": "^4.0.0",
         "komodo-sdk": "^1.45.6",
         "mkdirp": "^1.0.4",
         "moment": "^2.24.0",
@@ -2087,9 +2088,10 @@
       "dev": true
     },
     "node_modules/json-stringify-pretty-compact": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
-      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+      "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
+      "license": "MIT"
     },
     "node_modules/json-stringify-safe": {
       "version": "5.0.1",
@@ -2148,6 +2150,12 @@
         "sd-notify": "^2.8.0"
       }
     },
+    "node_modules/komodo-sdk/node_modules/json-stringify-pretty-compact": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==",
+      "license": "MIT"
+    },
     "node_modules/komodo-sdk/node_modules/uniqid": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz",
@@ -6327,9 +6335,9 @@
       "dev": true
     },
     "json-stringify-pretty-compact": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
-      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
+      "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q=="
     },
     "json-stringify-safe": {
       "version": "5.0.1",
@@ -6383,6 +6391,11 @@
         "uuid": "^3.4.0"
       },
       "dependencies": {
+        "json-stringify-pretty-compact": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+          "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+        },
         "uniqid": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/uniqid/-/uniqid-4.1.1.tgz",
diff --git a/package.json b/package.json
index 6426353..6c62c8a 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "express": "^4.17.1",
     "express-rate-limit": "^6.6.0",
     "join-path": "^1.1.1",
+    "json-stringify-pretty-compact": "^4.0.0",
     "komodo-sdk": "^1.45.6",
     "mkdirp": "^1.0.4",
     "moment": "^2.24.0",
-- 
1.9.0