Skip to content

Commit

Permalink
Merge remote-tracking branch 'ZeppLab/master'
Browse files Browse the repository at this point in the history
Fixes support for Django 1.6. Also PEP8 cleanups

Conflicts:
	.gitignore
  • Loading branch information
spookylukey committed Jan 3, 2014
2 parents 718dc71 + 121d108 commit 1b3cc31
Show file tree
Hide file tree
Showing 38 changed files with 626 additions and 377 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
/django_paypal.egg-info
*.swp
.tox
.idea/
3 changes: 2 additions & 1 deletion paypal/pro/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from string import split as L
from django.contrib import admin
from paypal.pro.models import PayPalNVP

Expand All @@ -9,4 +8,6 @@ class PayPalNVPAdmin(admin.ModelAdmin):
list_display = ('user', 'ipaddress', 'method', 'flag', 'flag_code', 'created_at')
list_filter = ('flag', 'created_at')
search_fields = ('user__email', 'ip_address', 'flag', 'firstname', 'lastname')


admin.site.register(PayPalNVP, PayPalNVPAdmin)
4 changes: 3 additions & 1 deletion paypal/pro/creditcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@
"5105105105105100", "4111111111111111", "4012888888881881", "4222222222222"
]


def verify_credit_card(number):
"""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 is_number(self):
"""True if there is at least one digit in number."""
self.number = re.sub(r'[^\d]', '', self.number)
Expand Down
13 changes: 8 additions & 5 deletions paypal/pro/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from calendar import monthrange
from datetime import date

from django.db import models
from django import forms
from django.utils.translation import ugettext as _

Expand All @@ -12,10 +11,11 @@

class CreditCardField(forms.CharField):
"""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 and stashes card type."""
if value:
Expand All @@ -30,6 +30,7 @@ def clean(self, value):
# http://www.djangosnippets.org/snippets/907/
class CreditCardExpiryWidget(forms.MultiWidget):
"""MultiWidget for representing credit card expiry date."""

def decompress(self, value):
if isinstance(value, date):
return [value.month, value.year]
Expand All @@ -42,6 +43,7 @@ def format_output(self, rendered_widgets):
html = u' / '.join(rendered_widgets)
return u'<span style="white-space: nowrap">%s</span>' % html


class CreditCardExpiryField(forms.MultiValueField):
EXP_MONTH = [(x, x) for x in xrange(1, 13)]
EXP_YEAR = [(x, x) for x in xrange(date.today().year, date.today().year + 15)]
Expand All @@ -55,12 +57,12 @@ def __init__(self, *args, **kwargs):
errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])

fields = (
forms.ChoiceField(choices=self.EXP_MONTH, error_messages={'invalid': errors['invalid_month']}),
forms.ChoiceField(choices=self.EXP_YEAR, error_messages={'invalid': errors['invalid_year']}),
)

super(CreditCardExpiryField, self).__init__(fields, *args, **kwargs)
self.widget = CreditCardExpiryWidget(widgets=[fields[0].widget, fields[1].widget])

Expand Down Expand Up @@ -90,7 +92,7 @@ class CreditCardCVV2Field(forms.CharField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 4)
super(CreditCardCVV2Field, self).__init__(*args, **kwargs)


# Country Field from:
# http://www.djangosnippets.org/snippets/494/
Expand Down Expand Up @@ -337,6 +339,7 @@ def __init__(self, *args, **kwargs):
('ZW', _('Zimbabwe')),
)


class CountryField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('choices', COUNTRIES)
Expand Down
5 changes: 4 additions & 1 deletion paypal/pro/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from paypal.pro.fields import CreditCardField, CreditCardExpiryField, CreditCardCVV2Field, CountryField
from paypal.pro.exceptions import PayPalFailure


class PaymentForm(forms.Form):
"""Form used to process direct payments."""
firstname = forms.CharField(255, label="First Name")
Expand All @@ -21,7 +22,8 @@ class PaymentForm(forms.Form):
def process(self, request, item):
"""Process a PayPal direct payment."""
from paypal.pro.helpers import PayPalWPP
wpp = PayPalWPP(request)

wpp = PayPalWPP(request)
params = self.cleaned_data
params['creditcardtype'] = self.fields['acct'].card_type
params['expdate'] = self.cleaned_data['expdate'].strftime("%m%Y")
Expand All @@ -39,6 +41,7 @@ def process(self, request, item):
return False
return True


class ConfirmForm(forms.Form):
"""Hidden form used by ExpressPay flow to keep track of payer information."""
token = forms.CharField(max_length=255, widget=forms.HiddenInput())
Expand Down
36 changes: 20 additions & 16 deletions paypal/pro/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
from paypal.pro.exceptions import PayPalFailure

TEST = settings.PAYPAL_TEST
USER = settings.PAYPAL_WPP_USER
USER = settings.PAYPAL_WPP_USER
PASSWORD = settings.PAYPAL_WPP_PASSWORD
SIGNATURE = settings.PAYPAL_WPP_SIGNATURE
VERSION = 54.0
BASE_PARAMS = dict(USER=USER , PWD=PASSWORD, SIGNATURE=SIGNATURE, VERSION=VERSION)
BASE_PARAMS = dict(USER=USER, PWD=PASSWORD, SIGNATURE=SIGNATURE, VERSION=VERSION)
ENDPOINT = "https://api-3t.paypal.com/nvp"
SANDBOX_ENDPOINT = "https://api-3t.sandbox.paypal.com/nvp"
NVP_FIELDS = fields_for_model(PayPalNVP).keys()
Expand All @@ -36,15 +36,16 @@ def paypal_time(time_obj=None):
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."""
return datetime.datetime(*(time.strptime(s, PayPalNVP.TIMESTAMP_FORMAT)[:6]))


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


class PayPalWPP(object):
"""
Expand All @@ -56,6 +57,7 @@ class PayPalWPP(object):
Name-Value Pair API Developer Guide and Reference:
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_NVPAPI_DeveloperGuide.pdf
"""

def __init__(self, request, params=BASE_PARAMS):
"""Required - USER / PWD / SIGNATURE / VERSION"""
self.request = request
Expand All @@ -69,7 +71,8 @@ def __init__(self, request, params=BASE_PARAMS):
def doDirectPayment(self, params):
"""Call PayPal DoDirectPayment method."""
defaults = {"method": "DoDirectPayment", "paymentaction": "Sale"}
required = L("creditcardtype acct expdate cvv2 ipaddress firstname lastname street city state countrycode zip amt")
required = L(
"creditcardtype acct expdate cvv2 ipaddress firstname lastname street city state countrycode zip amt")
nvp_obj = self._fetch(params, required, defaults)
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
Expand Down Expand Up @@ -110,7 +113,7 @@ def doExpressCheckoutPayment(self, params):
raise PayPalFailure(nvp_obj.flag_info)
payment_was_successful.send(params)
return nvp_obj

def createRecurringPaymentsProfile(self, params, direct=False):
"""
Set direct to True to indicate that this is being called as a directPayment.
Expand All @@ -126,7 +129,7 @@ def createRecurringPaymentsProfile(self, params, direct=False):
required + L("token payerid")

nvp_obj = self._fetch(params, required, defaults)

# Flag if profile_type != ActiveProfile
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
Expand Down Expand Up @@ -167,10 +170,10 @@ def updateRecurringPaymentsProfile(self, params):
if nvp_obj.flag:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj

def billOutstandingAmount(self, params):
raise NotImplementedError

def manangeRecurringPaymentsProfileStatus(self, params, fail_silently=False):
"""
Requires `profileid` and `action` params.
Expand All @@ -182,7 +185,8 @@ def manangeRecurringPaymentsProfileStatus(self, params, fail_silently=False):
nvp_obj = self._fetch(params, required, defaults)

# TODO: This fail silently check should be using the error code, but its not easy to access
if not nvp_obj.flag or (fail_silently and nvp_obj.flag_info == 'Invalid profile status for cancel action; profile should be active or suspended'):
if not nvp_obj.flag or (
fail_silently and nvp_obj.flag_info == 'Invalid profile status for cancel action; profile should be active or suspended'):
if params['action'] == 'Cancel':
recurring_cancel.send(sender=nvp_obj)
elif params['action'] == 'Suspend':
Expand All @@ -192,7 +196,7 @@ def manangeRecurringPaymentsProfileStatus(self, params, fail_silently=False):
else:
raise PayPalFailure(nvp_obj.flag_info)
return nvp_obj

def refundTransaction(self, params):
raise NotImplementedError

Expand All @@ -212,7 +216,7 @@ def _recurring_setExpressCheckout_adapter(self, params):
for k in params.keys():
if k in REMOVE:
del params[k]

return params

def _fetch(self, params, required, defaults):
Expand All @@ -222,7 +226,7 @@ def _fetch(self, params, required, defaults):
pp_string = self.signature + urlencode(pp_params)
response = self._request(pp_string)
response_params = self._parse_response(response)

if getattr(settings, 'PAYPAL_DEBUG', settings.DEBUG):
log.debug('PayPal Request:\n%s\n', pprint.pformat(defaults))
log.debug('PayPal Response:\n%s\n', pprint.pformat(response_params))
Expand All @@ -241,7 +245,7 @@ def _fetch(self, params, required, defaults):
nvp_obj.init(self.request, params, response_params)
nvp_obj.save()
return nvp_obj

def _request(self, data):
"""Moved out to make testing easier."""
return urllib2.urlopen(self.endpoint, data).read()
Expand All @@ -253,9 +257,9 @@ def _check_and_update_params(self, required, params):
"""
for r in required:
if r not in params:
raise PayPalError("Missing required param: %s" % r)
raise PayPalError("Missing required param: %s" % r)

# Upper case all the parameters for PayPal.
# Upper case all the parameters for PayPal.
return (dict((k.upper(), v) for k, v in params.iteritems()))

def _parse_response(self, response):
Expand Down
25 changes: 14 additions & 11 deletions paypal/pro/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from django.utils.http import urlencode
from django.forms.models import model_to_dict
from django.contrib.auth.models import User

try:
from idmapper.models import SharedMemoryModel as Model
except ImportError:
Model = models.Model


class PayPalNVP(Model):
"""Record of a NVP interaction with PayPal."""
TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # 2009-02-03T17:47:41Z
Expand All @@ -20,51 +22,51 @@ class PayPalNVP(Model):

# Response fields
method = models.CharField(max_length=64, blank=True)
ack = models.CharField(max_length=32, blank=True)
ack = models.CharField(max_length=32, blank=True)
profilestatus = models.CharField(max_length=32, blank=True)
timestamp = models.DateTimeField(blank=True, null=True)
profileid = models.CharField(max_length=32, blank=True) # I-E596DFUSD882
profilereference = models.CharField(max_length=128, blank=True) # PROFILEREFERENCE
correlationid = models.CharField(max_length=32, blank=True) # 25b380cda7a21
token = models.CharField(max_length=64, blank=True)
payerid = models.CharField(max_length=64, blank=True)

# Transaction Fields
firstname = models.CharField("First Name", max_length=255, blank=True)
lastname = models.CharField("Last Name", max_length=255, blank=True)
street = models.CharField("Street Address", max_length=255, blank=True)
city = models.CharField("City", max_length=255, blank=True)
state = models.CharField("State", max_length=255, blank=True)
countrycode = models.CharField("Country", max_length=2,blank=True)
countrycode = models.CharField("Country", max_length=2, blank=True)
zip = models.CharField("Postal / Zip Code", max_length=32, blank=True)

# Custom fields
invnum = models.CharField(max_length=255, blank=True)
custom = models.CharField(max_length=255, blank=True)
custom = models.CharField(max_length=255, blank=True)

# Admin fields
user = models.ForeignKey(User, blank=True, null=True)
flag = models.BooleanField(default=False, blank=True)
flag_code = models.CharField(max_length=32, blank=True)
flag_info = models.TextField(blank=True)
flag_info = models.TextField(blank=True)
ipaddress = models.IPAddressField(blank=True)
query = models.TextField(blank=True)
response = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
db_table = "paypal_nvp"
verbose_name = "PayPal NVP"

def init(self, request, paypal_request, paypal_response):
"""Initialize a PayPalNVP instance from a HttpRequest."""
self.ipaddress = request.META.get('REMOTE_ADDR', '').split(':')[0]
if hasattr(request, "user") and request.user.is_authenticated():
self.user = request.user

# No storing credit card info.
query_data = dict((k,v) for k, v in paypal_request.iteritems() if k not in self.RESTRICTED_FIELDS)
query_data = dict((k, v) for k, v in paypal_request.iteritems() if k not in self.RESTRICTED_FIELDS)
self.query = urlencode(query_data)
self.response = urlencode(paypal_response)

Expand All @@ -86,6 +88,7 @@ def set_flag(self, info, code=None):
def process(self, request, item):
"""Do a direct payment."""
from paypal.pro.helpers import PayPalWPP

wpp = PayPalWPP(request)

# Change the model information into a dict that PayPal can understand.
Expand All @@ -94,7 +97,7 @@ def process(self, request, item):
params['creditcardtype'] = self.creditcardtype
params['expdate'] = self.expdate
params['cvv2'] = self.cvv2
params.update(item)
params.update(item)

# Create recurring payment:
if 'billingperiod' in params:
Expand Down
4 changes: 2 additions & 2 deletions paypal/pro/templates/pro/confirm.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<html>

<head>
<title></title>
<title></title>
</head>
<body>

<form action="" method="post">
<table>
<tr>
{{ form.as_table }}
<td colspan="2" align="right"><input type="submit" value="confirm" /></td>
<td colspan="2" align="right"><input type="submit" value="confirm"/></td>
</tr>
</table>
</form>
Expand Down
Loading

0 comments on commit 1b3cc31

Please sign in to comment.