Skip to content

Commit

Permalink
+ creditcard.py: cleaned up the interface to CreditCard class.
Browse files Browse the repository at this point in the history
  • Loading branch information
John Boxall committed May 13, 2009
1 parent 616f56f commit f6c9299
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 119 deletions.
65 changes: 23 additions & 42 deletions pro/creditcard.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import datetime
import re
from string import digits, split as L

# Adapted from:
# http://www.djangosnippets.org/snippets/764/
# http://www.satchmoproject.com/trac/browser/satchmo/trunk/satchmo/apps/satchmo_utils/views.py
# http://tinyurl.com/shoppify-credit-cards

# Everything that isn't a digit.
RE_NOT_DIGIT = re.compile(r'[^\d]')

# Well known card regular expressions.
CARDS = {
'Visa': re.compile(r"^4\d{12}(\d{3})?$"),
Expand All @@ -20,26 +17,26 @@
'Discover': re.compile("^(6011|65\d{2})\d{12}$"),
}

# Well known test numberss
TEST_NUMBERS = ("378282246310005 371449635398431 378734493671000 30569309025904 38520000023237 6011111111111117 6011000990139424 555555555554444 5105105105105100 4111111111111111 4012888888881881 4222222222222").split()

# Well known test numbers
TEST_NUMBERS = L("378282246310005 371449635398431 378734493671000 30569309025904"
"38520000023237 6011111111111117 6011000990139424 555555555554444"
"5105105105105100 4111111111111111 4012888888881881 4222222222222")

def verify_credit_card(number):
"""
Returns the card type for number or None if invalid.
"""
"""Returns the card type for given card number or None if invalid."""
return CreditCard(number).verify()

class CreditCard(object):
def __init__(self, number):
self.number = number

def _mod10(self):
"""
Check a credit card number for validity using the mod10 algorithm.
"""
def is_number(self):
"""Returns True if there is at least one digit in number."""
self.number = "".join([c for c in self.number if c in digits])
return self.number.isdigit()

def is_mod10(self):
"""Returns True if the card number is valid according to mod10."""
double = 0
total = 0
for i in range(len(self.number) - 1, -1, -1):
Expand All @@ -48,35 +45,19 @@ def _mod10(self):
double = (double + 1) % 2
return (total % 10) == 0

def _strip(self):
"""
Everything that's not a digit must go.
"""
self.number = RE_NOT_DIGIT.sub('', self.number)
return self.number.isdigit()

def _test(self):
"""
Make sure its not a junk card.
"""
return self.number not in TEST_NUMBERS
def is_test(self):
"""Make sure its not a junk card."""
return self.number in TEST_NUMBERS

def _type(self):
"""
Return the type if it matches one of the cards.
"""
def get_type(self):
"""Return the type if it matches one of the cards."""
for card, pattern in CARDS.iteritems():
if pattern.match(self.number):
return card
return None

def verify(self):
"""
Returns the card type if valid else None.
"""
if self._strip() and self._test() and self._mod10():
return self._type()
"""Returns the card type if valid else None."""
if self.is_number() and not self.is_test() and self.is_mod10():
return self.get_type()
return None
27 changes: 7 additions & 20 deletions pro/fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-import re
import datetime
from datetime import date, datetime
# -*- coding: utf-8 -*-
from datetime import date
from calendar import monthrange

from django import forms
Expand All @@ -12,35 +11,23 @@


class CreditCardField(forms.CharField):
"""
Form field for checking out a credit card.
"""
"""Form field for checking out a credit card."""
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 20)
super(CreditCardField, self).__init__(*args, **kwargs)

def clean(self, value):
"""
Raises a ValidationError if the card is not valid.
Also sets card type!
"""
card_type = verify_credit_card(value)
if card_type is None:
"""Raises a ValidationError if the card is not valid and stashes card type."""
self.card_type = verify_credit_card(value)
if self.card_type is None:
raise forms.ValidationError("Invalid credit card number.")

self.card_type = card_type
return value


# Credit Card Expiry Fields from:
# http://www.djangosnippets.org/snippets/907/
class CreditCardExpiryWidget(forms.MultiWidget):
"""
MultiWidget for representing CC expiry date.
"""
"""MultiWidget for representing credit card expiry date."""
def decompress(self, value):
return [value.month, value.year] if value else [None, None]

Expand Down
18 changes: 5 additions & 13 deletions pro/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
# -*- coding: utf-8 -*-
from django import forms

from paypal.pro.fields import CreditCardField, CreditCardExpiryField, CreditCardCVV2Field, CountryField
from paypal.pro.fields import CreditCardField, CreditCardExpiryField,
CreditCardCVV2Field, CountryField


class PaymentForm(forms.Form):
"""
Form used to process direct payments.
"""
"""Form used to process direct payments."""
firstname = forms.CharField(255, label="First Name")
lastname = forms.CharField(255, label="Last Name")
street = forms.CharField(255, label="Street Address")
Expand All @@ -22,10 +20,7 @@ class PaymentForm(forms.Form):
cvv2 = CreditCardCVV2Field(label="Card Security Code")

def process(self, request, item):
"""
Process a PayPal direct payment.
"""
"""Process a PayPal direct payment."""
from paypal.pro.helpers import PayPalWPP
wpp = PayPalWPP(request)
params = self.cleaned_data
Expand All @@ -46,9 +41,6 @@ def process(self, request, item):


class ConfirmForm(forms.Form):
"""
Hidden form used by ExpressPay flow to keep track of payer information.
"""
"""Hidden form used by ExpressPay flow to keep track of payer information."""
token = forms.CharField(max_length=255, widget=forms.HiddenInput())
PayerID = forms.CharField(max_length=255, widget=forms.HiddenInput())
60 changes: 16 additions & 44 deletions pro/helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urllib
import urllib2
import time
import datetime
import pprint
import urllib, urllib2, time, datetime, pprint

from django.conf import settings
from django.forms.models import fields_for_model
Expand All @@ -24,23 +20,19 @@


def paypal_time(time_obj=None):
"""
Returns a time suitable for `profilestartdate` or other PayPal time fields.
"""
"""Returns a time suitable for PayPal time fields."""
if time_obj is None:
time_obj = time.gmtime()
return time.strftime(PayPalNVP.TIMESTAMP_FORMAT, time_obj)

def paypaltime2datetime(s):
"""
Convert a PayPal time string to a DateTime.
"""
"""Convert a PayPal time string to a DateTime."""
return datetime.datetime(*(time.strptime(s, PayPalNVP.TIMESTAMP_FORMAT)[:6]))


class PayPalError(Exception):
pass
"""Error thrown when something be wrong."""


class PayPalWPP(object):
"""
Expand All @@ -54,10 +46,7 @@ class PayPalWPP(object):
"""
def __init__(self, request, params=BASE_PARAMS):
"""
Required - USER / PWD / SIGNATURE / VERSION
"""
"""Required - USER / PWD / SIGNATURE / VERSION"""
self.request = request
if TEST:
self.endpoint = SANDBOX_ENDPOINT
Expand All @@ -67,14 +56,11 @@ def __init__(self, request, params=BASE_PARAMS):
self.signature = urllib.urlencode(self.signature_values) + "&"

def doDirectPayment(self, params):
"""
Do direct payment. Woot, this is where we take the money from the guy.
"""
"""Call PayPal DoDirectPayment method."""
defaults = {"method": "DoDirectPayment", "paymentaction": "Sale"}
required = L("creditcardtype acct expdate cvv2 ipaddress firstname lastname street city state countrycode zip amt")
nvp_obj = self._fetch(params, required, defaults)
# ### Could check cvv2match / avscode are both 'X' or '0'
# @@@ Could check cvv2match / avscode are both 'X' or '0'
# qd = django.http.QueryDict(nvp_obj.response)
# if qd.get('cvv2match') not in ['X', '0']:
# nvp_obj.set_flag("Invalid cvv2match: %s" % qd.get('cvv2match')
Expand Down Expand Up @@ -161,11 +147,7 @@ def refundTransaction(self, params):
raise NotImplementedError

def _is_recurring(self, params):
"""
Helper tries to determine whether an item is recurring by looking at
the parameters included. billingfrequency is not given for one time payments.
"""
"""Returns True if the item passed is a recurring transaction."""
return 'billingfrequency' in params

def _recurring_setExpressCheckout_adapter(self, params):
Expand All @@ -185,10 +167,7 @@ def _recurring_setExpressCheckout_adapter(self, params):
return params

def _fetch(self, params, required, defaults):
"""
Make the NVP request and store the response.
"""
"""Make the NVP request and store the response."""
defaults.update(params)
pp_params = self._check_and_update_params(required, defaults)
pp_string = self.signature + urllib.urlencode(pp_params)
Expand All @@ -201,14 +180,10 @@ def _fetch(self, params, required, defaults):
pprint.pprint(response_params)

# Put all fields from NVP into everything so we can pass it to `create`.
everything = {}
def merge(*dicts):
for d in dicts:
for k, v in d.iteritems():
if k in NVP_FIELDS:
everything[k] = v

merge(defaults, response_params)
everything = defaults
for k, v in response_params.iteritems():
if k in NVP_FIELDS:
everything[k] = v

# PayPal timestamp has to be set correctly to be stored.
if 'timestamp' in everything:
Expand All @@ -220,10 +195,7 @@ def merge(*dicts):
return nvp_obj

def _request(self, data):
"""
Moved out to make testing easier.
"""
"""Moved out to make testing easier."""
return urllib2.urlopen(self.endpoint, data).read()

def _check_and_update_params(self, required, params):
Expand Down

0 comments on commit f6c9299

Please sign in to comment.