Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Java

Java: Kiểm tra số thẻ tín dụng hợp lệ (Thuật toán Luhn)

Kiểm tra tính hợp lệ của số thẻ tín dụng sử dụng thuật toán Luhn (Mod 10) - tiêu chuẩn quốc tế ISO/IEC 7812-1.

Quy tắc cơ bản

  • Độ dài: 13-16 chữ số.
  • Prefix (số bắt đầu):
    • Visa: 4
    • MasterCard: 51-55 hoặc 2221-2720 (mới)
    • American Express: 34, 37
    • Discover: 6011, 65, 644-649
  • Checksum Luhn: Tổng các chữ số sau xử lý phải chia hết cho 10.

Thuật toán Luhn (Mod 10)

  1. Từ phải sang trái, nhân đôi các chữ số ở vị trí chẵn (tính từ 0: vị trí 1, 3, 5... từ phải).
  2. Nếu nhân đôi ≥ 10, cộng 2 chữ số (ví dụ: 8×2=16 → 1+6=7). Hoặc trừ 9.
  3. Cộng tất cả chữ số ở vị trí lẻ (từ phải) + kết quả bước 2.
  4. Nếu tổng % 10 == 0 → Hợp lệ.

Ví dụ hoàn chỉnh (Sửa lỗi logic code gốc)

public class CreditCardValidator {
    
    public static void main(String[] args) {
        String[] testCards = {
            "4440967484181607",  // Visa valid
            "379354508162306",   // Amex valid
            "5555555555554444",  // MasterCard valid
            "6011111111111117",  // Discover valid
            "1234567890123456",  // Invalid (Luhn fail)
            "4440967484181608",  // Invalid (checksum fail)
        };
        
        for (String card : testCards) {
            boolean valid = isValid(card);
            System.out.printf("%s -> %s%n", card, valid ? "VALID" : "INVALID");
        }
    }
    
    public static boolean isValid(String cardNumber) {
        // 1. Chỉ chứa chữ số, độ dài 13-19
        if (cardNumber == null || !cardNumber.matches("\\d{13,19}")) {
            return false;
        }
        
        // 2. Kiểm tra prefix (Issuer Identification Number)
        if (!hasValidPrefix(cardNumber)) {
            return false;
        }
        
        // 3. Luhn checksum
        return luhnCheck(cardNumber);
    }
    
    private static boolean hasValidPrefix(String card) {
        // Visa: 4
        if (card.startsWith("4")) return true;
        // MasterCard: 51-55, 2221-2720
        if (card.startsWith("5")) {
            int prefix2 = Integer.parseInt(card.substring(0, 2));
            if (prefix2 >= 51 && prefix2 <= 55) return true;
            if (card.length() >= 4) {
                int prefix4 = Integer.parseInt(card.substring(0, 4));
                if (prefix4 >= 2221 && prefix4 <= 2720) return true;
            }
        }
        // Amex: 34, 37
        if (card.startsWith("34") || card.startsWith("37")) return true;
        // Discover: 6011, 65, 644-649
        if (card.startsWith("6011")) return true;
        if (card.startsWith("65")) return true;
        if (card.length() >= 3) {
            int prefix3 = Integer.parseInt(card.substring(0, 3));
            if (prefix3 >= 644 && prefix3 <= 649) return true;
        }
        return false;
    }
    
    private static boolean luhnCheck(String card) {
        int sum = 0;
        boolean alternate = false; // Bắt đầu từ phải: vị trí 0 (lẻ) -> không nhân đôi
        
        for (int i = card.length() - 1; i >= 0; i--) {
            int digit = card.charAt(i) - '0';
            
            if (alternate) {
                digit *= 2;
                if (digit > 9) digit -= 9; // Tương đương cộng 2 chữ số
            }
            
            sum += digit;
            alternate = !alternate;
        }
        
        return sum % 10 == 0;
    }
}

Kết quả

4440967484181607 -> VALID
379354508162306 -> VALID
5555555555554444 -> VALID
6011111111111117 -> VALID
1234567890123456 -> INVALID
4440967484181608 -> INVALID

Phân tích lỗi code gốc

  • Code gốc dùng long làm tham số → tràn số cho thẻ 16 chữ số (Long.MAX_VALUE ≈ 9.22e18, thẻ 16 số max 9.99e15 - vừa nhưng rủi ro).
  • Hàm thesize(long) chuyển long → String → length() kém hiệu quả.
  • Logic prefixmatch và getprefx phức tạp hóa đơn giản.
  • Không validate prefix MasterCard/Amex/Discover đầy đủ.

Best Practices

  • Nhận input là String (giữ leading zeros, không tràn, dễ validate regex).
  • Dùng charAt() - '0' thay vì Integer.parseInt(char + "").
  • Tách riêng logic: validate format → validate prefix → Luhn check.
  • Trả về enum hoặc object chi tiết (valid, cardType, errorReason) thay vì boolean.

Mở rộng: Xác định loại thẻ

public enum CardType {
    VISA, MASTERCARD, AMEX, DISCOVER, UNKNOWN
}

public static CardType detectType(String card) {
    if (card == null) return CardType.UNKNOWN;
    if (card.matches("^4\\d{12,18}$")) return CardType.VISA;
    if (card.matches("^(5[1-5]\\d{14}|2(2[2-9]\\d|[3-6]\\d{2}|7[0-1]\\d|720)\\d{12})$")) return CardType.MASTERCARD;
    if (card.matches("^3[47]\\d{13}$")) return CardType.AMEX;
    if (card.matches("^(6011\\d{12}|65\\d{14}|64[4-9]\\d{13})$")) return CardType.DISCOVER;
    return CardType.UNKNOWN;
}