diff --git a/lib/message-extractor.js b/lib/message-extractor.js
new file mode 100644
index 0000000..3325ff6
--- /dev/null
+++ b/lib/message-extractor.js
@@ -0,0 +1,50 @@
+/* eslint-disable no-console */
+const MODULE_NAME = 'MESSAGE-PARSER.FINDER';
+
+/**
+ * Mencari nilai berdasarkan array of pattern
+ * @date 2021-07-05
+ * @param {Array} rules
+ * @param {string} originalMessage
+ * @param {any} isDebug
+ * @returns {string} string yang sesuai pattern, null jika tidak ditemukan
+ */
+module.exports = (rules, originalMessage, isDebug) => {
+    if (!Array.isArray(rules)) return null;
+    if (typeof originalMessage !== 'string') return null;
+
+    const msg = originalMessage.trim();
+
+    if (!rules || !msg) return null;
+
+    const ruleCount = rules.length;
+    for (let i = 0; i < ruleCount; i += 1) {
+        const rule = rules[i];
+
+        // eslint-disable-next-line no-continue
+        if (rule.disable || rule.disabled) continue;
+
+        const re = new RegExp(rule.pattern, rule.flags || '');
+
+        const matches = msg.match(re);
+        let result = matches && matches[rule.idx];
+
+        if (result && rule.postprocessing && typeof rule.postprocessing === 'function') {
+            result = rule.postprocessing(result);
+        }
+
+        if (result) {
+            if (isDebug) {
+                console.log(`${MODULE_NAME} 9FAF9286: Found matches`, { isDebug, result, rule });
+            }
+
+            return result;
+        }
+    }
+
+    if (isDebug) {
+        console.log(`${MODULE_NAME} 0240860D: Matches not found`, { isDebug });
+    }
+
+    return null;
+};
diff --git a/lib/parse-result.js b/lib/parse-result.js
index d8a3f96..87923a7 100644
--- a/lib/parse-result.js
+++ b/lib/parse-result.js
@@ -8,6 +8,8 @@ const parseXml = require('./parse-xml');
 
 const report = require('./report');
 const translateRc = require('./translate-rc');
+const messageExtractor = require('./message-extractor');
+const patternRules = require('./pattern-rules');
 
 module.exports = (xid, trxIdFromCaller, xml, isCallback) => {
     logger.verbose(`${MODULE_NAME} 58547863: Processing XML message`, { xid, isCallback });
@@ -72,9 +74,8 @@ module.exports = (xid, trxIdFromCaller, xml, isCallback) => {
     const responseCodeField = (config.partner.xmlrpc_field && config.partner.xmlrpc_field.responseCode) || 'RESPONSECODE';
     const responseCodeFromResponse = params[responseCodeField];
 
-    // eslint-disable-next-line max-len
-    // const messageField = (config.partner.xmlrpc_field && config.partner.xmlrpc_field.message) || 'MESSAGE';
-    // const messageFromResponse = params[messageField];
+    const messageField = (config.partner.xmlrpc_field && config.partner.xmlrpc_field.message) || 'MESSAGE';
+    const messageFromResponse = params[messageField];
 
     const snField = (config.partner.xmlrpc_field && config.partner.xmlrpc_field.sn) || 'SN';
     const snFromResponse = params[snField];
@@ -87,12 +88,22 @@ module.exports = (xid, trxIdFromCaller, xml, isCallback) => {
 
     const rc = translateRc(xid, responseCodeFromResponse) || '68';
 
-    const sn = (rc === '00' && snFromResponse) || null;
+    const sn = (
+        rc === '00' && (
+            snFromResponse
+            || messageExtractor(patternRules.sn, messageFromResponse)
+        )
+    ) || null;
+
+    const amount = Number(messageExtractor(patternRules.price, messageFromResponse) || '') || null;
+    const balance = Number(messageExtractor(patternRules.balance, messageFromResponse) || '') || null;
 
     report(xid, {
         trx_id: trxId.toString(),
         rc,
         sn,
+        amount,
+        balance,
         message: {
             xid,
             responseType: isCallback ? 'CALLBACK' : 'DIRECT-RESPONSE',
diff --git a/lib/pattern-rules.js b/lib/pattern-rules.js
new file mode 100644
index 0000000..8e14a5c
--- /dev/null
+++ b/lib/pattern-rules.js
@@ -0,0 +1,28 @@
+/**
+ * Rule pattern to be used by message extractor
+ */
+module.exports = {
+    sn: [
+        {
+            pattern: '(?:^|,|\\.|;|\\s)SN\\s*[=: ]\\s*(.+?)(?:;|$)',
+            idx: 1,
+            disable: false,
+        },
+    ],
+    price: [
+        {
+            pattern: '(?:,|\\.|;|\\s)(?:HRG|HARGA)\\s*[=: ]\\s*(?:RP)*\\s*(([0-9]|\\.||,)+)',
+            idx: 1,
+            disable: false,
+            postprocessing: (val) => (val || '').replace(/[,.]/g, ''),
+        },
+    ],
+    balance: [
+        {
+            pattern: '(?:,|\\.|;|\\s)SAL(?:DO)*\\s*[=: ]\\s*(?:RP)*\\s*(([0-9]|\\.||,)+)',
+            idx: 1,
+            disable: false,
+            postprocessing: (val) => (val || '').replace(/[,.]/g, ''),
+        },
+    ],
+};
diff --git a/test/message-extractor.js b/test/message-extractor.js
new file mode 100644
index 0000000..2f7cf8f
--- /dev/null
+++ b/test/message-extractor.js
@@ -0,0 +1,57 @@
+/* global describe it */
+
+// eslint-disable-next-line no-unused-vars
+const should = require('should');
+const finder = require('../lib/message-extractor');
+const organicRules = require('../lib/pattern-rules');
+
+describe('#message-extractor', () => {
+    it('should handle garbage input', () => {
+        should.not.exists(finder(null, null, '39A9909D'));
+        should.not.exists(finder(null, '', '8C046E19'));
+        should.not.exists(finder(null, 'ada', '4154D1DC'));
+        should.not.exists(finder([], 'ada'), '5511A78C');
+    });
+
+    it('should return correct sn', () => {
+        finder(
+            organicRules.sn,
+            'SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5;05/07/21 19:05 TRANSAKSI PLNB50 KE 14336230686, (ISO00) BERHASIL. SAL=RP 75.856.585 ,HRG=RP 50.020 ,ID=91280176,SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5@,REQID=2794362 *Channel Telegram : https://t.me/metroreloadinfoh2h',
+            false,
+        ).should.equal('3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5', 'ST24 metro-reload 5B164B64');
+
+        finder(
+            organicRules.sn,
+            'ISI TS10 KE 082324881979 , SUKSES. SAL=489700,HRG=10300,ID=1625459089388,SN=0061003777417733',
+            false,
+        ).should.equal('0061003777417733', 'PLINK 00FC0A03');
+    });
+
+    it('should return correct price', () => {
+        finder(
+            organicRules.price,
+            'SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5;05/07/21 19:05 TRANSAKSI PLNB50 KE 14336230686, (ISO00) BERHASIL. SAL=RP 75.856.585 ,HRG=RP 50.020 ,ID=91280176,SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5@,REQID=2794362 *Channel Telegram : https://t.me/metroreloadinfoh2h',
+            false,
+        ).should.equal('50020', 'ST24 metro-reload 572DAAE1');
+
+        finder(
+            organicRules.price,
+            'ISI TS10 KE 082324881979 , SUKSES. SAL=489700,HRG=10300,ID=1625459089388,SN=0061003777417733',
+            false,
+        ).should.equal('10300', 'PLINK DB7F7B1C');
+    });
+
+    it('should return correct ending balance', () => {
+        finder(
+            organicRules.balance,
+            'SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5;05/07/21 19:05 TRANSAKSI PLNB50 KE 14336230686, (ISO00) BERHASIL. SAL=RP 75.856.585 ,HRG=RP 50.020 ,ID=91280176,SN=3438-2805-5518-4885-2577/A HANIS SACHRA/R1 /1300 VA/31,5@,REQID=2794362 *Channel Telegram : https://t.me/metroreloadinfoh2h',
+            false,
+        ).should.equal('75856585', 'ST24 metro-reload 5E6CA14F');
+
+        finder(
+            organicRules.balance,
+            'ISI TS10 KE 082324881979 , SUKSES. SAL=489700,HRG=10300,ID=1625459089388,SN=0061003777417733',
+            false,
+        ).should.equal('489700', 'PLINK 29C24CEA');
+    });
+});