Panduan Setup dari Nol

Panduan ini untuk pemilik platform (admin QRISKu). Tujuannya: aplikasi jalan di server sendiri dengan domain HTTPS, terhubung ke QRIS dan Virtual Account DOKU milik ARENAGO, dan siap dipakai semua brand.

Penting soal nama di aplikasi bank. Semua brand memakai satu QRIS resmi milik ARENAGO di DOKU. Waktu pembeli scan, aplikasi bank/e-wallet menampilkan nama merchant yang terdaftar di DOKU (ARENAGO). Nama brand (AeroConvo, AbsenKuy, dan lain-lain) hanya tampil di halaman checkout QRISKu. Aplikasi ini tidak mengubah isi QR. Kalau mau nama lain muncul di aplikasi bank, nama itu harus didaftarkan resmi sebagai merchant QRIS sendiri lewat DOKU.

#1. Yang perlu disiapkan

Tidak perlu database terpisah: data disimpan di satu file SQLite (data/qrisku.db).

#2. Pasang aplikasi

# 1) Install Node 24 (Ubuntu/Debian)
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt-get install -y nodejs

# 2) Taruh kode di server (upload qris-platform.tar.gz dulu)
sudo mkdir -p /opt/qrisku && sudo chown $USER /opt/qrisku
tar -xzf qris-platform.tar.gz -C /opt/qrisku --strip-components=1
cd /opt/qrisku

# 3) Install dependensi (tanpa alat dev)
npm ci --omit=dev

# 4) Buat file konfigurasi dan folder data
cp .env.example .env
chmod 600 .env
mkdir -p data backups

Cek cepat: npm start lalu buka http://IP-SERVER:3000/healthz. Harus keluar {"ok":true,...}. Tekan Ctrl+C untuk berhenti. npm start otomatis membaca .env.

#3. Isi file .env

Buka .env dengan editor (nano .env). Semua variabel:

Variabel Contoh Penjelasan
PORT 3000 Port aplikasi di server. Biarkan 3000 kalau pakai Caddy/nginx
BASE_URL https://bayar.arenago.id Alamat publik HTTPS. Dipakai untuk link checkout, gambar QR, dan cookie aman. Wajib diganti saat online
ADMIN_PASSWORD (acak, min. 16 karakter) Password masuk panel admin
ADMIN_PATH /x-3f9a1c7e5b2d Alamat rahasia panel admin. Kosong = /admin. Buat dengan echo /x-$(openssl rand -hex 6). Kalau diisi, /admin jadi 404 seperti halaman tak dikenal. Jangan ditaruh di halaman publik
ADMIN_ALLOW_IPS 203.0.113.7, 10.0.0.0/8 Opsional. Hanya IP/CIDR ini yang bisa membuka panel admin, yang lain dapat 404. Kosong = semua IP (tetap butuh password)
REGISTER_PER_HOUR / REGISTER_MIN_SECONDS 5 / 3 Batas pendaftaran brand per IP per jam, dan waktu minimal mengisi form (anti-bot)
SESSION_SECRET (acak, 64 karakter) Kunci tanda tangan cookie login. Buat dengan openssl rand -hex 32. Kalau diganti, semua orang keluar
PLATFORM_FEE_PERCENT 0.7 Biaya layanan per transaksi sukses (persen). Satu tarif untuk semua brand
PLATFORM_FEE_FLAT 0 Biaya tetap tambahan per transaksi (rupiah)
PAYMENT_TTL_MINUTES 15 Masa berlaku QR, 15 sampai 30 menit
MIN_AMOUNT / MAX_AMOUNT 1000 / 10000000 Batas nominal per transaksi
MIN_WITHDRAW 10000 Minimal penarikan saldo brand
TRUST_PROXY loopback Biarkan loopback kalau Caddy/nginx di server yang sama. Untuk Docker + Caddy di compose pakai uniquelocal
RATE_LIMIT_API_PER_MIN 120 Batas request API per API key per menit
RATE_LIMIT_PUBLIC_PER_MIN 120 Batas request halaman publik per IP per menit
LOGIN_MAX_FAILS / LOGIN_WINDOW_MIN 5 / 15 Login dikunci sementara setelah 5x gagal dalam 15 menit
ALLOW_PRIVATE_WEBHOOKS 0 Biarkan 0. Isi 1 hanya di laptop untuk tes webhook ke localhost
DB_PATH data/qrisku.db Lokasi file database
DOKU_BASE_URL https://api.doku.com Production. Untuk sandbox DOKU: https://api-sandbox.doku.com
DOKU_CLIENT_ID BRN-xxxx-xxxxxxxxxx Dari DOKU, lihat langkah 4
DOKU_CLIENT_SECRET SK-xxxxxxxx Secret Key dari DOKU, lihat langkah 4. Rahasia
DOKU_PRIVATE_KEY_PATH ./keys/private.pem Private key RSA kamu (langkah 5). Rahasia
DOKU_PRIVATE_KEY_PASSPHRASE (kosong) Isi hanya kalau private key diberi passphrase
DOKU_MERCHANT_ID 91959 Merchant ID QRIS ARENAGO
DOKU_TERMINAL_ID WEB01 Kode terminal pilihan kamu, 3 sampai 16 huruf/angka
DOKU_POSTAL_CODE 11250 Kode pos alamat usaha ARENAGO
DOKU_NOTIFY_PATH /webhooks/doku/qris Path URL notifikasi DOKU. Biarkan default

Jangan pernah kirim isi .env lewat chat, email, atau commit ke Git.

#4. Ambil kredensial di dashboard DOKU

  1. Client ID dan Secret Key: dashboard DOKU > Settings > Account > API Keys. Salin Client ID ke DOKU_CLIENT_ID. Klik tampilkan Secret Key (minta OTP), salin ke DOKU_CLIENT_SECRET. Yang dipakai adalah Secret Key berawalan SK-.
  2. Merchant ID: Settings > Accept Payments > Checkout Appearance, tab Pengaturan Kredensial QRIS. Angka di kolom Client ID di tab itu (untuk ARENAGO: 91959) masuk ke DOKU_MERCHANT_ID. MPAN dan NMID di tab yang sama tidak perlu diisi.
  3. Terminal ID: bebas kamu tentukan, 3 sampai 16 huruf/angka, contoh WEB01. Pakai yang sama terus.
  4. Kode pos: Settings > Account > Business Info, lihat alamat usaha (untuk ARENAGO: 11250) lalu isi DOKU_POSTAL_CODE.

#5. Buat dan daftarkan kunci RSA

DOKU memakai pasangan kunci RSA untuk minta token. Private key tetap di server, public key diunggah ke DOKU.

cd /opt/qrisku
sh scripts/gen-keys.sh        # membuat keys/private.pem dan keys/public.pem
cat keys/public.pem           # yang ini aman ditampilkan

Di dashboard DOKU: Settings > Account > API Keys > Edit Merchant Public Key, tempel isi keys/public.pem, simpan.

Kalau server lama sudah punya kunci yang terdaftar di DOKU, jangan buat ulang. Salin saja folder keys/ lama ke server baru. Membuat kunci baru berarti harus unggah ulang public key, dan transaksi gagal sampai itu dilakukan.

DOKU Public Key yang ada di halaman yang sama boleh disimpan sebagai keys/doku_public.pem untuk arsip. Aplikasi saat ini tidak memakainya, karena notifikasi DOKU diverifikasi dengan HMAC dan status selalu dicek ulang ke DOKU sebelum saldo bertambah.

#6. Jalankan terus-menerus

Pilih salah satu.

#Opsi A: pm2

sudo npm install -g pm2
NODE_ENV=production pm2 start ecosystem.config.cjs
pm2 save
pm2 startup     # ikuti perintah yang dicetak supaya otomatis jalan setelah reboot
pm2 logs qrisku # lihat log

#Opsi B: systemd

sudo useradd --system --home /opt/qrisku qrisku
sudo chown -R qrisku:qrisku /opt/qrisku
sudo cp deploy/qrisku.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now qrisku
sudo journalctl -u qrisku -f

#Opsi C: Docker

# di folder proyek, setelah .env dan keys/ siap
mkdir -p data backups
sudo chown -R 1000:1000 data backups keys   # kontainer jalan sebagai user "node" (uid 1000)
DOMAIN=bayar.arenago.id docker compose up -d --build   # app + Caddy (HTTPS otomatis)
docker compose logs -f app

Di Docker, .env dan keys/ dipasang read-only ke kontainer, jadi rahasia tidak ikut masuk ke image.

Atau satu perintah: sh scripts/deploy.sh bayar.arenago.id. Skrip ini membuat .env (dengan password admin dan session secret acak) kalau belum ada, membuat kunci RSA kalau belum ada, lalu menjalankan Docker Compose plus Caddy untuk HTTPS otomatis.

#7. Domain dan HTTPS

  1. Di pengelola DNS domain kamu, buat A record: bayar mengarah ke IP publik server. Tunggu sampai ping bayar.arenago.id menunjukkan IP server.
  2. Buka port 80 dan 443 di firewall (sudo ufw allow 80,443/tcp). Port 3000 jangan dibuka ke publik.

#Caddy (disarankan, HTTPS otomatis)

sudo apt install -y caddy
sudo cp deploy/Caddyfile /etc/caddy/Caddyfile   # ganti bayar.arenago.id dengan domain kamu
sudo systemctl reload caddy

Isi deploy/Caddyfile:

bayar.arenago.id {
    encode gzip
    reverse_proxy 127.0.0.1:3000
}

#nginx (alternatif)

Pakai deploy/nginx.conf sebagai contoh, lalu pasang sertifikat dengan sudo certbot --nginx -d bayar.arenago.id.

  1. Ubah BASE_URL=https://bayar.arenago.id di .env, lalu restart (pm2 restart qrisku / sudo systemctl restart qrisku / docker compose up -d).
  2. Buka https://bayar.arenago.id/healthz. Harus {"ok":true,...}.

#8. URL notifikasi DOKU (tidak wajib)

Aplikasi ini tidak butuh URL notifikasi DOKU. Status transaksi real (QRIS maupun Virtual Account) dicek langsung ke API DOKU saat halaman checkout terbuka (tiap 3 detik) dan oleh pengecek latar belakang tiap 15 detik. Begitu DOKU bilang lunas, transaksi ditandai sukses dan saldo brand bertambah.

Kalau akun DOKU kamu sudah dipakai sistem lain (untuk ARENAGO: URL notifikasi sudah mengarah ke aplikasi ARENAGO), jangan diganti. Menggantinya bisa membuat sistem lain itu berhenti menerima kabar pembayaran. QRISKu tetap jalan tanpa notifikasi.

Hanya kalau akun DOKU itu khusus untuk QRISKu dan kamu mau deteksi QRIS sedikit lebih cepat: Dashboard DOKU > Settings > Payment Settings > QR Payment > Edit, isi https://bayar.arenago.id/webhooks/doku/qris (alamat lengkapnya juga tampil di panel admin > Pengaturan), lalu simpan. Notifikasi DOKU tidak pernah dipercaya mentah: saldo baru bertambah setelah status dikonfirmasi lewat API cek status DOKU.

#9. Metode pembayaran: QRIS dan Virtual Account

Halaman checkout punya dua tab:

Metode Cara bayar pembeli Yang tampil di aplikasi bank
QRIS Scan QR dari m-banking atau e-wallet apa pun Nama merchant QRIS ARENAGO
Transfer Bank (VA) Pilih bank, lalu transfer ke nomor Virtual Account sekali pakai Nama penerima ARENAGO (atau VA_NAME di .env)

Nomor VA dibuat saat pembeli memilih bank, untuk transaksi yang sama. Nominalnya terkunci (closed amount): pembeli harus transfer persis sesuai total. VA berakhir bersamaan dengan transaksi. Biaya layanan, saldo brand, mode Sandbox/Real, dan Mode brand berlaku sama seperti QRIS. Di mode Sandbox, nomor VA diawali 99 dan tidak bisa dibayar.

Bank yang aktif sekarang: BNI (bni), BTN (btn), Bank Neo Commerce (bnc). Ketiganya sudah dites ke DOKU production (buat VA, cek status "Belum Terbayar", hapus).

#Mengaktifkan bank lain

  1. Dashboard DOKU > Settings > Payment Settings > Virtual Account. Cek bank mana yang statusnya aktif. Bank yang belum aktif perlu diminta ke DOKU (tim support atau account manager DOKU).
  2. Minta ke DOKU BIN (partnerServiceId) dan prefix nomor VA untuk bank itu (di halaman konfigurasi bank tersebut kalau tersedia, atau tanyakan ke support DOKU).
  3. Isi di .env server dengan format DOKU_VA_<KODE>=BIN:prefix, lalu restart aplikasi:
DOKU_VA_BRI=12345:6        # contoh, ganti dengan BIN dan prefix dari DOKU
DOKU_VA_PERMATA=12345:6
DOKU_VA_DISABLE=bnc        # opsional: sembunyikan bank tertentu
  1. Tes tanpa membayar: node --env-file=.env scripts/va-live-check.js bri. Skrip ini membuat VA Rp 10.000, memastikan statusnya "Belum Terbayar", lalu menghapusnya. Kalau hasilnya create: ok dan status: pending, bank itu siap tampil di checkout.

Tidak perlu memasang URL notifikasi VA di DOKU. Sama seperti QRIS, status VA dicek lewat API cek status DOKU. Jangan ubah URL notifikasi yang sudah ada.

Kode bank yang dikenal: bni, btn, bnc, bri, permata. Bank lain di luar daftar ini perlu tambahan kode di src/vaBanks.js.

#Tes pertama VA (uang sungguhan)

Status "Belum Terbayar" sudah dites. Teks status "sudah bayar" dari DOKU belum pernah dilihat langsung, jadi lakukan sekali tes bayar sungguhan: buat pembayaran Rp 10.000, pilih Transfer Bank (VA) > BNI, transfer persis sesuai total, dan pastikan halaman berubah jadi "Pembayaran berhasil" dalam beberapa detik. Kalau tidak berubah, buka detail transaksi di panel admin: baris Nomor VA menampilkan teks status terakhir dari DOKU, dan kirim teks itu ke developer.

#10. Pindah dari Sandbox ke DOKU (real)

  1. Masuk panel admin (https://bayar.arenago.id + ADMIN_PATH, misalnya https://bayar.arenago.id/x-3f9a1c7e5b2d/login) dengan ADMIN_PASSWORD.
  2. Buka Pengaturan. Bagian Koneksi DOKU harus bertuliskan "Kredensial DOKU lengkap". Kalau ada yang kurang, namanya ditampilkan.
  3. Klik toggle Real (DOKU) di kanan atas. Mulai saat ini QRIS baru dibuat lewat DOKU. Brand tetap bisa membuat QR tes dengan test: 1 atau centang Mode tes.

#11. Tambah brand

Ada dua cara: brand daftar sendiri atau dibuat admin.

Daftar sendiri. Calon brand membuka /merchant/register (tombol Daftar Brand di beranda) dan mengisi nama brand, username, email, WhatsApp (opsional), password, dan keperluan. Statusnya menunggu: belum bisa masuk portal, belum punya API key, dan tidak bisa membuat pembayaran. Tidak ada email/WA otomatis, jadi cek sendiri:

  1. Panel admin > Pendaftaran Brand (angka di menu = jumlah yang menunggu).
  2. Setujui: API key dibuat, brand mulai di mode Sandbox (QR tes). Setelah yakin, buka Brand > Kelola > Mode brand untuk ikut mode platform (Real).
  3. Tolak: isi alasan (opsional). Brand melihat alasannya saat mencoba masuk.
  4. Kabari brand lewat kontak yang mereka isi. Semua tindakan tercatat di Riwayat tindakan brand. Brand yang sudah ada sebelum fitur ini otomatis berstatus disetujui.

Dibuat admin:

  1. Admin > Brand > Tambah brand: nama internal, nama tampil di checkout, URL webhook (opsional), email/username login dan password awal untuk Portal Brand.
  2. Berikan ke pemilik brand: alamat https://bayar.arenago.id/merchant/login, login, dan password awal. Minta mereka langsung ganti password di menu Akun.
  3. Developer brand mengikuti Tutorial Pasang.
  4. Lupa password? Admin > Brand > Kelola > Login Portal Brand > Reset password. Semua sesi brand itu langsung keluar.

#12. Tes pertama Rp 1.000 (uang sungguhan)

  1. Pastikan mode Real (DOKU) aktif.
  2. Admin > Buat Pembayaran, pilih brand, nominal 1000, fee ditanggung Brand.
  3. Buka checkout, scan dengan m-banking/e-wallet. Aplikasi bank menampilkan nama merchant ARENAGO.
  4. Bayar. Dalam beberapa detik halaman checkout berubah jadi "Pembayaran berhasil".
  5. Cek: transaksi berstatus Sukses, saldo brand bertambah, dan detail transaksi mencatat pembayaran.
  6. Uang Rp 1.000 masuk ke saldo DOKU ARENAGO, bukan dikembalikan otomatis.

#13. Penarikan saldo brand

Saldo brand adalah catatan di database QRISKu. Uang fisiknya ada di saldo DOKU ARENAGO.

  1. Brand mengajukan penarikan di Portal Brand. Saldo langsung ditahan.
  2. Admin > Tarik Dana: transfer manual ke rekening brand dari rekening/saldo ARENAGO, lalu klik Setujui (isi nomor referensi transfer di catatan). Kalau ditolak, klik Tolak dan saldo kembali ke brand.

#14. Backup database

Semua data ada di data/qrisku.db. Backup aman saat aplikasi jalan:

npm run backup                          # membuat backups/qrisku-YYYYMMDD-HHMMSS.db, simpan 14 terakhir
docker compose exec app node scripts/backup.js   # versi Docker

Jadwalkan tiap hari dengan cron (crontab -e):

15 2 * * * cd /opt/qrisku && /usr/bin/npm run backup >> backups/backup.log 2>&1

Salin folder backups/ ke tempat lain secara berkala (Google Drive, S3, atau komputer lain). Untuk restore: hentikan aplikasi, ganti data/qrisku.db dengan file backup, jalankan lagi. Folder keys/ dan file .env juga perlu dibackup, tapi simpan terpisah dan terenkripsi karena berisi rahasia.

#15. Update aplikasi

cd /opt/qrisku
npm run backup
tar -xzf qris-platform-baru.tar.gz --strip-components=1   # .env, keys/, data/ tidak tertimpa
npm ci --omit=dev
pm2 restart qrisku     # atau: sudo systemctl restart qrisku / docker compose up -d --build

Perubahan struktur database dijalankan otomatis saat aplikasi start.

#16. Checklist keamanan

#17. Kalau ada masalah

Gejala Penyebab umum
Aplikasi langsung berhenti: "masih default" Mode production menolak jalan kalau ADMIN_PASSWORD atau SESSION_SECRET belum diganti. Isi di .env
Halaman Pengaturan admin bilang kredensial belum lengkap Ada variabel DOKU_* kosong di .env, atau aplikasi belum direstart
Buat QRIS real error 502 Lihat log aplikasi. Biasanya Client ID/Secret Key salah, public key belum diunggah, atau Merchant ID salah
Nilai .env seperti tidak terbaca Variabel yang sama sudah ada di environment shell/sistem. Nilai di environment selalu menang atas .env. Hapus dari environment (unset NAMA)
Pembayaran sukses tapi status lambat berubah Status dicek ke DOKU tiap 3 detik (checkout terbuka) atau 15 detik (latar belakang). Cek log aplikasi kalau lebih lama
Bank VA tidak muncul di checkout BIN bank itu belum diisi (DOKU_VA_<KODE>) atau disembunyikan lewat DOKU_VA_DISABLE
Pilih bank VA error "gagal membuat VA" Bank itu belum aktif di DOKU atau BIN salah. Tes dengan scripts/va-live-check.js
Login brand terkunci Terlalu banyak salah password. Tunggu 15 menit atau restart aplikasi
Webhook brand gagal "tidak boleh mengarah ke server lokal" URL webhook brand menunjuk ke localhost/IP privat. Pakai domain publik