【问题标题】:Credit card number validator doesn't work correctly信用卡号码验证器无法正常工作
【发布时间】:2015-03-22 16:59:22
【问题描述】:
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

【问题讨论】:

标签: python checksum luhn


【解决方案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(如果总数以 0 结尾),则根据 Luhn 公式,该数有效;否则无效。

【讨论】:

  • 微不足道的“改进”:nums = map(int, reversed(n)) 避免了临时的list(对于 Py3 map,或 Py2 future_builtins.map/itertools.imap)和一个内置调用。假设n 是当然的序列。同理,只要你用map,不妨把内层sum改成sum(map(int, str(ele)));更短,也许更快(?)。是的,这是没有意义的,因为我们知道输入很小。投票赞成。 :-)
【解决方案2】:

这是我根据(挪威语)维基百科的描述写的 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 中减去,这意味着您只有在控制/校验和数字为 5 时才能得到正确的答案。

【讨论】:

    【解决方案3】:

    除了最后一位数字的初始截断之外,您几乎是正确的。您不能在一个切片符号中反转和切片一个,而是用两个:

    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

    【讨论】:

      【解决方案4】:

      这是我编写的信用卡实用程序库,涵盖 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 ""
      

      如果有任何有用的遗漏,请随时发表评论。干杯。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多