Cara Membuat Tiket QR dan Check-in Anti Dobel dengan Google Apps Script
Bayangkan pintu masuk sebuah workshop: ada 2 panitia memindai tiket di Gerbang A dan Gerbang B. Seorang peserta iseng memotret QR tiketnya lalu mengirimkannya ke temannya. Keduanya datang bersamaan dan dipindai hampir di detik yang sama. Kalau sistemnya tidak siap, dua orang masuk dengan satu tiket.
Di tutorial ini kita membuat tiket QR dan check-in yang aman dengan Google Apps Script dan Google Sheets: kode tiket yang sulit ditebak, kuota yang tidak bisa jebol, dan check-in yang menolak tiket dobel. Contohnya diambil dari aplikasi E-Tiket Event yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Struktur sheet
Kita butuh tiga sheet:
| Sheet | Isi |
|---|---|
Pengaturan | Nama acara, tanggal, PIN panitia, password admin |
Tiket | Kode, nama jenis tiket, kuota |
Peserta | ID tiket, nama, email, WA, jenis, status, waktu check-in, petugas |
Satu jebakan klasik di Sheets: teks seperti 081234567890 atau 2026-11-10 08:30 akan otomatis diubah menjadi angka atau tanggal. Nomor WA kehilangan angka nol, dan waktu bisa bergeser. Atur kolomnya sebagai teks sebelum menulis data:
var pes = ss.getSheetByName('Peserta') || ss.insertSheet('Peserta');
pes.getRange('A:J').setNumberFormat('@'); // semua kolom diperlakukan sebagai teks
2. Kode tiket yang sulit ditebak
Jangan pakai nomor urut seperti TIKET-001. Siapa pun bisa menebak TIKET-002, TIKET-003, dan seterusnya, lalu membuat QR palsu. Gunakan kode acak dari huruf yang mudah dibaca:
var HURUF_ID = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // tanpa 0/O dan 1/I
function idBaru_(sudahAda) {
var acak = function (n) {
var s = '';
for (var i = 0; i < n; i++) s += HURUF_ID.charAt(Math.floor(Math.random() * HURUF_ID.length));
return s;
};
var id;
do { id = 'TK-' + acak(4) + '-' + acak(4); } while (sudahAda[id]);
return id;
}
Dengan 32 karakter dan 8 posisi ada lebih dari 1 triliun kemungkinan kode. Huruf 0/O dan 1/I sengaja dibuang supaya panitia tidak salah baca saat mengetik kode manual.
QR-nya tidak perlu dibuat di server. Cukup kirim kode tiket ke browser, lalu gambar QR memakai library kecil qrcode-generator:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js"></script>
<script>
var qr = qrcode(0, 'M');
qr.addData(tiket.id);
qr.make();
document.getElementById('qr').innerHTML = qr.createSvgTag({ cellSize: 6, margin: 0, scalable: true });
</script>
3. Kuota yang tidak bisa jebol
Kuota VIP tinggal 1 kursi, lalu dua orang menekan tombol daftar bersamaan. Keduanya mengecek "kuota masih ada?" dan mendapat jawaban "ya". Akibatnya kuota 30 terisi 31 orang.
Solusinya, kunci bagian cek lalu simpan dengan LockService:
function daftar(form) {
// ... validasi nama, email, WA ...
var lock = LockService.getScriptLock();
lock.waitLock(20000); // request lain menunggu di sini
try {
var peserta = bacaPeserta_();
if (peserta.some(function (p) { return p.email === email; })) {
throw new Error('Email ini sudah terdaftar.');
}
var terpakai = peserta.filter(function (p) { return p.kode === kode; }).length;
if (terpakai >= jenis.kuota) throw new Error('Maaf, kuota tiket ' + jenis.nama + ' sudah habis.');
sheet.appendRow([idBaru_(sudahAda), sekarang, nama, email, wa, kode, instansi, 'TERDAFTAR', '', '']);
SpreadsheetApp.flush(); // pastikan tertulis sebelum kunci dilepas
} finally {
lock.releaseLock();
}
}
SpreadsheetApp.flush() penting. Tanpa itu, data bisa belum benar-benar tertulis ketika request berikutnya mulai membaca, sehingga pengecekan kuota memakai data lama.
4. Check-in yang menolak tiket dobel
Prinsipnya sama dengan kuota: membaca status dan menandai hadir harus terjadi dalam satu kunci.
function checkin(token, kodeQr, petugas) {
cekToken_(token, 'panitia');
var cocok = String(kodeQr).toUpperCase().match(/TK-[A-Z0-9]{4}-[A-Z0-9]{4}/);
if (!cocok) return { hasil: 'INVALID', pesan: 'QR bukan e-tiket acara ini.' };
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var p = bacaPeserta_().filter(function (x) { return x.id === cocok[0]; })[0];
if (!p) return { hasil: 'INVALID', pesan: 'Tiket tidak terdaftar.' };
if (p.status === 'HADIR') {
return { hasil: 'SUDAH', pesan: 'Sudah check-in pukul ' + p.checkin.slice(11, 16) + ' oleh ' + p.petugas };
}
sheetPeserta_().(p., , , ).([[, (), petugas]]);
.();
{ : , : + p. + };
} {
lock.();
}
}
Perhatikan dua hal:
match(/TK-.../)mengambil kode dari isi QR apa pun bentuknya. Jadi QR yang berisi link (...?t=TK-ABCD-1234) maupun kode polos sama-sama bisa dipindai.- Pesan "sudah dipakai" menyebut jam dan pos check-in sebelumnya. Panitia bisa langsung tahu kalau tiket itu dipakai orang lain.
5. Memindai QR dari kamera HP
Browser modern di Android sudah punya BarcodeDetector. Untuk browser yang belum mendukung, pakai jsQR sebagai cadangan:
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }).then(function (stream) {
video.srcObject = stream; video.play();
var detector = 'BarcodeDetector' in window ? new BarcodeDetector({ formats: ['qr_code'] }) : null;
(function loop() {
if (detector) {
detector.detect(video).then(function (codes) { if (codes.length) proses(codes[0].rawValue); });
} else {
ctx.drawImage(video, 0, 0, w, h);
var hasil = jsQR(ctx.getImageData(0, 0, w, h).data, w, h);
if (hasil) proses(hasil.data);
}
requestAnimationFrame(loop);
})();
});
Kamera membaca QR yang sama berkali-kali per detik. Simpan kode terakhir beserta waktunya, lalu abaikan kode yang sama selama beberapa detik. Kalau tidak, satu peserta akan "check-in" puluhan kali dan panitia melihat layar kuning "sudah dipakai" untuk tiket yang baru saja valid.
6. Sesi panitia tanpa database: token HMAC
Panitia login dengan PIN, tapi kita tidak ingin PIN dikirim ulang setiap kali memindai. Buat token yang ditandatangani:
function buatToken_(peran, jam) {
var isi = peran + '.' + (Date.now() + jam * 3600000);
var tanda = Utilities.base64EncodeWebSafe(
Utilities.computeHmacSha256Signature(isi + '|' + pinSaatIni, rahasia_()));
return isi + '.' + tanda;
}
Karena PIN saat ini ikut ditandatangani, mengganti PIN di sheet langsung membatalkan semua token lama. Ini berguna kalau HP panitia hilang di tengah acara.
Ringkasan
- Atur kolom sheet sebagai teks sebelum menulis nomor WA dan waktu.
- Pakai kode acak tanpa huruf yang mirip, bukan nomor urut.
- Bungkus "cek kuota lalu simpan" dan "cek status lalu tandai hadir" dengan LockService + flush().
- Abaikan QR yang sama selama beberapa detik di sisi pemindai.
- Gunakan token HMAC untuk sesi panitia, dan ikutkan PIN dalam tanda tangan.
Download source code lengkapnya
Semua teknik di atas dipakai di E-Tiket Event (Google Apps Script): halaman pendaftaran bergaya poster dengan hitung mundur, beberapa jenis tiket dengan kuota, e-tiket QR yang bisa disimpan sebagai gambar dan dikirim ke email, pemindai check-in dengan layar hijau/kuning/merah, pencarian peserta manual, serta dashboard admin dengan grafik dan ekspor Excel. Gratis dan tanpa hosting.
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.