Hướng Dẫn Sử Dụng API MBBank để Lấy Giao Dịch MBBank Tự Động qua Telegram và Webhook

Hướng dẫn chi tiết cách sử dụng API MBBank để lấy giao dịch MBBank tự động, gửi thông báo qua Telegram và một URL webhook bất kỳ khi có giao dịch mới.

Hướng Dẫn Sử Dụng API MBBank để Lấy Giao Dịch MBBank Tự Động qua Telegram và Webhook

Chào các bạn! Trong bài viết này, mình sẽ hướng dẫn chi tiết cách sử dụng API MBBank để lấy giao dịch MBBank tự động, gửi thông báo qua Telegram và một URL webhook bất kỳ khi có giao dịch mới. Hệ thống này tích hợp API MBBank với các tính năng quản lý qua API như bật/tắt, chỉnh cấu hình, kiểm tra trạng thái, xem log – tất cả đều dễ dàng và hiệu quả. Mình đã thử nghiệm thực tế, chạy ổn định, và giờ chia sẻ lại để ai cũng có thể tự động hóa việc lấy giao dịch MBBank tự động. Chỉ cần làm theo từng bước là OK!

Dự án làm gì?

  • Lấy giao dịch MBBank tự động: Sử dụng thư viện mbbank để khai thác API MBBank, lấy lịch sử giao dịch mỗi phút.
  • Phát hiện giao dịch mới: Chỉ gửi thông báo khi có giao dịch chưa từng thấy.
  • Gửi qua Telegram: Nhận thông báo tức thì trên điện thoại.
  • Gửi qua Webhook: Chuyển dữ liệu giao dịch mới đến URL của bạn.
  • Quản lý qua API: Dễ dàng bật/tắt, chỉnh config, kiểm tra trạng thái, xem log.

Chuẩn bị trước khi dùng API MBBank

1. Máy tính/Server

  • Dùng Ubuntu (hoặc bất kỳ OS nào chạy Node.js được).

Cài Node.js (khuyên dùng phiên bản LTS, ví dụ: v18.x):

sudo apt update
sudo apt install -y nodejs npm
node -v  # Kiểm tra phiên bản

2. Cài Tesseract-OCR (bắt buộc)

  • Nếu dùng OS khác:

Thư viện mbbank yêu cầu Tesseract-OCR để xử lý captcha khi đăng nhập API MBBank. Cài đặt trên Ubuntu:

sudo apt update
sudo apt install -y tesseract-ocr
tesseract --version  # Kiểm tra phiên bản

3. Tài khoản MBBank

  • Cần username (thường là số điện thoại) và password đăng nhập MBBank online – đây là thông tin để truy cập API MBBank.
  • Số tài khoản (accountNumber) để lấy giao dịch MBBank tự động.

4. Telegram Bot

  • Chat với @BotFather trên Telegram:
    • /newbot, đặt tên bot (ví dụ: AutoBankBot).
    • Lấy telegramBotToken (dạng 123456789:AAH...).
    • Tìm chat.id trong kết quả (ví dụ: 987654321).

Chat với bot vừa tạo, gõ /start, rồi lấy telegramChatId:

curl https://api.telegram.org/bot<YourBotToken>/getUpdates

5. Webhook (tùy chọn)

  • Dùng một URL nhận dữ liệu (như https://webhook-test.com/...) để test. Nếu chưa có, dùng Webhook.site để tạo URL tạm.

Cài đặt và Code để Lấy Giao Dịch MBBank Tự Động

Bước 1: Tạo thư mục dự án

mkdir MBBankAuto
cd MBBankAuto
npm init -y

Bước 2: Cài thư viện cần thiết cho API MBBank

npm install express axios mbbank node-schedule rotating-file-stream express-rate-limit

Bước 3: Tạo file index.js

Copy code dưới đây vào index.js – đây là đoạn code chính để khai thác API MBBanklấy giao dịch MBBank tự động:

const express = require("express");
const axios = require("axios");
const https = require("https");
const { MB } = require("mbbank");
const schedule = require("node-schedule");
const fs = require("fs");
const rfs = require("rotating-file-stream");
const rateLimit = require("express-rate-limit");

const app = express();
const port = 3000;
const CONFIG_FILE = "config.json";
const STATE_FILE = "state.json";
const logStream = rfs.createStream("server.log", { size: "1M", maxFiles: 5 });

app.use(express.json());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));

const logToFile = (message) => {
    const timestamp = new Date().toISOString();
    logStream.write(`[${timestamp}] ${message}\n`);
};

const httpsAgent = new https.Agent({
    keepAlive: true,
    family: 4,
    rejectUnauthorized: false,
});

const sendTelegramMessage = async (message, retries = 3) => {
    const { telegramBotToken, telegramChatId } = loadConfig();
    if (!telegramBotToken || !telegramChatId) {
        const errorMsg = "⚠️ Chưa cấu hình Telegram bot token hoặc chat ID.";
        logToFile(errorMsg);
        return false;
    }

    const url = `https://api.telegram.org/bot${telegramBotToken}/sendMessage`;
    const payload = {
        chat_id: telegramChatId,
        text: message.slice(0, 4096),
    };

    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            logToFile(`📤 Đang gửi Telegram (lần ${attempt}): ${message}`);
            const response = await axios.post(url, payload, {
                httpsAgent,
                timeout: 15000,
            });
            if (response.data.ok) {
                logToFile(`📩 Gửi Telegram thành công: ${message}`);
                return true;
            } else {
                logToFile(`❌ Telegram trả về lỗi: ${JSON.stringify(response.data)}`);
                return false;
            }
        } catch (error) {
            const errorDetail = error.response
                ? `${error.response.status} - ${error.response.data.description || JSON.stringify(error.response.data)}`
                : `Network error: ${error.message} (Code: ${error.code || "Unknown"})`;
            const errorMsg = `❌ Lỗi gửi Telegram (lần ${attempt}): ${errorDetail}`;
            logToFile(errorMsg);
            if (attempt < retries) {
                logToFile(`⏳ Thử lại sau 3s...`);
                await new Promise(resolve => setTimeout(resolve, 3000));
            } else {
                logToFile(`❌ Đã thử ${retries} lần nhưng vẫn thất bại`);
                return false;
            }
        }
    }
    return false;
};

const sendToTargetUrl = async (transaction) => {
    const { targetUrl } = loadConfig();
    if (!targetUrl) {
        logToFile("⚠️ Chưa cấu hình targetUrl để gửi giao dịch mới.");
        return false;
    }

    try {
        logToFile(`📤 Đang gửi giao dịch mới đến ${targetUrl}: ${transaction.refNo}`);
        await axios.post(targetUrl, transaction, { httpsAgent, timeout: 15000 });
        logToFile(`📩 Gửi giao dịch mới đến ${targetUrl} thành công: ${transaction.refNo}`);
        return true;
    } catch (error) {
        const errorDetail = error.response
            ? `${error.response.status} - ${error.response.data || "No response data"}`
            : `Network error: ${error.message} (Code: ${error.code || "Unknown"})`;
        const errorMsg = `❌ Lỗi gửi giao dịch mới đến ${targetUrl}: ${errorDetail}`;
        logToFile(errorMsg);
        return false;
    }
};

const loadConfig = () => {
    try {
        if (fs.existsSync(CONFIG_FILE)) {
            return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
        }
    } catch (error) {
        logToFile(`❌ Lỗi đọc config: ${error.message}`);
    }
    return {
        targetUrl: "",
        isAutoSendEnabled: false,
        schedule: "*/1 * * * *",
        username: "",
        password: "",
        accountNumber: "",
        daysBack: 10,
        telegramBotToken: "",
        telegramChatId: "",
    };
};

const saveConfig = (config) => {
    try {
        fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf8");
    } catch (error) {
        logToFile(`❌ Lỗi lưu config: ${error.message}`);
    }
};

const loadState = () => {
    try {
        if (fs.existsSync(STATE_FILE)) {
            return JSON.parse(fs.readFileSync(STATE_FILE, "utf8"));
        }
    } catch (error) {
        logToFile(`❌ Lỗi đọc state: ${error.message}`);
    }
    return { lastPostDate: "01/01/2000 00:00:00", seenRefNos: [] };
};

const saveState = (state) => {
    try {
        fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf8");
    } catch (error) {
        logToFile(`❌ Lỗi lưu state: ${error.message}`);
    }
};

let config = loadConfig();
let state = loadState();

const getFormattedDate = (date) => {
    return `${date.getDate().toString().padStart(2, "0")}/${
        (date.getMonth() + 1).toString().padStart(2, "0")
    }/${date.getFullYear()}`;
};

const parseDateTime = (dateTimeStr) => {
    const [date, time] = dateTimeStr.split(" ");
    const [day, month, year] = date.split("/").map(Number);
    const [hours, minutes, seconds] = time.split(":").map(Number);
    return new Date(year, month - 1, day, hours, minutes, seconds);
};

const isWithinDaysBack = (dateTimeStr, daysBack) => {
    const transactionDate = parseDateTime(dateTimeStr);
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysBack);
    return transactionDate >= cutoffDate;
};

let mb;
const initMB = async () => {
    const { username, password } = config;
    mb = new MB({ username, password });
};

const fetchTransactions = async (retries = 2) => {
    const { accountNumber, daysBack } = config;
    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            const today = new Date();
            const pastDate = new Date(today);
            pastDate.setDate(today.getDate() - daysBack);

            logToFile(`📡 Đang lấy dữ liệu từ MB Bank (lần ${attempt})... (from: ${getFormattedDate(pastDate)}, to: ${getFormattedDate(today)})`);
            const transactions = await mb.getTransactionsHistory({
                accountNumber,
                fromDate: getFormattedDate(pastDate),
                toDate: getFormattedDate(today),
            });

            if (!Array.isArray(transactions)) {
                throw new Error(`Dữ liệu từ MB Bank không phải mảng: ${JSON.stringify(transactions)}`);
            }

            const newTransactions = [];
            let maxPostDate = parseDateTime(state.lastPostDate);

            state.seenRefNos = state.seenRefNos.filter(refNo => {
                const transaction = transactions.find(t => t && t.refNo === refNo);
                return transaction && isWithinDaysBack(transaction.postDate, daysBack);
            });

            for (const tx of transactions) {
                if (!tx || !tx.postDate || !tx.refNo) {
                    logToFile(`⚠️ Giao dịch không hợp lệ: ${JSON.stringify(tx)}`);
                    continue;
                }
                const txPostDate = parseDateTime(tx.postDate);
                if (txPostDate > maxPostDate) {
                    maxPostDate = txPostDate;
                }
                if (!state.seenRefNos.includes(tx.refNo) && isWithinDaysBack(tx.postDate, daysBack)) {
                    newTransactions.push(tx);
                    state.seenRefNos.push(tx.refNo);
                }
            }

            for (const tx of newTransactions) {
                const amount = tx.creditAmount !== "0" ? `+${tx.creditAmount}` : `-${tx.debitAmount}`;
                const message = `Giao dịch mới:\n- Thời gian: ${tx.postDate}\n- Số tiền: ${amount} VND\n- Mô tả: ${tx.transactionDesc}\n- Số dư: ${tx.balanceAvailable} VND`;

                const telegramSent = await sendTelegramMessage(message);
                if (!telegramSent) {
                    logToFile(`⚠️ Không gửi được thông báo Telegram cho giao dịch: ${tx.refNo}`);
                } else {
                    logToFile(`✅ Đã gửi thông báo Telegram cho giao dịch: ${tx.refNo}`);
                }

                const urlSent = await sendToTargetUrl(tx);
                if (!urlSent) {
                    logToFile(`⚠️ Không gửi được giao dịch mới đến targetUrl: ${tx.refNo}`);
                } else {
                    logToFile(`✅ Đã gửi giao dịch mới đến targetUrl: ${tx.refNo}`);
                }
            }

            state.lastPostDate = maxPostDate.toLocaleString("vi-VN", {
                day: "2-digit",
                month: "2-digit",
                year: "numeric",
                hour: "2-digit",
                minute: "2-digit",
                second: "2-digit",
                hour12: false,
            }).replace(/,/, "");
            saveState(state);

            return transactions;
        } catch (error) {
            if (error.message.includes("session") || error.message.includes("login")) {
                logToFile("🔄 Session hết hạn, đăng nhập lại...");
                await initMB();
                continue;
            }
            const errorMsg = `❌ Lỗi lấy dữ liệu MB Bank (lần ${attempt}): ${error.message}`;
            logToFile(errorMsg);
            if (attempt < retries) {
                logToFile(`⏳ Thử lại sau 5s...`);
                await new Promise(resolve => setTimeout(resolve, 5000));
            } else {
                await sendTelegramMessage(errorMsg);
                return { error: "Không thể lấy dữ liệu giao dịch sau nhiều lần thử" };
            }
        }
    }
};

schedule.scheduleJob("autoSend", { rule: config.schedule, tz: "Asia/Ho_Chi_Minh" }, async () => {
    const { isAutoSendEnabled } = config;
    if (!isAutoSendEnabled) {
        logToFile("⏸ Tự động gửi bị tắt.");
        return;
    }
    await fetchTransactions();
});

app.get("/transactions", async (req, res) => {
    const data = await fetchTransactions();
    res.json(data);
});

app.post("/set-url", (req, res) => {
    const { url } = req.body;
    if (!url || !/^https?:\/\/\S+$/.test(url)) {
        return res.status(400).json({ error: "URL không hợp lệ" });
    }
    config.targetUrl = url;
    saveConfig(config);
    logToFile(`✅ URL nhận dữ liệu cập nhật: ${url}`);
    res.json({ message: `✅ URL nhận dữ liệu đã cập nhật: ${url}` });
});

app.post("/toggle-auto-send", async (req, res) => {
    if (typeof req.body.enable !== "boolean") {
        return res.status(400).json({ error: "Giá trị enable phải là true hoặc false" });
    }
    config.isAutoSendEnabled = req.body.enable;
    saveConfig(config);
    const statusMsg = `✅ Tự động gửi dữ liệu: ${config.isAutoSendEnabled ? "BẬT" : "TẮT"}`;
    logToFile(statusMsg);
    await sendTelegramMessage(statusMsg);
    res.json({ message: statusMsg });
});

app.post("/set-config", (req, res) => {
    const { username, password, accountNumber, daysBack, telegramBotToken, telegramChatId } = req.body;
    if (username) config.username = username;
    if (password) config.password = password;
    if (accountNumber) config.accountNumber = accountNumber;
    if (daysBack && !isNaN(daysBack) && daysBack > 0) config.daysBack = parseInt(daysBack);
    if (telegramBotToken) config.telegramBotToken = telegramBotToken;
    if (telegramChatId) config.telegramChatId = telegramChatId;

    saveConfig(config);
    initMB();
    logToFile(`✅ Config cập nhật: ${JSON.stringify({ username, password, accountNumber, daysBack, telegramBotToken, telegramChatId })}`);
    res.json({ message: "✅ Config đã được cập nhật", config });
});

app.get("/test-telegram", async (req, res) => {
    const sent = await sendTelegramMessage("🔔 Test từ server!");
    res.json({ success: sent, message: sent ? "Gửi Telegram thành công" : "Gửi Telegram thất bại, xem log để biết chi tiết" });
});

app.get("/status", (req, res) => {
    res.json({
        autoSendEnabled: config.isAutoSendEnabled,
        targetUrl: config.targetUrl || "Chưa cấu hình",
        schedule: config.schedule,
        username: config.username,
        accountNumber: config.accountNumber,
        daysBack: config.daysBack,
        telegramConfigured: !!config.telegramBotToken && !!config.telegramChatId,
        lastPostDate: state.lastPostDate,
        seenRefNosCount: state.seenRefNos.length,
    });
});

app.get("/logs", (req, res) => {
    try {
        const logData = fs.readFileSync("server.log", "utf8").split("\n").filter(Boolean);
        const logJson = logData.slice(-200).map(line => {
            const match = line.match(/\[(.*?)\] (.*)/);
            return match ? { timestamp: match[1], message: match[2] } : { message: line };
        });
        res.json(logJson);
    } catch (error) {
        res.status(500).json({ error: "Không thể đọc log" });
    }
});

app.listen(port, async () => {
    await initMB();
    logToFile(`🚀 API server chạy tại http://localhost:${port}`);
    await sendTelegramMessage("🚀 Server đã khởi động!");
});

Bước 4: Tạo file config.json

Tạo file config.json trong thư mục dự án, điền thông tin của bạn:

{
  "targetUrl": "https://webhook-test.com/your-webhook-id",
  "isAutoSendEnabled": false,
  "schedule": "*/1 * * * *",
  "username": "your-mbbank-username",
  "password": "your-mbbank-password",
  "accountNumber": "your-mbbank-account-number",
  "daysBack": 7,
  "telegramBotToken": "your-telegram-bot-token",
  "telegramChatId": "your-telegram-chat-id"
}

Hướng Dẫn Chạy và Sử Dụng API MBBank để Lấy Giao Dịch Tự Động

1. Khởi động server

node index.js
  • Telegram sẽ nhận tin nhắn "🚀 Server đã khởi động!" nếu cấu hình đúng.
  • Xem log: curl http://localhost:3000/logs.

2. Bật/Tắt tự động gửi

    • Telegram nhận "✅ Tự động gửi dữ liệu: BẬT".
    • Telegram nhận "✅ Tự động gửi dữ liệu: TẮT".
  • Lưu ý quan trọng: Trước khi đăng nhập app MBBank để chuyển khoản, tắt tự động gửi bằng lệnh trên để tránh bị thoát phiên đăng nhập của code do cơ chế bảo mật của API MBBank.

Tắt:

curl -X POST -H "Content-Type: application/json" -d '{"enable": false}' http://localhost:3000/toggle-auto-send

Bật:

curl -X POST -H "Content-Type: application/json" -d '{"enable": true}' http://localhost:3000/toggle-auto-send

3. Test gửi Telegram

curl http://localhost:3000/test-telegram
  • Telegram nhận "🔔 Test từ server!" → OK.

4. Cập nhật cấu hình cho API MBBank

    • Chỉ cần điền các trường muốn thay đổi, không cần điền hết.

Chỉnh thông tin như username, password, webhook URL, v.v.:

curl -X POST -H "Content-Type: application/json" -d '{"username": "new-username", "password": "new-password", "accountNumber": "new-account", "daysBack": 10, "telegramBotToken": "new-token", "telegramChatId": "new-chat-id"}' http://localhost:3000/set-config

5. Cập nhật Webhook URL

Đặt hoặc đổi URL nhận giao dịch mới:

curl -X POST -H "Content-Type: application/json" -d '{"url": "https://new-webhook-url.com"}' http://localhost:3000/set-url

6. Kiểm tra trạng thái hệ thống

Trả về JSON với thông tin như:

{
  "autoSendEnabled": true,
  "targetUrl": "https://webhook-test.com/...",
  "schedule": "*/1 * * * *",
  "username": "your-username",
  "accountNumber": "your-account",
  "daysBack": 7,
  "telegramConfigured": true,
  "lastPostDate": "23/02/2025 07:12:00",
  "seenRefNosCount": 10
}

Xem trạng thái hệ thống:

curl http://localhost:3000/status

7. Xem log hoạt động

Trả về JSON với các dòng log, ví dụ:

[
  {"timestamp": "2025-02-23T07:12:00.000Z", "message": "✅ Đã gửi thông báo Telegram cho giao dịch: FT25053439080611"},
  {"timestamp": "2025-02-23T07:12:01.000Z", "message": "✅ Đã gửi giao dịch mới đến targetUrl: FT25053439080611"}
]

Kiểm tra log (200 dòng gần nhất):

curl http://localhost:3000/logs

8. Theo dõi giao dịch mới với API MBBank

  • Server kiểm tra mỗi phút qua API MBBank, khi có giao dịch mới:
    • Telegram nhận thông báo (ví dụ: "Giao dịch mới: ...").
    • targetUrl nhận POST request với JSON của giao dịch mới.

Xử lý lỗi khi dùng API MBBank để Lấy Giao Dịch Tự Động

  1. Lỗi "Cannot read properties of undefined (reading 'find')":
    • Nguyên nhân: API MBBank thỉnh thoảng trả dữ liệu không đúng, thường 1-2 tiếng/lần.
    • Cách xử lý: Code tự retry 2 lần, cách nhau 5s. Nếu vẫn lỗi, bạn nhận thông báo qua Telegram.
  2. Telegram không gửi được:
    • Kiểm tra telegramBotTokentelegramChatId trong config.json.
  3. Webhook không nhận dữ liệu:

Kiểm tra targetUrl có đúng không:

curl -X POST -H "Content-Type: application/json" -d '{"test": "data"}' your-target-url

Test mạng:

curl -X POST -H "Content-Type: application/json" -d '{"chat_id": "your-chat-id", "text": "Test"}' https://api.telegram.org/bot<YourBotToken>/sendMessage

Tùy chỉnh nâng cao cho Lấy Giao Dịch MBBank Tự Động

  • Thay đổi tần suất: Chỉnh schedule trong config.json (ví dụ: "*/2 * * * *" để 2 phút/lần).
  • Tăng số ngày: Điều chỉnh daysBack để lấy giao dịch xa hơn qua API MBBank.

Chạy 24/7: Dùng PM2 để chạy liên tục:

npm install -g pm2
pm2 start index.js --name "MBBankAuto"
pm2 logs

Lưu ý quan trọng khi dùng API MBBank

Đăng nhập app MBBank: Khi cần đăng nhập ứng dụng MBBank (ví dụ: để chuyển khoản), phiên đăng nhập của code sẽ bị thoát do cơ chế bảo mật của API MBBank chỉ cho phép một phiên hoạt động. Hãy tắt tự động gửi trước:

curl -X POST -H "Content-Type: application/json" -d '{"enable": false}' http://localhost:3000/toggle-auto-send

Sau khi xong việc trên app, bật lại bằng "enable": true.


Lời cảm ơn

Cảm ơn tác giả thư viện mbbank (CookieGMVN/MBBank) đã cung cấp công cụ tuyệt vời để khai thác API MBBank. Cũng cảm ơn người bạn đã cùng mình test và hoàn thiện hệ thống lấy giao dịch MBBank tự động này – bạn là nguồn cảm hứng lớn để mình viết bài này!

Kết luận

Vậy là xong! Bạn đã có một hệ thống sử dụng API MBBank để lấy giao dịch MBBank tự động, gửi qua Telegram và webhook khi có giao dịch mới, cùng các tính năng quản lý qua API. Code này đã được test thực tế, chạy ổn định, xử lý lỗi tốt. Nếu gặp vấn đề, cứ comment dưới blog, mình sẽ hỗ trợ ngay!

Chúc các bạn thành công và kiếm được kha khá từ tự động hóa với API MBBank nhé! 😎