Menulis generator QRIS dari nol
Parsing TLV, urutan tag, dan CRC-16 rawan salah dan memakan waktu.
Tanpa dependensi tambahan untuk membuat tagihan. Cocok untuk Express, Fastify, Next.js, dan bot.
Halaman ini bagian dari layanan API QRIS gratis APIQris: pakai QRIS milik Anda sendiri, tanpa potongan dari kami, dana langsung ke rekening Anda.
const qris = await fetch(API, {method: 'POST',body: JSON.stringify({ amount: 75000 }),}).then(r => r.json());
Jawaban singkatDi Node.js 18 ke atas, panggil API QRIS dengan fetch bawaan: POST ke /invoices dengan header Authorization Bearer. Untuk webhook, terima body mentah (express.raw), hitung HMAC SHA-256 dengan modul crypto, dan bandingkan memakai timingSafeEqual.
Ingin tahu gambaran besarnya dulu? Baca juga pengertian QRIS API dan bagaimana API QRIS gratis APIQris menghubungkan QRIS Anda ke website, aplikasi, dan chatbot.
Parsing TLV, urutan tag, dan CRC-16 rawan salah dan memakan waktu.
Scraping internet banking rapuh, sering diblokir, dan butuh menyimpan password.
Endpoint callback yang tidak diverifikasi bisa dipalsukan siapa saja.
Tanpa sandbox, developer harus bayar sungguhan setiap kali mengetes alur checkout.
Solusinya: API QRIS Node.js dari APIQris mengotomatiskan semuanya, tanpa mengganti QRIS yang sudah Anda punya.
Tanda tangan dihitung dari body persis seperti yang dikirim. Jika JSON di-parse lalu di-stringify ulang, urutan atau spasi bisa berubah dan verifikasi gagal. Karena itu gunakan express.raw pada route webhook.
Beberapa pola umum di proyek Node.js:
Respons API berbentuk JSON sederhana sehingga mudah dibuatkan interface. Lihat struktur respons di halaman dokumentasi.
Dana dan data berjalan di jalur berbeda. Uang pembeli langsung ke QRIS Anda, APIQris hanya mengurus QRIS dinamis, pencocokan, dan webhook.
Buat akun APIQris dengan email, tanpa biaya dan tanpa dokumen perusahaan.
Unggah gambar QRIS merchant dari bank atau e-wallet Anda. Sistem membaca data QRIS secara otomatis.
Pasang aplikasi APIQris di HP Android yang menerima notifikasi dana masuk, lalu pindai kode pairing.
Salin API key, buat tagihan QRIS dinamis dari website atau chatbot, dan terima webhook saat lunas.
Developer freelance yang membangun toko online untuk klien. Ilustrasi alur kerja, bukan testimoni pelanggan.
Setiap proyek dimulai dari nol: mencari gateway, mengurus dokumen klien, dan menulis ulang modul pembayaran.
Dengan API QRIS Node.js, satu modul yang sama dipakai di semua proyek. Klien cukup upload QRIS miliknya, dana langsung ke rekening klien.
| Aspek | QRIS statis manual | API QRIS Node.js (APIQris) | Payment gateway |
|---|---|---|---|
| Nominal | Diketik pembeli | Terkunci otomatis | Terkunci otomatis |
| Konfirmasi bayar | Cek mutasi manual | Webhook otomatis | Webhook otomatis |
| Penerima dana | Rekening Anda | Rekening Anda | Rekening gateway |
| Potongan perantara | Rp0 | Rp0 | ±0,7% + biaya tarik |
| Bekerja 24 jam | Tergantung admin | Ya | Ya |
Kirim nominal dan order ID, terima QR dan total yang sudah ditambah kode unik.
const res = await fetch('https://api.apiqris.com/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.APIQRIS_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ amount: 50000, order_id: 'INV-1024' }),
});
const qris = await res.json();
console.log(qris.total_amount, qris.qr_image_url);curl -X POST https://api.apiqris.com/v1/invoices \
-H "Authorization: Bearer AQ_LIVE_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"amount":50000,"order_id":"INV-1024"}'<?php
$ch = curl_init('https://api.apiqris.com/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('APIQRIS_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 50000,
'order_id' => 'INV-1024',
]),
]);
$qris = json_decode(curl_exec($ch), true);
echo $qris['qr_image_url']; // tampilkan ke pembeliuse Illuminate\Support\Facades\Http;
$qris = Http::withToken(config('services.apiqris.key'))
->post('https://api.apiqris.com/v1/invoices', [
'amount' => $order->total,
'order_id' => $order->number,
])
->throw()
->json();
return view('checkout.qris', ['qris' => $qris]);import os, requests
r = requests.post(
"https://api.apiqris.com/v1/invoices",
headers={"Authorization": f"Bearer {os.environ['APIQRIS_KEY']}"},
json={"amount": 50000, "order_id": "INV-1024"},
timeout=15,
)
r.raise_for_status()
qris = r.json()
print(qris["total_amount"], qris["qr_image_url"])body := strings.NewReader(`{"amount":50000,"order_id":"INV-1024"}`)
req, _ := http.NewRequest("POST", "https://api.apiqris.com/v1/invoices", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("APIQRIS_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()Hitung HMAC SHA-256 dari timestamp dan body mentah, bandingkan secara constant-time.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/webhook/qris', express.raw({ type: 'application/json' }), (req, res) => {
const sig = Object.fromEntries(req.get('X-APIQris-Signature').split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', process.env.APIQRIS_SECRET)
.update(`${sig.t}.${req.body}`).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1 ?? ''))) return res.sendStatus(401);
const event = JSON.parse(req.body);
if (event.event === 'invoice.paid') markPaid(event.order_id);
res.sendStatus(200);
});# Header yang dikirim APIQris ke callback_url Anda
X-APIQris-Signature: t=1760670000,v1=5f2b...e9
Content-Type: application/json
{"event":"invoice.paid","id":"qr_9KX2T7","order_id":"INV-1024","total_amount":50123,"status":"paid"}<?php
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_APIQRIS_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, getenv('APIQRIS_SECRET'));
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
if ($event['event'] === 'invoice.paid') {
tandai_lunas($event['order_id']); // fungsi milik aplikasi Anda
}
http_response_code(200);Route::post('/webhook/qris', function (Request $request) {
parse_str(str_replace(',', '&', $request->header('X-APIQris-Signature')), $sig);
$expected = hash_hmac('sha256', $sig['t'].'.'.$request->getContent(), config('services.apiqris.secret'));
abort_unless(hash_equals($expected, $sig['v1'] ?? ''), 401);
if ($request->input('event') === 'invoice.paid') {
Order::where('number', $request->input('order_id'))->update(['status' => 'paid']);
}
return response()->noContent();
});import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhook/qris")
def webhook():
sig = dict(p.split("=", 1) for p in request.headers["X-APIQris-Signature"].split(","))
msg = f"{sig['t']}.".encode() + request.get_data()
expected = hmac.new(os.environ["APIQRIS_SECRET"].encode(), msg, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig.get("v1", "")):
abort(401)
event = request.get_json()
if event["event"] == "invoice.paid":
mark_paid(event["order_id"])
return "", 200func webhook(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
sig := map[string]string{}
for _, p := range strings.Split(r.Header.Get("X-APIQris-Signature"), ",") {
if k, v, ok := strings.Cut(p, "="); ok {
sig[k] = v
}
}
mac := hmac.New(sha256.New, []byte(os.Getenv("APIQRIS_SECRET")))
mac.Write([]byte(sig["t"] + "." + string(body)))
if !hmac.Equal([]byte(hex.EncodeToString(mac.Sum(nil))), []byte(sig["v1"])) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
}Data tagihan dan API key selalu terenkripsi saat dikirim.
Kunci bocor? Buat kunci baru dari dashboard, kunci lama langsung mati.
Setiap callback ditandatangani agar server Anda bisa menolak yang palsu.
Tidak ada saldo yang bisa dibekukan, karena uang langsung ke QRIS Anda.
Tidak wajib. fetch bawaan Node.js 18+ sudah cukup.
Bisa, termasuk Vercel dan Cloudflare Workers, selama route webhook membaca body mentah.
Tidak. Pembeli membayar ke QRIS milik Anda sendiri, sehingga dana langsung masuk ke rekening atau saldo merchant Anda.
Daftar gratis dan APIQris tidak memotong fee per transaksi. MDR dari penyedia QRIS Anda (bila ada) mengikuti ketentuan penyedia.
Cukup satu HP Android yang terpasang aplikasi merchant penerbit QRIS Anda dan aplikasi pendamping APIQris. HP lama pun bisa selama notifikasinya stabil.
Semua panduan berinduk ke halaman utama API QRIS gratis tanpa fee.
Gratis, tanpa potongan per transaksi, dana langsung ke rekening Anda. Pelajari dulu API QRIS selengkapnya atau langsung daftar.