El validador del número de tarjeta de crédito no funciona correctamente

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

Este algoritmo parece funcionar en ejemplos como “4556737586899855”, pero no en ejemplos como “30569309025904”. He seguido el proceso y no puedo encontrar fallas en la forma en que se manejan los números, probablemente me esté perdiendo una pieza del rompecabezas aquí.

Estoy siguiendo el esquema aquí y he usado ejemplos aquí .

Utilicé esta solución para un problema de código basado en la fórmula de 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 

Los pasos se enumeran en la descripción del problema:

Desde el dígito más a la derecha, que es el dígito de control, moviéndose a la izquierda, se duplica el valor de cada segundo dígito; Si el producto de esta operación de duplicación es mayor que 9 (por ejemplo, 7 × 2 = 14), sume los dígitos de los productos (por ejemplo, 12: 1 + 2 = 3, 14: 1 + 4 = 5). Toma la sum de todos los dígitos. Si el módulo total 10 es igual a 0 (si el total termina en cero), entonces, de acuerdo con la fórmula de Luhn, el número es válido; De lo contrario, no es válido.

Este es el código para MOD10 (el algoritmo de Luhn) que escribí de la descripción en (el noruego) 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) 

Parece que podrías entallsiffer saltado el manejo especial cuando el entallsiffer es cero, y tampoco estás restando de 10, lo que significa que solo obtendrás la respuesta correcta cuando el dígito de control / sum de comprobación sea 5 .

Casi lo has acertado, excepto por el corte inicial del último dígito. No puede invertir y cortar uno en una notación de corte, sino en dos:

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

Y luego, en la llamada a la rutina checksum (), la lógica fue exagerada. Prueba esto:

 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) 

Una entrada de 4556737586899855 produce:
Valid? 589986857376554 [1, 9, 7, 7, 5, 5, 1, 8] [8, 9, 6, 5, 3, 6, 5] True

Aquí hay una biblioteca de utilidades de tarjetas de crédito que escribí que cubre la sum de comprobación de luhn así como otras comprobaciones de tarjetas de crédito.

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

Por favor, siéntase libre de comentar si falta algo que sea útil. Aclamaciones.