Skip to content

a parser to convert Static QRIS to Dynamic QRIS

bang_syaii's profile photo@bang_syaii Sep 15, 2026 16

Function JavaScript untuk mengonversi QRIS statis menjadi QRIS dinamis dengan nominal transaksi tertentu, lengkap dengan perhitungan ulang CRC-16 secara otomatis.

javascript · 105 lines
class Parser {
  constructor() {
    this.qris = "";
  }

  /**
   * Converts a static QRIS to a dynamic QRIS by adding a transaction amount.
   *
   * Steps:
   * 1. Removes the old tag 54 (Transaction Amount) from the static QRIS
   * 2. Inserts a new tag 54 with the desired amount before tag 63 (CRC)
   * 3. Recalculates the CRC-16 CCITT to ensure data integrity
   * 4. Stores the resulting dynamic QRIS in this.qris
   *
   * @param {string} qris - Static QRIS string to be converted to dynamic
   * @param {string} amount - Transaction amount in string format (e.g. "10000")
   * @returns {string} the dynamic QRIS
   * @throws {Error} if the QRIS is invalid or tag 63 is not found
   *
   * @example
   * const parser = new Parser();
   * const dynamicQRIS = parser.parseQris("00020101021126...", "50000");
   */
  parseQris(qris, amount) {
    const amountLength = amount.length;
    const tag54 = `54${String(amountLength).padStart(2, "0")}${amount}`;

    qris = this.removeTag54(qris);

    const tag63Position = qris.indexOf("6304");
    if (tag63Position === -1) {
      throw new Error("tag 63 not found");
    }

    const modifiedPayload =
      qris.slice(0, tag63Position) + tag54 + qris.slice(tag63Position);

    const payloadWithoutCrc = modifiedPayload.slice(0, -4);
    const newCrc = this.calculateCrc(payloadWithoutCrc);

    this.qris = `${payloadWithoutCrc}${newCrc}`;
    return this.qris;
  }

  removeTag54(qris) {
    let position = 0;
    let result = "";

    while (position < qris.length) {
      if (position + 2 > qris.length) {
        throw new Error("invalid qris");
      }

      const tag = qris.substring(position, position + 2);
      position += 2;

      if (position + 2 > qris.length) {
        throw new Error("invalid qris");
      }

      const lengthStr = qris.substring(position, position + 2);
      const length = parseInt(lengthStr, 10);
      if (Number.isNaN(length)) {
        throw new Error("invalid qris");
      }
      position += 2;

      if (position + length > qris.length) {
        throw new Error("invalid qris");
      }

      const value = qris.substring(position, position + length);
      position += length;

      if (tag !== "54") {
        result += tag + String(length).padStart(2, "0") + value;
      }
    }

    return result;
  }

  calculateCrc(payload) {
    let crc = 0xffff;
    const polynomial = 0x1021;

    for (let i = 0; i < payload.length; i++) {
      crc ^= payload.charCodeAt(i) << 8;

      for (let j = 0; j < 8; j++) {
        if ((crc & 0x8000) !== 0) {
          crc = ((crc << 1) ^ polynomial) & 0xffff;
        } else {
          crc = (crc << 1) & 0xffff;
        }
      }
    }

    return crc.toString(16).toUpperCase().padStart(4, "0");
  }
}

module.exports = Parser;
// Untuk ES Module, ganti baris di atas dengan: export default Parser;
0 0

Comments (0)

No comments yet

Be the first to share feedback or ask a question.

Related snippets

See more javascript