Skip to content
endr
Go back

Algoritma Luhn: Cara Form Tau Nomor Kartu Salah Sebelum Lo Submit

ID

Pernah enggak lo penasaran waktu mau payment? Lo input nomor kartu kredit di form checkout, belum nekan tombol apa pun, tapi form-nya udah tau nomor yang lo masukin salah. Belum ada call ke bank, kartunya belum dicoba ditagih, wifi mati pun tetep jalan. Terus dia tau dari mana? Jawabannya ada di algoritma Luhn.

Digit terakhir itu bukan digit biasa

Nomor kartu bawa bukti dirinya sendiri. Digit paling kanan itu check digit, hasil hitungan dari digit-digit di depannya (Wikipedia, Payment card number). Yang nerbitin nomor ngitung digit itu pake satu rumus tetap, jadi form enggak perlu nanya siapa-siapa. Dia ngitung ulang dari digit yang lo ketik. Cocok, lolos. Enggak cocok, ada yang salah ketik.

Semuanya cuma aritmatika di atas string, jalan di browser tiap kali lo ngetik.

Luhn dan alat genggam dari 1954

Hans Peter Luhn, peneliti IBM, ngajuin paten buat ini tanggal 6 Januari 1954. Patennya (US 2,950,048, judulnya “Computer for Verifying Numbers”) baru keluar 23 Agustus 1960, enam tahun lebih kemudian (Google Patents).

Alat di paten itu mekanik dan muat di tangan: sabuk, katrol, dan pengunci bergigi (pawl), digerakin pake pensil atau stylus. Salah satu masalah yang mau diatasi di patennya adalah dua digit yang ketuker. Algoritmanya juga dikenal sebagai “mod 10”, karena total akhirnya harus kelipatan 10. Sekarang Luhn nongol di nomor kartu (distandarkan di ISO/IEC 7812), IMEI HP, sampai nomor VAT Italia (Wikipedia, Luhn algorithm).

Hitung manual pake 4111 1111 1111 1111

Semua nomor kartu di post ini nomor test, bukan kartu orang. 4111 1111 1111 1111 itu nomor test Visa yang udah lama dipake buat contoh. Langkahnya:

  1. Mulai dari digit paling kanan, jalan ke kiri. Digit pertama dibiarin, digit kedua dikali 2, ketiga dibiarin, keempat dikali 2, gitu terus.
  2. Kalau hasil kali 2 lebih dari 9, kurangin 9. Hasilnya sama kayak jumlahin dua digitnya: 14 jadi 1 + 4 = 5, atau 14 - 9 = 5.
  3. Jumlahin semua digit. Kalau total habis dibagi 10, nomornya lolos.

Nomor ini 16 digit, jadi yang dikali 2 adalah digit ke-1, 3, 5, dan seterusnya dari kiri:

asli    : 4  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1
dikali 2: x  .  x  .  x  .  x  .  x  .  x  .  x  .  x  .
hasil   : 8  1  2  1  2  1  2  1  2  1  2  1  2  1  2  1

Dijumlahin: 8 dari digit 4 yang dikali 2, tujuh angka 2 (14), plus delapan angka 1 (8). Totalnya 30. Habis dibagi 10, lolos.

Sekarang ganti digit terakhir jadi 2. Totalnya 31, enggak habis dibagi 10, jadi ketahuan.

Animasi algoritma Luhn pada nomor test 4111 1111 1111 1111: tiap digit kedua dari kanan dikali 2, semua digit dijumlahkan jadi 30 yang habis dibagi 10 sehingga lolos, lalu digit terakhir diganti 2 membuat total 31 sehingga ketahuan salah

Digit terakhir tadi asalnya dari mana? Ambil 15 digit di depannya (411111111111111), hitung pake cara yang sama, tapi digit paling kanan dari 15 itu langsung dikali 2 karena posisi paling kanan lagi dikosongin buat check digit. Hasilnya 29. Check digit-nya 10 - 9 = 1. Persis angka terakhir tadi.

Itu juga alasan hitungnya harus dari kanan. Panjang nomor beda-beda, Visa 16 sedangkan Amex 15. Kalau lo mulai dari kiri, di nomor 15 digit yang kekali 2 jadi digit yang salah.

Nebak brand dari prefix

Beberapa digit pertama nomor kartu namanya IIN (issuer identification number), sering juga disebut BIN. Dari situ ketauan siapa yang nerbitin. Per tabel di Wikipedia, Visa mulai dari 4, Mastercard mulai dari 51 sampai 55 atau 2221 sampai 2720 (16 digit), Amex mulai dari 34 atau 37 (15 digit).

Buat form, tau brand berarti tau panjang nomor yang diharapkan, jadi form tau kapan lo udah selesai ngetik. Tanpa itu, Luhn yang jalan di tiap ketikan bakal nampilin error merah pas lo baru ngetik tiga digit. Form yang kayak gitu nyebelin. Prefix juga cuma nebak brand, enggak bilang apa-apa soal nomornya ada atau enggak.

Dipasang di form

Vanilla JS, jalan di browser. Pake event input, bukan keyup, karena input juga kena paste dan autofill.

<input id="card" inputmode="numeric" autocomplete="cc-number" placeholder="4111 1111 1111 1111">
<p id="card-hint"></p>
const BRANDS = [
  // shortcut: Visa 13 dan 19 digit enggak dihandle, upgrade kalau perlu dukung kartu lama
  { name: "Visa", prefix: /^4/, length: 16 },
  { name: "Mastercard", prefix: /^(5[1-5]\d{2}|222[1-9]|22[3-9]\d|2[3-6]\d{2}|27[01]\d|2720)/, length: 16 },
  { name: "Amex", prefix: /^3[47]/, length: 15 },
];

function luhnCheck(digits) {
  let sum = 0;
  let doubleIt = false;
  for (let i = digits.length - 1; i >= 0; i--) {
    let d = Number(digits[i]);
    if (doubleIt) {
      d *= 2;
      if (d > 9) d -= 9;
    }
    sum += d;
    doubleIt = !doubleIt;
  }
  return sum % 10 === 0;
}

function checkCard(raw) {
  const digits = raw.replace(/\D/g, ""); // spasi dan strip dibuang
  const brand = BRANDS.find((b) => b.prefix.test(digits));
  if (!brand) return { brand: null, state: "unknown" };
  if (digits.length !== brand.length) return { brand: brand.name, state: "typing" };
  return { brand: brand.name, state: luhnCheck(digits) ? "valid" : "invalid" };
}

const input = document.querySelector("#card");
const hint = document.querySelector("#card-hint");

input.addEventListener("input", () => {
  const { brand, state } = checkCard(input.value);
  if (state === "valid") hint.textContent = `${brand}, format nomor oke`;
  else if (state === "invalid") hint.textContent = `${brand}, ada digit yang salah, cek lagi`;
  else hint.textContent = brand ?? "";
});

Cek cepat pake nomor test tadi, plus nomor test Visa dan Amex dari dokumentasi Stripe. Kalau semuanya bener, console-nya diem:

console.assert(checkCard("4111 1111 1111 1111").state === "valid");
console.assert(checkCard("4111 1111 1111 1112").state === "invalid"); // satu digit salah
console.assert(checkCard("4242 4242 4242 4224").state === "invalid"); // 4242 4242 4242 4242 yang dua digit terakhirnya ketuker
console.assert(checkCard("3782 822463 10005").state === "valid");     // Amex

Cek prefix dan panjang dulu sebelum Luhn itu disengaja. String isi nol semua totalnya 0, jadi lolos Luhn. Tanpa cek prefix, 0000 0000 0000 0000 bakal dianggap nomor yang bener.

Lolos Luhn belum berarti kartunya ada

Yang ketangkep Luhn: semua kesalahan satu digit, hampir semua dua digit bersebelahan yang ketuker, dan kebanyakan salah ketik digit kembar. Contoh tadi, 4242 4242 4242 4242 jadi …4224, totalnya geser dari 80 ke 78.

Yang lolos: 09 jadi 90 (dan sebaliknya). Kalau 0 yang dikali 2 dan 9 yang enggak, jumlahnya 0 + 9. Ditukar, 9 yang dikali 2 (18, dikurang 9 jadi 9) dan 0 yang enggak, jumlahnya 9 + 0. Totalnya sama, Luhn enggak ngerasa ada yang berubah. Kata Wikipedia, beberapa pasangan digit kembar juga lolos: 22 jadi 55, 33 jadi 66, 44 jadi 77.

Selain itu, digit terakhir ditentuin sepenuhnya sama digit di depannya, jadi dari 10 kemungkinan cuma 1 yang pas. String 16 digit ngasal lolos Luhn kira-kira 1 dari 10.

Yang lebih penting, Luhn sama sekali enggak ngecek kartunya. Kartu itu asli, aktif, atau ada saldonya, dia enggak tau. Itu cuma bisa dijawab penerbit kartu, lewat payment processor pas transaksi diotorisasi.

Luhn juga bukan security. Wikipedia nyebut algoritma ini dirancang buat kesalahan yang enggak disengaja, serangan ada di luar cakupannya. Siapa pun yang tau aturannya bisa bikin nomor yang lolos: ambil 15 digit, hitung check digit pake langkah di atas. Aturannya publik, dan patennya udah keluar sejak 1960.

Jadi pengecekan di form itu kenyamanan buat user. JS di browser bisa dimatiin atau diubah, dan request bisa langsung ditembak tanpa lewat form, makanya server tetep harus ngecek ulang. Argumennya sama kayak di post Broken Access Control: gating di sisi tampilan itu buat kerapian, yang ngejaga harus server. Dan nomor kartu yang bocor beneran, kayak yang kebaca di halaman Users di post itu, enggak ketolong sama Luhn sama sekali.

Satu hal kecil soal teks di UI: jangan tulis “Kartu valid” pas Luhn lolos. Itu ngasih kesan kartunya udah dicek, padahal yang dicek cuma angkanya. “Format nomor oke” lebih jujur.


Sumber:


Share this post:

Previous Post
Broken Access Control: Panel Admin yang Cuma Ngecek Login, Bukan Role