diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2658d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules/
diff --git a/README b/README
new file mode 100644
index 0000000..e69de29
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..dd057e2
--- /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('sate24/httpserver.js');
+var httpServer = HttpServer.start(config);
+
+var aaa = require('sate24/aaa.js');
+var partner = require('./partner-datacell.js');
+
+partner.start(config, aaa.callbackReport);
+aaa.start(config, partner);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..73c1b6a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "sate24-to-datacell",
+  "version": "0.0.1",
+  "description": "ST24 to Datacell",
+  "main": "index.js",
+  "scripts": {
+    "test": "mocha"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@gitlab.kodesumber.com:reload97/sate24-to-datacell.git"
+  },
+  "keywords": [
+    "datacell",
+    "ppob",
+    "st24"
+  ],
+  "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>",
+  "license": "BSD",
+  "dependencies": {
+    "sate24": "git+http://git@gitlab.kodesumber.com/reload97/node-sate24.git",
+    "iniparser": "~1.0.5",
+    "base64-xor": "~0.10.0",
+    "request": "~2.60.0",
+    "mathjs": "~1.7.1",
+    "xml": "~1.0.0",
+    "xml2js": "~0.4.9",
+    "strftime": "~0.9.2"
+  }
+}
diff --git a/partner-datacell.js b/partner-datacell.js
new file mode 100644
index 0000000..9c389f0
--- /dev/null
+++ b/partner-datacell.js
@@ -0,0 +1,328 @@
+var http = require('http');
+var url = require('url');
+var math = require('mathjs');
+var xml = require('xml');
+var xml2js = require('xml2js').parseString;
+var strftime = require('strftime');
+var xor = require('base64-xor');
+var request = require('request');
+
+var config;
+var callbackReport;
+
+var max_retry = 2;
+var sleep_before_retry = 2000;
+
+var trx_balances = {};
+var trx_prices = {};
+
+function calculateSignature(userid, password, msisdn, timestamp) {
+    var a = msisdn.substr(msisdn.length - 4) + timestamp;
+    var b = userid.substr(0, 4) + password;
+    
+    return xor.encode(a,b);
+}
+
+function calculateBalanceSignature(userid, password, timestamp) {
+    var a = '0000' + timestamp;
+    var b = userid.substr(0, 4) + password;
+    
+    return xor.encode(a,b);
+}
+
+
+function createPayload(task) {
+    var timestamp = strftime('%H%M%S');
+    
+    var payload = {
+        datacell: [
+            { perintah: 'charge'},
+            {oprcode: task['remoteProduct']},
+            {userid: config.h2h_out.userid},
+            {time: timestamp},
+            {msisdn: task['destination']},
+            {ref_trxid: task['requestId']},
+            {sgn: calculateSignature(config.h2h_out.userid, config.h2h_out.password, task['destination'], timestamp)}
+        ]
+    };
+    
+    console.log(payload);
+    return "<?xml version=\"1.0\" ?>\n" + xml(payload);
+}
+
+function topupRequest(task, retry) {
+    //balanceCheck();
+    
+    var payload_xml = createPayload(task);
+    //console.log(payload_xml);
+    
+    var postRequest = {
+        host: "202.152.62.2",
+        path: "/RELOAD97.php",
+        port: 7713,
+        method: "POST",
+        headers: {
+            'Content-Type': 'text/xml',
+            'Content-Length': Buffer.byteLength(payload_xml)
+        }
+    };
+    
+    var buffer = "";
+    var req = http.request( postRequest, function( res )    {
+
+        console.log('Status code: ' + res.statusCode );
+        var buffer = "";
+        res.on( "data", function( data ) { buffer = buffer + data; } );
+        res.on( "end", function( data ) {             
+                topupResponseHandler(buffer);
+        });
+
+    });
+
+    req.on('error', function(e) {
+        console.log('problem with request: ' + e.message);
+        callbackReport(task['requestId'], '40', e.message);
+    });
+
+    req.write( payload_xml );
+    req.end();
+}
+
+function topupResponseHandler(body, request_id) {
+    xml2js(body, function (err, result) {
+        if (err) {
+            console.log(body);
+            callbackReport(request_id, '40', buffer);
+            return;
+        }
+        
+        console.log(result);
+        
+        request_id = result.datacell.ref_trxid[0].trim();
+        
+        var response_code = '68';
+        
+        var message = '';
+        try {
+            if (result.datacell.message && result.datacell.message.length > 0) {
+                message = result.datacell.message[0].trim();
+            } else if (result.datacell.msg && result.datacell.msg.length > 0) {
+                message = result.datacell.msg[0].trim();
+            } 
+        }
+        catch(err) {
+            message = 'exception saat parsing message';
+        }
+        
+        if (result.datacell.resultcode && result.datacell.resultcode[0] == '999') {
+            response_code = '40';
+        }
+        
+        if (message.indexOf('Nomor tujuan salah') >= 0) {
+            response_code = '14';
+        } else if (message.indexOf('*GAGAL, transaksi yang sama sudah ada dalam 10 menit') >= 0) {
+            response_code = '55';
+        } else if (message.indexOf('saldo sdh dikembalikan') >= 0) {
+            response_code = '40'
+        } else if (message.indexOf('Trx dpt diulang') >= 0) {
+            response_code = '40'
+        } else if (message.indexOf('SUKSES SN Operator:') >= 0) {
+            response_code = '00';
+            
+            var sn = parseSN(message);
+            console.log ('SN Operator: ' + sn);
+            
+            /*
+            if (!sn) {
+                
+                console.log('Missing real operator SN, using SN from suplier');
+                try {
+                    sn = result.datacell.trxid[0].trim();
+                }
+                catch(err) {
+                    sn = '';
+                }
+            }
+            */
+            
+            if (sn) {
+                message = 'SN=' + sn + '; ' + message;
+            } else {
+                message = 'SN belum didapat. ' + message;
+                response_code = '68';
+            }
+        }
+
+        
+        var price = priceFromMessage(message);
+        if (price != null) {
+            console.log('Harga: ' + price);
+            trx_prices[request_id] = price;
+            setTimeout(deleteTrxPrice, 3 * 24 * 3600 * 1000, request_id);
+        } else if (response_code == '00' && trx_prices[request_id] !==  undefined) {
+            price = trx_prices[request_id];
+            console.log('Harga: ' + price);
+            message = message + ' -- Harga: ' + price;
+        }
+        
+        var balance = balanceFromMessage(message);
+        if (balance != null) {
+            console.log('Saldo: ' + balance);
+            trx_balances[request_id] = balance;
+            setTimeout(deleteTrxBalance, 3 * 24 * 3600 * 1000, request_id);
+        } else if (response_code == '00' && trx_balances[request_id] !==  undefined) {
+            balance = trx_balances[request_id];
+            console.log('Saldo: ' + balance);
+            message = message + ' -- Saldo: ' + balance;
+        }
+    
+        callbackReport(request_id, response_code, message);
+    });
+}
+
+function deleteTrxPrice(request_id) {
+    delete trx_prices[request_id];
+}
+
+function deleteTrxBalance(request_id) {
+    delete trx_balances[request_id];
+}
+
+function parseSN(message) {
+    var results = message.match(/SN Operator: .+ SN Kami/);
+    if (!results || results.length <= 0) {
+        return '';
+    }
+    
+    var result = results[0];
+    result = result.replace('SN Operator:', '');
+    result = result.replace('SN Kami', '');
+    result = result.trim();
+    
+    if (result == '00') {
+        result = '';
+    }
+    
+    return result;
+}
+
+function createServer() {
+
+    var httpServer = http.createServer(function(req, res) {
+        var parsed_url = url.parse(req.url, true, true);
+
+        console.log('Got request from partner ("' + req.url + '")');
+        
+        var body = "";
+        req.on('data', function (chunk) {
+            body += chunk;
+        });
+        
+        req.on('end', function () {
+            res.writeHead(200);
+            res.end('OK');
+            
+            //console.log(body);
+            
+            if (parsed_url.pathname == '/sn') {
+                console.log('Reverse report -- SN');
+                topupResponseHandler(body);
+                
+            } else if (parsed_url.pathname = '/refund') {
+                console.log('Reverse report -- REFUND');
+                callbackReport(parsed_url.query.ref_trxid, '40', parsed_url.query.message);
+                
+            } else {
+                console.log('Reverse report -- UNKNOWN');
+                console.log('Ignore unknown request on reverse url');
+            }
+        });
+    });
+    
+    httpServer.listen(config.h2h_out.listen_port, function() {
+        console.log('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port);
+    });
+}
+
+function balanceCheck() {
+    var timestamp = strftime('%H%M%S');
+    
+    var payload = {
+        datacell: [
+            {perintah: 'saldo'},
+            {userid: config.h2h_out.userid},
+            {time: timestamp},
+            {sgn: calculateBalanceSignature(config.h2h_out.userid, config.h2h_out.password, timestamp)}
+        ]
+    };
+    
+    var postRequest = {
+        host: "202.152.62.2",
+        path: "/RELOAD97.php",
+        port: 7713,
+        method: "POST",
+        headers: {
+            'Content-Type': 'text/xml',
+            'Content-Length': Buffer.byteLength(payload_xml)
+        }
+    };
+    
+    var buffer = "";
+    var req = http.request( postRequest, function( res )    {
+
+        console.log('Status code: ' + res.statusCode );
+        var buffer = "";
+        res.on( "data", function( data ) { buffer = buffer + data; } );
+        res.on( "end", function( data ) {             
+            console.log('CHECK BALANCE RESULT:');
+            console.log(buffer);
+        });
+
+    });
+
+    req.on('error', function(e) {
+        console.log('problem with request: ' + e.message);
+    });
+
+    req.write( payload_xml );
+    req.end();
+    
+}
+
+function balanceFromMessage(message) {
+    var matches = message.match(/Saldo: Rp (\d+)/);
+    
+    if (!matches) {
+        return null;
+    }
+    if (matches.length < 2) {
+        return null;
+    }
+    
+    return matches[1];
+}
+
+function priceFromMessage(message) {
+    var matches = message.match(/Harga: (\d+)/);
+    
+    if (!matches) {
+        return null;
+    }
+    if (matches.length < 2) {
+        return null;
+    }
+    
+    return matches[1];
+}
+
+function start(_config, _callbackReport) {
+    config = _config;
+    callbackReport = _callbackReport
+
+    createServer();
+}
+
+exports.start = start;
+exports.topupRequest = topupRequest;
+exports.balanceFromMessage = balanceFromMessage;
+exports.priceFromMessage = priceFromMessage;
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..00ee8f9
--- /dev/null
+++ b/test.js
@@ -0,0 +1,49 @@
+var assert = require("assert");
+
+describe('partner-datacell', function() {
+    var partner = require('./partner-datacell');
+    
+    describe('#balanceFromMessage', function() {
+        var message;
+        
+        it('should return 8306874', function() {
+            assert.equal(8306874, partner.balanceFromMessage('IR25 No: 085697273881 sudah diterima dan sdg diproses. SN Kami :243588112. Harga: 24750. Saldo: Rp 8306874.'));
+        });
+        
+        it('should return 8351574', function() {
+            assert.equal(8351574, partner.balanceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 8351574.'));
+        });
+        
+        it('should return 0', function() {
+            assert.equal(0, partner.balanceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 0.'));
+        });
+        
+        it('should return null', function() {
+            assert.equal(null, partner.balanceFromMessage('XL25 No: 08174945541 SUKSES SN Operator: 970729963933 SN Kami: 243591297.'));
+        });
+    });
+    
+    describe('#priceFromMessage', function() {
+        var message;
+        
+        it('should return 24750', function() {
+            assert.equal(24750, partner.priceFromMessage('IR25 No: 085697273881 sudah diterima dan sdg diproses. SN Kami :243588112. Harga: 24750. Saldo: Rp 8306874.'));
+        });
+        
+        it('should return 19950', function() {
+            assert.equal(19950, partner.priceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 19950. Saldo: Rp 8351574.'));
+        });
+        
+        it('should return 0', function() {
+            assert.equal(0, partner.priceFromMessage('TEL20 No: 085372113774 sudah diterima dan sdg diproses. SN Kami :243586975. Harga: 0. Saldo: Rp 8351574.'));
+        });
+        
+        it('should return null', function() {
+            assert.equal(null, partner.priceFromMessage('XL25 No: 08174945541 SUKSES SN Operator: 970729963933 SN Kami: 243591297.'));
+        });
+    });
+    
+    
+    
+    
+});