API QRIS Parser: Cara Baca dan Validasi QRIS Toko dari Gambar Sebelum Dicetak

Oleh , Tim Engineering APIPedia · Terbit · Diperbarui · 10 menit baca

API QRIS Parser APIPedia membaca QRIS dari gambar, mengurai nama merchant, NMID, dan nominal, lalu memeriksa CRC lewat dua endpoint POST.

Sampul APIPedia: judul "Baca dan cek QRIS toko sebelum dicetak" dengan chip POST /v1/qr/decode dan POST /v1/qris/parse, serta kartu fakta QRIS Parser v1.0.0: contoh TOKO CONTOH SEJAHTERA, crc_valid true, metode statis, bukan verifikasi merchant terdaftar.

API QRIS Parser APIPedia mengurai string QRIS menjadi nama merchant, kota, MCC, NMID, dan nominal, lalu menghitung ulang CRC untuk memastikan payload-nya utuh. Anda mengirim payload ke POST /v1/qris/parse, atau mengirim gambar QR ke POST /v1/qr/decode dan menerima hasil urai QRIS di respons yang sama.

Tutorial ini memakai kasus nyata: merchant mengunggah gambar QRIS statis ke aplikasi kasir atau marketplace Anda, lalu backend memeriksanya sebelum gambar itu dicetak menjadi stiker atau ditampilkan di halaman pembayaran. Semua contoh memakai kunci apk_xxx dan data merchant fiktif. Satu hal perlu jelas sejak awal: pemeriksaan ini berjalan offline atas struktur payload, bukan verifikasi bahwa merchant terdaftar.

Ringkasan cepat: apa yang perlu Anda tahu?

Alur ini memakai dua endpoint POST dari dua produk di katalog: QR Code Generator untuk membaca gambar dan QRIS Parser untuk mengurai payload. Tabel berikut merangkum intinya.

ItemDetail
Baca gambar QRPOST https://api.apipedia.id/v1/qr/decode (PNG, JPEG, WebP, GIF, BMP; maks. 8 MB)
Urai payloadPOST https://api.apipedia.id/v1/qris/parse dengan body {"payload": "000201..."}
AutentikasiHeader Authorization: Bearer apk_xxx
Yang diuraiNama, kota, kode pos, negara, MCC, NMID, kriteria merchant, mata uang, nominal, data tambahan tag 62
Pemeriksaan keutuhanCRC-16/CCITT-FALSE dihitung ulang, hasilnya di crc_valid
Jenis QRISmetode.keterangan: statis (kode 11) atau dinamis (kode 12)
Yang tidak dicekApakah merchant terdaftar atau aktif di penyelenggara jasa pembayaran
TierFree pada katalog, versi QRIS Parser 1.0.0

Masalah apa yang diselesaikan API QRIS Parser?

API ini menangkap gambar QRIS yang rusak, salah jenis, atau bukan milik merchant yang bersangkutan sebelum gambar itu dipakai menerima pembayaran. Tanpa pemeriksaan, tim operasional baru tahu ada masalah ketika pembeli gagal memindai atau uang masuk ke merchant lain.

Tiga kesalahan paling sering muncul saat merchant mengunggah QRIS sendiri:

  • Gambar bukan QRIS. Yang diunggah ternyata QR tautan toko atau QR kontak.
  • QRIS dinamis dijadikan stiker. QRIS dinamis dibuat untuk satu tagihan dengan nominal tertentu, jadi tidak cocok dipajang permanen.
  • Nama merchant tidak cocok. QRIS milik toko lain terunggah, sengaja atau tidak.

Dengan dua request, backend Anda bisa menolak ketiganya secara otomatis dan menyimpan data merchant yang sudah terstruktur.

Bagaimana struktur payload QRIS dibaca?

Payload QRIS adalah deretan potongan TLV (tag, length, value) mengikuti standar EMVCo: dua digit tag, dua digit panjang, lalu nilainya. Contohnya 5921TOKO CONTOH SEJAHTERA berarti tag 59 (nama merchant), panjang 21 karakter, dengan nilai "TOKO CONTOH SEJAHTERA".

Diagram anatomi payload QRIS berformat TLV EMVCo: setiap potongan berisi tag, panjang, lalu nilai. Contoh potongan: 00 02 01 payload format; 01 02 11 metode, 11 statis dan 12 dinamis; tag 26 sampai 51 berisi sub-TLV akun merchant termasuk NMID; 52 04 5411 MCC kategori usaha; 53 03 360 mata uang rupiah; 54 06 393000 nominal yang hanya ada di QRIS dinamis; 59 21 nama merchant; 60 07 BANDUNG kota merchant; 63 04 10F7 adalah CRC-16/CCITT-FALSE atas seluruh payload, sehingga satu karakter berubah membuat nilainya tidak cocok. Hasil dari API: merchant.nama, merchant.nmid, metode.keterangan, transaksi.nominal, crc_valid.

Tag terakhir, 63, berisi CRC-16/CCITT-FALSE yang dihitung dari seluruh payload sebelumnya. Bila satu karakter saja berubah, CRC yang tertera tidak lagi sama dengan CRC hasil hitung ulang. API melaporkan keduanya lewat crc_tertera dan crc_dihitung.

Apa yang perlu disiapkan sebelum mulai?

Anda hanya butuh satu kunci API APIPedia. Daftar atau masuk di console.apipedia.id, buka menu Token API, lalu buat kunci baru. Kunci berawalan apk_ dan dikirim lewat header Authorization: Bearer apk_xxx.

Simpan kunci di variabel lingkungan server, misalnya APIPEDIA_KEY, dan panggil API dari backend, bukan dari browser.

Langkah 1: bagaimana mengurai payload QRIS dengan curl?

Kirim string QRIS lengkap, yang diawali 000201, sebagai field payload ke POST /v1/qris/parse. Payload di bawah adalah QRIS dinamis fiktif senilai Rp 393.000.

curl -s -X POST https://api.apipedia.id/v1/qris/parse \
  -H "Authorization: Bearer apk_xxx" \
  -H "Content-Type: application/json" \
  -d '{"payload": "00020101021226660016ID.CO.CONTOH.WWW01189360000000000000010213CONTOH00000010303UMI51440014ID.CO.QRIS.WWW0215ID10200000000010303UMI52045411530336054063930005802ID5921TOKO CONTOH SEJAHTERA6007BANDUNG61054011162220111INV202600420703K016304A7D0"}'

Seperti apa contoh respons API QRIS Parser?

Respons memakai amplop standar APIPedia { is_success, message, data }, dengan ringkasan merchant dan transaksi di bagian atas dan daftar tag mentah di tags. Berikut respons nyata untuk payload di atas, dengan isi tags dipersingkat:

{
  "is_success": true,
  "message": "Payload QRIS berhasil diuraikan",
  "data": {
    "valid_tlv": true,
    "crc_valid": true,
    "crc_tertera": "A7D0",
    "crc_dihitung": "A7D0",
    "payload_format": "01",
    "metode": { "kode": "12", "keterangan": "dinamis" },
    "merchant": {
      "nama": "TOKO CONTOH SEJAHTERA",
      "kota": "BANDUNG",
      "kode_pos": "40111",
      "negara": "ID",
      "mcc": "5411",
      "nmid": "CONTOH0000001",
      "kriteria": "UMI"
    },
    "transaksi": { "mata_uang": "360", "nominal": 393000 },
    "tags": [
      { "tag": "00", "nama": "Payload Format Indicator", "panjang": 2, "nilai": "01" },
      { "tag": "62", "nama": "Additional Data Field Template", "panjang": 22,
        "nilai": "0111INV202600420703K01",
        "sub": [
          { "tag": "01", "nama": "Bill Number", "panjang": 11, "nilai": "INV20260042" },
          { "tag": "07", "nama": "Terminal Label", "panjang": 3, "nilai": "K01" }
        ] },
      { "tag": "63", "nama": "CRC", "panjang": 4, "nilai": "A7D0" }
    ],
    "catatan": "Hasil ini adalah analisis struktur payload secara offline, bukan verifikasi merchant ke ASPI atau penyelenggara jasa pembayaran. crc_valid hanya menandakan payload utuh, bukan bahwa merchant terdaftar atau aktif."
  }
}
FieldArti
valid_tlvtrue bila seluruh payload bisa dibaca sebagai TLV.
crc_validtrue bila crc_tertera sama dengan crc_dihitung.
metodeKode 11 berarti statis, kode 12 berarti dinamis.
merchantNama, kota, kode pos, negara, MCC, NMID, dan kriteria merchant.
transaksi.nominalAngka nominal pada QRIS dinamis, atau null pada QRIS statis.
tagsSemua tag mentah beserta sub-TLV, berguna untuk audit atau debugging.

Langkah 2: bagaimana membaca QRIS langsung dari gambar?

Kirim gambar sebagai base64 ke POST /v1/qr/decode, dan Anda tidak perlu memanggil parser secara terpisah. Bila isi QR dikenali sebagai QRIS, endpoint ini langsung menyertakan hasil urainya di symbols[].parsed dengan type: "qris".

jq -n --arg img "$(base64 -w0 qris-toko.png)" '{image_base64: $img}' \
  | curl -s -X POST https://api.apipedia.id/v1/qr/decode \
      -H "Authorization: Bearer apk_xxx" \
      -H "Content-Type: application/json" \
      -d @-

Kami menguji endpoint ini dengan gambar QRIS statis fiktif berukuran 160 piksel. Respons nyatanya, dipersingkat:

{
  "is_success": true,
  "message": "1 barcode berhasil dibaca",
  "data": {
    "count": 1,
    "symbols": [
      {
        "format": "QRCode",
        "text": "00020101021126660016ID.CO.CONTOH.WWW...5921TOKO CONTOH SEJAHTERA6007BANDUNG610540111630410F7",
        "encoding": "ascii",
        "parsed": {
          "type": "qris",
          "valid_tlv": true,
          "crc_valid": true,
          "metode": { "kode": "11", "keterangan": "statis" },
          "merchant": { "nama": "TOKO CONTOH SEJAHTERA", "kota": "BANDUNG", "mcc": "5411", "nmid": "CONTOH0000001" },
          "transaksi": { "mata_uang": "360", "nominal": null }
        }
      }
    ],
    "image": { "mime": "image/png", "bytes": 2206 }
  }
}

Gambar tanpa QR bukan error. Responsnya tetap sukses dengan count: 0, jadi periksa count sebelum membaca symbols[0]. Nama field body bisa dicek ulang di spesifikasi OpenAPI produk QR Code Generator pada konsol.

Langkah 3: bagaimana memasangnya di backend Node.js?

Buat satu fungsi yang menerima berkas gambar dan nama merchant terdaftar, lalu mengembalikan keputusan terima atau tolak. Contoh ini berjalan di Node.js 18 ke atas tanpa dependensi tambahan.

import { readFile } from "node:fs/promises";

const BASE = "https://api.apipedia.id/v1";
const headers = {
  Authorization: `Bearer ${process.env.APIPEDIA_KEY}`, // apk_xxx
  "Content-Type": "application/json",
};

const rapikan = (s) => String(s ?? "").toUpperCase().replace(/[^A-Z0-9]/g, "");

export async function periksaQrisToko(pathGambar, namaTerdaftar) {
  const image_base64 = (await readFile(pathGambar)).toString("base64");
  const res = await fetch(`${BASE}/qr/decode`, {
    method: "POST",
    headers,
    body: JSON.stringify({ image_base64, max_symbols: 1 }),
  });
  const body = await res.json();
  if (!body.is_success) throw new Error(`HTTP ${res.status}: ${body.message}`);

  if (body.data.count === 0) return { ok: false, alasan: "Tidak ada QR pada gambar." };

  const q = body.data.symbols[0].parsed;
  if (q?.type !== "qris") return { ok: false, alasan: "QR terbaca, tetapi isinya bukan QRIS." };
  if (!q.crc_valid) return { ok: false, alasan: "Payload QRIS tidak utuh (CRC tidak cocok)." };
  if (q.metode.keterangan !== "statis") {
    return { ok: false, alasan: "Ini QRIS dinamis untuk satu tagihan. Unggah QRIS statis." };
  }
  if (rapikan(q.merchant.nama) !== rapikan(namaTerdaftar)) {
    return { ok: false, alasan: `Nama di QRIS "${q.merchant.nama}" berbeda dari data pendaftaran.` };
  }

  // Simpan data terstruktur dan payload aslinya, bukan hanya gambarnya.
  return {
    ok: true,
    merchant: q.merchant,
    payload: body.data.symbols[0].text,
  };
}

console.log(await periksaQrisToko("qris-toko.png", "Toko Contoh Sejahtera"));

Fungsi ini memeriksa lima hal secara berurutan: ada QR, isinya QRIS, CRC cocok, jenisnya statis, dan namanya sama dengan data pendaftaran. Perbandingan nama mengabaikan huruf besar-kecil, spasi, dan tanda baca. Untuk nama yang disingkat di QRIS, arahkan ke tinjauan manual, jangan langsung ditolak.

Langkah 4: bagaimana mengurai payload di Python?

Di Python, kirim payload dengan requests ke /qris/parse lalu baca crc_valid dan metode. Pola ini cocok bila payload sudah Anda dapat dari pemindai di aplikasi mobile.

import os

import requests  # pip install requests

BASE = "https://api.apipedia.id/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['APIPEDIA_KEY']}"}  # apk_xxx


def urai_qris(payload):
    res = requests.post(f"{BASE}/qris/parse", headers=HEADERS, json={"payload": payload}, timeout=10)
    body = res.json()

    if res.status_code == 400:
        return {"ok": False, "alasan": body.get("message")}
    if not body.get("is_success"):
        raise RuntimeError(f"HTTP {res.status_code}: {body.get('message')}")

    data = body["data"]
    if not data["crc_valid"]:
        return {"ok": False, "alasan": "CRC tidak cocok, payload tidak utuh."}
    return {
        "ok": True,
        "nama": data["merchant"]["nama"],
        "nmid": data["merchant"]["nmid"],
        "jenis": data["metode"]["keterangan"],
        "nominal": data["transaksi"]["nominal"],
    }


if __name__ == "__main__":
    contoh = (
        "00020101021126660016ID.CO.CONTOH.WWW01189360000000000000010213CONTOH0000001"
        "0303UMI51440014ID.CO.QRIS.WWW0215ID10200000000010303UMI5204541153033605802ID"
        "5921TOKO CONTOH SEJAHTERA6007BANDUNG610540111630410F7"
    )
    print(urai_qris(contoh))
    print(urai_qris("https://contoh.co.id/bayar"))

Seperti apa respons untuk QRIS yang rusak atau bukan QRIS?

Payload yang diubah tetap dijawab status 200 dengan crc_valid: false, sedangkan teks yang bukan TLV dijawab 400. Kami menguji empat masukan terhadap POST /v1/qris/parse pada 12 November 2026.

Empat kartu hasil uji POST /v1/qris/parse beserta keputusannya. QRIS statis utuh: status 200, crc_valid true, metode statis, nominal null, layak dicetak setelah nama dicocokkan. QRIS dinamis: status 200, crc_valid true, metode dinamis, nominal 393000, perlu ditinjau karena dibuat untuk satu tagihan. Satu huruf diubah: status 200, crc_valid false, crc_tertera 10F7 dan crc_dihitung DE2B, ditolak karena payload tidak utuh. Bukan QRIS: status 400, is_success false, data null, masukan berupa URL biasa. Di luar cakupan: apakah merchant terdaftar atau aktif, karena crc_valid true hanya berarti payload utuh.

MasukanStatuscrc_validCatatan
QRIS statis utuh200truemetode statis, nominal null
QRIS dinamis200truemetode dinamis, nominal 393000
Satu huruf nama diubah200falsecrc_tertera 10F7, crc_dihitung DE2B
URL biasa400-Bukan TLV EMVCo

Respons 400 untuk masukan yang bukan QRIS berbentuk seperti ini:

{
  "is_success": false,
  "message": "Payload QRIS bukan TLV EMVCo yang sah: tag \"ht\" atau panjang \"tp\" pada posisi 0 bukan dua digit angka.",
  "data": null
}

Apa batas pemeriksaan ini?

Pemeriksaan ini hanya menjawab "apakah payload-nya utuh dan apa isinya", bukan "apakah merchant ini terdaftar dan aktif". Seluruh pekerjaan dilakukan offline dan tidak memanggil pihak mana pun, termasuk ASPI atau penyelenggara jasa pembayaran.

Artinya, crc_valid: true tidak membuktikan keaslian. Siapa pun yang menyusun payload dengan benar bisa menghitung CRC yang cocok. Karena itu, pakai hasilnya sebagai saringan pertama, cocokkan nama dan NMID dengan dokumen pendaftaran merchant, dan lakukan transaksi uji bernominal kecil sebelum QRIS dipakai luas.

Kesalahan umum apa yang perlu dihindari?

Kesalahan paling sering adalah menganggap is_success: true sebagai tanda QRIS lolos. Periksa juga hal-hal berikut.

  • Hanya membaca is_success. Nilainya true walau CRC tidak cocok. Keputusan ada di crc_valid.
  • Menganggap CRC cocok berarti merchant asli. CRC hanya menandakan payload utuh.
  • Tidak membedakan statis dan dinamis. Baca metode.keterangan sebelum menjadikan QRIS sebagai stiker tetap.
  • Mengirim payload yang terpotong. Spasi di nama merchant adalah bagian dari payload. Jangan di-trim atau diubah hurufnya.
  • Tidak mengecek count. Gambar tanpa QR dijawab sukses dengan count: 0.
  • Mengunggah foto buram. Minta gambar asli dari aplikasi merchant, bukan foto stiker dari jauh.
  • Memanggil dari browser. Kunci apk_xxx akan terbaca siapa pun. Panggil dari backend.

Kode status yang perlu ditangani

StatusArtiTindakan
200Payload berhasil diuraiBaca crc_valid dan metode
400Payload bukan TLV EMVCo yang sahTampilkan pesan "ini bukan QRIS" ke pengguna
401Token tidak ada, tidak dikenal, atau kedaluwarsaPeriksa header Authorization

Pertanyaan yang sering diajukan

Apakah API ini bisa memastikan merchant QRIS terdaftar?

Tidak. API ini menganalisis struktur payload secara offline. Ia tidak menghubungi ASPI atau penyelenggara jasa pembayaran, sehingga tidak tahu apakah merchant terdaftar atau aktif.

Apa bedanya QRIS statis dan dinamis di respons?

QRIS statis memiliki metode.kode 11 dan transaksi.nominal bernilai null, karena pembeli mengetik nominalnya sendiri. QRIS dinamis memiliki kode 12 dan biasanya membawa nominal serta nomor tagihan di tag 62.

Apa arti crc_valid: false?

Artinya CRC yang tertera di tag 63 berbeda dari hasil hitung ulang atas payload. Payload itu terpotong, salah salin, atau sudah diubah. Minta merchant mengunggah ulang gambar aslinya.

Apakah saya harus memanggil dua endpoint untuk gambar QRIS?

Tidak. POST /v1/qr/decode sudah mengembalikan hasil urai QRIS di symbols[].parsed. Endpoint /v1/qris/parse dipakai bila Anda sudah memegang string payload-nya.

Bisakah asisten AI memeriksa QRIS lewat API ini?

Bisa. Server MCP APIPedia menyediakan tool parse_qris dan qr_decode yang memanggil endpoint yang sama. Cara menyambungkannya ada di panduan menghubungkan APIPedia ke Claude lewat MCP.

Langkah berikutnya: coba di konsol

Pemeriksaan QRIS toko cukup butuh satu request POST /v1/qr/decode dan lima pengecekan di backend. Buat kunci di console.apipedia.id, jalankan contoh curl di atas dengan gambar QRIS Anda sendiri, lalu pasang fungsi periksaQrisToko di handler unggah.

Untuk melengkapi alur pendaftaran merchant, baca API OCR KTP untuk membaca e-KTP dan memeriksa NIK. Setelah pembayaran masuk, kirim buktinya dengan invoice PDF otomatis dari template HTML.

Topik:

Tentang penulis

Tim APIPedia — Tim Engineering APIPedia

Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.