信用卡号验证器无法正常工作

时间:2015-03-22 16:59:22

标签: python checksum luhn

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]))

此算法似乎适用于“4556737586899855”等示例,但不适用于“30569309025904”等示例。我已经按照这个过程找不到它处理数字的缺陷了,我可能只是错过了这里的一些难题。

我正在关注大纲here并使用了示例here

4 个答案:

答案 0 :(得分:1)

我使用这个解决方案来解决基于Luhn公式的代码问题:

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

问题描述中列出了这些步骤:

  

从最右边的数字,即校验位,向左移动,每隔一位数的值加倍;如果该倍增操作的乘积大于9(例如,7×2 = 14),则将乘积的数字相加(例如,12:1 + 2 = 3,14:1 + 4 = 5)。   取所有数字的总和。   如果总模10等于0(如果总数以零结束)那么,根据Luhn公式,该数是有效的;否则,它无效。

答案 1 :(得分:0)

这是我从(挪威语)维基百科的描述中写的MOD10(Luhn算法)的代码:

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)

看起来你可能在entallsiffer为零时跳过特殊处理,而你也没有从10中减去,这意味着当控制/校验和数字为{{{}时,你只会得到正确的答案。 1}}。

答案 2 :(得分:0)

你几乎把它弄好了,除了最后一个数字的初始切断。您不能以一个切片表示法反转和切片一个,而是两个:

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

然后在对checksum()例程的调用中,逻辑变得过火。试试这个:

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)

输入4556737586899855得出:
Valid? 589986857376554 [1, 9, 7, 7, 5, 5, 1, 8] [8, 9, 6, 5, 3, 6, 5] True

答案 3 :(得分:0)

这是我写的信用卡工具库,涵盖了luhn校验和以及其他信用卡支票。

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 ""

如果有任何遗漏有用,请随时发表评论。欢呼声。