diff --git a/package.json b/package.json
index 57c4623..8b64ca1 100644
--- a/package.json
+++ b/package.json
@@ -21,8 +21,11 @@
   "author": "Adhidarma Hadiwinoto <gua@adhisimon.org>",
   "license": "ISC",
   "dependencies": {
+    "redis": "^2.6.0-2",
+    "request": "^2.72.0",
     "sate24": "git+http://gitlab.kodesumber.com/reload97/node-sate24.git",
     "sate24-expresso": "git+http://gitlab.kodesumber.com/reload97/sate24-expresso.git",
+    "strftime": "^0.9.2",
     "winston": "^2.2.0"
   }
 }
diff --git a/partner-masterpulsa-voucher.js b/partner-masterpulsa-voucher.js
new file mode 100644
index 0000000..d8eab93
--- /dev/null
+++ b/partner-masterpulsa-voucher.js
@@ -0,0 +1,284 @@
+var winston = require('winston');
+var request = require('request');
+var strftime = require('strftime');
+var crypto = require('crypto');
+var redis = require('redis');
+
+var config;
+var callbackReport;
+var aaa;
+var logger;
+var options;
+var redisClient;
+
+var adviceDelay = 10000;
+
+function createRedisClient() {
+    redisClient = redis.createClient(config.globals.redis_port, config.globals.redis_host);
+}
+
+function start(_config, _callbackReport, options) {
+    config = _config;
+    callbackReport = _callbackReport
+
+    if (options && options.aaa) {
+            aaa = options.aaa;
+    }
+
+    if (options && options.logger) {
+        logger = options.logger;
+    } else {
+        logger = new winston.Logger({
+            transports: [
+              new (winston.transports.Console)()
+            ]
+        });
+    }
+
+    createRedisClient();
+}
+
+function getRedisKey(task) {
+    return config.globals.gateway_name + '.tid:' + task.requestId;
+}
+
+function putTaskToRedis(task) {
+    var redisKey = getRedisKey(task);
+    var taskInJSON = JSON.stringify(task);
+
+    redisClient.set(redisKey, taskInJSON);
+    redisClient.expire(redisKey, 3600*24*7);
+}
+
+function topupRequest(task, retry) {
+    var redisKey = getRedisKey(task);
+
+    redisClient.get(redisKey, function(err, result) {
+        if (err || !result) {
+
+            //logger.info('Redis error or not found', {redisKey: redisKey, error: err, result: result});
+            putTaskToRedis(task);
+            payVoucher(task);
+
+        } else {
+            advice(task);
+        }
+    });
+}
+
+function calculateSignature(cid, secret, dt) {
+    return crypto.createHash('sha256').update(cid + dt + secret).digest().toString('hex');
+}
+
+function parsePaymentResponse(message) {
+    var data = message.split('#');
+    var retval = {
+        raw: message
+    };
+
+    if (data[0] == 'ERROR') {
+        retval = {
+            status: data[0],
+            rc: data[1],
+            rcmessage: data[2],
+        }
+
+    } else {
+
+        var i = 0;
+        retval = {
+            status: data[i++],
+            rc: data[i++],
+            rcmessage: data[i++],
+            resptext: data[i++],
+            dt: data[i++],
+            refnum: data[i++],
+            voucherid: data[i++],
+            nominal: data[i++]
+        }
+    }
+
+    return retval;
+}
+
+function reportPaymentSuccess(task, response) {
+    var message = 'SN=' + response.refnum + '; ' + response.raw;
+
+    logger.info('Report payment success to ST24', {task: task, response: response});
+
+    callbackReport(task.requestId, '00', message);
+}
+
+function reportPaymentError(task, response) {
+    var errorCode = getErrorCode(response.rcmessage);
+    var st24rc = getST24ResponseCode(errorCode);
+
+    if (st24rc == '68') {
+        logger.info('Got pending response, requesting advice in ' + adviceDelay + 'ms', {task: task, response: response});
+        setTimeout(advice, adviceDelay, task);
+    }
+
+    logger.info('Report payment error/pending to ST24', {supplier_rc: errorCode, st24_rc: st24rc, task: task, response: response});
+
+    callbackReport(
+        task.requestId,
+        getST24ResponseCode(errorCode),
+        response.raw
+    );
+}
+
+function getST24ResponseCode(supplierResponseCode) {
+    var st24rc = '40';
+
+    if (supplierResponseCode.length == 1) {
+        supplierResponseCode = '0' + supplierResponseCode;
+    }
+
+    if (['00', '13', '14', '47', '68'].indexOf(supplierResponseCode) >= 0) {
+        st24rc = supplierResponseCode;
+    }
+    else if (supplierResponseCode == '15') {
+        st24rc = '14';
+    }
+    else if (['05', '18', '63', '68'].indexOf(supplierResponseCode) >= 0) {
+        st24rc = '68';
+    }
+    else if (supplierResponseCode == '67') {
+        st24rc = '91'
+    }
+    else if (supplierResponseCode == '46') {
+        st24rc = '40'
+
+        if (aaa && config && config.globals && config.globals.pause_on_not_enough_balance
+            && (config.globals.pause_on_not_enough_balance == '1')) {
+
+            logger.warn('Not enough balance detected. Going to pause the system.');
+            aaa.pause();
+        }
+    }
+
+    return st24rc;
+}
+
+function getErrorCode(rcmessage) {
+    try {
+        var errorCode = rcmessage.match(/\[(\d+)\]/);
+        return errorCode[1];
+    }
+    catch(err) {
+        logger.warn('Empty RCMESSAGE, returning 68 as RC for safety');
+        return '68';
+    }
+}
+
+function generateDt(taskTimestamp) {
+    if (!taskTimestamp) {
+        return strftime('%Y%m%d', new Date());
+    }
+
+    return taskTimestamp.slice(0, 8);
+}
+
+function generateRequestOptions(userid, password, partnerUrl, task) {
+    var dt = generateDt(task.timestamp);
+    var sign = calculateSignature(userid, password, dt);
+
+    var requestOptions = {
+        method: 'GET',
+        url: partnerUrl,
+        qs: {
+            modul: '',
+            command: '',
+            tujuan: task['destination'],
+            voucherid: task['remoteProduct'],
+            cid: userid,
+            dt: dt,
+            hc: sign,
+            trxid: task['requestId'],
+        }
+    }
+
+    return requestOptions;
+}
+
+function advice(task, retry) {
+
+    if (retry === null || retry === undefined) {
+        retry = 10;
+    }
+
+    var requestOptions = generateRequestOptions(config.h2h_out.userid, config.h2h_out.password, config.h2h_out.partner, task);
+
+    requestOptions.qs.modul = 'ISI';
+    requestOptions.qs.command = 'ADV';
+
+    logger.info('Requesting advice to supplier', {requestOptions: requestOptions});
+    request(requestOptions, function(requestError, requestResponse, requestResponseBody) {
+        if (requestError || requestResponse.statusCode != 200) {
+            logger.warn('Advice error', {error: request_error, http_response: requestResponse.statusCode});
+
+            if (retry > 0) {
+                logger.warn('Going to retry advice in ' + adviceDelay + 'ms', {task: task, retry: retry});
+                setTimeout(advice, adviceDelay, task, --retry);
+            }
+
+            return;
+        }
+
+        var paymentResponse = parsePaymentResponse(requestResponseBody);
+        logger.info('Got advice payment response', {paymentResponse: paymentResponse});
+
+        if (paymentResponse.status == 'SUCCESS') {
+            reportPaymentSuccess(task, paymentResponse);
+        }
+        else {
+            reportPaymentError(task, paymentResponse);
+        }
+    });
+
+}
+
+function voucherPay(task) {
+    var requestOptions = generateRequestOptions(config.h2h_out.userid, config.h2h_out.password, config.h2h_out.partner, task);
+
+    requestOptions.qs.modul = 'ISI';
+    requestOptions.qs.command = 'PAY';
+
+    logger.info('Requesting auto payment to supplier', {requestOptions: requestOptions});
+    request(requestOptions, function(requestError, requestResponse, requestResponseBody) {
+        if (requestError) {
+            logger.warn('Request error', {error: requestError});
+
+            setTimeout(advice, adviceDelay, task);
+            return;
+        }
+
+        if (requestResponse.statusCode != 200) {
+            logger.warn('HTTP response status code is not 200', {http_response: requestResponse.statusCode});
+
+            setTimeout(advice, adviceDelay, task);
+            return;
+        }
+
+        logger.info('Supplier response: ' + requestResponseBody);
+
+        var paymentResponse = parsePaymentResponse(requestResponseBody);
+        logger.info('Got payment response', {paymentResponse: paymentResponse});
+
+        if (paymentResponse.status == 'SUCCESS') {
+            reportPaymentSuccess(task, paymentResponse);
+        }
+        else {
+            reportPaymentError(task, paymentResponse);
+        }
+    });
+}
+
+exports.start = start;
+exports.topupRequest = topupRequest;
+exports.calculateSignature = calculateSignature;
+exports.parsePaymentResponse = parsePaymentResponse;
+exports.getErrorCode = getErrorCode;
+exports.getST24ResponseCode = getST24ResponseCode;
+exports.generateSN = generateSN;
+exports.generateDt = generateDt;