FQrisDocs
Panduan

Hosted checkout

Halaman bayar yang dikelola FQris: QR, hitung mundur, dan status realtime, tanpa kamu membangun antarmuka sendiri.

4 menit bacaDiperbarui 29 Okt 2026

Alur pembeli

  1. 1
    Buat payment Backend-mu memanggil POST /v1/payments. Respons memuat checkout_url.
  2. 2
    Arahkan pembeli Redirect ke checkout_url. Pembeli melihat final_amount, rincian nominal + kode unik, QR, dan hitung mundur.
  3. 3
    Pembeli membayar Begitu pembayaran terdeteksi, halaman berganti sendiri ke sukses dengan struk ringkas. Pembeli tidak perlu memuat ulang.
  4. 4
    Pembeli kembali Kalau kamu mengirim return_url, tombol "Kembali ke {nama project}" muncul dan pembeli diarahkan otomatis setelah 5 detik (bisa dibatalkan).
buat-payment
1import FQris from '@fqris/node';
2const fqris = new FQris(process.env.FQRIS_KEY);
3
4const payment = await fqris.payments.create({
5 amount: 20000,
6 reference_id: 'TOPUP-88812',
7});
8console.log(payment.checkout_url);

Di mode live, checkout_url berbentuk …/p/ck_…. Di mode uji, …/test/p/ck_…. Token ck_ acak dan terpisah dari id payment, jadi tidak bisa ditebak dari id.

Yang pembeli lihat

Halaman ini publik, tanpa login, dan tidak diindeks mesin pencari. Isinya: nama project dan logo, deskripsi, final_amount yang besar, rincian nominal + kode unik, QR, hitung mundur, serta tombol Simpan QR dan Salin nominal.

Halaman tidak pernah menampilkan metadata, data customer, atau reference_id.

Hitung mundur dihitung dari expires_at dan server_time dari server, jadi tetap akurat walau jam HP pembeli meleset.

Di HP, pembeli menekan Simpan QR untuk mendapat gambar PNG berisi QR, nominal, dan batas waktu, lalu memilih gambar itu dari galeri di aplikasi pembayarannya. QRIS tidak punya deep link universal, jadi tidak ada tombol yang membuka e-wallet langsung.

Bahasanya Indonesia. Kirim checkout.locale: "en" saat membuat payment, atau tambahkan ?lang=en di URL, untuk bahasa Inggris.

State halaman

  1. 1
    Menunggu Payment pending. QR, nominal, dan hitung mundur tampil. Dua menit terakhir ditandai mendesak.
  2. 2
    Sukses Payment paid. Pembeli melihat animasi sukses dan struk. Pembayaran yang masuk setelah batas waktu tetap menjadi sukses, dengan catatan "Dibayar setelah batas waktu — tetap tercatat".
  3. 3
    Kedaluwarsa Payment expired. QR disembunyikan. Kalau allow_retry aktif, ada tombol "Buat pembayaran baru". Pembayaran yang sudah terlanjur dilakukan tetap dikonfirmasi selama masa tenggang.
  4. 4
    Dibatalkan Payment cancelled. QR disembunyikan dengan pesan agar pembeli tidak membayar QR itu.
  5. 5
    Dikembalikan Payment refunded atau partially_refunded. Pembeli melihat bahwa merchant sudah mencatat pengembalian dana.
  6. 6
    Koneksi terputus Kalau halaman tidak menerima kabar lebih dari 20 detik, banner muncul dan QR tetap tampil.
  7. 7
    Tidak ditemukan Token tidak valid atau sudah tidak tersedia: pesan umum tanpa detail pembayaran.

Halaman memakai koneksi realtime (SSE) dengan heartbeat tiap 15 detik. Kalau koneksi itu gagal dua kali, halaman beralih ke polling tiap 4 detik.

return_url dan cancel_url

Kirim return_url dan cancel_url (URL http atau https) saat membuat payment. Saat pembeli kembali, FQris mengalihkannya ke return_url dengan tambahan query payment_id, status, dan reference_id (hanya bila payment punya reference_id).

status adalah status saat pembeli dialihkan. Pembeli yang menekan Kembali sebelum membayar tiba dengan status=pending, dan yang menekannya setelah kedaluwarsa tiba dengan status=expired.

Tombol Batalkan pembayaran hanya muncul bila ada cancel_url. Pembeli dialihkan ke cancel_url dengan query yang sama, tetapi status payment tidak berubah: payment tetap pending sampai kamu membatalkannya lewat API atau dashboard, atau sampai kedaluwarsa.

Jangan percaya return_url
Query di return_url bisa dibuat siapa saja. Kredit koin atau tandai pesanan lunas hanya dari webhook terverifikasi plus GET /v1/payments/{id}. Lihat panduan Webhook.

Buat pembayaran baru

Di state kedaluwarsa, tombol "Buat pembayaran baru" membuat payment baru dengan nominal dasar, deskripsi, metadata, customer, return_url, dan cancel_url yang sama, lalu membuka checkout barunya. Lama expiry disalin dari payment lama (5–60 menit).

Payment baru, id baru
Payment hasil retry punya id dan kode unik baru, dan tidak membawa reference_id. Kenali pesananmu lewat metadata. Webhook payment.paid datang untuk payment baru itu, bukan untuk yang lama.

Retry hanya untuk payment expired (bukan cancelled) dan sekali per payment. Matikan dengan checkout.allow_retry: false saat membuat payment.

Satu order, satu kredit
Payment lama yang expired masih bisa menjadi paid selama masa tenggang, jadi payment lama dan hasil retry bisa sama-sama lunas untuk satu order. Kreditkan order sekali saja, dengan kunci unik per order di DB (kenali order lewat metadata). Payment kedua yang lunas kamu refund lewat POST /v1/payments/{id}/refunds. Kalau kamu hanya membatalkan payment lama sebelum membuat yang baru, itu tidak cukup, karena payment cancelled pun masih bisa menjadi paid selama masa tenggang.
Apakah halaman ini membantu?