Credit card number validator doesn't work corr

2019-01-27 03:01发布

def checksum(card_without_check):
    card_without_check = card_without_check[-1::-1]
    def numbers(string):
        return [int(x) for x in string]
    print(card_without_check)
    odd_numbers = numbers(card_without_check[0::2])
    even_numbers = numbers(card_without_check[1::2])

    odd_numbers = [x * 2 for x in odd_numbers]
    odd_numbers = [x - 9 if x > 9 else x for x in odd_numbers]
    print(even_numbers)
    print(odd_numbers)
    return sum(odd_numbers) + sum(even_numbers)

def check(checksum, check):
    return checksum % 10 == int(check)

card_number = input("Enter card number:\n")
print(checksum(card_number[:-1]))
print("Card is", check(checksum(card_number[:-1]), card_number[-1]))

This algorithm appears to work on examples like "4556737586899855", but not on examples like "30569309025904". I've followed the process and can't find flaws in how it's handling the numbers, I'm probably just missing some piece of the puzzle here.

I'm following the outline here and have used examples here.

4条回答
小情绪 Triste *
2楼-- · 2019-01-27 03:36

You nearly got it right, except for the initial cutting off of the last digit. You cannot reverse and slice one off in one slice notation but in two:

card_without_check = card[::-1][1:]

And then in the call to the checksum() routine the logic went overboard. Try this:

def checksum(card):
    def numbers(string):
        return [int(x) for x in string]

    card_without_check = card[::-1][1:]
    print(card_without_check)
    odd_numbers = numbers(card_without_check[0::2])
    even_numbers = numbers(card_without_check[1::2])

    odd_numbers = [x * 2 for x in odd_numbers]
    odd_numbers = [x - 9 if x > 9 else x for x in odd_numbers]
    print odd_numbers
    print even_numbers
    return (sum(odd_numbers) + sum(even_numbers)) % 10

def check(card):
    return checksum(card) == int(card[-1])

def main():
    card = str(input("Enter card number:\n"))
    print "Valid? ", check(card)

An input of 4556737586899855 yields:
Valid? 589986857376554 [1, 9, 7, 7, 5, 5, 1, 8] [8, 9, 6, 5, 3, 6, 5] True

查看更多
爷的心禁止访问
3楼-- · 2019-01-27 03:42

I used this solution for a codeeval problem based on Luhn's formula:

def checksum(n):
    nums = reversed(list(map(int, n)))
    doubled = (ele * 2 if ind % 2 else ele for ind, ele in enumerate(nums))
    return not sum(sum(map(int, str(ele))) for ele in doubled) % 10

The steps are listed in the problem description:

From the rightmost digit, which is the check digit, moving left, double the value of every second digit; if the product of this doubling operation is greater than 9 (for example, 7×2=14), then sum the digits of the products (for example, 12:1+2=3, 14:1+4=5). Take the sum of all the digits. If the total modulo 10 is equal to 0 (if the total ends in zero) then, according to the Luhn formula, the number is valid; otherwise, it is not valid.

查看更多
戒情不戒烟
4楼-- · 2019-01-27 03:42

This is the code for MOD10 (the Luhn algorithm) that I wrote from the description on (the Norwegian) Wikipedia:

def weights(n, base):
    """Repeating series of numbers from base.
       For kontroll_10 it returns the series: 2,1,2,1,2,1...
       which is equivalent to multiplying every other digit
       by two (code is originally from a broader checsum module).
    """
    for i in range(n):
        yield base[i % len(base)]

def luhn_algorithm(s):
    """Also known as the MOD10 algorithm.
    """
    digits = map(int, list(s))[::-1]  # reversed
    _weights = weights(len(digits), [2, 1])
    products = ''.join(str(s * v) for (s, v) in zip(digits, _weights))
    sum_of_digits = sum(int(c) for c in products)
    check_digit = sum_of_digits % 10
    if check_digit == 0:
        checksum = check_digit
    else:
        checksum = 10 - check_digit
    return str(checksum)

Looks like you might have skipped the special handling when the entallsiffer is zero, and you're also not subtracting from 10, meaning you'll only get the correct answer when the control/checksum digit is 5.

查看更多
时光不老,我们不散
5楼-- · 2019-01-27 03:42

Here is a credit card utils library I wrote that covers luhn checksum as well as other credit card checks.

import re
import logging
import datetime

VISA_CC = 'Visa'  # Visa
MASTERCARD_CC = 'MasterCard'  # MasterCard
AMEX_CC = 'American Express'  # American Express

JCB_CC = 'JCB'
DISCOVER_CC = 'DISCOVER'
DINERS_CC = 'DINERS'
MAESTRO_CC = 'MAESTRO'
LASER_CC = 'LASER'
OTHER_CC = ''  # UNKNOWN

# Set which cards you accept
ACCEPTED_CARDS = [VISA_CC, MASTERCARD_CC, AMEX_CC]


def is_american_express(cc_number):
    """Checks if the card is an american express. If us billing address country code, & is_amex, use vpos
    https://en.wikipedia.org/wiki/Bank_card_number#cite_note-GenCardFeatures-3
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^3[47][0-9]{13}$', cc_number))


def is_visa(cc_number):
    """Checks if the card is a visa, begins with 4 and 12 or 15 additional digits.
    :param cc_number: unicode card number
    """

    # Standard Visa is 13 or 16, debit can be 19
    if bool(re.match(r'^4', cc_number)) and len(cc_number) in [13, 16, 19]:
        return True

    return False


def is_mastercard(cc_number):
    """Checks if the card is a mastercard. Begins with 51-55 or 2221-2720 and 16 in length.
    :param cc_number: unicode card number
    """
    if len(cc_number) == 16 and cc_number.isdigit():  # Check digit, before cast to int
        return bool(re.match(r'^5[1-5]', cc_number)) or int(cc_number[:4]) in range(2221, 2721)
    return False


def is_discover(cc_number):
    """Checks if the card is discover, re would be too hard to maintain. Not a supported card.
    :param cc_number: unicode card number
    """
    if len(cc_number) == 16:
        try:
            # return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or cc_number[:6] in range(622126, 622926))
            return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or 622126 <= int(cc_number[:6]) <= 622925)
        except ValueError:
            return False
    return False


def is_jcb(cc_number):
    """Checks if the card is a jcb. Not a supported card.
    :param cc_number: unicode card number
    """
    # return bool(re.match(r'^(?:2131|1800|35\d{3})\d{11}$', cc_number))  # wikipedia
    return bool(re.match(r'^35(2[89]|[3-8][0-9])[0-9]{12}$', cc_number))  # PawelDecowski


def is_diners_club(cc_number):
    """Checks if the card is a diners club. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^3(?:0[0-6]|[68][0-9])[0-9]{11}$', cc_number))  # 0-5 = carte blance, 6 = international


def is_laser(cc_number):
    """Checks if the card is laser. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^(6304|670[69]|6771)', cc_number))


def is_maestro(cc_number):
    """Checks if the card is maestro. Not a supported card.
    :param cc_number: unicode card number
    """
    possible_lengths = [12, 13, 14, 15, 16, 17, 18, 19]
    return bool(re.match(r'^(50|5[6-9]|6[0-9])', cc_number)) and len(cc_number) in possible_lengths


# Child cards

def is_visa_electron(cc_number):
    """Child of visa. Checks if the card is a visa electron. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^(4026|417500|4508|4844|491(3|7))', cc_number)) and len(cc_number) == 16


def is_total_rewards_visa(cc_number):
    """Child of visa. Checks if the card is a Total Rewards Visa. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^41277777[0-9]{8}$', cc_number))


def is_diners_club_carte_blanche(cc_number):
    """Child card of diners. Checks if the card is a diners club carte blance. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^30[0-5][0-9]{11}$', cc_number))  # github PawelDecowski, jquery-creditcardvalidator


def is_diners_club_carte_international(cc_number):
    """Child card of diners. Checks if the card is a diners club international. Not a supported card.
    :param cc_number: unicode card number
    """
    return bool(re.match(r'^36[0-9]{12}$', cc_number))  # jquery-creditcardvalidator


def get_card_type_by_number(cc_number):
    """Return card type constant by credit card number
    :param cc_number: unicode card number
    """
    if is_visa(cc_number):
        return VISA_CC
    if is_mastercard(cc_number):
        return MASTERCARD_CC
    if is_american_express(cc_number):
        return AMEX_CC
    if is_discover(cc_number):
        return DISCOVER_CC
    if is_jcb(cc_number):
        return JCB_CC
    if is_diners_club(cc_number):
        return DINERS_CC
    if is_laser(cc_number):  # Check before maestro, as its inner range
        return LASER_CC
    if is_maestro(cc_number):
        return MAESTRO_CC
    return OTHER_CC


def get_card_type_by_name(cc_type):
    """Return card type constant by string
    :param cc_type: dirty string card type or name
    """
    cc_type_str = cc_type.replace(' ', '').replace('-', '').lower()
    # Visa
    if 'visa' in cc_type_str:
        return VISA_CC
    # MasterCard
    if 'mc' in cc_type_str or 'mastercard' in cc_type_str:
        return MASTERCARD_CC
    # American Express
    if cc_type_str in ('americanexpress', 'amex'):
        return AMEX_CC
    # Discover
    if 'discover' in cc_type_str:
        return DISCOVER_CC
    # JCB
    if 'jcb' in cc_type_str:
        return JCB_CC
    # Diners
    if 'diners' in cc_type_str:
        return DINERS_CC
    # Maestro
    if 'maestro' in cc_type_str:
        return MAESTRO_CC
    # Laser
    if 'laser' in cc_type_str:
        return LASER_CC
    # Other Unsupported Cards  Dankort, Union, Cartebleue, Airplus etc..
    return OTHER_CC


def credit_card_luhn_checksum(card_number):
    """Credit card luhn checksum
    :param card_number: unicode card number
    """
    def digits_of(cc):
        return [int(_digit) for _digit in str(cc)]
    digits = digits_of(card_number)
    odd_digits = digits[-1::-2]
    even_digits = digits[-2::-2]
    checksum = sum(odd_digits)
    for digit in even_digits:
        checksum += sum(digits_of(digit * 2))
    return checksum % 10


def is_valid_cvv(card_type, cvv):
    """Validates the cvv based on card type
    :param cvv: card cvv security code
    :param card_type: string card type
    """
    if card_type == AMEX_CC:
        return len(str(cvv)) == 4
    else:
        return len(str(cvv)) == 3


def is_cc_luhn_valid(card_number):
    """Returns true if the luhn code is 0
    :param card_number: unicode string for card_number, cannot be other type.
    """
    is_valid_cc = card_number.isdecimal() and credit_card_luhn_checksum(card_number) == 0
    if not is_valid_cc:
        logging.error("Invalid Credit Card Number, fails luhn: {}".format(card_number))
    return is_valid_cc


def is_valid_cc_expiry(expiry_month, expiry_year):
    """Returns true if the card expiry is not good.
    Edge case: It's end of month, the expiry is on the current month and user is in a different timezone.
    :param expiry_year: unicode two digit year
    :param expiry_month: unicode two digit month
    """
    try:
        today = datetime.date.today()
        cur_month, cur_year = today.month, int(str(today.year)[2:])
        expiry_month, expiry_year = int(expiry_month), int(expiry_year)
        is_invalid_year = expiry_year < cur_year
        is_invalid_month = False
        if not is_invalid_year:
            is_invalid_month = ((expiry_month < cur_month and cur_year == expiry_year) or
                                expiry_month not in range(1, 13))
        if is_invalid_year or is_invalid_month:
            logging.info("Invalid credit card expiry {}/{}.".format(expiry_month, expiry_year))
            return False
    except ValueError:
        logging.error("Could not calculate valid expiry for month year {}/{}.".format(expiry_month, expiry_year))
        return False
    return True


def is_supported_credit_card(card_type):
    """Checks if card type is in accepted cards
    :param card_type: string card type
    """
    if card_type in ACCEPTED_CARDS:
        return True
    logging.error("Card type not supported, {}.".format(card_type))
    return False  # (OTHER_CC, DISCOVER_CC)


def cc_card_to_mask(cc_number, show_first=6, show_last=4):
    """Returns masked credit card number
    :param show_last: beginning of card, chars not to mask
    :param show_first: end of card, chars not to mask
    :param cc_number: unicode card number
    """
    cc_number = str(cc_number)
    if cc_number:
        return "{}{}{}".format(cc_number[:show_first],
                               "X" * (len(cc_number) - show_first - show_last),
                               cc_number[show_last * -1:])
    else:
        return ""


def string_to_full_mask(cc_field):
    """Returns credit card field or any string converted to a full mask.
    I.e. cvv, expiry month, expiry year, password.
    :param cc_field: a generic credit card field, other than cc card no
    """
    try:
        cc_field = cc_field.strip()
        return "X" * len(cc_field)
    except (TypeError, AttributeError):
        return ""

Please feel free to comment if there is anything missing that is useful. Cheers.

查看更多
登录 后发表回答