npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

qris-saurus

v1.0.0

Published

Bun/TypeScript SDK to parse, validate, and transform static QRIS into dynamic QRIS.

Downloads

247

Readme

qris-saurus

qris-saurus hero

Bun/TypeScript SDK untuk parse, validasi, deteksi provider, dan transformasi QRIS statis menjadi QRIS dinamis.

npm version license ci status bun

English | Indonesian

Table of Contents

Apa itu QRIS?

QRIS adalah standar QR payment di Indonesia yang menyatukan banyak metode pembayaran di bawah satu format QR. Secara teknis, payload QRIS adalah string TLV (Tag-Length-Value) berbasis spesifikasi EMVCo. Setiap segmen punya:

  • Tag: identitas field, misalnya 54 untuk amount
  • Length: panjang isi field
  • Value: isi field itu sendiri

Contoh sederhananya:

540812500.00

Artinya:

  • 54 = transaction amount
  • 08 = panjang value
  • 12500.00 = nilai amount

Bagaimana QRIS bekerja?

Secara umum, alurnya seperti ini:

  1. Merchant memiliki QRIS payload
    • bisa QRIS statis dari acquirer/gateway
    • bisa QRIS dinamis yang sudah digenerate gateway
  2. Customer scan QR dengan app seperti ShopeePay, GoPay, mobile banking, atau aplikasi lain yang mendukung QRIS
  3. App membaca payload TLV dan menampilkan informasi merchant/transaksi
  4. Switching dan routing dilakukan oleh ekosistem pembayaran sesuai identifier merchant dan acquirer
  5. Issuer memproses pembayaran
  6. Merchant menerima notifikasi/settlement dari gateway atau acquirer

Library ini bekerja di lapisan payload construction/manipulation, bukan di lapisan settlement atau switching network.

sequenceDiagram
    participant M as Merchant
    participant C as Customer
    participant A as Payment App
    participant N as Payment Network
    participant I as Issuer

    M->>C: 1. Tampilkan QR Code
    C->>A: 2. Scan QR
    A->>A: 3. Baca payload TLV
    A->>N: 4. Routing & switching
    N->>I: 5. Proses pembayaran
    I-->>N: 6. Hasil
    N-->>A: 7. Settlement
    A-->>M: 8. Notifikasi

Static vs dynamic QRIS

QRIS statis

Biasanya dipakai untuk merchant display tetap. Nominal tidak tertanam di payload, sehingga customer mengisi nominal sendiri atau nominal ditentukan dari flow di sisi aplikasi pembayaran.

Ciri umumnya:

  • point of initiation method 11
  • bisa dipakai berkali-kali
  • tidak spesifik ke satu transaksi

QRIS dinamis

Dibuat untuk transaksi tertentu. Nominal dan data tambahan bisa disematkan ke payload.

Ciri umumnya:

  • point of initiation method 12
  • nominal transaksi ada di tag 54
  • dapat membawa reference tambahan di tag 62
  • lebih cocok untuk checkout, invoice, POS, dan order-based payments

Bagaimana qris-saurus bekerja?

qris-saurus mengikuti alur berikut:

  1. parse payload QRIS ke struktur TLV
  2. validate struktur dasar dan CRC
  3. detectProvider bila identifier provider dikenali
  4. transform QRIS statis menjadi dinamis
  5. serialize payload baru dan hitung ulang CRC
flowchart TD
    A[String QRIS Statis] --> B["parse() → TLV Nodes"]
    B --> C{"validate()\nCRC & tag valid?"}
    C -- Invalid --> X[Throw Error]
    C -- Valid --> D["Tag 01: 11 → 12\n(static → dynamic)"]
    D --> E["Sisipkan Tag 54 (amount)"]
    E --> F["Sisipkan Tag 62\n(merchant ref, terminal)"]
    F --> G["Recalculate CRC\n(Tag 63)"]
    G --> H["serialize()\n→ String QRIS Dinamis"]

Untuk fase sekarang, fokus utama library ini adalah transformasi lokal dari QRIS statis menjadi QRIS dinamis yang valid. Integrasi API gateway seperti Midtrans/Xendit/Duitku bisa ditambahkan kemudian sebagai layer terpisah.

Goals

  • Mengubah QRIS statis menjadi QRIS dinamis secara lokal
  • Memastikan payload tetap valid dengan CRC yang benar
  • Menyediakan fondasi provider-aware untuk ShopeePay, GoPay, Midtrans, Xendit, dan Duitku
  • Mudah di-import dari project Bun/TypeScript lain

How It Works

QRIS mengikuti EMVCo QR Code Specification menggunakan encoding TLV (Tag-Length-Value):

[Tag: 2 digit][Length: 2 digit][Value: variable]

Contoh annotasi payload nyata:

00020101021126360014ID.CO.QRIS.WWW0114GENERICSTORE01520458125303360
│    │    │    │
│    │    │    └─ 26: merchant account info (length 36)
│    │    └─────── 01: initiation method (length 2, value "11" = static)
│    └──────────── 00: format indicator  (length 2, value "01")
│
5802ID5911QRIS SAURUS6007JAKARTA63041669
│         │           │         │
│         │           │         └─ 63: CRC (length 4)
│         │           └──────────── 60: city (length 7)
│         └────────────────────────── 59: merchant name (length 11)
└──────────────────────────────────────── 58: country code (length 2)

Proses konversi static → dynamic

Saat staticToDynamic() dipanggil, library melakukan:

  1. Parse — payload dipecah menjadi array TLV nodes
  2. Validasi — cek kehadiran dan validitas CRC (tag 63)
  3. Ubah initiation method — tag 01 dari 1112
  4. Sisipkan amount — tambahkan tag 54 dengan nilai amount
  5. Sisipkan additional data — tag 62 berisi sub-tag:
    • 05 = merchant reference (bila ada)
    • 07 = terminal label (bila ada)
  6. Sisipkan tip — bila tipType diberikan, sisipkan tag root:
    • 55 = tip indicator (02 = fixed, 03 = percent)
    • 56 = nominal tip fixed
    • 57 = persentase tip
  7. Hitung ulang CRC — CRC16/CCITT atas seluruh payload kecuali 4 char terakhir
  8. Serialize — nodes dikembalikan ke string payload

Key QRIS Tags

| Tag | Nama | Contoh nilai | | --------- | ---------------------------- | ------------------------- | | 00 | Format indicator | 01 | | 01 | Initiation method | 11 statis, 12 dinamis | | 2651 | Merchant account info | per provider | | 52 | Merchant category code (MCC) | 5812 | | 53 | Currency code | 360 (IDR) | | 54 | Transaction amount | 25000.00 | | 55 | Tip or convenience indicator | 02 fixed, 03 percent | | 56 | Fixed convenience fee | 1000.00 | | 57 | Percentage convenience fee | 2.00 | | 58 | Country code | ID | | 59 | Merchant name | QRIS SAURUS | | 60 | Merchant city | JAKARTA | | 62 | Additional data field | sub-tag 05, 07, 08 | | 63 | CRC | 4 char hex |

Install

Install from your preferred package manager:

npm install qris-saurus
pnpm add qris-saurus
bun add qris-saurus

If you are working on this repository locally:

bun install

Configure Environment

Buat file .env dari template:

cp .env.example .env

.env.example:

# Midtrans
MIDTRANS_SERVER_KEY=SB-Mid-server-xxxxxxxxxxxxxxxxxxxx
MIDTRANS_SANDBOX=true

# Xendit
XENDIT_SECRET_KEY=xnd_development_xxxxxxxxxxxxxxxxxxxxxxxx

# Duitku
DUITKU_MERCHANT_CODE=Dxxxxx
DUITKU_MERCHANT_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DUITKU_SANDBOX=true

Gunakan dalam kode:

import { midtransAdapter, xenditAdapter, duitkuAdapter } from "qris-saurus";

const midtransConfig = {
  serverKey: process.env.MIDTRANS_SERVER_KEY!,
  sandbox: process.env.MIDTRANS_SANDBOX === "true",
};

const xenditConfig = {
  secretKey: process.env.XENDIT_SECRET_KEY!,
};

const duitkuConfig = {
  merchantCode: process.env.DUITKU_MERCHANT_CODE!,
  merchantKey: process.env.DUITKU_MERCHANT_KEY!,
  sandbox: process.env.DUITKU_SANDBOX === "true",
};

Quick start

import { makeDynamic, staticToDynamic, validate } from "qris-saurus";

const dynamicQris = staticToDynamic(staticQrisString, {
  amount: 12500,
  merchantRef: "INV-001",
  terminalLabel: "POS-A",
});

const result = makeDynamic(staticQrisString, {
  amount: 12500,
});

console.log(dynamicQris);
console.log(result.provider);
console.log(validate(dynamicQris));

Implementasi Sederhana

Ada dua cara menggunakan qris-saurus: sebagai package (SDK) di project TypeScript/Bun kamu, atau langsung via CLI di terminal.

flowchart LR
    subgraph SDK["Paket SDK"]
        direction TB
        S1["bun add qris-saurus"] --> S2["import & transform"]
        S2 --> S3["render / gateway"]
    end
    subgraph Terminal["CLI Terminal"]
        direction TB
        C1["bun run build"] --> C2["validate → dynamic"]
        C2 --> C3["render → PNG"]
    end

Package (SDK)

bun add qris-saurus
import { makeDynamic, renderQrToDataUrl, validate } from "qris-saurus";

const STATIC_QRIS = "00020101021126610016ID.CO.SHOPEE.WWW...";

// 1. Transformasi statis → dinamis
const { qrisString, provider } = makeDynamic(STATIC_QRIS, {
  amount: 25000,
  merchantRef: "INV-001",
});

// 2. Validasi hasil
validate(qrisString); // { valid: true, errors: [] }

// 3. Render ke gambar
const qrImage = await renderQrToDataUrl(qrisString, { width: 320 });
// <img src={qrImage} />

CLI

# Build CLI
bun run build

# Validasi payload
bun run dist/cli.js validate "000201010211..."

# Transformasi statis → dinamis
bun run dist/cli.js dynamic "000201010211..." --amount 25000 --merchant-ref INV-001

# Render ke file PNG
bun run dist/cli.js render "000201010211..." --output ./qris.png

Contoh Lengkap

Contoh standalone tersedia di folder examples:

bun run examples/basic.ts    # Core API: parse, validate, detect, transform
bun run examples/render.ts   # Render QR ke file dan data URL
bun run examples/gateway.ts  # Integrasi gateway (Midtrans, Xendit, Duitku)

Parse payload QRIS

import { parse } from "qris-saurus";

const qris =
  "00020101021126610016ID.CO.SHOPEE.WWW01189360091800230223530208230223530303UMI51440014ID.CO.QRIS.WWW0215ID10265163524850303UMI5204581753033605802ID5913Chick n booth6010PEKALONGAN61055118262070703A016304B9ED";

const parsed = parse(qris);

parsed.nodes.find((n) => n.id === "59")?.value; // "Chick n booth"
parsed.nodes.find((n) => n.id === "60")?.value; // "PEKALONGAN"
parsed.nodes.find((n) => n.id === "53")?.value; // "360" (IDR)
parsed.crc; // "B9ED"

Validasi payload

import { validate } from "qris-saurus";

const result = validate(qris);
// { valid: true, errors: [] }

const tampered = qris.slice(0, -4) + "0000";
const invalid = validate(tampered);
// { valid: false, errors: ["Invalid CRC value"] }

Deteksi provider

import { detectProvider, listProviders } from "qris-saurus";

const provider = detectProvider(qris);
console.log(provider?.info.code);    // "shopeepay"
console.log(provider?.info.name);    // "ShopeePay"
console.log(provider?.info.supportsApiDynamic); // false

const all = listProviders();
for (const p of all) {
  console.log(`${p.info.code}: ${p.info.name}`);
}

Transformasi statis ke dinamis

import { staticToDynamic } from "qris-saurus";

const dynamic = staticToDynamic(qris, {
  amount: 75000,
  merchantRef: "ORD-2024-001",
  terminalLabel: "POS-01",
});

Transformasi dengan tip

import { staticToDynamic } from "qris-saurus";

// Tip tetap (Rp 2.000)
const withFixedTip = staticToDynamic(qris, {
  amount: 50000,
  tipType: "fixed",
  tipValue: 2000,
});

// Tip persen (5%)
const withPercentTip = staticToDynamic(qris, {
  amount: 50000,
  tipType: "percent",
  tipValue: 5,
});

makeDynamic dengan deteksi provider

import { makeDynamic } from "qris-saurus";

const result = makeDynamic(qris, {
  amount: 25000,
  merchantRef: "INV-001",
});

console.log(result.source);   // "local" (transformasi lokal)
console.log(result.provider); // "shopeepay"
console.log(result.amount);   // 25000
console.log(result.qrisString); // payload dinamis baru

CRC dan serialisasi

import { computeCrc, verifyCrc, parse, serialize } from "qris-saurus";

// Hitung CRC dari payload (termasuk "6304" di akhir)
const payload = qris.slice(0, -4); // buang 4 char CRC, simpan "6304"
const crc = computeCrc(payload);
console.log(crc); // "1669"

// Verifikasi CRC pada string QRIS
const isValid = verifyCrc(qris);
console.log(isValid); // true

// Parse lalu serialize — payload harus sama
const parsed = parse(qris);
const reserialized = serialize(parsed);
console.log(qris === reserialized); // true

Render QR ke gambar

import { renderQrToDataUrl, renderQrToFile, makeDynamic } from "qris-saurus";

const { qrisString } = makeDynamic(qris, { amount: 50000 });

// Base64 data URL — langsung bisa dipakai di HTML
const dataUrl = await renderQrToDataUrl(qrisString, { width: 320 });
// data:image/png;base64,...

// Simpan ke file
await renderQrToFile(qrisString, "./qris.png", { width: 400, margin: 3 });

Error handling

import { validate, parse, makeDynamic, staticToDynamic } from "qris-saurus";

// validate() tidak pernah throw — kembalikan { valid, errors }
const check = validate("bukan-qris");
// { valid: false, errors: ["QRIS payload too short"] }

// parse() throw bila CRC hilang/salah
try {
  parse("invalid");
} catch (err) {
  console.error(err.message); // "QRIS payload is missing CRC tag"
}

// staticToDynamic() throw bila sudah dinamis
const dynamic = staticToDynamic(qris, { amount: 10000 });
try {
  staticToDynamic(dynamic, { amount: 10000 });
} catch (err) {
  console.error(err.message); // "QRIS payload is already dynamic"
}

// staticToDynamic() throw bila amount negatif
try {
  staticToDynamic(qris, { amount: -500 });
} catch (err) {
  console.error(err.message); // "Amount must be a positive number"
}

Error Handling

validate() bersifat sinkron dan tidak pernah throw — hasil dikembalikan via ValidationResult. Namun parse(), staticToDynamic(), dan makeDynamic() dapat throw bila input tidak valid (CRC hilang/salah, amount tidak valid, dsb). Gateway adapters menggunakan async/await dan dapat throw bila request gagal.

import { validate, makeDynamic, parse, midtransAdapter } from "qris-saurus";

// validate() — tidak throw, cek .valid
const check = validate(qrisString);
if (!check.valid) {
  console.error("Invalid QRIS:", check.errors);
  // errors: ["Invalid CRC value", "Missing required tag 00", ...]
}

// parse() / makeDynamic() / staticToDynamic() — dapat throw, gunakan try/catch
try {
  const dynamic = makeDynamic(qrisString, { amount: 25000 });
  console.log(dynamic.source); // "local"
} catch (err) {
  // Input tidak valid, CRC salah, atau amount tidak valid
  console.error("Transform error:", err);
}

// Gateway adapter — dapat throw, tangkap dengan try/catch
try {
  const result = await midtransAdapter.createDynamicQr(
    { orderId: "INV-001", amount: 25000 },
    midtransConfig,
    { overrideNotificationUrl: "https://merchant.example/webhooks/midtrans" },
  );
  console.log(result.qrisString);   // payload QRIS mentah
  console.log(result.qrImageUrl);   // URL PNG QR bila Midtrans mengembalikannya
} catch (err) {
  // Network error, auth error, atau response tidak valid
  console.error("Gateway error:", err);
}

// Cek status pembayaran
try {
  const status = await midtransAdapter.checkPaymentStatus("INV-001", midtransConfig);
  // status.status: "pending" | "paid" | "expired" | "failed" | "cancelled"
  if (status.status === "paid") {
    console.log("Lunas pada:", status.paidAt);
  }
} catch (err) {
  console.error("Status check error:", err);
}

Gateway & Custom Providers

SDK ini menyediakan gateway singleton untuk mempermudah integrasi berbagai provider (Midtrans, Xendit, Duitku) melalui satu interface yang terpusat. Gateway mendelegasikan panggilan ke adapter tanpa perlu pengecekan provider secara manual di kodemu:

import { gateway } from "qris-saurus";

// 1. Configure
gateway.configure({ 
  provider: "midtrans",
  serverKey: "SB-Mid-server-xxx", // atau set environment variable MIDTRANS_SERVER_KEY
  sandbox: true 
}); 

// 2. Gunakan method abstrak (charge, verify, status) tanpa peduli provider yang sedang aktif
const chargeResult = await gateway.charge("INV-001", 50000);
const statusResult = await gateway.status("INV-001");
const verifyResult = gateway.verify(webhookPayload, headers); // verify bersifat sinkron

Mendukung Custom Provider (Scaling)

Arsitektur gateway sangat scalable. Kamu bisa dengan mudah membawa provider-mu sendiri (misal Biller lain atau gateway internal) tanpa perlu memodifikasi core library. Cukup implementasikan interface GatewayAdapter yang wajib menyertakan 4 operasi inti: createDynamicQr, checkPaymentStatus, parseWebhook, dan pollPaymentStatus.

Ada dua pendekatan untuk memasang custom adapter:

1. gateway.useAdapter() (Direct Injection)

Gunakan cara ini untuk melempar instance adapter langsung ke singleton. Sangat cocok jika kamu membuat instance di module sendiri:

import { gateway, type GatewayAdapter } from "qris-saurus";

class FinpayAdapter implements GatewayAdapter {
  // ...implementasi 4 operasi inti
}

// Pasang adapter langsung
gateway.useAdapter("finpay", new FinpayAdapter(), { apiKey: "secret" });

// Langsung bisa dipakai
await gateway.charge("INV-FIN", 10000); 

2. Gateway.registerProvider() (Factory Registration)

Gunakan cara ini jika kamu membuat library atau helper yang mendaftarkan provider secara global, sehingga nantinya aplikasi kamu hanya perlu memanggil gateway.configure():

import { Gateway, gateway } from "qris-saurus";

// Daftarkan ke factory bawaan SDK
Gateway.registerProvider("finpay", () => new FinpayAdapter());

// Sekarang bisa dipakai selayaknya provider bawaan
gateway.configure({ 
  provider: "finpay",
  apiKey: "secret" 
} as any); // custom provider belum ada di tipe GatewayConfig

CLI

Setelah build, CLI tersedia sebagai qris-saurus.

Build CLI

bun run build

Help

bun run dist/cli.js --help
qris-saurus CLI

Usage:
  qris-saurus validate [<qris>] [--input-file <file>]
  qris-saurus parse [<qris>] [--input-file <file>]
  qris-saurus detect [<qris>] [--input-file <file>]
  qris-saurus dynamic [<qris>] --amount <number> [--merchant-ref <text>] [--terminal-label <text>] [--input-file <file>]
  qris-saurus render [<qris>] --output <file.png> [--width <number>] [--margin <number>] [--input-file <file>]

Input priority:
  1. positional <qris>
  2. --input-file <file>
  3. stdin pipe

validate

Memeriksa CRC dan tag wajib pada payload.

bun run dist/cli.js validate "<QRIS_PAYLOAD>"
# atau
bun run dist/cli.js validate --input-file ./payload.txt

Output:

{
  "valid": true,
  "errors": []
}

Jika ada masalah:

{
  "valid": false,
  "errors": [
    "Invalid CRC value"
  ]
}

parse

Mem-parse payload menjadi struktur TLV.

bun run dist/cli.js parse "<QRIS_PAYLOAD>"
# atau
cat ./payload.txt | bun run dist/cli.js parse

Output:

{
  "raw": "00020101021126360014ID.CO.QRIS.WWW0114GENERICSTORE01520458125303605802ID5911QRIS SAURUS6007JAKARTA63041669",
  "nodes": [
    { "id": "00", "length": 2, "value": "01" },
    { "id": "01", "length": 2, "value": "11" },
    {
      "id": "26",
      "length": 36,
      "value": "0014ID.CO.QRIS.WWW0114GENERICSTORE01",
      "children": [
        { "id": "00", "length": 14, "value": "ID.CO.QRIS.WWW" },
        { "id": "01", "length": 14, "value": "GENERICSTORE01" }
      ]
    },
    { "id": "52", "length": 4, "value": "5812" },
    { "id": "53", "length": 3, "value": "360" },
    { "id": "58", "length": 2, "value": "ID" },
    { "id": "59", "length": 11, "value": "QRIS SAURUS" },
    { "id": "60", "length": 7, "value": "JAKARTA" }
  ],
  "crc": "1669"
}

detect

Mendeteksi provider dari merchant account identifier.

bun run dist/cli.js detect "<QRIS_PAYLOAD>"
# atau
bun run dist/cli.js detect --input-file ./payload.txt

Jika provider dikenali (contoh ShopeePay):

{
  "code": "shopeepay",
  "name": "ShopeePay",
  "aliases": ["shopeepay", "shopee pay"],
  "merchantInfoTagIds": ["26", "27", "28", "..."],
  "identifiers": ["shopee"],
  "supportsApiDynamic": false,
  "notes": "Standalone public dynamic QRIS API evidence is limited; use local QRIS transformation by default."
}

Jika tidak dikenali:

null

dynamic

Mengubah QRIS statis menjadi dinamis dengan nominal transaksi.

bun run dist/cli.js dynamic "<QRIS_PAYLOAD>" --amount 25000 --merchant-ref INV-001 --terminal-label POS-A
# atau
cat ./payload.txt | bun run dist/cli.js dynamic --amount 25000 --merchant-ref INV-001

Output adalah string payload QRIS dinamis baru, siap dirender:

00020101021226360014ID.CO.QRIS.WWW0114GENERICSTORE01520458125303360540825000.005802ID5911QRIS SAURUS6007JAKARTA62200507INV-0010705POS-A6304391F

Perbedaan dari payload asli:

  • tag 01 berubah dari 1112 (static → dynamic)
  • tag 54 ditambahkan dengan nominal 25000.00
  • tag 62 ditambahkan dengan merchantRef dan terminalLabel
  • tag 63 (CRC) dihitung ulang

render

Membuat file PNG dari payload QRIS.

bun run dist/cli.js render "<QRIS_PAYLOAD>" --output ./qris.png --width 320 --margin 2
# atau
cat ./payload.txt | bun run dist/cli.js render --output ./qris.png

Output adalah path file PNG yang berhasil dibuat:

./qris.png

Rendering dari library

Simpan ke file

import { renderQrToFile } from "qris-saurus";

await renderQrToFile(qrisPayload, "./qris.png", { width: 320, margin: 2 });
// → file ./qris.png tersimpan

Output sebagai Base64 data URL

import { renderQrToDataUrl } from "qris-saurus";

const dataUrl = await renderQrToDataUrl(qrisPayload, { width: 320 });
console.log(dataUrl);
// data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAFACAYAAADNkKWqAAAAAklEQVR4Ae...

Hasil dataUrl langsung bisa dipakai di HTML:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAFACAYAAADNkKWqAAAAAklEQVR4Ae..." />

Atau dikirim sebagai JSON response:

return Response.json({ qrImage: dataUrl });

Helper ini berguna kalau kamu ingin:

  • menampilkan preview QR di web/app internal
  • menyimpan QR image ke file
  • mengirim hasil render ke pipeline lain setelah payload selesai dibentuk

Available API

Core:

  • parse(qrisString) — string → TLV nodes
  • serialize(qrisData) — TLV nodes → string
  • validate(qrisString) — cek CRC + tag wajib
  • computeCrc(input) / verifyCrc(qrisString)

Transform:

  • staticToDynamic(qrisString, options) — local transform, return string
  • makeDynamic(qrisString, options) — local transform + provider detection, return DynamicResult

Providers:

  • detectProvider(qrisString) — return ProviderAdapter | null
  • listProviders() — return semua provider terdaftar

Gateway adapters:

  • midtransAdapter.createDynamicQr(options, config, notificationOptions?) — buat QR via Midtrans API, dengan opsi override/append webhook per transaksi
  • midtransAdapter.checkPaymentStatus(orderId, config) — cek status pembayaran
  • midtransAdapter.verifyWebhook(payload, config) / parseWebhook(payload, config) / getWebhookStatus(payload) — validasi dan normalisasi webhook Midtrans
  • xenditAdapter.createDynamicQr(options, config) — buat QR via Xendit API
  • xenditAdapter.checkPaymentStatus(gatewayOrderId, config) — cek status pembayaran
  • duitkuAdapter.createDynamicQr(options, config) — buat QR via Duitku API
  • duitkuAdapter.checkPaymentStatus(orderId, config) — cek status pembayaran

Render:

  • renderQrToDataUrl(qrisString, options?) — return Base64 PNG data URL
  • renderQrToFile(qrisString, outputPath, options?) — simpan ke file PNG

CLI input priority

CLI menerima input dengan urutan prioritas:

  1. argumen langsung
  2. --input-file <file>
  3. stdin / pipe
# 1 — argumen langsung
bun run dist/cli.js validate "00020101021126..."

# 2 — dari file
bun run dist/cli.js dynamic --input-file payload.txt --amount 25000

# 3 — dari stdin / pipe
cat payload.txt | bun run dist/cli.js render --output qris.png

Development

bun install
bun test
bun run typecheck
bun run build

Documentation

Lihat folder docs: