bank/BRI.class.js


const moment = require("moment");
const ScraperBank = require("../BrowserClasses");
const RecaptchaSolver = require("puppeteer-recaptcha-bypass");
const BRISelectors = require("../helper/selector/BRISelector");

/**
 * ScrapBRI class for scraping BRI (Bank Rakyat Indonesia) data.
 * @class ScrapBRI
 * @extends ScraperBank
 */

class ScrapBRI extends ScraperBank {
  /**
   * Constructor for ScrapBRI class.
   * @constructor
   * @param {string} corpID - BRI corporate ID.
   * @param {string} user - BRI username.
   * @param {string} pass - BRI password.
   * @param {string} norek - BRI account number.
   * @param {string} apiKey - API key for RecaptchaSolver.
   * @param {boolean} useFingerprintInjector - Flag to use fingerprint injector.
   */
  constructor(corpID, user, pass, norek, apikey, useFingerprintInjector) {
    super(user, pass, useFingerprintInjector);
    this.corpID = corpID;
    this.norek = norek;
    this.apiKey = apikey
  }

  /**
   * Logs in to the BRI website.
   * @async
   * @returns {Object} - Page object.
   */
  async loginToBRI() {
    this.page = await this.launchBrowser();

    const selectors = BRISelectors.LOGIN_PAGE;
    await this.page.goto(selectors.url);
    await this.page.waitForSelector(selectors.corpID);
    await RecaptchaSolver(this.page, this.apiKey)
    await this.page.type(selectors.corpID, this.corpID);
    await this.page.type(selectors.userID, this.user);
    await this.page.type(selectors.passwordField, this.pass);
    await this.page.click(selectors.submitButton);
    await this.page.keyboard.press("Enter");
    await this.page.waitForNavigation();
    await this.page.goto(selectors.url);
    await this.page.waitForSelector(selectors.debitAccountView, {
      timeout: 1000,
    });

    return this.page;
  }

  /**
   * Gets the account statement from the BRI website.
   * @async
   * @returns {Object} - Account statement data.
   */
  async getStatement() {
    if (!this.apiKey) {
      return {
        status: false,
        message: "you must include the api key"
      }
    }
    try {
      const selectors = BRISelectors.LOGIN_PAGE;
      const page = await this.loginToBRI();
      await page.click(selectors.debitAccountView);
      await page.waitForSelector(selectors.selectResultsOptions);

      const options = await page.$$eval(selectors.selectResultsOptions, (nodes) =>
        nodes.map((n) => ({
          value: n.innerText,
          id: n.getAttribute("id"),
        }))
      );

      const option = options.find((o) => o.value.includes(this.norek));
      let accountValue;
      if (option) {
        await page.click(`li#${option.id}`);
        accountValue = await page.$$eval(
          `option`,
          (options, val) => {
            const element = options.find((option) =>
              option.textContent.includes(val)
            );
            return element ? element.value : null;
          },
          option.value
        );
      } else {
        console.log("Option not found");
      }

      const token = await page.$eval(selectors.tokenInput, (el) => el.value);
      const formattedDate = this.formatCurrentDate();
      await page.type(selectors.periodeView, formattedDate);
      await page.waitForSelector(selectors.applyDateButton);
      await page.click(selectors.applyDateButton);
      await page.click(selectors.submitViewButton);

      const data = await this.fetchAccountDetails(accountValue, formattedDate, token);

      await page.goto("https://newbiz.bri.co.id/logout");
      const result = this.parseAccountData(data);

      return result;
    } catch (error) {
      console.log(error)
    }
  }

  /**
   * Fetches account details from the BRI website.
   * @async
   * @param {string} accountValue - Account value.
   * @param {string} formattedDate - Formatted date.
   * @param {string} token - Security token.
   * @returns {Object} - Account details data.
   */
  async fetchAccountDetails(accountValue, formattedDate, token) {
    const accountOption = await this.page.evaluate(
      async (accountValue, formattedDate, token) => {
        const response = await fetch(
          "https://newbiz.bri.co.id/account/requestView",
          {
            headers: {
              accept: "*/*",
              "accept-language": "en-US,en;q=0.9",
              "content-type": "application/json",
              "sec-ch-ua":
                '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"',
              "sec-ch-ua-mobile": "?0",
              "sec-ch-ua-platform": '"Linux"',
              "sec-fetch-dest": "empty",
              "sec-fetch-mode": "cors",
              "sec-fetch-site": "same-origin",
              "x-requested-with": "XMLHttpRequest",
            },
            referrer: "https://newbiz.bri.co.id/accountinformation",
            referrerPolicy: "strict-origin-when-cross-origin",
            body: JSON.stringify({
              debitaccount: accountValue,
              periode: formattedDate,
              _token: token,
            }),
            method: "POST",
            mode: "cors",
            credentials: "include",
          }
        );
        const jsonResponse = await response.json();
        return jsonResponse;
      },
      accountValue,
      formattedDate,
      token
    );
    const data = accountOption.data.list;
    return data;
  }

  /**
   * Formats the current date.
   * @returns {string} - Formatted date string.
   */
  formatCurrentDate() {
    const currentDate = new Date();
    const formattedDate = `${currentDate
      .getDate()
      .toString()
      .padStart(2, "0")}-${(currentDate.getMonth() + 1)
        .toString()
        .padStart(2, "0")}-${currentDate.getFullYear()} - ${currentDate
          .getDate()
          .toString()
          .padStart(2, "0")}-${(currentDate.getMonth() + 1)
            .toString()
            .padStart(2, "0")}-${currentDate.getFullYear()}`;
    return formattedDate;
  }

  /**
   * Parses account data received from the BRI website.
   * @param {Object} data - Raw account data.
   * @returns {Array} - Parsed account data.
   */
  parseAccountData(data) {

    let newData = data.map((obj) => {
      if (obj.DESK_TRAN === "Transaksi tidak ditemukan") {
        return data;
      } else {
        let tanggal = obj.TGL_TRAN.slice(8, 10) + "/" + obj.TGL_TRAN.slice(5, 7);
        let mutasi = obj.MUTASI_DEBET == "0.00" ? "CR" : "DB";
        let nominal = (
          obj.MUTASI_DEBET == "0.00" ? obj.MUTASI_KREDIT : obj.MUTASI_DEBET
        ).replace(".00", "");
        let saldoakhir = obj.SALDO_AKHIR_MUTASI.replace(".00", "");
        let words = obj.DESK_TRAN.split(" ");
        let nama = words[1] + " " + words[2];
        const currentYear = moment().year();
        const parsedDate = moment(tanggal + "/" + currentYear, "DD/MM/YYYY");
        const formattedDate = parsedDate.format("YYYY-MM-DD HH:mm:ss");
        return {
          tanggal: formattedDate,
          keterangan: obj.DESK_TRAN,
          name: nama,
          mutasi,
          nominal,
          saldoakhir,
        };
      }

    });
    return newData;
  }
}

module.exports = ScrapBRI;