Модуль проверки банковской карты

Я надеюсь, что кто-то сможет просмотреть модуль, который я написал для проверки банковской карты. Я включил класс, который проверяет некоторые из методов, которые предназначены исключительно для демонстрации работы методов, а не для модульного тестирования.

валидатор

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This module provides static methods for credit card validation based on
 * Luhn's algorithm <br>
 * {@link http://en.wikipedia.org/wiki/Luhn_algorithm}
 * 
 * 
 * @author NRKirby
 *
 */     
public final class CardValidator {

    private CardValidator() {}

    private static int[] array;
    private static int doubleNumber;
    private static int length;
    private static String number;
    private static int sum;

    private static final String MAESTRO = "(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{8}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{9}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{10}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{11}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{12}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{13}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{14}|(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\\d{15}$";
    private static final String MASTERCARD = "^(?!.*(?:(?:5018|5020|5038\\d{12})))[5][0-5].{14}$";
    private static final String SOLO = "(6334|6767)\\d{12}|(6334|6767)\\d{14}|(6334|6767)\\d{15}";
    private static final String SWITCH = "(4903|4905|4911|4936|6333|6759)\\d{12}|(4903|4905|4911|4936|6333|6759)\\d{14}|(4903|4905|4911|4936|6333|6759)\\d{15}|(564182|633110)\\d{10}|(564182|633110)\\d{12}|(564182|633110)\\d{13}$";
    private static final String VISA = "^(?!.*(?:(?:4026|4405|4508|4844|4913|4917)\\d{12}|417500\\d{10}))4\\d{15}$";
    private static final String VISA_ELECTRON = "(4026|4405|4508|4844|4913|4917)\\d{12}|417500\\d{10}$";

    /**
     * Returns the concatenation of {@code number} with a valid check digit.
     * 
     * @param number    the number to which a check digit will be added.
     * @return          the concatenation of {@code number} with it's valid
     *                  check digit.
     */
    public static String getCardNumber(String number) {
        return number + Integer.toString(getCheckDigit(number));
    }

    /**
     * Returns the name of the card vendor for {@code cardNumber}.
     * 
     * @param cardNumber 
     * @return  the card vendor (if known) <br>
     *          {@code else} returns an empty string
     */
    public static String getCardVendor(String cardNumber) {
            if (isVisa(cardNumber)) { return "Visa"; }
            else if (isVisaElectron(cardNumber)) { return "Visa Electron"; }
            else if (isMasterCard(cardNumber)) { return "MasterCard"; }
            else if (isMaestro(cardNumber)) { return "Maestro"; }
            else if (isSolo(cardNumber)) { return "Solo"; }
            else if (isSwitch(cardNumber)) { return "Switch"; }
            else return "";
    }

    /**
     * Returns the valid check digit for {@code number}.
     * 
     * @param number    the number for the check digit to be generated.
     * @return          the valid check digit for {@code number}. 
     */
    public static int getCheckDigit(String number) {
        getStringLength(number);
        toIntArray(number);
        sum = 0;

        for (int i=length-1;i>=0;i-=2) {
            doubleNumber = array[i]*2;
            if (doubleNumber == 10) { sum += 1; }
            else if (doubleNumber > 10) { sum += 1 + (doubleNumber % 10); }
            else { sum += doubleNumber; }
        }
        for (int i=length-2;i>=1;i-=2) {
            sum += array[i];
        }
        if (sum % 10 != 0) {
            return 10 - (sum % 10);
        }
        else { return 0; }
    }

    private static void getStringLength(String number) {
        length = number.length();
    }

    /**
     * Returns a string with any non-digit characters removed from
     * {@code str}.
     * 
     * @param str   the string to have non-digits removed from.
     * @return      a string containing only numerical digits.
     */
    public static String removeNonDigits(String str) {
        return str.replaceAll("\\D", "");
    }

    /**
     * Validates {@code cardNumber}.
     * 
     * @param cardNumber    the card number to be validated.
     * @return              {@code true} if the card number is valid <br>
     *                      {@code false} if the card number is invalid
     */
    public static boolean validate(String cardNumber) {
        number = removeNonDigits(cardNumber);
        if (number == null || number == "") { 
            return false; 
        }   
        getStringLength(number);
        array = toIntArray(cardNumber);

        sum = 0;
        for (int i=length-2;i>=0;i-=2) {
            doubleNumber = array[i]*2;
            if (doubleNumber == 10) { sum += 1; }
            else if (doubleNumber > 10) { sum += 1 + (doubleNumber % 10); }
            else { sum += doubleNumber; }
        }
        for (int i=length-1;i>=1;i-=2) {
            sum += array[i];
        }
        return sum % 10 == 0;
    }

    private static int[] toIntArray(String number) {
        int length = number.length();
        int[] intArray = new int[length];
        for (int i = 0; i < length; i++) {
            intArray[i] = Integer.parseInt(number.substring(i, i+1));
        }
        return intArray;
    }

    private static boolean isMaestro(String number) {
        Pattern p = Pattern.compile(MAESTRO);
        Matcher m = p.matcher(number);
        return m.matches();
    }

    private static boolean isMasterCard(String number) {
        Pattern p = Pattern.compile(MASTERCARD);
        Matcher m = p.matcher(number);
        return m.matches();
    }

    private static boolean isSolo(String number) {
        Pattern p = Pattern.compile(SOLO);
        Matcher m = p.matcher(number);
        return m.matches();
    }

    private static boolean isSwitch(String number) {
        Pattern p = Pattern.compile(SWITCH);
        Matcher m = p.matcher(number);
        return m.matches();
    }

    private static boolean isVisa(String number) {
        Pattern p = Pattern.compile(VISA);
        Matcher m = p.matcher(number);
        return m.matches();
    }

    private static boolean isVisaElectron(String number) {
        Pattern p = Pattern.compile(VISA_ELECTRON);
        Matcher m = p.matcher(number);
        return m.matches();
    }
}

Test

public class CardValidatorTest {

    public static void main(String[] args) {
        System.out.println(CardValidator.validate("5522139001463839"));
        System.out.println(CardValidator.validate("4751290028474914"));
        System.out.println(CardValidator.validate("6011000990139424"));
        System.out.println(CardValidator.validate("5555555555554444"));
        System.out.println(CardValidator.validate("515105105105100"));
        System.out.println(CardValidator.validate("4111111111111111"));
        System.out.println(CardValidator.validate("4012888888881881"));
        System.out.println(CardValidator.validate("6331101999990016"));
        System.out.println(CardValidator.getCheckDigit("475129002698491"));
        System.out.println(CardValidator.getCheckDigit("552213900896383"));
        System.out.println(CardValidator.getCardNumber("552213969846383"));
        System.out.println(CardValidator.getCardVendor("5018000000000000"));
        System.out.println(CardValidator.getCardVendor("6334000000000000"));
        System.out.println(CardValidator.getCardVendor("5522139698463839"));
        System.out.println(CardValidator.getCardVendor("5018139564463839"));
    }
}
11 голосов | спросил NRKirby 22 J0000006Europe/Moscow 2014, 14:05:23

3 ответа


11

Для вашего кода есть три цели:

  1. Преобразование удобной для пользователя строки ввода в формат канонических цифр
  2. Проверка правдоподобия номера с помощью алгоритма Луна
  3. Классификация типа карты в соответствии с длиной и префиксом

Поскольку класс должен идеально иметь одну цель, рассмотрите разделение вашего класса на два, особенно для (2) и (3).

Ваша конструкция довольно жесткая и негибкая, когда дело касается поддержки различных типов карт. Каждый тип карты имеет шаблон (в виде строковой константы), метод (в основном копирование и вставка) и условное внутри getCardVendor() (который включает название типа карты). Это проблема обслуживания кода: добавление нового типа карты требует, чтобы вы коснулись трех мест. Не говоря уже о том, что вам нужно перестроить этот класс, чтобы это произошло; поддержка нового типа карты не может быть добавлена ​​во время выполнения.

Решение всех этих проблем - Рефакторинг, а именно преобразование Заменить условное с полиморфизмом . Должен быть абстрактный базовый класс CardType и подклассы для каждого поставщика. Что-то вроде:

public abstract class CardType {
    /**
     * A regular expression for the valid card numbers of this CardType.
     * It is safe to assume that the number has been canonicalized.
     */
    protected abstract Pattern getNumberPattern();

    /**
     * The name of this type of card, e.g. "Visa Electron"
     */
    public abstract String toString();

    /**
     * Checks whether the given number appears to be valid
     * for this CardType, assuming that the number has already
     * passed the Luhn Algorithm verification.
     */
    public boolean isCardNumber(String number) {
        …
    }
}
ответил 200_success 22 J0000006Europe/Moscow 2014, 14:35:59
10

Еще несколько вещей, рядом с ответом на 200_sucess:

  • Ваш класс поддерживает состояние и как таковой не должен быть всеми статическими методами, а работать с соответствующими экземплярами. Однако вы также используете состояние довольно странным образом. Нет необходимости иметь длину поля и установить ее перед началом работы с ней. Тем более, что вы используете его непоследовательно (см. Метод toIntArray).
  • В основном вы используете «string-typing», т. е. все это строка. В частности, типы карт /поставщиков должны быть, по крайней мере, перечислением, если не отдельными типами, предложенными в 200_success.
  • Нет никаких оснований для компиляции шаблона снова и снова. Весь смысл иметь возможность отдельно компилировать его, так что эти накладные расходы не должны выполняться каждый раз, когда вы хотите что-то проверить против шаблона.
  • Никогда не сравнивайте строки с помощью ==! Всегда используйте equals.
  • Проверка строки для нулевой или пустой является очень старой ситуацией с плохой практикой. По крайней мере, обрезать его перед проверкой на пустоту, то есть foo == null || foo.trim().isEmpty(). Многие библиотеки, такие как Apache Commons или Guava, предлагают ярлыки для этого.
  • Если вы не можете определить поставщика карт, пустая строка действительно хороший результат? Почему бы не выбрасывать, например. a UnknownVendorException? Или, по крайней мере, вернуть null. Возвращающаяся пустая строка выглядит неудобной.
ответил Ingo Bürk 22 J0000006Europe/Moscow 2014, 17:41:52
5

Я хотел бы предложить еще несколько моментов:

  • Как вы можете обрабатывать разные типы (AmEx, хранить карты, подарочные карты и т. д.). Это может быть вне сферы действия - но это так близко к его принятию.

  • В настоящее время вы возвращаете String. Но как я могу использовать это в своем коде? Теперь я должен увидеть, есть ли VISA в любом месте в строке ... и надеюсь, что никто не использует карту Visa Master Care или какой-либо другой маркетинговый продукт.

Основываясь на @ 200_success, я бы рассмотрел enum для MajorCardType (Amex, Visa, MasterCard, Discover, custom и т. д.), и другое свойство isValid (число проходит тест Luhn).

  • Восстановите свой getCardVendor. Если карта не передает validate - это не действительная карта - и это действительно не имеет значения для поставщика. Потому что у вас нет действительной карты. И путь getCardVendor теперь, вы должны тщательно найти правильное размещение чека, а не return слишком рано.

Наконец - рассмотрим обзор Wiki , а затем внедряем несколько настраиваемых (до 10) парсеров на MII, а затем подпанели на основе IIN. Попросите их вернуть код CardType.

ответил flbas1 22 J0000006Europe/Moscow 2014, 18:45:50

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132