diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e20c3ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+config.ini
+log.txt
+logs/log*
diff --git a/aaa.js b/aaa.js
new file mode 100644
index 0000000..752bfe3
--- /dev/null
+++ b/aaa.js
@@ -0,0 +1,159 @@
+var request = require('request');
+var strftime = require('strftime');
+
+var max_retry = 10;
+var sleep_before_retry = 3000;
+
+var config;
+var partner;
+
+var available_products = [];
+
+function unaliasResponseCode(response_code, config_responsecode_alias) {
+    if (config_responsecode_alias == undefined && config && config.h2h_out && config.h2h_out.responsecode_alias) {
+        config_responsecode_alias = config.h2h_out.responsecode_alias;
+    }
+    
+    if (!config_responsecode_alias) {
+        return response_code;
+    }
+    
+    items = config_responsecode_alias.split(',');
+    items_count = items.length;
+    
+    for (var i=0; i < items_count; i++) {
+        codes = items[i].split(':');
+        
+        if (codes.length <= 0) { continue; }
+        
+        if (response_code == codes[0]) {
+            console.log('Change response code from ' + codes[0] + ' to ' + codes[1]);
+            return codes[1];
+        }
+    }
+    
+    return response_code;
+}
+
+function pullCity() {
+    var url = config.globals.aaa_host + '/pull_city';
+    console.log('Pull cities from AAA - ' + url);
+    request(url, function (error, response, body) {
+        if (!error && response.statusCode == 200) {
+            //console.log('city=' + body);
+        } else {
+            console.log('Error in pull city');
+        }
+    });
+}
+
+function pullProduct() {
+    var url = config.globals.aaa_host + '/pull_product?opr_name=' + config.globals.operators;
+    console.log('Pull products from AAA - ' + url);
+
+    request(url, function (error, response, body) {
+        if (error || response.statusCode != 200) {
+            console.log('Error in pull products');
+            return;
+        }
+
+        var productsAndOperators = body.split(';');
+        var productsCount = productsAndOperators.length;
+
+        for (var i=0; i < productsCount; i++) {
+            var product = productsAndOperators[i].split(',', 1)[0];
+            available_products.push(product);
+        }
+        //console.log(available_products);
+    });
+}
+
+function pull() {
+    var url = config.globals.aaa_host
+        + '/pull?city=ALL&nom=' + config.globals.products
+        + '&chip_info=' + config.globals.gateway_name;
+
+    //console.log('AAA PULL - ' + url);
+    request(url, function (error, response, body) {
+        if (!error && response.statusCode == 200) {
+            if (body == 'NONE') {
+                return;
+            }
+            console.log(body);
+
+            var result = body.split(';');
+            if (result[0] != 'OK') {
+                return;
+            }
+
+            var task = [];
+            task['requestId'] = result[1];
+            task['timestamp'] = result[3];
+            task['destination'] = result[4];
+            task['product'] = result[7];
+
+            if (config.products[task['product']] !== undefined) {
+                task['remoteProduct'] = config.products[task['product']];
+            } else {
+                task['remoteProduct'] = task['product'];
+            }
+
+            partner.topupRequest(task);
+
+        } else {
+            console.log('Error in pull task');
+            return;
+        }
+    });
+}
+
+function pullLoop() {
+    if (!config.globals.pause) {
+        pull();
+    }
+    
+    setTimeout(pullLoop, config.globals.interval);
+}
+
+function callbackReport(requestId, responseCode, message, retry) {
+    if (retry === undefined) {
+        retry = max_retry;
+    }
+    
+    responseCode = unaliasResponseCode(responseCode);
+    
+    timestamp = strftime('%Y%m%d%H%M%S');
+    var url = config.globals.aaa_host
+        + '/topup?trans_id=' + requestId
+        + '&trans_date' + timestamp
+        + '&trans_date=' + timestamp
+        + '&resp_code=' + responseCode
+        + '&ussd_msg=' + config.globals.gateway_name
+        + '$' + encodeURIComponent(message);
+
+    console.log('Report to AAA - ' + url);
+    request(url, function (error, response, body) {
+        if (error || response.statusCode != 200) {
+            console.log('Error report to AAA');
+            
+            if (retry) {
+                console.log('Retrying to report to AAA (' + retry + ')');
+                callbackReport(requestId, responseCode, message, retry - 1);
+            }
+        }
+    });
+}
+
+function start(_config, _partner) {
+    config = _config;
+    partner = _partner;
+
+    pullCity();
+    pullProduct();
+
+    setTimeout(pullLoop, 10 * 1000);
+}
+
+exports.start = start;
+exports.callbackReport = callbackReport;
+exports.unaliasResponseCode = unaliasResponseCode;
diff --git a/config.sample.ini b/config.sample.ini
new file mode 100644
index 0000000..4fad175
--- /dev/null
+++ b/config.sample.ini
@@ -0,0 +1,18 @@
+[globals]
+operators=TEST,XL_ALTERNATIF
+products=TST1
+gateway_name=NODEJS-DEV
+aaa_host=http://172.23.0.12:4250
+interval=1000
+admin_port=17456
+
+[h2h_out]
+partner=https://172.23.0.12:6789
+userid=R97DEV
+password=czcb
+listen_port=13522
+check_interval=2000
+
+[products]
+TST1=TR1
+
diff --git a/httpserver.js b/httpserver.js
new file mode 100644
index 0000000..d90ecc6
--- /dev/null
+++ b/httpserver.js
@@ -0,0 +1,90 @@
+var http = require('http');
+var nsr = require('node-simple-router');
+var router = nsr();
+
+var config;
+var httpServer;
+
+function start(_config) {
+    if (_config != undefined) {
+        config = _config;
+    }
+    listenPort = config.globals.admin_port;
+    
+    router.get("/info", function(request, response) {
+        response.setHeader("Content-Type", "text/plain");
+        response.write('CHIPINFO / GATEWAY NAME: ' + config.globals.gateway_name + "\n");
+        response.write('PRODUCTS: ' + config.globals.products + "\n");
+        response.write('AAA HOST: ' + config.globals.aaa_host + "\n");
+        response.write('PARTNER: ' + config.h2h_out.partner + "\n");
+        response.write('PAUSED: ' + config.globals.pause + "\n");
+        response.write('UPTIME: ' + process.uptime() + "\n");
+        response.write('REQUESTS COUNT: ' + config.globals.requests_count + "\n");
+        response.write('ACTIVE REQUESTS COUNT: ' + config.globals.active_requests_count + "\n");
+        response.write('MAX ACTIVE REQUESTS COUNT: ' + config.globals.max_active_requests_count + "\n");
+        
+        response.end();
+    });
+    
+    router.get("/pause/:apikey", function(request, response) {
+        if (!config.globals.apikey) {
+            response.end('Undefined APIKEY on config');
+            return;
+        }
+        
+        if (request.params.apikey != config.globals.apikey) {
+            response.end('Invalid APIKEY');
+            return;
+        }
+        
+        config.globals.pause = 1;
+        response.end('Paused');
+    });
+    
+    router.get("/resume/:apikey", function(request, response) {
+        if (!config.globals.apikey) {
+            response.end('Undefined APIKEY on config');
+            return;
+        }
+        
+        if (request.params.apikey != config.globals.apikey) {
+            response.end('Invalid APIKEY');
+            return;
+        }
+        
+        delete config.globals.pause;
+        response.end('Resume');
+    });
+    
+    router.get("/reset-stats/:apikey", function(request, response) {
+        if (!config.globals.apikey) {
+            response.end('Undefined APIKEY on config');
+            return;
+        }
+        
+        if (request.params.apikey != config.globals.apikey) {
+            response.end('Invalid APIKEY');
+            return;
+        }
+        
+        config.globals.max_active_requests_count = 0;
+        
+        response.writeHead(307, {
+          'Location': '/info'
+        });
+        
+        response.end();
+    });
+
+    httpServer = http.createServer(router).listen(listenPort);
+    console.log('HTTP server listens on port ' + listenPort);
+
+    return httpServer;
+}
+
+function setConfig(_config) {
+    config = _config;
+}
+
+exports.start = start;
+exports.setConfig = setConfig;
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..79404a8
--- /dev/null
+++ b/index.js
@@ -0,0 +1,13 @@
+var iniparser = require('iniparser');
+var config = iniparser.parseSync('./config.ini');
+
+var aaaHost = config.globals.aaa_host;
+
+HttpServer = require('./httpserver.js');
+var httpServer = HttpServer.start(config);
+
+var aaa = require('./aaa.js');
+var partner = require('./partner-scrappingkisel.js');
+
+partner.start(config, aaa.callbackReport);
+aaa.start(config, partner);
diff --git a/logs/empty b/logs/empty
new file mode 100644
index 0000000..e69de29
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..21e1d23
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "sate24-to-sc",
+  "version": "0.0.1",
+  "description": "ST24 to SimpleConnect H2H OUT",
+  "main": "index.js",
+  "scripts": {
+    "test": "mocha"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@gitlab.kodesumber.com:reload97/sate24-to-sc.git"
+  },
+  "keywords": [
+    "st24",
+    "reload97",
+    "ppob",
+    "h2h",
+    "m2m",
+    "xmlrpc"
+  ],
+  "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>",
+  "license": "BSD",
+  "dependencies": {
+    "mocha": "~2.2.5",
+    "request": "~2.57.0",
+    "strftime": "~0.9.2",
+    "iniparser": "~1.0.5",
+    "mathjs": "~1.7.0",
+    "xmlrpc": "~1.3.1",
+    "xml2js": "~0.4.9",
+    "redis": "~0.12.1",
+    "hiredis": "~0.4.0",
+    "node-simple-router": "~0.9.4-2"
+  }
+}
diff --git a/partner-scrappingkisel.js b/partner-scrappingkisel.js
new file mode 100644
index 0000000..ad77f06
--- /dev/null
+++ b/partner-scrappingkisel.js
@@ -0,0 +1,104 @@
+var fs = require('fs');
+var https = require('https');
+var http = require('http');
+var url = require('url');
+var request = require('request');
+var xml2js = require('xml2js').parseString;
+var strftime = require('strftime');
+var math = require('mathjs');
+
+var config;
+var httpServer;
+
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+
+var logTag = __filename.split('/').reverse()[0];
+
+function topupRequest(task) {
+    if (config.globals.requests_count == undefined) {
+        config.globals.requests_count = 1;
+    } else {
+        config.globals.requests_count++;
+    }
+    
+    if (config.globals.active_requests_count == undefined) {
+        config.globals.active_requests_count = 1;
+    } else {
+        config.globals.active_requests_count++;
+    }
+    
+    if (config.globals.max_active_requests_count == undefined) {
+        config.globals.max_active_requests_count = config.globals.active_requests_count;
+    } else {
+        config.globals.max_active_requests_count = math.max(config.globals.max_active_requests_count, config.globals.active_requests_count);
+    }
+        
+    var options = {
+        url: config.h2h_out.partner,
+        qs: {
+            reqid: task['requestId'],
+            msisdn: task['destination'],
+            product: task['product']
+        }
+    };
+    console.log(options);
+
+    request(options, function (error, response, body) {
+        if (config.globals.active_requests_count == undefined) {
+            config.globals.active_requests_count = 0;
+        } else {
+            config.globals.active_requests_count--;
+        }
+        
+        if (error || response.statusCode != 200) {
+            console.log(logTag + ': Gateway Error');
+            callbackReport(task['requestId'], '40', 'Gateway Error');
+            return;
+        } 
+
+        console.log(logTag + ': Supplier response:');
+        console.log(body);
+
+        xml2js(body, function (err, result) {
+            if (err) {
+                callbackReport(task['requestId'], '40', body);
+                return;
+            } 
+                
+            console.log(result);
+        
+            var response_code = '68';
+            var message = result.trx_response.info[0].trim();
+            
+            if (message == 'Error Parsing') {
+                
+                response_code = '40';
+                
+            } else if (message == "Phone number's not found") {
+                
+                response_code = '14';
+                
+            } else if (message == "TRANSAKSI SUKSES !!!") {
+                var destination = result.trx_response.msisdn.join(' ').trim();
+                var product = result.trx_response.product.join(' ').trim();
+                var ref_num = result.trx_response.ref_num.join(' ').trim();
+                var harga = result.trx_response.harga.join(' ').trim();
+                var kode_voucher = result.trx_response.kode_voucher.join(' ').trim();
+                
+                response_code = '00';
+                
+                message = 'SN=' + ref_num + '; ' + product + ' ' + msisdn + ' ' + harga + ' ref_num: ' + ref_num + ' kode_voucher: ' + kode_voucher;
+            }
+            
+            callbackReport(task['requestId'], response_code, message);
+        });
+    });
+}
+
+function start(_config, _callbackReport) {
+    config = _config;
+    callbackReport = _callbackReport
+}
+
+exports.start = start;
+exports.topupRequest = topupRequest;
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..46a97e3
--- /dev/null
+++ b/test.js
@@ -0,0 +1,49 @@
+var assert = require("assert");
+
+
+describe('aaa', function() {
+    var aaa = require('./aaa');
+    
+    describe("#unaliasResponseCode()", function() {
+        it('should return 68', function() {
+            assert.equal('68', aaa.unaliasResponseCode('01', '01:68'));
+        });
+        
+        it('should return 68', function() {
+            assert.equal('68', aaa.unaliasResponseCode('68', '01:68'));
+        });
+        
+        it('should return 00', function() {
+            assert.equal('00', aaa.unaliasResponseCode('00', '01:68'));
+        });
+        
+        it('should return 40', function() {
+            assert.equal('40', aaa.unaliasResponseCode('40', ''));
+        });
+        
+        it('should return 40', function() {
+            assert.equal('40', aaa.unaliasResponseCode('40', ''));
+        });
+        
+        it('should return 40', function() {
+            assert.equal('40', aaa.unaliasResponseCode('40'));
+        });
+    
+    });
+});
+
+describe('aaa', function() {
+    var partner = require('./httppulsakita');
+    
+    describe("#parseResult()", function() {
+        message = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><respon><tanggal>2015/6/16 15:43:35</tanggal><idagen>P0039</idagen><refid>AEE15B32987941D89FFF4BC7EF676C13</refid><produk>PLN20</produk><tujuan>14204279369</tujuan><rc>0000</rc><data> </data><token> </token><pesan>#14836 PLN20 ke:14204279369 SUKSES. SN:3520-2887-6627-6699-4826/TestDummyPanjang6955555/P1/7000VA/32,4. \
+Sisa saldo  Rp. 5,000,000 - Rp. 18,700 = Rp. 4,981,300</pesan></respon>';
+
+        data = partner.parseResult(message);
+        console.log(data);
+        
+        it('should return 2015/6/16 15:43:35', function() {
+            assert.equal('2015/6/16 15:43:35', data.respon.tanggal);
+        });
+    });
+});