Модуль проверки банковской карты
Я надеюсь, что кто-то сможет просмотреть модуль, который я написал для проверки банковской карты. Я включил класс, который проверяет некоторые из методов, которые предназначены исключительно для демонстрации работы методов, а не для модульного тестирования.
валидатор
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"));
}
}
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_sucess:
- Ваш класс поддерживает состояние и как таковой не должен быть всеми статическими методами, а работать с соответствующими экземплярами. Однако вы также используете состояние довольно странным образом. Нет необходимости иметь длину поля и установить ее перед началом работы с ней. Тем более, что вы используете его непоследовательно (см. Метод
toIntArray
). - В основном вы используете «string-typing», т. е. все это строка. В частности, типы карт /поставщиков должны быть, по крайней мере, перечислением, если не отдельными типами, предложенными в 200_success.
- Нет никаких оснований для компиляции шаблона снова и снова. Весь смысл иметь возможность отдельно компилировать его, так что эти накладные расходы не должны выполняться каждый раз, когда вы хотите что-то проверить против шаблона.
- Никогда не сравнивайте строки с помощью
==
! Всегда используйтеequals
. - Проверка строки для нулевой или пустой является очень старой ситуацией с плохой практикой. По крайней мере, обрезать его перед проверкой на пустоту, то есть
foo == null || foo.trim().isEmpty()
. Многие библиотеки, такие как Apache Commons или Guava, предлагают ярлыки для этого. - Если вы не можете определить поставщика карт, пустая строка действительно хороший результат? Почему бы не выбрасывать, например. a
UnknownVendorException
? Или, по крайней мере, вернутьnull
. Возвращающаяся пустая строка выглядит неудобной.
Я хотел бы предложить еще несколько моментов:
-
Как вы можете обрабатывать разные типы (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
.