Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

CC validation fix, deprecated function fix, pep8 fix #24

Open
wants to merge 8 commits into from

1 participant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 9, 2012
  1. remove deprecated has_key for dict, use in instead.

    Andy Zhang authored
  2. pep8 fix

    Andy Zhang authored
  3. Merge branch 'hotfix/pep8_ccvalidate'

    Andyiso authored
  4. Merge branch 'hotfix/pep8_ccvalidate' into dev

    Andyiso authored
Commits on Nov 9, 2012
  1. Allow initial data for forms.

    Andy Zhang authored
Commits on Nov 12, 2012
  1. Allow CC field to be empty.

    Andy Zhang authored
Commits on Jan 18, 2013
  1. Merge branch 'paypal_pro'

    Andy Zhang authored
This page is out of date. Refresh to see the latest.
View
4 paypal/pro/creditcard.py
@@ -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)
View
3  paypal/pro/exceptions.py
@@ -1 +1,2 @@
-class PayPalFailure(Exception): pass
+class PayPalFailure(Exception):
+ pass
View
13 paypal/pro/fields.py
@@ -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 _
@@ -15,7 +14,7 @@ class CreditCardField(forms.CharField):
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:
@@ -42,6 +41,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)]
@@ -55,12 +55,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])
@@ -90,7 +90,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/
@@ -337,7 +337,8 @@ def __init__(self, *args, **kwargs):
('ZW', _('Zimbabwe')),
)
+
class CountryField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('choices', COUNTRIES)
- super(CountryField, self).__init__(*args, **kwargs)
+ super(CountryField, self).__init__(*args, **kwargs)
View
8 paypal/pro/views.py
@@ -90,7 +90,7 @@ def __init__(self, item=None, payment_form_cls=PaymentForm,
self.context = context or {}
self.form_context_name = form_context_name
- def __call__(self, request):
+ def __call__(self, request, initial=None):
"""Return the appropriate response for the state of the transaction."""
self.request = request
if request.method == "GET":
@@ -99,7 +99,7 @@ def __call__(self, request):
elif self.should_render_confirm_form():
return self.render_confirm_form()
elif self.should_render_payment_form():
- return self.render_payment_form()
+ return self.render_payment_form(initial=initial)
else:
if self.should_validate_confirm_form():
return self.validate_confirm_form()
@@ -127,9 +127,9 @@ def should_validate_confirm_form(self):
def should_validate_payment_form(self):
return True
- def render_payment_form(self):
+ def render_payment_form(self, *args, **kwargs):
"""Display the DirectPayment for entering payment information."""
- self.context[self.form_context_name] = self.payment_form_cls()
+ self.context[self.form_context_name] = self.payment_form_cls(*args, **kwargs)
return render_to_response(self.payment_template, self.context, RequestContext(self.request))
def validate_payment_form(self):
View
4 paypal/standard/conf.py
@@ -1,8 +1,9 @@
from django.conf import settings
+
class PayPalSettingsError(Exception):
"""Raised when settings be bad."""
-
+
TEST = getattr(settings, "PAYPAL_TEST", True)
@@ -21,4 +22,3 @@ class PayPalSettingsError(Exception):
SANDBOX_IMAGE = getattr(settings, "PAYPAL_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif")
SUBSCRIPTION_SANDBOX_IMAGE = getattr(settings, "PAYPAL_SUBSCRIPTION_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif")
DONATION_SANDBOX_IMAGE = getattr(settings, "PAYPAL_DONATION_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_donateCC_LG.gif")
-
View
73 paypal/standard/forms.py
@@ -5,8 +5,7 @@
from django.utils.safestring import mark_safe
from paypal.standard.conf import *
from paypal.standard.widgets import ValueHiddenInput, ReservedValueHiddenInput
-from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT,
- RECEIVER_EMAIL)
+from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, RECEIVER_EMAIL)
# 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box.
@@ -17,33 +16,34 @@
"%H:%M:%S %b %d, %Y PST",
"%H:%M:%S %b %d, %Y PDT",)
+
class PayPalPaymentsForm(forms.Form):
"""
Creates a PayPal Payments Standard "Buy It Now" button, configured for a
selling a single item with no shipping.
-
+
For a full overview of all the fields you can set (there is a lot!) see:
http://tinyurl.com/pps-integration
-
+
Usage:
>>> f = PayPalPaymentsForm(initial={'item_name':'Widget 001', ...})
>>> f.render()
u'<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> ...'
-
- """
+
+ """
CMD_CHOICES = (
- ("_xclick", "Buy now or Donations"),
- ("_cart", "Shopping cart"),
+ ("_xclick", "Buy now or Donations"),
+ ("_cart", "Shopping cart"),
("_xclick-subscriptions", "Subscribe")
)
SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"))
NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note"))
RECURRING_PAYMENT_CHOICES = (
- (1, "Subscription Payments Recur"),
+ (1, "Subscription Payments Recur"),
(0, "Subscription payments do not recur")
)
REATTEMPT_ON_FAIL_CHOICES = (
- (1, "reattempt billing on Failure"),
+ (1, "reattempt billing on Failure"),
(0, "Do Not reattempt on failure")
)
@@ -53,46 +53,46 @@ class PayPalPaymentsForm(forms.Form):
# Where the money goes.
business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL)
-
+
# Item information.
amount = forms.IntegerField(widget=ValueHiddenInput())
item_name = forms.CharField(widget=ValueHiddenInput())
item_number = forms.CharField(widget=ValueHiddenInput())
quantity = forms.CharField(widget=ValueHiddenInput())
-
+
# Subscription Related.
- a1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Price
- p1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Duration
- t1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 unit of Duration, default to Month
- a2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Price
- p2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Duration
- t2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 unit of Duration, default to Month
- a3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Price
- p3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Duration
- t3 = forms.CharField(widget=ValueHiddenInput()) # Subscription unit of Duration, default to Month
- src = forms.CharField(widget=ValueHiddenInput()) # Is billing recurring? default to yes
- sra = forms.CharField(widget=ValueHiddenInput()) # Reattempt billing on failed cc transaction
- no_note = forms.CharField(widget=ValueHiddenInput())
+ a1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Price
+ p1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Duration
+ t1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 unit of Duration, default to Month
+ a2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Price
+ p2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Duration
+ t2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 unit of Duration, default to Month
+ a3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Price
+ p3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Duration
+ t3 = forms.CharField(widget=ValueHiddenInput()) # Subscription unit of Duration, default to Month
+ src = forms.CharField(widget=ValueHiddenInput()) # Is billing recurring? default to yes
+ sra = forms.CharField(widget=ValueHiddenInput()) # Reattempt billing on failed cc transaction
+ no_note = forms.CharField(widget=ValueHiddenInput())
# Can be either 1 or 2. 1 = modify or allow new subscription creation, 2 = modify only
- modify = forms.IntegerField(widget=ValueHiddenInput()) # Are we modifying an existing subscription?
-
+ modify = forms.IntegerField(widget=ValueHiddenInput()) # Are we modifying an existing subscription?
+
# Localization / PayPal Setup
lc = forms.CharField(widget=ValueHiddenInput())
page_style = forms.CharField(widget=ValueHiddenInput())
cbt = forms.CharField(widget=ValueHiddenInput())
-
+
# IPN control.
notify_url = forms.CharField(widget=ValueHiddenInput())
cancel_return = forms.CharField(widget=ValueHiddenInput())
- return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name":"return"}))
+ return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name": "return"}))
custom = forms.CharField(widget=ValueHiddenInput())
invoice = forms.CharField(widget=ValueHiddenInput())
-
+
# Default fields.
cmd = forms.ChoiceField(widget=forms.HiddenInput(), initial=CMD_CHOICES[0][0])
charset = forms.CharField(widget=forms.HiddenInput(), initial="utf-8")
currency_code = forms.CharField(widget=forms.HiddenInput(), initial="USD")
- no_shipping = forms.ChoiceField(widget=forms.HiddenInput(), choices=SHIPPING_CHOICES,
+ no_shipping = forms.ChoiceField(widget=forms.HiddenInput(), choices=SHIPPING_CHOICES,
initial=SHIPPING_CHOICES[0][0])
def __init__(self, button_type="buy", *args, **kwargs):
@@ -104,14 +104,13 @@ def render(self):
%s
<input type="image" src="%s" border="0" name="submit" alt="Buy it Now" />
</form>""" % (POSTBACK_ENDPOINT, self.as_p(), self.get_image()))
-
-
+
def sandbox(self):
return mark_safe(u"""<form action="%s" method="post">
%s
<input type="image" src="%s" border="0" name="submit" alt="Buy it Now" />
</form>""" % (SANDBOX_POSTBACK_ENDPOINT, self.as_p(), self.get_image()))
-
+
def get_image(self):
return {
(True, self.SUBSCRIBE): SUBSCRIPTION_SANDBOX_IMAGE,
@@ -139,7 +138,7 @@ class PayPalEncryptedPaymentsForm(PayPalPaymentsForm):
Based on example at:
http://blog.mauveweb.co.uk/2007/10/10/paypal-with-django/
-
+
"""
def _encrypt(self):
"""Use your key thing to encrypt things."""
@@ -164,7 +163,7 @@ def _encrypt(self):
name = "return"
plaintext += u'%s=%s\n' % (name, value)
plaintext = plaintext.encode('utf-8')
-
+
# Begin crypto weirdness.
s = SMIME.SMIME()
s.load_key_bio(BIO.openfile(CERT), BIO.openfile(PUB_CERT))
@@ -180,7 +179,7 @@ def _encrypt(self):
out = BIO.MemoryBuffer()
p7.write(out)
return out.read()
-
+
def as_p(self):
return mark_safe(u"""
<input type="hidden" name="cmd" value="_s-xclick" />
@@ -192,7 +191,7 @@ class PayPalSharedSecretEncryptedPaymentsForm(PayPalEncryptedPaymentsForm):
"""
Creates a PayPal Encrypted Payments "Buy It Now" button with a Shared Secret.
Shared secrets should only be used when your IPN endpoint is on HTTPS.
-
+
Adds a secret to the notify_url based on the contents of the form.
"""
View
21 paypal/standard/helpers.py
@@ -2,33 +2,34 @@
# -*- coding: utf-8 -*-
from django.conf import settings
+
def duplicate_txn_id(ipn_obj):
- """Returns True if a record with this transaction id exists and it is not
+ """
+ Returns True if a record with this transaction id exists and it is not
a payment which has gone from pending to completed.
-
"""
- query = ipn_obj._default_manager.filter(txn_id = ipn_obj.txn_id)
-
+ query = ipn_obj._default_manager.filter(txn_id=ipn_obj.txn_id)
+
if ipn_obj.payment_status == "Completed":
# A payment that was pending and is now completed will have the same
# IPN transaction id, so don't flag them as duplicates because it
# means that the payment was finally successful!
- query = query.exclude(payment_status = "Pending")
-
+ query = query.exclude(payment_status="Pending")
+
return query.count() > 0
-
+
+
def make_secret(form_instance, secret_fields=None):
"""
Returns a secret for use in a EWP form or an IPN verification based on a
selection of variables in params. Should only be used with SSL.
-
"""
# @@@ Moved here as temporary fix to avoid dependancy on auth.models.
from django.contrib.auth.models import get_hexdigest
# @@@ amount is mc_gross on the IPN - where should mapping logic go?
# @@@ amount / mc_gross is not nessecarily returned as it was sent - how to use it? 10.00 vs. 10.0
# @@@ the secret should be based on the invoice or custom fields as well - otherwise its always the same.
-
+
# Build the secret with fields availible in both PaymentForm and the IPN. Order matters.
if secret_fields is None:
secret_fields = ['business', 'item_name']
@@ -48,11 +49,11 @@ def make_secret(form_instance, secret_fields=None):
secret = get_hexdigest('sha1', settings.SECRET_KEY, data)
return secret
+
def check_secret(form_instance, secret):
"""
Returns true if received `secret` matches expected secret for form_instance.
Used to verify IPN.
-
"""
# @@@ add invoice & custom
# secret_fields = ['business', 'item_name']
View
20 paypal/standard/ipn/admin.py
@@ -10,8 +10,8 @@ class PayPalIPNAdmin(admin.ModelAdmin):
(None, {
"fields": [
"flag", "txn_id", "txn_type", "payment_status", "payment_date",
- "transaction_entity", "reason_code", "pending_reason",
- "mc_gross", "mc_fee", "auth_status", "auth_amount", "auth_exp",
+ "transaction_entity", "reason_code", "pending_reason",
+ "mc_gross", "mc_fee", "auth_status", "auth_amount", "auth_exp",
"auth_id"
]
}),
@@ -20,7 +20,7 @@ class PayPalIPNAdmin(admin.ModelAdmin):
'classes': ('collapse',),
"fields": [
"address_city", "address_country", "address_country_code",
- "address_name", "address_state", "address_status",
+ "address_name", "address_state", "address_status",
"address_street", "address_zip"
]
}),
@@ -36,7 +36,7 @@ class PayPalIPNAdmin(admin.ModelAdmin):
"description": "The information about the Seller.",
'classes': ('collapse',),
"fields": [
- "business", "item_name", "item_number", "quantity",
+ "business", "item_name", "item_number", "quantity",
"receiver_email", "receiver_id", "custom", "invoice", "memo"
]
}),
@@ -44,9 +44,9 @@ class PayPalIPNAdmin(admin.ModelAdmin):
"description": "Information about recurring Payments.",
"classes": ("collapse",),
"fields": [
- "profile_status", "initial_payment_amount", "amount_per_cycle",
- "outstanding_balance", "period_type", "product_name",
- "product_type", "recurring_payment_id", "receipt_id",
+ "profile_status", "initial_payment_amount", "amount_per_cycle",
+ "outstanding_balance", "period_type", "product_name",
+ "product_type", "recurring_payment_id", "receipt_id",
"next_payment_date"
]
}),
@@ -54,16 +54,16 @@ class PayPalIPNAdmin(admin.ModelAdmin):
"description": "Additional Info.",
"classes": ('collapse',),
"fields": [
- "test_ipn", "ipaddress", "query", "response", "flag_code",
+ "test_ipn", "ipaddress", "query", "response", "flag_code",
"flag_info"
]
}),
)
list_display = [
- "__unicode__", "flag", "flag_info", "invoice", "custom",
+ "__unicode__", "flag", "flag_info", "invoice", "custom",
"payment_status", "created_at"
]
search_fields = ["txn_id", "recurring_payment_id"]
-admin.site.register(PayPalIPN, PayPalIPNAdmin)
+admin.site.register(PayPalIPN, PayPalIPNAdmin)
View
5 paypal/standard/ipn/forms.py
@@ -1,16 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-from paypal.standard.forms import PayPalStandardBaseForm
+from paypal.standard.forms import PayPalStandardBaseForm
from paypal.standard.ipn.models import PayPalIPN
class PayPalIPNForm(PayPalStandardBaseForm):
"""
Form used to receive and record PayPal IPN notifications.
-
+
PayPal IPN test tool:
https://developer.paypal.com/us/cgi-bin/devscr?cmd=_tools-session
"""
class Meta:
model = PayPalIPN
-
View
6 paypal/standard/ipn/models.py
@@ -16,11 +16,11 @@ class Meta:
def _postback(self):
"""Perform PayPal Postback validation."""
return urllib2.urlopen(self.get_endpoint(), "cmd=_notify-validate&%s" % self.query).read()
-
+
def _verify_postback(self):
if self.response != "VERIFIED":
self.set_flag("Invalid postback. (%s)" % self.response)
-
+
def send_signals(self):
"""Shout for the world to hear whether a txn was successful."""
# Transaction signals:
@@ -51,4 +51,4 @@ def send_signals(self):
elif self.is_subscription_end_of_term():
subscription_eot.send(sender=self)
elif self.is_subscription_modified():
- subscription_modify.send(sender=self)
+ subscription_modify.send(sender=self)
View
4 paypal/standard/ipn/signals.py
@@ -1,5 +1,5 @@
"""
-Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
+Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers:
http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave
@@ -34,4 +34,4 @@
recurring_skipped = Signal()
-recurring_failed = Signal()
+recurring_failed = Signal()
View
75 paypal/standard/ipn/tests/test_ipn.py
@@ -5,7 +5,7 @@
from paypal.standard.models import ST_PP_CANCELLED
from paypal.standard.ipn.models import PayPalIPN
-from paypal.standard.ipn.signals import (payment_was_successful,
+from paypal.standard.ipn.signals import (payment_was_successful,
payment_was_flagged, recurring_skipped, recurring_failed,
recurring_create, recurring_payment, recurring_cancel)
@@ -46,7 +46,7 @@
}
-class IPNTest(TestCase):
+class IPNTest(TestCase):
urls = 'paypal.standard.ipn.tests.test_urls'
def setUp(self):
@@ -57,56 +57,54 @@ def setUp(self):
self.old_postback = PayPalIPN._postback
PayPalIPN._postback = lambda self: "VERIFIED"
- self.payment_was_successful_receivers = payment_was_successful.receivers
- self.payment_was_flagged_receivers = payment_was_flagged.receivers
- self.recurring_skipped_receivers = recurring_skipped.receivers
- self.recurring_failed_receivers = recurring_failed.receivers
- self.recurring_create_receivers = recurring_create.receivers
- self.recurring_payment_receivers = recurring_payment.receivers
- self.recurring_cancel_receivers = recurring_cancel.receivers
+ self.payment_was_successful_receivers = payment_was_successful.receivers
+ self.payment_was_flagged_receivers = payment_was_flagged.receivers
+ self.recurring_skipped_receivers = recurring_skipped.receivers
+ self.recurring_failed_receivers = recurring_failed.receivers
+ self.recurring_create_receivers = recurring_create.receivers
+ self.recurring_payment_receivers = recurring_payment.receivers
+ self.recurring_cancel_receivers = recurring_cancel.receivers
payment_was_successful.receivers = []
payment_was_flagged.receivers = []
- recurring_skipped.receivers = []
- recurring_failed.receivers = []
- recurring_create.receivers = []
+ recurring_skipped.receivers = []
+ recurring_failed.receivers = []
+ recurring_create.receivers = []
recurring_payment.receivers = []
- recurring_cancel.receivers = []
-
-
+ recurring_cancel.receivers = []
+
def tearDown(self):
settings.DEBUG = self.old_debug
PayPalIPN._postback = self.old_postback
-
- payment_was_successful.receivers =self.payment_was_successful_receivers
+
+ payment_was_successful.receivers = self.payment_was_successful_receivers
payment_was_flagged.receivers = self.payment_was_flagged_receivers
recurring_skipped.receivers = self.recurring_skipped_receivers
recurring_failed.receivers = self.recurring_failed_receivers
recurring_create.receivers = self.recurring_create_receivers
recurring_payment.receivers = self.recurring_payment_receivers
- recurring_cancel.receivers = self.recurring_cancel_receivers
-
+ recurring_cancel.receivers = self.recurring_cancel_receivers
def assertGotSignal(self, signal, flagged, params=IPN_POST_PARAMS):
# Check the signal was sent. These get lost if they don't reference self.
self.got_signal = False
self.signal_obj = None
-
+
def handle_signal(sender, **kwargs):
self.got_signal = True
self.signal_obj = sender
signal.connect(handle_signal)
-
+
response = self.client.post("/ipn/", params)
self.assertEqual(response.status_code, 200)
ipns = PayPalIPN.objects.all()
- self.assertEqual(len(ipns), 1)
- ipn_obj = ipns[0]
+ self.assertEqual(len(ipns), 1)
+ ipn_obj = ipns[0]
self.assertEqual(ipn_obj.flag, flagged)
-
+
self.assertTrue(self.got_signal)
self.assertEqual(self.signal_obj, ipn_obj)
-
+
def test_correct_ipn(self):
self.assertGotSignal(payment_was_successful, False)
@@ -132,7 +130,7 @@ def test_invalid_payment_status(self):
update = {"payment_status": "Failed"}
flag_info = u"Invalid payment_status. (Failed)"
self.assertFlagged(update, flag_info)
-
+
def test_vaid_payment_status_cancelled(self):
update = {"payment_status": ST_PP_CANCELLED}
params = IPN_POST_PARAMS.copy()
@@ -141,12 +139,11 @@ def test_vaid_payment_status_cancelled(self):
self.assertEqual(response.status_code, 200)
ipn_obj = PayPalIPN.objects.all()[0]
self.assertEqual(ipn_obj.flag, False)
-
- def test_duplicate_txn_id(self):
+ def test_duplicate_txn_id(self):
self.client.post("/ipn/", IPN_POST_PARAMS)
self.client.post("/ipn/", IPN_POST_PARAMS)
- self.assertEqual(len(PayPalIPN.objects.all()), 2)
+ self.assertEqual(len(PayPalIPN.objects.all()), 2)
ipn_obj = PayPalIPN.objects.order_by('-created_at', '-pk')[0]
self.assertEqual(ipn_obj.flag, True)
self.assertEqual(ipn_obj.flag_info, "Duplicate txn_id. (51403485VH153354B)")
@@ -159,7 +156,7 @@ def test_recurring_payment_skipped_ipn(self):
}
params = IPN_POST_PARAMS.copy()
params.update(update)
-
+
self.assertGotSignal(recurring_skipped, False, params)
def test_recurring_payment_failed_ipn(self):
@@ -170,7 +167,7 @@ def test_recurring_payment_failed_ipn(self):
}
params = IPN_POST_PARAMS.copy()
params.update(update)
-
+
self.assertGotSignal(recurring_failed, False, params)
def test_recurring_payment_create_ipn(self):
@@ -181,7 +178,7 @@ def test_recurring_payment_create_ipn(self):
}
params = IPN_POST_PARAMS.copy()
params.update(update)
-
+
self.assertGotSignal(recurring_create, False, params)
def test_recurring_payment_cancel_ipn(self):
@@ -192,13 +189,13 @@ def test_recurring_payment_cancel_ipn(self):
}
params = IPN_POST_PARAMS.copy()
params.update(update)
-
+
self.assertGotSignal(recurring_cancel, False, params)
def test_recurring_payment_ipn(self):
"""
- The wat the code is written in
- PayPalIPN.send_signals the recurring_payment
+ The wat the code is written in
+ PayPalIPN.send_signals the recurring_payment
will never be sent because the paypal ipn
contains a txn_id, if this test failes you
might break some compatibility
@@ -209,17 +206,17 @@ def test_recurring_payment_ipn(self):
}
params = IPN_POST_PARAMS.copy()
params.update(update)
-
+
self.got_signal = False
self.signal_obj = None
-
+
def handle_signal(sender, **kwargs):
self.got_signal = True
self.signal_obj = sender
recurring_payment.connect(handle_signal)
-
+
response = self.client.post("/ipn/", params)
self.assertEqual(response.status_code, 200)
ipns = PayPalIPN.objects.all()
- self.assertEqual(len(ipns), 1)
+ self.assertEqual(len(ipns), 1)
self.assertFalse(self.got_signal)
View
4 paypal/standard/ipn/urls.py
@@ -1,5 +1,5 @@
from django.conf.urls.defaults import *
-urlpatterns = patterns('paypal.standard.ipn.views',
+urlpatterns = patterns('paypal.standard.ipn.views',
url(r'^$', 'ipn', name="paypal-ipn"),
-)
+)
View
14 paypal/standard/ipn/views.py
@@ -5,8 +5,8 @@
from django.views.decorators.csrf import csrf_exempt
from paypal.standard.ipn.forms import PayPalIPNForm
from paypal.standard.ipn.models import PayPalIPN
-
-
+
+
@require_POST
@csrf_exempt
def ipn(request, item_check_callable=None):
@@ -14,7 +14,7 @@ def ipn(request, item_check_callable=None):
PayPal IPN endpoint (notify_url).
Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
http://tinyurl.com/d9vu9d
-
+
PayPal IPN Simulator:
https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
"""
@@ -22,7 +22,7 @@ def ipn(request, item_check_callable=None):
# of if checks just to determine if flag is set.
flag = None
ipn_obj = None
-
+
# Clean up the data as PayPal sends some weird values such as "N/A"
data = request.POST.copy()
date_fields = ('time_created', 'payment_date', 'next_payment_date',
@@ -35,15 +35,15 @@ def ipn(request, item_check_callable=None):
if form.is_valid():
try:
#When commit = False, object is returned without saving to DB.
- ipn_obj = form.save(commit = False)
+ ipn_obj = form.save(commit=False)
except Exception, e:
flag = "Exception while processing. (%s)" % e
else:
flag = "Invalid form. (%s)" % form.errors
-
+
if ipn_obj is None:
ipn_obj = PayPalIPN()
-
+
#Set query params and sender's IP address
ipn_obj.initialize(request)
View
74 paypal/standard/models.py
@@ -24,6 +24,7 @@
except ImportError:
Model = models.Model
+
class PayPalStandardBase(Model):
"""Meta class for common variables shared by IPN and PDT: http://tinyurl.com/cuq6sj"""
# @@@ Might want to add all these one distant day.
@@ -37,10 +38,10 @@ class PayPalStandardBase(Model):
# PENDING_REASON = "address authorization echeck intl multi-currency unilateral upgrade verify other".split()
# REASON_CODE = "chargeback guarantee buyer_complaint refund other".split()
# TRANSACTION_ENTITY_CHOICES = "auth reauth order payment".split()
-
+
# Transaction and Notification-Related Variables
business = models.CharField(max_length=127, blank=True, help_text="Email where the money was sent.")
- charset=models.CharField(max_length=32, blank=True)
+ charset = models.CharField(max_length=32, blank=True)
custom = models.CharField(max_length=255, blank=True)
notify_version = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
parent_txn_id = models.CharField("Parent Transaction ID", max_length=19, blank=True)
@@ -50,8 +51,8 @@ class PayPalStandardBase(Model):
test_ipn = models.BooleanField(default=False, blank=True)
txn_id = models.CharField("Transaction ID", max_length=19, blank=True, help_text="PayPal transaction ID.", db_index=True)
txn_type = models.CharField("Transaction Type", max_length=128, blank=True, help_text="PayPal transaction type.")
- verify_sign = models.CharField(max_length=255, blank=True)
-
+ verify_sign = models.CharField(max_length=255, blank=True)
+
# Buyer Information Variables
address_country = models.CharField(max_length=64, blank=True)
address_city = models.CharField(max_length=40, blank=True)
@@ -67,12 +68,12 @@ class PayPalStandardBase(Model):
payer_business_name = models.CharField(max_length=127, blank=True)
payer_email = models.CharField(max_length=127, blank=True)
payer_id = models.CharField(max_length=13, blank=True)
-
+
# Payment Information Variables
auth_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
auth_exp = models.CharField(max_length=28, blank=True)
auth_id = models.CharField(max_length=19, blank=True)
- auth_status = models.CharField(max_length=9, blank=True)
+ auth_status = models.CharField(max_length=9, blank=True)
exchange_rate = models.DecimalField(max_digits=64, decimal_places=16, default=0, blank=True, null=True)
invoice = models.CharField(max_length=127, blank=True)
item_name = models.CharField(max_length=127, blank=True)
@@ -92,7 +93,7 @@ class PayPalStandardBase(Model):
payment_status = models.CharField(max_length=9, blank=True)
payment_type = models.CharField(max_length=7, blank=True)
pending_reason = models.CharField(max_length=14, blank=True)
- protection_eligibility=models.CharField(max_length=32, blank=True)
+ protection_eligibility = models.CharField(max_length=32, blank=True)
quantity = models.IntegerField(blank=True, default=1, null=True)
reason_code = models.CharField(max_length=15, blank=True)
remaining_settle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
@@ -102,28 +103,28 @@ class PayPalStandardBase(Model):
shipping_method = models.CharField(max_length=255, blank=True)
tax = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
transaction_entity = models.CharField(max_length=7, blank=True)
-
+
# Auction Variables
auction_buyer_id = models.CharField(max_length=64, blank=True)
auction_closing_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
auction_multi_item = models.IntegerField(blank=True, default=0, null=True)
for_auction = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
-
+
# Recurring Payments Variables
amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
amount_per_cycle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
initial_payment_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
next_payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
outstanding_balance = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
- payment_cycle= models.CharField(max_length=32, blank=True) #Monthly
+ payment_cycle = models.CharField(max_length=32, blank=True) # Monthly
period_type = models.CharField(max_length=32, blank=True)
product_name = models.CharField(max_length=128, blank=True)
- product_type= models.CharField(max_length=128, blank=True)
+ product_type = models.CharField(max_length=128, blank=True)
profile_status = models.CharField(max_length=32, blank=True)
recurring_payment_id = models.CharField(max_length=128, blank=True) # I-FA4XVST722B9
- rp_invoice_id= models.CharField(max_length=127, blank=True) # 1335-7816-2936-1451
+ rp_invoice_id = models.CharField(max_length=127, blank=True) # 1335-7816-2936-1451
time_created = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
-
+
# Subscription Variables
amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
@@ -143,22 +144,22 @@ class PayPalStandardBase(Model):
subscr_effective = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
subscr_id = models.CharField(max_length=19, blank=True)
username = models.CharField(max_length=64, blank=True)
-
+
# Dispute Resolution Variables
case_creation_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST")
case_id = models.CharField(max_length=14, blank=True)
case_type = models.CharField(max_length=24, blank=True)
-
+
# Variables not categorized
- receipt_id= models.CharField(max_length=64, blank=True) # 1335-7816-2936-1451
+ receipt_id = models.CharField(max_length=64, blank=True) # 1335-7816-2936-1451
currency_code = models.CharField(max_length=32, default="USD", blank=True)
handling_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
transaction_subject = models.CharField(max_length=255, blank=True)
# @@@ Mass Pay Variables (Not Implemented, needs a separate model, for each transaction x)
- # fraud_managment_pending_filters_x = models.CharField(max_length=255, blank=True)
- # option_selection1_x = models.CharField(max_length=200, blank=True)
- # option_selection2_x = models.CharField(max_length=200, blank=True)
+ # fraud_managment_pending_filters_x = models.CharField(max_length=255, blank=True)
+ # option_selection1_x = models.CharField(max_length=200, blank=True)
+ # option_selection2_x = models.CharField(max_length=200, blank=True)
# masspay_txn_id_x = models.CharField(max_length=19, blank=True)
# mc_currency_x = models.CharField(max_length=32, default="USD", blank=True)
# mc_fee_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True)
@@ -171,7 +172,7 @@ class PayPalStandardBase(Model):
# status_x = models.CharField(max_length=9, blank=True)
# unique_id_x = models.CharField(max_length=13, blank=True)
- # Non-PayPal Variables - full IPN/PDT query and time fields.
+ # Non-PayPal Variables - full IPN/PDT query and time fields.
ipaddress = models.IPAddressField(blank=True)
flag = models.BooleanField(default=False, blank=True)
flag_code = models.CharField(max_length=16, blank=True)
@@ -192,22 +193,22 @@ def __unicode__(self):
return self.format % ("Transaction", self.txn_id)
else:
return self.format % ("Recurring", self.recurring_payment_id)
-
+
def is_transaction(self):
return len(self.txn_id) > 0
def is_recurring(self):
return len(self.recurring_payment_id) > 0
-
+
def is_subscription_cancellation(self):
return self.txn_type == "subscr_cancel"
-
+
def is_subscription_end_of_term(self):
return self.txn_type == "subscr_eot"
-
+
def is_subscription_modified(self):
return self.txn_type == "subscr_modify"
-
+
def is_subscription_signup(self):
return self.txn_type == "subscr_signup"
@@ -216,36 +217,36 @@ def is_recurring_create(self):
def is_recurring_payment(self):
return self.txn_type == "recurring_payment"
-
+
def is_recurring_cancel(self):
return self.txn_type == "recurring_payment_profile_cancel"
-
+
def is_recurring_skipped(self):
return self.txn_type == "recurring_payment_skipped"
-
+
def is_recurring_failed(self):
return self.txn_type == "recurring_payment_failed"
-
+
def set_flag(self, info, code=None):
"""Sets a flag on the transaction and also sets a reason."""
self.flag = True
self.flag_info += info
if code is not None:
self.flag_code = code
-
+
def verify(self, item_check_callable=None):
"""
Verifies an IPN and a PDT.
Checks for obvious signs of weirdness in the payment and flags appropriately.
-
+
Provide a callable that takes an instance of this class as a parameter and returns
a tuple (False, None) if the item is valid. Should return (True, "reason") if the
- item isn't valid. Strange but backward compatible :) This function should check
+ item isn't valid. Strange but backward compatible :) This function should check
that `mc_gross`, `mc_currency` `item_name` and `item_number` are all correct.
"""
self.response = self._postback()
- self._verify_postback()
+ self._verify_postback()
if not self.flag:
if self.is_transaction():
if self.payment_status not in self.PAYMENT_STATUS_CHOICES:
@@ -261,7 +262,7 @@ def verify(self, item_check_callable=None):
else:
# @@@ Run a different series of checks on recurring payments.
pass
-
+
self.save()
self.send_signals()
@@ -301,8 +302,7 @@ def send_signals(self):
elif self.is_subscription_end_of_term():
subscription_eot.send(sender=self)
elif self.is_subscription_modified():
- subscription_modify.send(sender=self)
-
+ subscription_modify.send(sender=self)
def initialize(self, request):
"""Store the data we'll need to make the postback from the request object."""
@@ -312,7 +312,7 @@ def initialize(self, request):
def _postback(self):
"""Perform postback to PayPal and store the response in self.response."""
raise NotImplementedError
-
+
def _verify_postback(self):
"""Check self.response is valid andcall self.set_flag if there is an error."""
raise NotImplementedError
View
2  paypal/standard/pdt/admin.py
@@ -45,4 +45,4 @@ class PayPalPDTAdmin(admin.ModelAdmin):
)
list_display = L("__unicode__ flag invoice custom payment_status created_at")
search_fields = L("txn_id recurring_payment_id")
-admin.site.register(PayPalPDT, PayPalPDTAdmin)
+admin.site.register(PayPalPDT, PayPalPDTAdmin)
View
2  paypal/standard/pdt/forms.py
@@ -6,4 +6,4 @@
class PayPalPDTForm(PayPalStandardBaseForm):
class Meta:
- model = PayPalPDT
+ model = PayPalPDT
View
17 paypal/standard/pdt/models.py
@@ -13,6 +13,8 @@
# ### Todo: Move this logic to conf.py:
# if paypal.standard.pdt is in installed apps
# ... then check for this setting in conf.py
+
+
class PayPalSettingsError(Exception):
"""Raised when settings are incorrect."""
@@ -40,19 +42,18 @@ def _postback(self):
Perform PayPal PDT Postback validation.
Sends the transaction ID and business token to PayPal which responses with
SUCCESS or FAILED.
-
"""
postback_dict = dict(cmd="_notify-synch", at=IDENTITY_TOKEN, tx=self.tx)
postback_params = urlencode(postback_dict)
return urllib2.urlopen(self.get_endpoint(), postback_params).read()
-
+
def get_endpoint(self):
"""Use the sandbox when in DEBUG mode as we don't have a test_ipn variable in pdt."""
if getattr(settings, 'PAYPAL_DEBUG', settings.DEBUG):
return SANDBOX_POSTBACK_ENDPOINT
else:
return POSTBACK_ENDPOINT
-
+
def _verify_postback(self):
# ### Now we don't really care what result was, just whether a flag was set or not.
from paypal.standard.pdt.forms import PayPalPDTForm
@@ -60,7 +61,7 @@ def _verify_postback(self):
response_list = self.response.split('\n')
response_dict = {}
for i, line in enumerate(response_list):
- unquoted_line = unquote_plus(line).strip()
+ unquoted_line = unquote_plus(line).strip()
if i == 0:
self.st = unquoted_line
if self.st == "SUCCESS":
@@ -69,9 +70,9 @@ def _verify_postback(self):
if self.st != "SUCCESS":
self.set_flag(line)
break
- try:
+ try:
if not unquoted_line.startswith(' -'):
- k, v = unquoted_line.split('=')
+ k, v = unquoted_line.split('=')
response_dict[k.strip()] = v.strip()
except ValueError, e:
pass
@@ -81,10 +82,10 @@ def _verify_postback(self):
qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info))
pdt_form = PayPalPDTForm(qd, instance=self)
pdt_form.save(commit=False)
-
+
def send_signals(self):
# Send the PDT signals...
if self.flag:
pdt_failed.send(sender=self)
else:
- pdt_successful.send(sender=self)
+ pdt_successful.send(sender=self)
View
9 paypal/standard/pdt/signals.py
@@ -1,8 +1,7 @@
"""
-Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
+Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems.
If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers:
http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave
-
"""
from django.dispatch import Signal
@@ -14,12 +13,12 @@
# # Sent when a subscription was cancelled.
# subscription_cancel = Signal()
-#
+#
# # Sent when a subscription expires.
# subscription_eot = Signal()
-#
+#
# # Sent when a subscription was modified.
# subscription_modify = Signal()
-#
+#
# # Sent when a subscription ends.
# subscription_signup = Signal()
View
29 paypal/standard/pdt/tests/test_pdt.py
@@ -12,38 +12,39 @@
class DummyPayPalPDT(object):
-
+
def __init__(self, update_context_dict={}):
- self.context_dict = {'st': 'SUCCESS', 'custom':'cb736658-3aad-4694-956f-d0aeade80194',
- 'txn_id':'1ED550410S3402306', 'mc_gross': '225.00',
+ self.context_dict = {'st': 'SUCCESS', 'custom': 'cb736658-3aad-4694-956f-d0aeade80194',
+ 'txn_id': '1ED550410S3402306', 'mc_gross': '225.00',
'business': settings.PAYPAL_RECEIVER_EMAIL, 'error': 'Error code: 1234'}
-
+
self.context_dict.update(update_context_dict)
self.response = ''
-
+
def update_with_get_params(self, get_params):
- if get_params.has_key('tx'):
+ if 'tx' in get_params:
self.context_dict['txn_id'] = get_params.get('tx')
- if get_params.has_key('amt'):
+ if 'amt' in get_params:
self.context_dict['mc_gross'] = get_params.get('amt')
- if get_params.has_key('cm'):
+ if 'cm' in get_params:
self.context_dict['custom'] = get_params.get('cm')
-
+
def _postback(self, test=True):
"""Perform a Fake PayPal PDT Postback request."""
# @@@ would be cool if this could live in the test templates dir...
return render_to_response("pdt/test_pdt_response.html", self.context_dict).content
+
class PDTTest(TestCase):
urls = "paypal.standard.pdt.tests.test_urls"
- template_dirs = [os.path.join(os.path.dirname(__file__), 'templates'),]
+ template_dirs = [os.path.join(os.path.dirname(__file__), 'templates'), ]
def setUp(self):
# set up some dummy PDT get parameters
- self.get_params = {"tx":"4WJ86550014687441", "st":"Completed", "amt":"225.00", "cc":"EUR",
- "cm":"a3e192b8-8fea-4a86-b2e8-d5bf502e36be", "item_number":"",
- "sig":"blahblahblah"}
-
+ self.get_params = {"tx": "4WJ86550014687441", "st": "Completed", "amt": "225.00", "cc": "EUR",
+ "cm": "a3e192b8-8fea-4a86-b2e8-d5bf502e36be", "item_number": "",
+ "sig": "blahblahblah"}
+
# monkey patch the PayPalPDT._postback function
self.dpppdt = DummyPayPalPDT()
self.dpppdt.update_with_get_params(self.get_params)
View
2  paypal/standard/pdt/urls.py
@@ -2,4 +2,4 @@
urlpatterns = patterns('paypal.standard.pdt.views',
url(r'^$', 'pdt', name="paypal-pdt"),
-)
+)
View
20 paypal/standard/pdt/views.py
@@ -5,8 +5,8 @@
from django.views.decorators.http import require_GET
from paypal.standard.pdt.models import PayPalPDT
from paypal.standard.pdt.forms import PayPalPDTForm
-
-
+
+
@require_GET
def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None):
"""Payment data transfer implementation: http://tinyurl.com/c9jjmw"""
@@ -21,7 +21,7 @@ def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None
except PayPalPDT.DoesNotExist:
# This is a new transaction so we continue processing PDT request
pass
-
+
if pdt_obj is None:
form = PayPalPDTForm(request.GET)
if form.is_valid():
@@ -33,18 +33,18 @@ def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None
else:
error = form.errors
failed = True
-
+
if failed:
pdt_obj = PayPalPDT()
pdt_obj.set_flag("Invalid form. %s" % error)
-
+
pdt_obj.initialize(request)
-
+
if not failed:
# The PDT object gets saved during verify
pdt_obj.verify(item_check_callable)
else:
- pass # we ignore any PDT requests that don't have a transaction id
-
- context.update({"failed":failed, "pdt_obj":pdt_obj})
- return render_to_response(template, context, RequestContext(request))
+ pass # we ignore any PDT requests that don't have a transaction id
+
+ context.update({"failed": failed, "pdt_obj": pdt_obj})
+ return render_to_response(template, context, RequestContext(request))
View
3  paypal/standard/widgets.py
@@ -17,6 +17,7 @@ def render(self, name, value, attrs=None):
else:
return super(ValueHiddenInput, self).render(name, value, attrs)
+
class ReservedValueHiddenInput(ValueHiddenInput):
"""
Overrides the default name attribute of the form.
@@ -28,4 +29,4 @@ def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type=self.input_type)
if value != '':
final_attrs['value'] = force_unicode(value)
- return mark_safe(u'<input%s />' % flatatt(final_attrs))
+ return mark_safe(u'<input%s />' % flatatt(final_attrs))
Something went wrong with that request. Please try again.