diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb02d95
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/node_modules/
+/config.ini
+/logs/
diff --git a/README b/README
index e69de29..b57fe04 100644
--- a/README
+++ b/README
@@ -0,0 +1,2 @@
+ST24 Gateway to KOMODO
+
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..9929c3b
--- /dev/null
+++ b/index.js
@@ -0,0 +1,31 @@
+"use strict";
+
+var aaa = require('sate24/aaa.js');
+var expresso = require('sate24-expresso');
+var logger = require('sate24/logger.js').start();
+var HttpServer = require('sate24/httpserver.js');
+var partner = require('./partner-komodo.js');
+
+var fs = require('fs');
+var ini = require('ini');
+var config = ini.parse(fs.readFileSync(__dirname + '/config.ini', 'utf-8'));
+
+process.chdir(__dirname);
+
+var logDirectory = __dirname + '/logs';
+fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory);
+
+var matrix = aaa.prepareMatrix();
+
+var options = {
+    'aaa': aaa,
+    'logger': logger,
+    'config': config,
+    'matrix': matrix,
+}
+
+var httpServer = HttpServer.start(config, options);
+
+partner.start(options);
+aaa.start(config, partner, options);
+expresso.start(options);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a15503a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+  "name": "sate24-to-komodo",
+  "version": "1.0.0",
+  "description": "ST24 Gateway to KOMODO",
+  "main": "index.js",
+  "scripts": {
+    "test": "mocha"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@gitlab.kodesumber.com:reload97/sate24-to-komodo.git"
+  },
+  "keywords": [
+    "ppob",
+    "st24",
+    "payment",
+    "reload97",
+    "r97",
+    "komodo"
+  ],
+  "author": "Adhidarma Hadiwinoto <me@adhisimon.org>",
+  "license": "ISC",
+  "dependencies": {
+    "request": "^2.81.0",
+    "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git",
+    "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git"
+  }
+}
diff --git a/partner-komodo.js b/partner-komodo.js
new file mode 100644
index 0000000..cabb7fd
--- /dev/null
+++ b/partner-komodo.js
@@ -0,0 +1,142 @@
+"use strict";
+
+const request = require('request');
+const resendDelay = require('sate24/resend-delay')
+
+var config;
+var aaa;
+var logger;
+
+let komodoRc = {
+    '03': '40',
+    '13': '13',
+    '14': '14',
+    '30': '40',
+    '68': '68',
+    '55': '55',
+    '91': '91',
+    '92': '40',
+    '96': '68'
+}
+
+function start(options) {
+    if (!options) {
+        console.log('Undefined options, terminating....');
+        process.exit(1);
+    }
+
+    if (options.config) {
+        config = options.config;
+    } else {
+        console.log('Undefined options.config, terminating....')
+        process.exit(1);
+    }
+
+    if (options.aaa) {
+        aaa = options.aaa;
+    } else {
+        console.log('Undefined options.aaa, terminating....')
+        process.exit(1);
+    }
+
+    if (options && options.logger) {
+        logger = options.logger;
+    } else {
+        console.log('Undefined options.logger, terminating....')
+        process.exit(1);
+    }
+
+    resendDelay.init({config: config, logger: logger, topupRequest: topupAdvice});
+}
+
+function callbackReport(requestId, rc, message, options) {
+    aaa.callbackReportWithPushToMongoDb(requestId, rc, message);
+
+    if (!options.task) {
+        return;
+    }
+
+    if (rc == '68') {
+        resendDelay.register(options.task);
+    } else {
+        resendDelay.cancel(options.task)
+    }
+
+}
+
+function topupRequest(task, pendingOnConnectError) {
+    aaa.insertTaskToMongoDb(task);
+
+    const requestOptions = {
+        url: config.h2h_out.partner,
+        qs: {
+            terminal_name: config.h2h_out.username,
+            password: config.h2h_out.password,
+            product_name: task.remoteProduct,
+            destination: task.destination,
+            request_id: task.requestId
+        }
+    }
+
+    request(requestOptions, function(err, response, body) {
+        if (err) {
+            logger.warn('Error requesting to partner', {err: err});
+
+            let rc = '68';
+
+            if (err.syscall == 'connect' && !pendingOnConnectError) {
+                rc = '91';
+            }
+            callbackReport(task.requestId, rc, 'Error requesting to partner: ' + err);
+            return;
+        }
+
+        if (response.statusCode != 200) {
+            let rc = '68';
+
+            callbackReport(task.requestId, rc, 'Partner returning HTTP status code ' + response.statusCode + ', not 200');
+            return;
+        }
+
+        let result = parsePartnerMessage(body);
+        if (!result) {
+            callbackReport(task.requestId, '40', 'Error parsing response from partner. Partner response: ' + body);
+            return;
+        }
+
+        let st24Rc = '68';
+
+        if (komodoRc[result.rc]) {
+            st24Rc = komodoRc[result.rc];
+        }
+
+        let st24Message = result.message;
+        if (result.sn) {
+            st24Message = 'SN=' + result.sn + '; ' + st24Message;
+        }
+
+        callbackReport(task.requestId, st24Rc, st24Message);
+    })
+}
+
+function topupAdvice(task) {
+    topupRequest(task, true);
+}
+
+function parsePartnerMessage(partner_message) {
+    let result;
+    try {
+        result = JSON.parse(partner_message);
+
+    }
+    catch(e) {
+        result = null;
+    }
+
+    return result;
+}
+
+
+exports.start = start;
+exports.topupRequest = topupRequest;
+exports.topupAdvice = topupAdvice;