Source code for qualpay.card

# Based on Michael Angeletti's MIT Licensed pycard
# https://github.com/orokusaki/pycard

import calendar
import datetime
import re

from .gateway import PaymentGateway

__all__ = ('Card',)

non_digit_regexp = re.compile(r'\D')


[docs]class Card(object): """ A credit card that may be valid or invalid. """ # A mapping from common credit card brands to their number regexps BRAND_VISA = 'visa' BRAND_MASTERCARD = 'mastercard' BRAND_AMEX = 'amex' BRAND_DISCOVER = 'discover' BRAND_UNKNOWN = u'unknown' BRANDS = { BRAND_VISA: re.compile(r'^4\d{12}(\d{3})?$'), BRAND_MASTERCARD: re.compile(r'^(5[1-5]\d{4}|677189)\d{10}$'), BRAND_AMEX: re.compile(r'^3[47]\d{13}$'), BRAND_DISCOVER: re.compile(r'^(6011|65\d{2})\d{12}$'), } def __init__(self, number, exp_month, exp_year, cvv2): """ Attaches the provided card data and holder to the card after removing non-digits from the provided number. """ self.number = non_digit_regexp.sub('', number) self.exp_date = ExpDate(exp_month, exp_year) self.cvv2 = cvv2 def __repr__(self): """ Returns a typical repr with a simple representation of the masked card number and the exp date. """ return u'<Card brand={b} number={n}, exp_date={e}>'.format( b=self.brand, n=self.mask, e=self.exp_date.mmyyyy ) @property def mask(self): """ Returns the credit card number with each of the number's digits but the first six and the last four digits replaced by an X, formatted the way they appear on their respective brands' cards. """ # If the card is invalid, return an "invalid" message if not self.is_luhn_valid: return u'invalid' # If the card is an Amex, it will have special formatting if self.brand == self.BRAND_AMEX: return u'XXXX-XXXXXX-X{0}'.format(self.number[11:15]) # All other cards return u'XXXX-XXXX-XXXX-{0}'.format(self.number[12:16]) @property def brand(self): """ Returns the brand of the card, if applicable, else an "unknown" brand. """ # Check if the card is of known type for brand, regexp in self.BRANDS.items(): if regexp.match(self.number): return brand # Default to unknown brand return self.BRAND_UNKNOWN @property def is_expired(self): """ Returns whether or not the card is expired. """ return self.exp_date.is_expired @property def is_valid(self): """ Returns whether or not the card is a valid card for making payments. """ return not self.is_expired and self.is_luhn_valid @property def is_luhn_valid(self): """ Returns whether or not the card's number validates against the luhn algorithm, automatically returning False on an empty value. """ # Check for empty string if not self.number: return False r = [int(ch) for ch in str(self.number)][::-1] s = (sum(r[0::2]) + sum(sum(divmod(d * 2, 10)) for d in r[1::2])) return s % 10 == 0 def authorize(self, amt_tran, **kwargs): assert self.is_valid # CONSIDER: What should this raise? gateway = PaymentGateway() return gateway.authorize( card_number=self.number, exp_date=self.exp_date.mmyy, amt_tran=amt_tran, **kwargs ) def verify(self, **kwargs): assert self.is_valid # CONSIDER: What should this raise? gateway = PaymentGateway() return gateway.verify( card_number=self.number, exp_date=self.exp_date.mmyy, cvv2=self.cvv2, **kwargs ) def sale(self, amt_tran, **kwargs): assert self.is_valid # CONSIDER: What should this raise? gateway = PaymentGateway() return gateway.sale( card_number=self.number, exp_date=self.exp_date.mmyy, cvv2=self.cvv2, amt_tran=amt_tran, **kwargs ) def tokenize(self, **kwargs): assert self.is_valid # CONSIDER: What should this raise? gateway = PaymentGateway() return gateway.tokenize( card_number=self.number, exp_date=self.exp_date.mmyy, cvv2=self.cvv2, **kwargs )
class ExpDate(object): """ An expiration date of a credit card. """ def __init__(self, month, year): """ Attaches the last possible datetime for the given month and year, as well as the raw month and year values. """ # Attach month and year self.month = month self.year = year # Get the month's day count weekday, day_count = calendar.monthrange(year, month) # Attach the last possible datetime for the provided month and year self.expired_after = datetime.datetime( year, month, day_count, 23, 59, 59, 999999 ) def __repr__(self): """ Returns a typical repr with a simple representation of the exp date. """ return u'<ExpDate expired_after={d}>'.format( d=self.expired_after.strftime('%m/%Y') ) @property def is_expired(self): """ Returns whether or not the expiration date has passed in American Samoa (the last timezone). """ # Get the current datetime in UTC utcnow = datetime.datetime.utcnow() # Get the datetime minus 11 hours (Samoa is UTC-11) samoa_now = utcnow - datetime.timedelta(hours=11) # Return whether the exipred after time has passed in American Samoa return samoa_now > self.expired_after @property def mmyyyy(self): """ Returns the expiration date in MM/YYYY format. """ return self.expired_after.strftime('%m/%Y') @property def mmyy(self): """ Returns the expiration date in MMYY format. """ return self.expired_after.strftime('%m%y') @property def mm(self): """ Returns the expiration date in MM format. """ return self.expired_after.strftime('%m') @property def yyyy(self): """ Returns the expiration date in YYYY format. """ return self.expired_after.strftime('%Y')