From 3b5bb00b7e294925a55428fcca8ff001406ce515 Mon Sep 17 00:00:00 2001 From: Daniel Sokolowski Date: Fri, 30 Mar 2012 22:28:26 +0000 Subject: [PATCH 1/5] Added option 2 to 'no_shipping' and added paypal variable 'rm' support. Shipping option 2 prompts the buyer for an address, and requires one before payment is taken. PayPal 'rm' variable specifies the return FORM method used upon success payment: Allowable values are: 0 . all shopping cart payments use the GET method 1 . the buyer.s browser is redirected to the return URL by using the GET method, but no payment variables are included 2 . the buyer.s browser is redirected to the return URL by using the POST method, and all payment variables are included The default is 0. Note: The rm variable takes effect only if the return variable is set. --- paypal/standard/forms.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/paypal/standard/forms.py b/paypal/standard/forms.py index e9992a5..2e5bc15 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -36,7 +36,12 @@ class PayPalPaymentsForm(forms.Form): ("_cart", "Shopping cart"), ("_xclick-subscriptions", "Subscribe") ) - SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping")) + RM_CHOICES = ( + (0, "All shopping cart payments use the GET method"), + (1, "The buyer’s browser is redirected to the return URL by using the GET method, but no payment variables are included"), + (2, "The buyer’s browser is redirected to the return URL by using the POST method, and all payment variables are included") + ) + SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"), (2, "Shipping Required")) NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note")) RECURRING_PAYMENT_CHOICES = ( (1, "Subscription Payments Recur"), @@ -85,6 +90,7 @@ class PayPalPaymentsForm(forms.Form): notify_url = forms.CharField(widget=ValueHiddenInput()) cancel_return = forms.CharField(widget=ValueHiddenInput()) return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name":"return"})) + rm = forms.ChoiceField(widget=forms.HiddenInput(), initial=RM_CHOICES[0][0]) custom = forms.CharField(widget=ValueHiddenInput()) invoice = forms.CharField(widget=ValueHiddenInput()) From c3831407cb0b3806c5fa993437b082ec93dea619 Mon Sep 17 00:00:00 2001 From: Daniel Sokolowski Date: Tue, 4 Sep 2012 15:04:23 -0400 Subject: [PATCH 2/5] Added `render_as_GET_url()`, `sandbox_as_GET_url()` methods and 'image_url' header image support to form. The method `.render_as_GET_url()` and it's sandbox version can be used to directly redirect to PayPal payment page from view that performs pre-payment processing; for example a view that check if shipping information was entered and is correct, to confirming that inventory is still adequate for order. Readme documentation was updated to reflect these changes. --- README.md | 25 ++++++++++++++++ paypal/standard/forms.py | 64 +++++++++++++++++++++++++++------------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 1a0b063..990de7f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,11 @@ Using PayPal Payments Standard IPN: ... INSTALLED_APPS = (... 'paypal.standard.ipn', ...) ... + PAYPAL_TEST = False # sandbox or not mode and True for live mode PAYPAL_RECEIVER_EMAIL = "yourpaypalemail@example.com" + PAYPAL_IMAGE = 'https://www.paypalobjects.com/en_US/i/btn/btn_buynowCC_LG.gif' + # default button image is http://images.paypal.com/images/x-click-but01.gif, for a list see: + # https://cms.paypal.com/cms_content/US/en_US/files/developer/paypal_button_chart_en.html 1. Create an instance of the `PayPalPaymentsForm` in the view where you would like to collect money. Call `render` on the instance in your template to @@ -45,6 +49,8 @@ Using PayPal Payments Standard IPN: # What you want the button to do. paypal_dict = { "business": "yourpaypalemail@example.com", + #"image": "https://www.paypalobjects.com/en_US/i/btn/btn_buynowCC_LG.gif" + "image_url": "http://www.example.com/your-header-logo-image.jpg", # max height: 98px width: 794px "amount": "10000000.00", "item_name": "name of the item", "invoice": "unique-invoice-id", @@ -65,6 +71,25 @@ Using PayPal Payments Standard IPN:

Show me the money!

{{ form.render }} + + a. Alternatively you can create an intermediate view that performs pre-payment required processing and + then redirects to the PayPal processing page. + + class PaymentPreProcessView(RedirectView): + """ Verifies cart is ready for payment - things like shipping cost is calculated """ + permanent = False + + def get_redirect_url(self, **kwargs): + """ Return the URL redirect to. URL matches are passed as kwargs""" + ... + pre-payment code here + ... + paypal_dict = { + "business": "yourpaypalemail@example.com", + ... } + + form = PayPalPaymentsForm(initial=paypal_dict) + return form.render_as_GET_url() 1. When someone uses this button to buy something PayPal makes a HTTP POST to your "notify_url". PayPal calls this Instant Payment Notification (IPN). diff --git a/paypal/standard/forms.py b/paypal/standard/forms.py index 2e5bc15..51cb042 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -5,9 +5,9 @@ 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, +from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, RECEIVER_EMAIL) - +import urllib # 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box. # PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", "%H:%M:%S %b %d, %Y PST",) @@ -30,25 +30,25 @@ class PayPalPaymentsForm(forms.Form): >>> f.render() u'
...' - """ + """ CMD_CHOICES = ( - ("_xclick", "Buy now or Donations"), - ("_cart", "Shopping cart"), + ("_xclick", "Buy now or Donations"), + ("_cart", "Shopping cart"), ("_xclick-subscriptions", "Subscribe") ) RM_CHOICES = ( - (0, "All shopping cart payments use the GET method"), - (1, "The buyer’s browser is redirected to the return URL by using the GET method, but no payment variables are included"), + (0, "All shopping cart payments use the GET method"), + (1, "The buyer’s browser is redirected to the return URL by using the GET method, but no payment variables are included"), (2, "The buyer’s browser is redirected to the return URL by using the POST method, and all payment variables are included") ) SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"), (2, "Shipping Required")) 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") ) @@ -58,13 +58,14 @@ class PayPalPaymentsForm(forms.Form): # Where the money goes. business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL) - + image_url = forms.CharField(widget=ValueHiddenInput()) + # 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 @@ -77,15 +78,15 @@ class PayPalPaymentsForm(forms.Form): 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()) + 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? - + # 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()) @@ -93,31 +94,52 @@ class PayPalPaymentsForm(forms.Form): rm = forms.ChoiceField(widget=forms.HiddenInput(), initial=RM_CHOICES[0][0]) 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): super(PayPalPaymentsForm, self).__init__(*args, **kwargs) self.button_type = button_type + def render_as_GET_url(self): + """ Returns the form as a GET url in live mode which can be used in 'redirects' in pre-payment processing views """ + data = {} + for name, field in self.fields.items(): + if self[name].value() != None: + data[name] = self[name].value() + + url_args = urllib.urlencode(data) + return '{0}?{1}'.format(POSTBACK_ENDPOINT, url_args) + def render(self): return mark_safe(u""" %s
""" % (POSTBACK_ENDPOINT, self.as_p(), self.get_image())) - - + + + def sandbox_as_GET_url(self): + """ Returns the form as a GET url in sandbox mode which can be used in 'redirects' in pre-payment processing views """ + data = {} + for name, field in self.fields.items(): + if self[name].value() != None: + data[name] = self[name].value() + + url_args = urllib.urlencode(data) + return '{0}?{1}'.format(SANDBOX_POSTBACK_ENDPOINT, url_args) + + def sandbox(self): return mark_safe(u"""
%s
""" % (SANDBOX_POSTBACK_ENDPOINT, self.as_p(), self.get_image())) - + def get_image(self): return { (True, self.SUBSCRIBE): SUBSCRIPTION_SANDBOX_IMAGE, @@ -170,7 +192,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)) @@ -186,7 +208,7 @@ def _encrypt(self): out = BIO.MemoryBuffer() p7.write(out) return out.read() - + def as_p(self): return mark_safe(u""" From 6455b164ea9df9b9c1a8e856856e9f9f0509d30a Mon Sep 17 00:00:00 2001 From: Daniel Sokolowski Date: Mon, 1 Apr 2013 17:05:36 -0400 Subject: [PATCH 3/5] Changing from spaces to tabs; in practice better to manage than spaces. For example in Eclipse one can enable "Show whitespace characters" which helps with code blocks and for this tabs are a better choice than spaces. Another plus once can easily specify 'tab' character visual length which you can't do for spaces; hence this developer uses tabs against 'PEP-8' strong recommendations. --- paypal/pro/admin.py | 6 +- paypal/pro/creditcard.py | 90 +-- paypal/pro/fields.py | 614 +++++++++--------- paypal/pro/forms.py | 66 +- paypal/pro/helpers.py | 458 ++++++------- paypal/pro/models.py | 172 ++--- paypal/pro/tests.py | 180 ++--- paypal/pro/views.py | 364 +++++------ paypal/standard/conf.py | 4 +- paypal/standard/forms.py | 418 ++++++------ paypal/standard/helpers.py | 98 +-- paypal/standard/ipn/admin.py | 118 ++-- paypal/standard/ipn/forms.py | 16 +- .../ipn/migrations/0001_first_migration.py | 474 +++++++------- paypal/standard/ipn/models.py | 88 +-- paypal/standard/ipn/tests/test_ipn.py | 422 ++++++------ paypal/standard/ipn/tests/test_urls.py | 2 +- paypal/standard/ipn/urls.py | 4 +- paypal/standard/ipn/views.py | 90 +-- paypal/standard/models.py | 552 ++++++++-------- paypal/standard/pdt/admin.py | 76 +-- paypal/standard/pdt/forms.py | 4 +- .../pdt/migrations/0001_first_migration.py | 512 +++++++-------- paypal/standard/pdt/models.py | 128 ++-- paypal/standard/pdt/tests/test_pdt.py | 198 +++--- paypal/standard/pdt/tests/test_urls.py | 2 +- paypal/standard/pdt/urls.py | 2 +- paypal/standard/pdt/views.py | 76 +-- paypal/standard/widgets.py | 40 +- runtests.py | 46 +- setup.py | 40 +- 31 files changed, 2680 insertions(+), 2680 deletions(-) diff --git a/paypal/pro/admin.py b/paypal/pro/admin.py index 5a9dc28..2b41b7f 100644 --- a/paypal/pro/admin.py +++ b/paypal/pro/admin.py @@ -6,7 +6,7 @@ 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') + 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) diff --git a/paypal/pro/creditcard.py b/paypal/pro/creditcard.py index d709c93..ea6b0da 100644 --- a/paypal/pro/creditcard.py +++ b/paypal/pro/creditcard.py @@ -2,68 +2,68 @@ # -*- coding: utf-8 -*- """ 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 + - 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 """ import re # Well known card regular expressions. CARDS = { - 'Visa': re.compile(r"^4\d{12}(\d{3})?$"), - 'Mastercard': re.compile(r"^(5[1-5]\d{4}|677189)\d{10}$"), - 'Dinersclub': re.compile(r"^3(0[0-5]|[68]\d)\d{11}$"), - 'Amex': re.compile("^3[47]\d{13}$"), - 'Discover': re.compile("^(6011|65\d{2})\d{12}$"), + 'Visa': re.compile(r"^4\d{12}(\d{3})?$"), + 'Mastercard': re.compile(r"^(5[1-5]\d{4}|677189)\d{10}$"), + 'Dinersclub': re.compile(r"^3(0[0-5]|[68]\d)\d{11}$"), + 'Amex': re.compile("^3[47]\d{13}$"), + 'Discover': re.compile("^(6011|65\d{2})\d{12}$"), } # Well known test numbers TEST_NUMBERS = [ - "378282246310005", "371449635398431", "378734493671000", "30569309025904", - "38520000023237", "6011111111111117", "6011000990139424", "555555555554444", - "5105105105105100", "4111111111111111", "4012888888881881", "4222222222222" + "378282246310005", "371449635398431", "378734493671000", "30569309025904", + "38520000023237", "6011111111111117", "6011000990139424", "555555555554444", + "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() + """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 __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) - return self.number.isdigit() + def is_number(self): + """True if there is at least one digit in number.""" + self.number = re.sub(r'[^\d]', '', self.number) + return self.number.isdigit() - def is_mod10(self): - """Returns True if number is valid according to mod10.""" - double = 0 - total = 0 - for i in range(len(self.number) - 1, -1, -1): - for c in str((double + 1) * int(self.number[i])): - total = total + int(c) - double = (double + 1) % 2 - return (total % 10) == 0 + def is_mod10(self): + """Returns True if number is valid according to mod10.""" + double = 0 + total = 0 + for i in range(len(self.number) - 1, -1, -1): + for c in str((double + 1) * int(self.number[i])): + total = total + int(c) + double = (double + 1) % 2 + return (total % 10) == 0 - def is_test(self): - """Returns True if number is a test card number.""" - # Note: test numbers cannot be used in the PP Pro sandbox. - # Instead, use the credit card number associated with a - # sandbox account (Test Accounts -> View Details). - return self.number in TEST_NUMBERS + def is_test(self): + """Returns True if number is a test card number.""" + # Note: test numbers cannot be used in the PP Pro sandbox. + # Instead, use the credit card number associated with a + # sandbox account (Test Accounts -> View Details). + return self.number in TEST_NUMBERS - 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 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.is_number() and not self.is_test() and self.is_mod10(): - return self.get_type() - return None + def verify(self): + """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 diff --git a/paypal/pro/fields.py b/paypal/pro/fields.py index 8b577a1..dbf4b41 100644 --- a/paypal/pro/fields.py +++ b/paypal/pro/fields.py @@ -11,333 +11,333 @@ 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: - value = value.replace('-', '').replace(' ', '') - self.card_type = verify_credit_card(value) - if self.card_type is None: - raise forms.ValidationError("Invalid credit card number.") - return value + """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: + value = value.replace('-', '').replace(' ', '') + self.card_type = verify_credit_card(value) + if self.card_type is None: + raise forms.ValidationError("Invalid credit card number.") + return value # Credit Card Expiry Fields from: # 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] - elif isinstance(value, basestring): - return [value[0:2], value[2:]] - else: - return [None, None] + """MultiWidget for representing credit card expiry date.""" + def decompress(self, value): + if isinstance(value, date): + return [value.month, value.year] + elif isinstance(value, basestring): + return [value[0:2], value[2:]] + else: + return [None, None] - def format_output(self, rendered_widgets): - html = u' / '.join(rendered_widgets) - return u'%s' % html + def format_output(self, rendered_widgets): + html = u' / '.join(rendered_widgets) + return u'%s' % 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)] + 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)] - default_error_messages = { - 'invalid_month': u'Enter a valid month.', - 'invalid_year': u'Enter a valid year.', - } + default_error_messages = { + 'invalid_month': u'Enter a valid month.', + 'invalid_year': u'Enter a valid year.', + } - 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]) + 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]) - def clean(self, value): - exp = super(CreditCardExpiryField, self).clean(value) - if date.today() > exp: - raise forms.ValidationError("The expiration date you entered is in the past.") - return exp + def clean(self, value): + exp = super(CreditCardExpiryField, self).clean(value) + if date.today() > exp: + raise forms.ValidationError("The expiration date you entered is in the past.") + return exp - def compress(self, data_list): - if data_list: - if data_list[1] in forms.fields.EMPTY_VALUES: - error = self.error_messages['invalid_year'] - raise forms.ValidationError(error) - if data_list[0] in forms.fields.EMPTY_VALUES: - error = self.error_messages['invalid_month'] - raise forms.ValidationError(error) - year = int(data_list[1]) - month = int(data_list[0]) - # find last day of the month - day = monthrange(year, month)[1] - return date(year, month, day) - return None + def compress(self, data_list): + if data_list: + if data_list[1] in forms.fields.EMPTY_VALUES: + error = self.error_messages['invalid_year'] + raise forms.ValidationError(error) + if data_list[0] in forms.fields.EMPTY_VALUES: + error = self.error_messages['invalid_month'] + raise forms.ValidationError(error) + year = int(data_list[1]) + month = int(data_list[0]) + # find last day of the month + day = monthrange(year, month)[1] + return date(year, month, day) + return None class CreditCardCVV2Field(forms.CharField): - def __init__(self, *args, **kwargs): - kwargs.setdefault('max_length', 4) - super(CreditCardCVV2Field, self).__init__(*args, **kwargs) - + 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/ # http://xml.coverpages.org/country3166.html COUNTRIES = ( - ('US', _('United States of America')), - ('CA', _('Canada')), - ('AF', _('Afghanistan')), - ('AL', _('Albania')), - ('DZ', _('Algeria')), - ('AS', _('American Samoa')), - ('AD', _('Andorra')), - ('AO', _('Angola')), - ('AI', _('Anguilla')), - ('AQ', _('Antarctica')), - ('AG', _('Antigua & Barbuda')), - ('AR', _('Argentina')), - ('AM', _('Armenia')), - ('AW', _('Aruba')), - ('AU', _('Australia')), - ('AT', _('Austria')), - ('AZ', _('Azerbaijan')), - ('BS', _('Bahama')), - ('BH', _('Bahrain')), - ('BD', _('Bangladesh')), - ('BB', _('Barbados')), - ('BY', _('Belarus')), - ('BE', _('Belgium')), - ('BZ', _('Belize')), - ('BJ', _('Benin')), - ('BM', _('Bermuda')), - ('BT', _('Bhutan')), - ('BO', _('Bolivia')), - ('BA', _('Bosnia and Herzegovina')), - ('BW', _('Botswana')), - ('BV', _('Bouvet Island')), - ('BR', _('Brazil')), - ('IO', _('British Indian Ocean Territory')), - ('VG', _('British Virgin Islands')), - ('BN', _('Brunei Darussalam')), - ('BG', _('Bulgaria')), - ('BF', _('Burkina Faso')), - ('BI', _('Burundi')), - ('KH', _('Cambodia')), - ('CM', _('Cameroon')), - ('CV', _('Cape Verde')), - ('KY', _('Cayman Islands')), - ('CF', _('Central African Republic')), - ('TD', _('Chad')), - ('CL', _('Chile')), - ('CN', _('China')), - ('CX', _('Christmas Island')), - ('CC', _('Cocos (Keeling) Islands')), - ('CO', _('Colombia')), - ('KM', _('Comoros')), - ('CG', _('Congo')), - ('CK', _('Cook Iislands')), - ('CR', _('Costa Rica')), - ('HR', _('Croatia')), - ('CU', _('Cuba')), - ('CY', _('Cyprus')), - ('CZ', _('Czech Republic')), - ('DK', _('Denmark')), - ('DJ', _('Djibouti')), - ('DM', _('Dominica')), - ('DO', _('Dominican Republic')), - ('TP', _('East Timor')), - ('EC', _('Ecuador')), - ('EG', _('Egypt')), - ('SV', _('El Salvador')), - ('GQ', _('Equatorial Guinea')), - ('ER', _('Eritrea')), - ('EE', _('Estonia')), - ('ET', _('Ethiopia')), - ('FK', _('Falkland Islands (Malvinas)')), - ('FO', _('Faroe Islands')), - ('FJ', _('Fiji')), - ('FI', _('Finland')), - ('FR', _('France')), - ('FX', _('France, Metropolitan')), - ('GF', _('French Guiana')), - ('PF', _('French Polynesia')), - ('TF', _('French Southern Territories')), - ('GA', _('Gabon')), - ('GM', _('Gambia')), - ('GE', _('Georgia')), - ('DE', _('Germany')), - ('GH', _('Ghana')), - ('GI', _('Gibraltar')), - ('GR', _('Greece')), - ('GL', _('Greenland')), - ('GD', _('Grenada')), - ('GP', _('Guadeloupe')), - ('GU', _('Guam')), - ('GT', _('Guatemala')), - ('GN', _('Guinea')), - ('GW', _('Guinea-Bissau')), - ('GY', _('Guyana')), - ('HT', _('Haiti')), - ('HM', _('Heard & McDonald Islands')), - ('HN', _('Honduras')), - ('HK', _('Hong Kong')), - ('HU', _('Hungary')), - ('IS', _('Iceland')), - ('IN', _('India')), - ('ID', _('Indonesia')), - ('IQ', _('Iraq')), - ('IE', _('Ireland')), - ('IR', _('Islamic Republic of Iran')), - ('IL', _('Israel')), - ('IT', _('Italy')), - ('CI', _('Ivory Coast')), - ('JM', _('Jamaica')), - ('JP', _('Japan')), - ('JO', _('Jordan')), - ('KZ', _('Kazakhstan')), - ('KE', _('Kenya')), - ('KI', _('Kiribati')), - ('KP', _('Korea, Democratic People\'s Republic of')), - ('KR', _('Korea, Republic of')), - ('KW', _('Kuwait')), - ('KG', _('Kyrgyzstan')), - ('LA', _('Lao People\'s Democratic Republic')), - ('LV', _('Latvia')), - ('LB', _('Lebanon')), - ('LS', _('Lesotho')), - ('LR', _('Liberia')), - ('LY', _('Libyan Arab Jamahiriya')), - ('LI', _('Liechtenstein')), - ('LT', _('Lithuania')), - ('LU', _('Luxembourg')), - ('MO', _('Macau')), - ('MG', _('Madagascar')), - ('MW', _('Malawi')), - ('MY', _('Malaysia')), - ('MV', _('Maldives')), - ('ML', _('Mali')), - ('MT', _('Malta')), - ('MH', _('Marshall Islands')), - ('MQ', _('Martinique')), - ('MR', _('Mauritania')), - ('MU', _('Mauritius')), - ('YT', _('Mayotte')), - ('MX', _('Mexico')), - ('FM', _('Micronesia')), - ('MD', _('Moldova, Republic of')), - ('MC', _('Monaco')), - ('MN', _('Mongolia')), - ('MS', _('Monserrat')), - ('MA', _('Morocco')), - ('MZ', _('Mozambique')), - ('MM', _('Myanmar')), - ('NA', _('Namibia')), - ('NR', _('Nauru')), - ('NP', _('Nepal')), - ('NL', _('Netherlands')), - ('AN', _('Netherlands Antilles')), - ('NC', _('New Caledonia')), - ('NZ', _('New Zealand')), - ('NI', _('Nicaragua')), - ('NE', _('Niger')), - ('NG', _('Nigeria')), - ('NU', _('Niue')), - ('NF', _('Norfolk Island')), - ('MP', _('Northern Mariana Islands')), - ('NO', _('Norway')), - ('OM', _('Oman')), - ('PK', _('Pakistan')), - ('PW', _('Palau')), - ('PA', _('Panama')), - ('PG', _('Papua New Guinea')), - ('PY', _('Paraguay')), - ('PE', _('Peru')), - ('PH', _('Philippines')), - ('PN', _('Pitcairn')), - ('PL', _('Poland')), - ('PT', _('Portugal')), - ('PR', _('Puerto Rico')), - ('QA', _('Qatar')), - ('RE', _('Reunion')), - ('RO', _('Romania')), - ('RU', _('Russian Federation')), - ('RW', _('Rwanda')), - ('LC', _('Saint Lucia')), - ('WS', _('Samoa')), - ('SM', _('San Marino')), - ('ST', _('Sao Tome & Principe')), - ('SA', _('Saudi Arabia')), - ('SN', _('Senegal')), - ('SC', _('Seychelles')), - ('SL', _('Sierra Leone')), - ('SG', _('Singapore')), - ('SK', _('Slovakia')), - ('SI', _('Slovenia')), - ('SB', _('Solomon Islands')), - ('SO', _('Somalia')), - ('ZA', _('South Africa')), - ('GS', _('South Georgia and the South Sandwich Islands')), - ('ES', _('Spain')), - ('LK', _('Sri Lanka')), - ('SH', _('St. Helena')), - ('KN', _('St. Kitts and Nevis')), - ('PM', _('St. Pierre & Miquelon')), - ('VC', _('St. Vincent & the Grenadines')), - ('SD', _('Sudan')), - ('SR', _('Suriname')), - ('SJ', _('Svalbard & Jan Mayen Islands')), - ('SZ', _('Swaziland')), - ('SE', _('Sweden')), - ('CH', _('Switzerland')), - ('SY', _('Syrian Arab Republic')), - ('TW', _('Taiwan, Province of China')), - ('TJ', _('Tajikistan')), - ('TZ', _('Tanzania, United Republic of')), - ('TH', _('Thailand')), - ('TG', _('Togo')), - ('TK', _('Tokelau')), - ('TO', _('Tonga')), - ('TT', _('Trinidad & Tobago')), - ('TN', _('Tunisia')), - ('TR', _('Turkey')), - ('TM', _('Turkmenistan')), - ('TC', _('Turks & Caicos Islands')), - ('TV', _('Tuvalu')), - ('UG', _('Uganda')), - ('UA', _('Ukraine')), - ('AE', _('United Arab Emirates')), - ('GB', _('United Kingdom (Great Britain)')), - ('UM', _('United States Minor Outlying Islands')), - ('VI', _('United States Virgin Islands')), - ('ZZ', _('Unknown or unspecified country')), - ('UY', _('Uruguay')), - ('UZ', _('Uzbekistan')), - ('VU', _('Vanuatu')), - ('VA', _('Vatican City State (Holy See)')), - ('VE', _('Venezuela')), - ('VN', _('Viet Nam')), - ('WF', _('Wallis & Futuna Islands')), - ('EH', _('Western Sahara')), - ('YE', _('Yemen')), - ('YU', _('Yugoslavia')), - ('ZR', _('Zaire')), - ('ZM', _('Zambia')), - ('ZW', _('Zimbabwe')), + ('US', _('United States of America')), + ('CA', _('Canada')), + ('AF', _('Afghanistan')), + ('AL', _('Albania')), + ('DZ', _('Algeria')), + ('AS', _('American Samoa')), + ('AD', _('Andorra')), + ('AO', _('Angola')), + ('AI', _('Anguilla')), + ('AQ', _('Antarctica')), + ('AG', _('Antigua & Barbuda')), + ('AR', _('Argentina')), + ('AM', _('Armenia')), + ('AW', _('Aruba')), + ('AU', _('Australia')), + ('AT', _('Austria')), + ('AZ', _('Azerbaijan')), + ('BS', _('Bahama')), + ('BH', _('Bahrain')), + ('BD', _('Bangladesh')), + ('BB', _('Barbados')), + ('BY', _('Belarus')), + ('BE', _('Belgium')), + ('BZ', _('Belize')), + ('BJ', _('Benin')), + ('BM', _('Bermuda')), + ('BT', _('Bhutan')), + ('BO', _('Bolivia')), + ('BA', _('Bosnia and Herzegovina')), + ('BW', _('Botswana')), + ('BV', _('Bouvet Island')), + ('BR', _('Brazil')), + ('IO', _('British Indian Ocean Territory')), + ('VG', _('British Virgin Islands')), + ('BN', _('Brunei Darussalam')), + ('BG', _('Bulgaria')), + ('BF', _('Burkina Faso')), + ('BI', _('Burundi')), + ('KH', _('Cambodia')), + ('CM', _('Cameroon')), + ('CV', _('Cape Verde')), + ('KY', _('Cayman Islands')), + ('CF', _('Central African Republic')), + ('TD', _('Chad')), + ('CL', _('Chile')), + ('CN', _('China')), + ('CX', _('Christmas Island')), + ('CC', _('Cocos (Keeling) Islands')), + ('CO', _('Colombia')), + ('KM', _('Comoros')), + ('CG', _('Congo')), + ('CK', _('Cook Iislands')), + ('CR', _('Costa Rica')), + ('HR', _('Croatia')), + ('CU', _('Cuba')), + ('CY', _('Cyprus')), + ('CZ', _('Czech Republic')), + ('DK', _('Denmark')), + ('DJ', _('Djibouti')), + ('DM', _('Dominica')), + ('DO', _('Dominican Republic')), + ('TP', _('East Timor')), + ('EC', _('Ecuador')), + ('EG', _('Egypt')), + ('SV', _('El Salvador')), + ('GQ', _('Equatorial Guinea')), + ('ER', _('Eritrea')), + ('EE', _('Estonia')), + ('ET', _('Ethiopia')), + ('FK', _('Falkland Islands (Malvinas)')), + ('FO', _('Faroe Islands')), + ('FJ', _('Fiji')), + ('FI', _('Finland')), + ('FR', _('France')), + ('FX', _('France, Metropolitan')), + ('GF', _('French Guiana')), + ('PF', _('French Polynesia')), + ('TF', _('French Southern Territories')), + ('GA', _('Gabon')), + ('GM', _('Gambia')), + ('GE', _('Georgia')), + ('DE', _('Germany')), + ('GH', _('Ghana')), + ('GI', _('Gibraltar')), + ('GR', _('Greece')), + ('GL', _('Greenland')), + ('GD', _('Grenada')), + ('GP', _('Guadeloupe')), + ('GU', _('Guam')), + ('GT', _('Guatemala')), + ('GN', _('Guinea')), + ('GW', _('Guinea-Bissau')), + ('GY', _('Guyana')), + ('HT', _('Haiti')), + ('HM', _('Heard & McDonald Islands')), + ('HN', _('Honduras')), + ('HK', _('Hong Kong')), + ('HU', _('Hungary')), + ('IS', _('Iceland')), + ('IN', _('India')), + ('ID', _('Indonesia')), + ('IQ', _('Iraq')), + ('IE', _('Ireland')), + ('IR', _('Islamic Republic of Iran')), + ('IL', _('Israel')), + ('IT', _('Italy')), + ('CI', _('Ivory Coast')), + ('JM', _('Jamaica')), + ('JP', _('Japan')), + ('JO', _('Jordan')), + ('KZ', _('Kazakhstan')), + ('KE', _('Kenya')), + ('KI', _('Kiribati')), + ('KP', _('Korea, Democratic People\'s Republic of')), + ('KR', _('Korea, Republic of')), + ('KW', _('Kuwait')), + ('KG', _('Kyrgyzstan')), + ('LA', _('Lao People\'s Democratic Republic')), + ('LV', _('Latvia')), + ('LB', _('Lebanon')), + ('LS', _('Lesotho')), + ('LR', _('Liberia')), + ('LY', _('Libyan Arab Jamahiriya')), + ('LI', _('Liechtenstein')), + ('LT', _('Lithuania')), + ('LU', _('Luxembourg')), + ('MO', _('Macau')), + ('MG', _('Madagascar')), + ('MW', _('Malawi')), + ('MY', _('Malaysia')), + ('MV', _('Maldives')), + ('ML', _('Mali')), + ('MT', _('Malta')), + ('MH', _('Marshall Islands')), + ('MQ', _('Martinique')), + ('MR', _('Mauritania')), + ('MU', _('Mauritius')), + ('YT', _('Mayotte')), + ('MX', _('Mexico')), + ('FM', _('Micronesia')), + ('MD', _('Moldova, Republic of')), + ('MC', _('Monaco')), + ('MN', _('Mongolia')), + ('MS', _('Monserrat')), + ('MA', _('Morocco')), + ('MZ', _('Mozambique')), + ('MM', _('Myanmar')), + ('NA', _('Namibia')), + ('NR', _('Nauru')), + ('NP', _('Nepal')), + ('NL', _('Netherlands')), + ('AN', _('Netherlands Antilles')), + ('NC', _('New Caledonia')), + ('NZ', _('New Zealand')), + ('NI', _('Nicaragua')), + ('NE', _('Niger')), + ('NG', _('Nigeria')), + ('NU', _('Niue')), + ('NF', _('Norfolk Island')), + ('MP', _('Northern Mariana Islands')), + ('NO', _('Norway')), + ('OM', _('Oman')), + ('PK', _('Pakistan')), + ('PW', _('Palau')), + ('PA', _('Panama')), + ('PG', _('Papua New Guinea')), + ('PY', _('Paraguay')), + ('PE', _('Peru')), + ('PH', _('Philippines')), + ('PN', _('Pitcairn')), + ('PL', _('Poland')), + ('PT', _('Portugal')), + ('PR', _('Puerto Rico')), + ('QA', _('Qatar')), + ('RE', _('Reunion')), + ('RO', _('Romania')), + ('RU', _('Russian Federation')), + ('RW', _('Rwanda')), + ('LC', _('Saint Lucia')), + ('WS', _('Samoa')), + ('SM', _('San Marino')), + ('ST', _('Sao Tome & Principe')), + ('SA', _('Saudi Arabia')), + ('SN', _('Senegal')), + ('SC', _('Seychelles')), + ('SL', _('Sierra Leone')), + ('SG', _('Singapore')), + ('SK', _('Slovakia')), + ('SI', _('Slovenia')), + ('SB', _('Solomon Islands')), + ('SO', _('Somalia')), + ('ZA', _('South Africa')), + ('GS', _('South Georgia and the South Sandwich Islands')), + ('ES', _('Spain')), + ('LK', _('Sri Lanka')), + ('SH', _('St. Helena')), + ('KN', _('St. Kitts and Nevis')), + ('PM', _('St. Pierre & Miquelon')), + ('VC', _('St. Vincent & the Grenadines')), + ('SD', _('Sudan')), + ('SR', _('Suriname')), + ('SJ', _('Svalbard & Jan Mayen Islands')), + ('SZ', _('Swaziland')), + ('SE', _('Sweden')), + ('CH', _('Switzerland')), + ('SY', _('Syrian Arab Republic')), + ('TW', _('Taiwan, Province of China')), + ('TJ', _('Tajikistan')), + ('TZ', _('Tanzania, United Republic of')), + ('TH', _('Thailand')), + ('TG', _('Togo')), + ('TK', _('Tokelau')), + ('TO', _('Tonga')), + ('TT', _('Trinidad & Tobago')), + ('TN', _('Tunisia')), + ('TR', _('Turkey')), + ('TM', _('Turkmenistan')), + ('TC', _('Turks & Caicos Islands')), + ('TV', _('Tuvalu')), + ('UG', _('Uganda')), + ('UA', _('Ukraine')), + ('AE', _('United Arab Emirates')), + ('GB', _('United Kingdom (Great Britain)')), + ('UM', _('United States Minor Outlying Islands')), + ('VI', _('United States Virgin Islands')), + ('ZZ', _('Unknown or unspecified country')), + ('UY', _('Uruguay')), + ('UZ', _('Uzbekistan')), + ('VU', _('Vanuatu')), + ('VA', _('Vatican City State (Holy See)')), + ('VE', _('Venezuela')), + ('VN', _('Viet Nam')), + ('WF', _('Wallis & Futuna Islands')), + ('EH', _('Western Sahara')), + ('YE', _('Yemen')), + ('YU', _('Yugoslavia')), + ('ZR', _('Zaire')), + ('ZM', _('Zambia')), + ('ZW', _('Zimbabwe')), ) class CountryField(forms.ChoiceField): - def __init__(self, *args, **kwargs): - kwargs.setdefault('choices', COUNTRIES) - super(CountryField, self).__init__(*args, **kwargs) \ No newline at end of file + def __init__(self, *args, **kwargs): + kwargs.setdefault('choices', COUNTRIES) + super(CountryField, self).__init__(*args, **kwargs) \ No newline at end of file diff --git a/paypal/pro/forms.py b/paypal/pro/forms.py index 42418f3..85db249 100644 --- a/paypal/pro/forms.py +++ b/paypal/pro/forms.py @@ -6,40 +6,40 @@ from paypal.pro.exceptions import PayPalFailure class PaymentForm(forms.Form): - """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") - city = forms.CharField(255, label="City") - state = forms.CharField(255, label="State") - countrycode = CountryField(label="Country", initial="US") - zip = forms.CharField(32, label="Postal / Zip Code") - acct = CreditCardField(label="Credit Card Number") - expdate = CreditCardExpiryField(label="Expiration Date") - cvv2 = CreditCardCVV2Field(label="Card Security Code") + """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") + city = forms.CharField(255, label="City") + state = forms.CharField(255, label="State") + countrycode = CountryField(label="Country", initial="US") + zip = forms.CharField(32, label="Postal / Zip Code") + acct = CreditCardField(label="Credit Card Number") + expdate = CreditCardExpiryField(label="Expiration Date") + cvv2 = CreditCardCVV2Field(label="Card Security Code") - def process(self, request, item): - """Process a PayPal direct payment.""" - from paypal.pro.helpers import PayPalWPP - wpp = PayPalWPP(request) - params = self.cleaned_data - params['creditcardtype'] = self.fields['acct'].card_type - params['expdate'] = self.cleaned_data['expdate'].strftime("%m%Y") - params['ipaddress'] = request.META.get("REMOTE_ADDR", "") - params.update(item) + def process(self, request, item): + """Process a PayPal direct payment.""" + from paypal.pro.helpers import PayPalWPP + wpp = PayPalWPP(request) + params = self.cleaned_data + params['creditcardtype'] = self.fields['acct'].card_type + params['expdate'] = self.cleaned_data['expdate'].strftime("%m%Y") + params['ipaddress'] = request.META.get("REMOTE_ADDR", "") + params.update(item) - try: - # Create single payment: - if 'billingperiod' not in params: - nvp_obj = wpp.doDirectPayment(params) - # Create recurring payment: - else: - nvp_obj = wpp.createRecurringPaymentsProfile(params, direct=True) - except PayPalFailure: - return False - return True + try: + # Create single payment: + if 'billingperiod' not in params: + nvp_obj = wpp.doDirectPayment(params) + # Create recurring payment: + else: + nvp_obj = wpp.createRecurringPaymentsProfile(params, direct=True) + except PayPalFailure: + 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()) - PayerID = forms.CharField(max_length=255, widget=forms.HiddenInput()) \ No newline at end of file + """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()) \ No newline at end of file diff --git a/paypal/pro/helpers.py b/paypal/pro/helpers.py index 4942bf2..e6e4ad7 100644 --- a/paypal/pro/helpers.py +++ b/paypal/pro/helpers.py @@ -28,238 +28,238 @@ def paypal_time(time_obj=None): - """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) - + """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.""" - return datetime.datetime(*(time.strptime(s, PayPalNVP.TIMESTAMP_FORMAT)[:6])) + """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.""" - + """Error thrown when something be wrong.""" + class PayPalWPP(object): - """ - Wrapper class for the PayPal Website Payments Pro. - - Website Payments Pro Integration Guide: - https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_WPP_IntegrationGuide.pdf - - 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 - if TEST: - self.endpoint = SANDBOX_ENDPOINT - else: - self.endpoint = ENDPOINT - self.signature_values = params - self.signature = urlencode(self.signature_values) + "&" - - 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") - nvp_obj = self._fetch(params, required, defaults) - if nvp_obj.flag: - raise PayPalFailure(nvp_obj.flag_info) - payment_was_successful.send(params) - # @@@ 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') - # if qd.get('avscode') not in ['X', '0']: - # nvp_obj.set_flag("Invalid avscode: %s" % qd.get('avscode') - return nvp_obj - - def setExpressCheckout(self, params): - """ - Initiates an Express Checkout transaction. - Optionally, the SetExpressCheckout API operation can set up billing agreements for - reference transactions and recurring payments. - Returns a NVP instance - check for token and payerid to continue! - """ - if self._is_recurring(params): - params = self._recurring_setExpressCheckout_adapter(params) - - defaults = {"method": "SetExpressCheckout", "noshipping": 1} - required = L("returnurl cancelurl amt") - nvp_obj = self._fetch(params, required, defaults) - if nvp_obj.flag: - raise PayPalFailure(nvp_obj.flag_info) - return nvp_obj - - def doExpressCheckoutPayment(self, params): - """ - Check the dude out: - """ - defaults = {"method": "DoExpressCheckoutPayment", "paymentaction": "Sale"} - required = L("returnurl cancelurl amt token payerid") - nvp_obj = self._fetch(params, required, defaults) - if nvp_obj.flag: - 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. - Returns True PayPal successfully creates the profile otherwise False. - """ - defaults = {"method": "CreateRecurringPaymentsProfile"} - required = L("profilestartdate billingperiod billingfrequency amt") - - # Direct payments require CC data - if direct: - required + L("creditcardtype acct expdate firstname lastname") - else: - 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) - payment_profile_created.send(params) - return nvp_obj - - def getExpressCheckoutDetails(self, params): - defaults = {"method": "GetExpressCheckoutDetails"} - required = L("token") - nvp_obj = self._fetch(params, required, defaults) - if nvp_obj.flag: - raise PayPalFailure(nvp_obj.flag_info) - return nvp_obj - - def setCustomerBillingAgreement(self, params): - raise DeprecationWarning - - def getTransactionDetails(self, params): - defaults = {"method": "GetTransactionDetails"} - required = L("transactionid") - - nvp_obj = self._fetch(params, required, defaults) - if nvp_obj.flag: - raise PayPalFailure(nvp_obj.flag_info) - return nvp_obj - - def massPay(self, params): - raise NotImplementedError - - def getRecurringPaymentsProfileDetails(self, params): - raise NotImplementedError - - def updateRecurringPaymentsProfile(self, params): - defaults = {"method": "UpdateRecurringPaymentsProfile"} - required = L("profileid") - - nvp_obj = self._fetch(params, required, defaults) - 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. - Action must be either "Cancel", "Suspend", or "Reactivate". - """ - defaults = {"method": "ManageRecurringPaymentsProfileStatus"} - required = L("profileid action") - - 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 params['action'] == 'Cancel': - recurring_cancel.send(sender=nvp_obj) - elif params['action'] == 'Suspend': - recurring_suspend.send(sender=nvp_obj) - elif params['action'] == 'Reactivate': - recurring_reactivate.send(sender=nvp_obj) - else: - raise PayPalFailure(nvp_obj.flag_info) - return nvp_obj - - def refundTransaction(self, params): - raise NotImplementedError - - def _is_recurring(self, params): - """Returns True if the item passed is a recurring transaction.""" - return 'billingfrequency' in params - - def _recurring_setExpressCheckout_adapter(self, params): - """ - The recurring payment interface to SEC is different than the recurring payment - interface to ECP. This adapts a normal call to look like a SEC call. - """ - params['l_billingtype0'] = "RecurringPayments" - params['l_billingagreementdescription0'] = params['desc'] - - REMOVE = L("billingfrequency billingperiod profilestartdate desc") - for k in params.keys(): - if k in REMOVE: - del params[k] - - return params - - def _fetch(self, params, required, defaults): - """Make the NVP request and store the response.""" - defaults.update(params) - pp_params = self._check_and_update_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): - print 'PayPal Request:' - pprint.pprint(defaults) - print '\nPayPal Response:' - pprint.pprint(response_params) - - # Gather all NVP parameters to pass to a new instance. - nvp_params = {} - for k, v in MergeDict(defaults, response_params).items(): - if k in NVP_FIELDS: - nvp_params[str(k)] = v - - # PayPal timestamp has to be formatted. - if 'timestamp' in nvp_params: - nvp_params['timestamp'] = paypaltime2datetime(nvp_params['timestamp']) - - nvp_obj = PayPalNVP(**nvp_params) - 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() - - def _check_and_update_params(self, required, params): - """ - Ensure all required parameters were passed to the API call and format - them correctly. - """ - for r in required: - if r not in params: - raise PayPalError("Missing required param: %s" % r) - - # Upper case all the parameters for PayPal. - return (dict((k.upper(), v) for k, v in params.iteritems())) - - def _parse_response(self, response): - """Turn the PayPal response into a dict""" - response_tokens = {} - for kv in response.split('&'): - key, value = kv.split("=") - response_tokens[key.lower()] = urllib.unquote(value) - return response_tokens \ No newline at end of file + """ + Wrapper class for the PayPal Website Payments Pro. + + Website Payments Pro Integration Guide: + https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_WPP_IntegrationGuide.pdf + + 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 + if TEST: + self.endpoint = SANDBOX_ENDPOINT + else: + self.endpoint = ENDPOINT + self.signature_values = params + self.signature = urlencode(self.signature_values) + "&" + + 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") + nvp_obj = self._fetch(params, required, defaults) + if nvp_obj.flag: + raise PayPalFailure(nvp_obj.flag_info) + payment_was_successful.send(params) + # @@@ 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') + # if qd.get('avscode') not in ['X', '0']: + # nvp_obj.set_flag("Invalid avscode: %s" % qd.get('avscode') + return nvp_obj + + def setExpressCheckout(self, params): + """ + Initiates an Express Checkout transaction. + Optionally, the SetExpressCheckout API operation can set up billing agreements for + reference transactions and recurring payments. + Returns a NVP instance - check for token and payerid to continue! + """ + if self._is_recurring(params): + params = self._recurring_setExpressCheckout_adapter(params) + + defaults = {"method": "SetExpressCheckout", "noshipping": 1} + required = L("returnurl cancelurl amt") + nvp_obj = self._fetch(params, required, defaults) + if nvp_obj.flag: + raise PayPalFailure(nvp_obj.flag_info) + return nvp_obj + + def doExpressCheckoutPayment(self, params): + """ + Check the dude out: + """ + defaults = {"method": "DoExpressCheckoutPayment", "paymentaction": "Sale"} + required = L("returnurl cancelurl amt token payerid") + nvp_obj = self._fetch(params, required, defaults) + if nvp_obj.flag: + 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. + Returns True PayPal successfully creates the profile otherwise False. + """ + defaults = {"method": "CreateRecurringPaymentsProfile"} + required = L("profilestartdate billingperiod billingfrequency amt") + + # Direct payments require CC data + if direct: + required + L("creditcardtype acct expdate firstname lastname") + else: + 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) + payment_profile_created.send(params) + return nvp_obj + + def getExpressCheckoutDetails(self, params): + defaults = {"method": "GetExpressCheckoutDetails"} + required = L("token") + nvp_obj = self._fetch(params, required, defaults) + if nvp_obj.flag: + raise PayPalFailure(nvp_obj.flag_info) + return nvp_obj + + def setCustomerBillingAgreement(self, params): + raise DeprecationWarning + + def getTransactionDetails(self, params): + defaults = {"method": "GetTransactionDetails"} + required = L("transactionid") + + nvp_obj = self._fetch(params, required, defaults) + if nvp_obj.flag: + raise PayPalFailure(nvp_obj.flag_info) + return nvp_obj + + def massPay(self, params): + raise NotImplementedError + + def getRecurringPaymentsProfileDetails(self, params): + raise NotImplementedError + + def updateRecurringPaymentsProfile(self, params): + defaults = {"method": "UpdateRecurringPaymentsProfile"} + required = L("profileid") + + nvp_obj = self._fetch(params, required, defaults) + 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. + Action must be either "Cancel", "Suspend", or "Reactivate". + """ + defaults = {"method": "ManageRecurringPaymentsProfileStatus"} + required = L("profileid action") + + 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 params['action'] == 'Cancel': + recurring_cancel.send(sender=nvp_obj) + elif params['action'] == 'Suspend': + recurring_suspend.send(sender=nvp_obj) + elif params['action'] == 'Reactivate': + recurring_reactivate.send(sender=nvp_obj) + else: + raise PayPalFailure(nvp_obj.flag_info) + return nvp_obj + + def refundTransaction(self, params): + raise NotImplementedError + + def _is_recurring(self, params): + """Returns True if the item passed is a recurring transaction.""" + return 'billingfrequency' in params + + def _recurring_setExpressCheckout_adapter(self, params): + """ + The recurring payment interface to SEC is different than the recurring payment + interface to ECP. This adapts a normal call to look like a SEC call. + """ + params['l_billingtype0'] = "RecurringPayments" + params['l_billingagreementdescription0'] = params['desc'] + + REMOVE = L("billingfrequency billingperiod profilestartdate desc") + for k in params.keys(): + if k in REMOVE: + del params[k] + + return params + + def _fetch(self, params, required, defaults): + """Make the NVP request and store the response.""" + defaults.update(params) + pp_params = self._check_and_update_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): + print 'PayPal Request:' + pprint.pprint(defaults) + print '\nPayPal Response:' + pprint.pprint(response_params) + + # Gather all NVP parameters to pass to a new instance. + nvp_params = {} + for k, v in MergeDict(defaults, response_params).items(): + if k in NVP_FIELDS: + nvp_params[str(k)] = v + + # PayPal timestamp has to be formatted. + if 'timestamp' in nvp_params: + nvp_params['timestamp'] = paypaltime2datetime(nvp_params['timestamp']) + + nvp_obj = PayPalNVP(**nvp_params) + 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() + + def _check_and_update_params(self, required, params): + """ + Ensure all required parameters were passed to the API call and format + them correctly. + """ + for r in required: + if r not in params: + raise PayPalError("Missing required param: %s" % r) + + # Upper case all the parameters for PayPal. + return (dict((k.upper(), v) for k, v in params.iteritems())) + + def _parse_response(self, response): + """Turn the PayPal response into a dict""" + response_tokens = {} + for kv in response.split('&'): + key, value = kv.split("=") + response_tokens[key.lower()] = urllib.unquote(value) + return response_tokens \ No newline at end of file diff --git a/paypal/pro/models.py b/paypal/pro/models.py index 9359887..ff1e780 100644 --- a/paypal/pro/models.py +++ b/paypal/pro/models.py @@ -6,99 +6,99 @@ from django.forms.models import model_to_dict from django.contrib.auth.models import User try: - from idmapper.models import SharedMemoryModel as Model + from idmapper.models import SharedMemoryModel as Model except ImportError: - Model = models.Model + 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 - RESTRICTED_FIELDS = L("expdate cvv2 acct") - ADMIN_FIELDS = L("id user flag flag_code flag_info query response created_at updated_at ") - ITEM_FIELDS = L("amt custom invnum") - DIRECT_FIELDS = L("firstname lastname street city state countrycode zip") + """Record of a NVP interaction with PayPal.""" + TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # 2009-02-03T17:47:41Z + RESTRICTED_FIELDS = L("expdate cvv2 acct") + ADMIN_FIELDS = L("id user flag flag_code flag_info query response created_at updated_at ") + ITEM_FIELDS = L("amt custom invnum") + DIRECT_FIELDS = L("firstname lastname street city state countrycode zip") - # Response fields - method = models.CharField(max_length=64, 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) - 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) - - # 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) - 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 + # Response fields + method = models.CharField(max_length=64, 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) + 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) + + # 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) + 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) - self.query = urlencode(query_data) - self.response = urlencode(paypal_response) + # No storing credit card info. + 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) - # Was there a flag on the play? - ack = paypal_response.get('ack', False) - if ack != "Success": - if ack == "SuccessWithWarning": - self.flag_info = paypal_response.get('l_longmessage0', '') - else: - self.set_flag(paypal_response.get('l_longmessage0', ''), paypal_response.get('l_errorcode', '')) + # Was there a flag on the play? + ack = paypal_response.get('ack', False) + if ack != "Success": + if ack == "SuccessWithWarning": + self.flag_info = paypal_response.get('l_longmessage0', '') + else: + self.set_flag(paypal_response.get('l_longmessage0', ''), paypal_response.get('l_errorcode', '')) - def set_flag(self, info, code=None): - """Flag this instance for investigation.""" - self.flag = True - self.flag_info += info - if code is not None: - self.flag_code = code + def set_flag(self, info, code=None): + """Flag this instance for investigation.""" + self.flag = True + self.flag_info += info + if code is not None: + self.flag_code = code - def process(self, request, item): - """Do a direct payment.""" - from paypal.pro.helpers import PayPalWPP - wpp = PayPalWPP(request) + 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. - params = model_to_dict(self, exclude=self.ADMIN_FIELDS) - params['acct'] = self.acct - params['creditcardtype'] = self.creditcardtype - params['expdate'] = self.expdate - params['cvv2'] = self.cvv2 - params.update(item) + # Change the model information into a dict that PayPal can understand. + params = model_to_dict(self, exclude=self.ADMIN_FIELDS) + params['acct'] = self.acct + params['creditcardtype'] = self.creditcardtype + params['expdate'] = self.expdate + params['cvv2'] = self.cvv2 + params.update(item) - # Create recurring payment: - if 'billingperiod' in params: - return wpp.createRecurringPaymentsProfile(params, direct=True) - # Create single payment: - else: - return wpp.doDirectPayment(params) + # Create recurring payment: + if 'billingperiod' in params: + return wpp.createRecurringPaymentsProfile(params, direct=True) + # Create single payment: + else: + return wpp.doDirectPayment(params) diff --git a/paypal/pro/tests.py b/paypal/pro/tests.py index cbf360f..4af3357 100644 --- a/paypal/pro/tests.py +++ b/paypal/pro/tests.py @@ -12,111 +12,111 @@ from paypal.pro.exceptions import PayPalFailure class RequestFactory(Client): - # Used to generate request objects. - def request(self, **request): - environ = { - 'HTTP_COOKIE': self.cookies, - 'PATH_INFO': '/', - 'QUERY_STRING': '', - 'REQUEST_METHOD': 'GET', - 'SCRIPT_NAME': '', - 'SERVER_NAME': 'testserver', - 'SERVER_PORT': 80, - 'SERVER_PROTOCOL': 'HTTP/1.1', - } - environ.update(self.defaults) - environ.update(request) - return WSGIRequest(environ) + # Used to generate request objects. + def request(self, **request): + environ = { + 'HTTP_COOKIE': self.cookies, + 'PATH_INFO': '/', + 'QUERY_STRING': '', + 'REQUEST_METHOD': 'GET', + 'SCRIPT_NAME': '', + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + 'SERVER_PROTOCOL': 'HTTP/1.1', + } + environ.update(self.defaults) + environ.update(request) + return WSGIRequest(environ) RF = RequestFactory() REQUEST = RF.get("/pay/", REMOTE_ADDR="127.0.0.1:8000") class DummyPayPalWPP(PayPalWPP): - pass -# """Dummy class for testing PayPalWPP.""" -# responses = { -# # @@@ Need some reals data here. -# "DoDirectPayment": """ack=Success×tamp=2009-03-12T23%3A52%3A33Z&l_severitycode0=Error&l_shortmessage0=Security+error&l_longmessage0=Security+header+is+not+valid&version=54.0&build=854529&l_errorcode0=&correlationid=""", -# } + pass +# """Dummy class for testing PayPalWPP.""" +# responses = { +# # @@@ Need some reals data here. +# "DoDirectPayment": """ack=Success×tamp=2009-03-12T23%3A52%3A33Z&l_severitycode0=Error&l_shortmessage0=Security+error&l_longmessage0=Security+header+is+not+valid&version=54.0&build=854529&l_errorcode0=&correlationid=""", +# } # -# def _request(self, data): -# return self.responses["DoDirectPayment"] +# def _request(self, data): +# return self.responses["DoDirectPayment"] class CreditCardFieldTest(TestCase): - def test_CreditCardField(self): - field = CreditCardField() - field.clean('4797503429879309') - self.assertEquals(field.card_type, "Visa") - self.assertRaises(ValidationError, CreditCardField().clean, '1234567890123455') + def test_CreditCardField(self): + field = CreditCardField() + field.clean('4797503429879309') + self.assertEquals(field.card_type, "Visa") + self.assertRaises(ValidationError, CreditCardField().clean, '1234567890123455') - def test_invalidCreditCards(self): - self.assertEquals(CreditCardField().clean('4797-5034-2987-9309'), '4797503429879309') - + def test_invalidCreditCards(self): + self.assertEquals(CreditCardField().clean('4797-5034-2987-9309'), '4797503429879309') + class PayPalWPPTest(TestCase): - def setUp(self): - - # Avoding blasting real requests at PayPal. - self.old_debug = settings.DEBUG - settings.DEBUG = True - - self.item = { - 'amt': '9.95', - 'inv': 'inv', - 'custom': 'custom', - 'next': 'http://www.example.com/next/', - 'returnurl': 'http://www.example.com/pay/', - 'cancelurl': 'http://www.example.com/cancel/' - } - self.wpp = DummyPayPalWPP(REQUEST) - - def tearDown(self): - settings.DEBUG = self.old_debug + def setUp(self): + + # Avoding blasting real requests at PayPal. + self.old_debug = settings.DEBUG + settings.DEBUG = True + + self.item = { + 'amt': '9.95', + 'inv': 'inv', + 'custom': 'custom', + 'next': 'http://www.example.com/next/', + 'returnurl': 'http://www.example.com/pay/', + 'cancelurl': 'http://www.example.com/cancel/' + } + self.wpp = DummyPayPalWPP(REQUEST) + + def tearDown(self): + settings.DEBUG = self.old_debug - def test_doDirectPayment_missing_params(self): - data = {'firstname': 'Chewbacca'} - self.assertRaises(PayPalError, self.wpp.doDirectPayment, data) + def test_doDirectPayment_missing_params(self): + data = {'firstname': 'Chewbacca'} + self.assertRaises(PayPalError, self.wpp.doDirectPayment, data) - def test_doDirectPayment_valid(self): - data = { - 'firstname': 'Brave', - 'lastname': 'Star', - 'street': '1 Main St', - 'city': u'San Jos\xe9', - 'state': 'CA', - 'countrycode': 'US', - 'zip': '95131', - 'expdate': '012019', - 'cvv2': '037', - 'acct': '4797503429879309', - 'creditcardtype': 'visa', - 'ipaddress': '10.0.1.199',} - data.update(self.item) - self.assertTrue(self.wpp.doDirectPayment(data)) - - def test_doDirectPayment_invalid(self): - data = { - 'firstname': 'Epic', - 'lastname': 'Fail', - 'street': '100 Georgia St', - 'city': 'Vancouver', - 'state': 'BC', - 'countrycode': 'CA', - 'zip': 'V6V 1V1', - 'expdate': '012019', - 'cvv2': '999', - 'acct': '1234567890', - 'creditcardtype': 'visa', - 'ipaddress': '10.0.1.199',} - data.update(self.item) - self.assertRaises(PayPalFailure, self.wpp.doDirectPayment, data) + def test_doDirectPayment_valid(self): + data = { + 'firstname': 'Brave', + 'lastname': 'Star', + 'street': '1 Main St', + 'city': u'San Jos\xe9', + 'state': 'CA', + 'countrycode': 'US', + 'zip': '95131', + 'expdate': '012019', + 'cvv2': '037', + 'acct': '4797503429879309', + 'creditcardtype': 'visa', + 'ipaddress': '10.0.1.199',} + data.update(self.item) + self.assertTrue(self.wpp.doDirectPayment(data)) + + def test_doDirectPayment_invalid(self): + data = { + 'firstname': 'Epic', + 'lastname': 'Fail', + 'street': '100 Georgia St', + 'city': 'Vancouver', + 'state': 'BC', + 'countrycode': 'CA', + 'zip': 'V6V 1V1', + 'expdate': '012019', + 'cvv2': '999', + 'acct': '1234567890', + 'creditcardtype': 'visa', + 'ipaddress': '10.0.1.199',} + data.update(self.item) + self.assertRaises(PayPalFailure, self.wpp.doDirectPayment, data) - def test_setExpressCheckout(self): - # We'll have to stub out tests for doExpressCheckoutPayment and friends - # because they're behind paypal's doors. - nvp_obj = self.wpp.setExpressCheckout(self.item) - self.assertTrue(nvp_obj.ack == "Success") + def test_setExpressCheckout(self): + # We'll have to stub out tests for doExpressCheckoutPayment and friends + # because they're behind paypal's doors. + nvp_obj = self.wpp.setExpressCheckout(self.item) + self.assertTrue(nvp_obj.ack == "Success") ### DoExpressCheckoutPayment diff --git a/paypal/pro/views.py b/paypal/pro/views.py index e28978f..a898325 100644 --- a/paypal/pro/views.py +++ b/paypal/pro/views.py @@ -17,185 +17,185 @@ class PayPalPro(object): - """ - This class-based view takes care of PayPal WebsitePaymentsPro (WPP). - PayPalPro has two separate flows - DirectPayment and ExpressPayFlow. In - DirectPayment the user buys on your site. In ExpressPayFlow the user is - direct to PayPal to confirm their purchase. PayPalPro implements both - flows. To it create an instance using the these parameters: - - item: a dictionary that holds information about the item being purchased. - - For single item purchase (pay once): - - Required Keys: - * amt: Float amount of the item. - - Optional Keys: - * custom: You can set this to help you identify a transaction. - * invnum: Unique ID that identifies this transaction. - - For recurring billing: - - Required Keys: - * amt: Float amount for each billing cycle. - * billingperiod: String unit of measure for the billing cycle (Day|Week|SemiMonth|Month|Year) - * billingfrequency: Integer number of periods that make up a cycle. - * profilestartdate: The date to begin billing. "2008-08-05T17:00:00Z" UTC/GMT - * desc: Description of what you're billing for. - - Optional Keys: - * trialbillingperiod: String unit of measure for trial cycle (Day|Week|SemiMonth|Month|Year) - * trialbillingfrequency: Integer # of periods in a cycle. - * trialamt: Float amount to bill for the trial period. - * trialtotalbillingcycles: Integer # of cycles for the trial payment period. - * failedinitamtaction: set to continue on failure (ContinueOnFailure / CancelOnFailure) - * maxfailedpayments: number of payments before profile is suspended. - * autobilloutamt: automatically bill outstanding amount. - * subscribername: Full name of the person who paid. - * profilereference: Unique reference or invoice number. - * taxamt: How much tax. - * initamt: Initial non-recurring payment due upon creation. - * currencycode: defaults to USD - * + a bunch of shipping fields - - payment_form_cls: form class that will be used to display the payment form. - It should inherit from `paypal.pro.forms.PaymentForm` if you're adding more. - - payment_template: template used to ask the dude for monies. To comply with - PayPal standards it must include a link to PayPal Express Checkout. - - confirm_form_cls: form class that will be used to display the confirmation form. - It should inherit from `paypal.pro.forms.ConfirmForm`. It is only used in the Express flow. - - success_url / fail_url: URLs to be redirected to when the payment successful or fails. - """ - errors = { - "processing": "There was an error processing your payment. Check your information and try again.", - "form": "Please correct the errors below and try again.", - "paypal": "There was a problem contacting PayPal. Please try again later." - } - - def __init__(self, item=None, payment_form_cls=PaymentForm, - payment_template="pro/payment.html", confirm_form_cls=ConfirmForm, - confirm_template="pro/confirm.html", success_url="?success", - fail_url=None, context=None, form_context_name="form"): - self.item = item - self.payment_form_cls = payment_form_cls - self.payment_template = payment_template - self.confirm_form_cls = confirm_form_cls - self.confirm_template = confirm_template - self.success_url = success_url - self.fail_url = fail_url - self.context = context or {} - self.form_context_name = form_context_name - - def __call__(self, request): - """Return the appropriate response for the state of the transaction.""" - self.request = request - if request.method == "GET": - if self.should_redirect_to_express(): - return self.redirect_to_express() - elif self.should_render_confirm_form(): - return self.render_confirm_form() - elif self.should_render_payment_form(): - return self.render_payment_form() - else: - if self.should_validate_confirm_form(): - return self.validate_confirm_form() - elif self.should_validate_payment_form(): - return self.validate_payment_form() - - # Default to the rendering the payment form. - return self.render_payment_form() - - def is_recurring(self): - return self.item is not None and 'billingperiod' in self.item - - def should_redirect_to_express(self): - return 'express' in self.request.GET - - def should_render_confirm_form(self): - return 'token' in self.request.GET and 'PayerID' in self.request.GET - - def should_render_payment_form(self): - return True - - def should_validate_confirm_form(self): - return 'token' in self.request.POST and 'PayerID' in self.request.POST - - def should_validate_payment_form(self): - return True - - def render_payment_form(self): - """Display the DirectPayment for entering payment information.""" - self.context[self.form_context_name] = self.payment_form_cls() - return render_to_response(self.payment_template, self.context, RequestContext(self.request)) - - def validate_payment_form(self): - """Try to validate and then process the DirectPayment form.""" - form = self.payment_form_cls(self.request.POST) - if form.is_valid(): - success = form.process(self.request, self.item) - if success: - return HttpResponseRedirect(self.success_url) - else: - self.context['errors'] = self.errors['processing'] - - self.context[self.form_context_name] = form - self.context.setdefault("errors", self.errors['form']) - return render_to_response(self.payment_template, self.context, RequestContext(self.request)) - - def get_endpoint(self): - if TEST: - return SANDBOX_EXPRESS_ENDPOINT - else: - return EXPRESS_ENDPOINT - - def redirect_to_express(self): - """ - First step of ExpressCheckout. Redirect the request to PayPal using the - data returned from setExpressCheckout. - """ - wpp = PayPalWPP(self.request) - try: - nvp_obj = wpp.setExpressCheckout(self.item) - except PayPalFailure: - self.context['errors'] = self.errors['paypal'] - return self.render_payment_form() - else: - pp_params = dict(token=nvp_obj.token, AMT=self.item['amt'], - RETURNURL=self.item['returnurl'], - CANCELURL=self.item['cancelurl']) - pp_url = self.get_endpoint() % urlencode(pp_params) - return HttpResponseRedirect(pp_url) - - def render_confirm_form(self): - """ - Second step of ExpressCheckout. Display an order confirmation form which - contains hidden fields with the token / PayerID from PayPal. - """ - initial = dict(token=self.request.GET['token'], PayerID=self.request.GET['PayerID']) - self.context[self.form_context_name] = self.confirm_form_cls(initial=initial) - return render_to_response(self.confirm_template, self.context, RequestContext(self.request)) - - def validate_confirm_form(self): - """ - Third and final step of ExpressCheckout. Request has pressed the confirmation but - and we can send the final confirmation to PayPal using the data from the POST'ed form. - """ - wpp = PayPalWPP(self.request) - pp_data = dict(token=self.request.POST['token'], payerid=self.request.POST['PayerID']) - self.item.update(pp_data) - - # @@@ This check and call could be moved into PayPalWPP. - try: - if self.is_recurring(): - nvp_obj = wpp.createRecurringPaymentsProfile(self.item) - else: - nvp_obj = wpp.doExpressCheckoutPayment(self.item) - except PayPalFailure: - self.context['errors'] = self.errors['processing'] - return self.render_payment_form() - else: - return HttpResponseRedirect(self.success_url) + """ + This class-based view takes care of PayPal WebsitePaymentsPro (WPP). + PayPalPro has two separate flows - DirectPayment and ExpressPayFlow. In + DirectPayment the user buys on your site. In ExpressPayFlow the user is + direct to PayPal to confirm their purchase. PayPalPro implements both + flows. To it create an instance using the these parameters: + + item: a dictionary that holds information about the item being purchased. + + For single item purchase (pay once): + + Required Keys: + * amt: Float amount of the item. + + Optional Keys: + * custom: You can set this to help you identify a transaction. + * invnum: Unique ID that identifies this transaction. + + For recurring billing: + + Required Keys: + * amt: Float amount for each billing cycle. + * billingperiod: String unit of measure for the billing cycle (Day|Week|SemiMonth|Month|Year) + * billingfrequency: Integer number of periods that make up a cycle. + * profilestartdate: The date to begin billing. "2008-08-05T17:00:00Z" UTC/GMT + * desc: Description of what you're billing for. + + Optional Keys: + * trialbillingperiod: String unit of measure for trial cycle (Day|Week|SemiMonth|Month|Year) + * trialbillingfrequency: Integer # of periods in a cycle. + * trialamt: Float amount to bill for the trial period. + * trialtotalbillingcycles: Integer # of cycles for the trial payment period. + * failedinitamtaction: set to continue on failure (ContinueOnFailure / CancelOnFailure) + * maxfailedpayments: number of payments before profile is suspended. + * autobilloutamt: automatically bill outstanding amount. + * subscribername: Full name of the person who paid. + * profilereference: Unique reference or invoice number. + * taxamt: How much tax. + * initamt: Initial non-recurring payment due upon creation. + * currencycode: defaults to USD + * + a bunch of shipping fields + + payment_form_cls: form class that will be used to display the payment form. + It should inherit from `paypal.pro.forms.PaymentForm` if you're adding more. + + payment_template: template used to ask the dude for monies. To comply with + PayPal standards it must include a link to PayPal Express Checkout. + + confirm_form_cls: form class that will be used to display the confirmation form. + It should inherit from `paypal.pro.forms.ConfirmForm`. It is only used in the Express flow. + + success_url / fail_url: URLs to be redirected to when the payment successful or fails. + """ + errors = { + "processing": "There was an error processing your payment. Check your information and try again.", + "form": "Please correct the errors below and try again.", + "paypal": "There was a problem contacting PayPal. Please try again later." + } + + def __init__(self, item=None, payment_form_cls=PaymentForm, + payment_template="pro/payment.html", confirm_form_cls=ConfirmForm, + confirm_template="pro/confirm.html", success_url="?success", + fail_url=None, context=None, form_context_name="form"): + self.item = item + self.payment_form_cls = payment_form_cls + self.payment_template = payment_template + self.confirm_form_cls = confirm_form_cls + self.confirm_template = confirm_template + self.success_url = success_url + self.fail_url = fail_url + self.context = context or {} + self.form_context_name = form_context_name + + def __call__(self, request): + """Return the appropriate response for the state of the transaction.""" + self.request = request + if request.method == "GET": + if self.should_redirect_to_express(): + return self.redirect_to_express() + elif self.should_render_confirm_form(): + return self.render_confirm_form() + elif self.should_render_payment_form(): + return self.render_payment_form() + else: + if self.should_validate_confirm_form(): + return self.validate_confirm_form() + elif self.should_validate_payment_form(): + return self.validate_payment_form() + + # Default to the rendering the payment form. + return self.render_payment_form() + + def is_recurring(self): + return self.item is not None and 'billingperiod' in self.item + + def should_redirect_to_express(self): + return 'express' in self.request.GET + + def should_render_confirm_form(self): + return 'token' in self.request.GET and 'PayerID' in self.request.GET + + def should_render_payment_form(self): + return True + + def should_validate_confirm_form(self): + return 'token' in self.request.POST and 'PayerID' in self.request.POST + + def should_validate_payment_form(self): + return True + + def render_payment_form(self): + """Display the DirectPayment for entering payment information.""" + self.context[self.form_context_name] = self.payment_form_cls() + return render_to_response(self.payment_template, self.context, RequestContext(self.request)) + + def validate_payment_form(self): + """Try to validate and then process the DirectPayment form.""" + form = self.payment_form_cls(self.request.POST) + if form.is_valid(): + success = form.process(self.request, self.item) + if success: + return HttpResponseRedirect(self.success_url) + else: + self.context['errors'] = self.errors['processing'] + + self.context[self.form_context_name] = form + self.context.setdefault("errors", self.errors['form']) + return render_to_response(self.payment_template, self.context, RequestContext(self.request)) + + def get_endpoint(self): + if TEST: + return SANDBOX_EXPRESS_ENDPOINT + else: + return EXPRESS_ENDPOINT + + def redirect_to_express(self): + """ + First step of ExpressCheckout. Redirect the request to PayPal using the + data returned from setExpressCheckout. + """ + wpp = PayPalWPP(self.request) + try: + nvp_obj = wpp.setExpressCheckout(self.item) + except PayPalFailure: + self.context['errors'] = self.errors['paypal'] + return self.render_payment_form() + else: + pp_params = dict(token=nvp_obj.token, AMT=self.item['amt'], + RETURNURL=self.item['returnurl'], + CANCELURL=self.item['cancelurl']) + pp_url = self.get_endpoint() % urlencode(pp_params) + return HttpResponseRedirect(pp_url) + + def render_confirm_form(self): + """ + Second step of ExpressCheckout. Display an order confirmation form which + contains hidden fields with the token / PayerID from PayPal. + """ + initial = dict(token=self.request.GET['token'], PayerID=self.request.GET['PayerID']) + self.context[self.form_context_name] = self.confirm_form_cls(initial=initial) + return render_to_response(self.confirm_template, self.context, RequestContext(self.request)) + + def validate_confirm_form(self): + """ + Third and final step of ExpressCheckout. Request has pressed the confirmation but + and we can send the final confirmation to PayPal using the data from the POST'ed form. + """ + wpp = PayPalWPP(self.request) + pp_data = dict(token=self.request.POST['token'], payerid=self.request.POST['PayerID']) + self.item.update(pp_data) + + # @@@ This check and call could be moved into PayPalWPP. + try: + if self.is_recurring(): + nvp_obj = wpp.createRecurringPaymentsProfile(self.item) + else: + nvp_obj = wpp.doExpressCheckoutPayment(self.item) + except PayPalFailure: + self.context['errors'] = self.errors['processing'] + return self.render_payment_form() + else: + return HttpResponseRedirect(self.success_url) diff --git a/paypal/standard/conf.py b/paypal/standard/conf.py index 5c5fd45..7d54e87 100644 --- a/paypal/standard/conf.py +++ b/paypal/standard/conf.py @@ -1,8 +1,8 @@ from django.conf import settings class PayPalSettingsError(Exception): - """Raised when settings be bad.""" - + """Raised when settings be bad.""" + TEST = getattr(settings, "PAYPAL_TEST", True) diff --git a/paypal/standard/forms.py b/paypal/standard/forms.py index 51cb042..dcec094 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -6,242 +6,242 @@ from paypal.standard.conf import * from paypal.standard.widgets import ValueHiddenInput, ReservedValueHiddenInput from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, - RECEIVER_EMAIL) + RECEIVER_EMAIL) import urllib # 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box. # PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", "%H:%M:%S %b %d, %Y PST",) # PayPal dates have been spotted in the wild with these formats, beware! PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", - "%H:%M:%S %b. %d, %Y PDT", - "%H:%M:%S %b %d, %Y PST", - "%H:%M:%S %b %d, %Y PDT",) + "%H:%M:%S %b. %d, %Y PDT", + "%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'
...' - - """ - CMD_CHOICES = ( - ("_xclick", "Buy now or Donations"), - ("_cart", "Shopping cart"), - ("_xclick-subscriptions", "Subscribe") - ) - RM_CHOICES = ( - (0, "All shopping cart payments use the GET method"), - (1, "The buyer’s browser is redirected to the return URL by using the GET method, but no payment variables are included"), - (2, "The buyer’s browser is redirected to the return URL by using the POST method, and all payment variables are included") - ) - SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"), (2, "Shipping Required")) - NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note")) - RECURRING_PAYMENT_CHOICES = ( - (1, "Subscription Payments Recur"), - (0, "Subscription payments do not recur") - ) - REATTEMPT_ON_FAIL_CHOICES = ( - (1, "reattempt billing on Failure"), - (0, "Do Not reattempt on failure") - ) - - BUY = 'buy' - SUBSCRIBE = 'subscribe' - DONATE = 'donate' - - # Where the money goes. - business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL) - image_url = forms.CharField(widget=ValueHiddenInput()) - - # 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()) - # 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? - - # 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"})) - rm = forms.ChoiceField(widget=forms.HiddenInput(), initial=RM_CHOICES[0][0]) - 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, - initial=SHIPPING_CHOICES[0][0]) - - def __init__(self, button_type="buy", *args, **kwargs): - super(PayPalPaymentsForm, self).__init__(*args, **kwargs) - self.button_type = button_type - - def render_as_GET_url(self): - """ Returns the form as a GET url in live mode which can be used in 'redirects' in pre-payment processing views """ - data = {} - for name, field in self.fields.items(): - if self[name].value() != None: - data[name] = self[name].value() - - url_args = urllib.urlencode(data) - return '{0}?{1}'.format(POSTBACK_ENDPOINT, url_args) - - def render(self): - return mark_safe(u""" - %s - + """ + 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' ...' + + """ + CMD_CHOICES = ( + ("_xclick", "Buy now or Donations"), + ("_cart", "Shopping cart"), + ("_xclick-subscriptions", "Subscribe") + ) + RM_CHOICES = ( + (0, "All shopping cart payments use the GET method"), + (1, "The buyer’s browser is redirected to the return URL by using the GET method, but no payment variables are included"), + (2, "The buyer’s browser is redirected to the return URL by using the POST method, and all payment variables are included") + ) + SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping"), (2, "Shipping Required")) + NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note")) + RECURRING_PAYMENT_CHOICES = ( + (1, "Subscription Payments Recur"), + (0, "Subscription payments do not recur") + ) + REATTEMPT_ON_FAIL_CHOICES = ( + (1, "reattempt billing on Failure"), + (0, "Do Not reattempt on failure") + ) + + BUY = 'buy' + SUBSCRIBE = 'subscribe' + DONATE = 'donate' + + # Where the money goes. + business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL) + image_url = forms.CharField(widget=ValueHiddenInput()) + + # 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()) + # 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? + + # 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"})) + rm = forms.ChoiceField(widget=forms.HiddenInput(), initial=RM_CHOICES[0][0]) + 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, + initial=SHIPPING_CHOICES[0][0]) + + def __init__(self, button_type="buy", *args, **kwargs): + super(PayPalPaymentsForm, self).__init__(*args, **kwargs) + self.button_type = button_type + + def render_as_GET_url(self): + """ Returns the form as a GET url in live mode which can be used in 'redirects' in pre-payment processing views """ + data = {} + for name, field in self.fields.items(): + if self[name].value() != None: + data[name] = self[name].value() + + url_args = urllib.urlencode(data) + return '{0}?{1}'.format(POSTBACK_ENDPOINT, url_args) + + def render(self): + return mark_safe(u""" + %s +
""" % (POSTBACK_ENDPOINT, self.as_p(), self.get_image())) - def sandbox_as_GET_url(self): - """ Returns the form as a GET url in sandbox mode which can be used in 'redirects' in pre-payment processing views """ - data = {} - for name, field in self.fields.items(): - if self[name].value() != None: - data[name] = self[name].value() + def sandbox_as_GET_url(self): + """ Returns the form as a GET url in sandbox mode which can be used in 'redirects' in pre-payment processing views """ + data = {} + for name, field in self.fields.items(): + if self[name].value() != None: + data[name] = self[name].value() - url_args = urllib.urlencode(data) - return '{0}?{1}'.format(SANDBOX_POSTBACK_ENDPOINT, url_args) + url_args = urllib.urlencode(data) + return '{0}?{1}'.format(SANDBOX_POSTBACK_ENDPOINT, url_args) - def sandbox(self): - return mark_safe(u"""
- %s - + def sandbox(self): + return mark_safe(u""" + %s +
""" % (SANDBOX_POSTBACK_ENDPOINT, self.as_p(), self.get_image())) - def get_image(self): - return { - (True, self.SUBSCRIBE): SUBSCRIPTION_SANDBOX_IMAGE, - (True, self.BUY): SANDBOX_IMAGE, - (True, self.DONATE): DONATION_SANDBOX_IMAGE, - (False, self.SUBSCRIBE): SUBSCRIPTION_IMAGE, - (False, self.BUY): IMAGE, - (False, self.DONATE): DONATION_IMAGE, - }[TEST, self.button_type] + def get_image(self): + return { + (True, self.SUBSCRIBE): SUBSCRIPTION_SANDBOX_IMAGE, + (True, self.BUY): SANDBOX_IMAGE, + (True, self.DONATE): DONATION_SANDBOX_IMAGE, + (False, self.SUBSCRIBE): SUBSCRIPTION_IMAGE, + (False, self.BUY): IMAGE, + (False, self.DONATE): DONATION_IMAGE, + }[TEST, self.button_type] - def is_transaction(self): - return not self.is_subscription() + def is_transaction(self): + return not self.is_subscription() - def is_donation(self): - return self.button_type == self.DONATE + def is_donation(self): + return self.button_type == self.DONATE - def is_subscription(self): - return self.button_type == self.SUBSCRIBE + def is_subscription(self): + return self.button_type == self.SUBSCRIBE class PayPalEncryptedPaymentsForm(PayPalPaymentsForm): - """ - Creates a PayPal Encrypted Payments "Buy It Now" button. - Requires the M2Crypto package. - - 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.""" - from M2Crypto import BIO, SMIME, X509 - # @@@ Could we move this to conf.py? - CERT = settings.PAYPAL_PRIVATE_CERT - PUB_CERT = settings.PAYPAL_PUBLIC_CERT - PAYPAL_CERT = settings.PAYPAL_CERT - CERT_ID = settings.PAYPAL_CERT_ID - - # Iterate through the fields and pull out the ones that have a value. - plaintext = 'cert_id=%s\n' % CERT_ID - for name, field in self.fields.iteritems(): - value = None - if name in self.initial: - value = self.initial[name] - elif field.initial is not None: - value = field.initial - if value is not None: - # @@@ Make this less hackish and put it in the widget. - if name == "return_url": - 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)) - p7 = s.sign(BIO.MemoryBuffer(plaintext), flags=SMIME.PKCS7_BINARY) - x509 = X509.load_cert_bio(BIO.openfile(settings.PAYPAL_CERT)) - sk = X509.X509_Stack() - sk.push(x509) - s.set_x509_stack(sk) - s.set_cipher(SMIME.Cipher('des_ede3_cbc')) - tmp = BIO.MemoryBuffer() - p7.write_der(tmp) - p7 = s.encrypt(tmp, flags=SMIME.PKCS7_BINARY) - out = BIO.MemoryBuffer() - p7.write(out) - return out.read() - - def as_p(self): - return mark_safe(u""" + """ + Creates a PayPal Encrypted Payments "Buy It Now" button. + Requires the M2Crypto package. + + 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.""" + from M2Crypto import BIO, SMIME, X509 + # @@@ Could we move this to conf.py? + CERT = settings.PAYPAL_PRIVATE_CERT + PUB_CERT = settings.PAYPAL_PUBLIC_CERT + PAYPAL_CERT = settings.PAYPAL_CERT + CERT_ID = settings.PAYPAL_CERT_ID + + # Iterate through the fields and pull out the ones that have a value. + plaintext = 'cert_id=%s\n' % CERT_ID + for name, field in self.fields.iteritems(): + value = None + if name in self.initial: + value = self.initial[name] + elif field.initial is not None: + value = field.initial + if value is not None: + # @@@ Make this less hackish and put it in the widget. + if name == "return_url": + 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)) + p7 = s.sign(BIO.MemoryBuffer(plaintext), flags=SMIME.PKCS7_BINARY) + x509 = X509.load_cert_bio(BIO.openfile(settings.PAYPAL_CERT)) + sk = X509.X509_Stack() + sk.push(x509) + s.set_x509_stack(sk) + s.set_cipher(SMIME.Cipher('des_ede3_cbc')) + tmp = BIO.MemoryBuffer() + p7.write_der(tmp) + p7 = s.encrypt(tmp, flags=SMIME.PKCS7_BINARY) + out = BIO.MemoryBuffer() + p7.write(out) + return out.read() + + def as_p(self): + return mark_safe(u""" - """ % self._encrypt()) + """ % self._encrypt()) 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. - - """ - def __init__(self, *args, **kwargs): - "Make the secret from the form initial data and slip it into the form." - from paypal.standard.helpers import make_secret - super(PayPalSharedSecretEncryptedPaymentsForm, self).__init__(*args, **kwargs) - # @@@ Attach the secret parameter in a way that is safe for other query params. - secret_param = "?secret=%s" % make_secret(self) - # Initial data used in form construction overrides defaults - if 'notify_url' in self.initial: - self.initial['notify_url'] += secret_param - else: - self.fields['notify_url'].initial += secret_param + """ + 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. + + """ + def __init__(self, *args, **kwargs): + "Make the secret from the form initial data and slip it into the form." + from paypal.standard.helpers import make_secret + super(PayPalSharedSecretEncryptedPaymentsForm, self).__init__(*args, **kwargs) + # @@@ Attach the secret parameter in a way that is safe for other query params. + secret_param = "?secret=%s" % make_secret(self) + # Initial data used in form construction overrides defaults + if 'notify_url' in self.initial: + self.initial['notify_url'] += secret_param + else: + self.fields['notify_url'].initial += secret_param class PayPalStandardBaseForm(forms.ModelForm): - """Form used to receive and record PayPal IPN/PDT.""" - # PayPal dates have non-standard formats. - time_created = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) - payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) - next_payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) - subscr_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) - subscr_effective = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) + """Form used to receive and record PayPal IPN/PDT.""" + # PayPal dates have non-standard formats. + time_created = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) + payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) + next_payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) + subscr_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) + subscr_effective = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) diff --git a/paypal/standard/helpers.py b/paypal/standard/helpers.py index f7bb269..2edbec7 100644 --- a/paypal/standard/helpers.py +++ b/paypal/standard/helpers.py @@ -3,57 +3,57 @@ 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 - a payment which has gone from pending to completed. - - """ - 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") - - return query.count() > 0 - + """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) + + 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") + + 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'] + """ + 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'] - data = "" - for name in secret_fields: - if hasattr(form_instance, 'cleaned_data'): - if name in form_instance.cleaned_data: - data += unicode(form_instance.cleaned_data[name]) - else: - # Initial data passed into the constructor overrides defaults. - if name in form_instance.initial: - data += unicode(form_instance.initial[name]) - elif name in form_instance.fields and form_instance.fields[name].initial is not None: - data += unicode(form_instance.fields[name].initial) + data = "" + for name in secret_fields: + if hasattr(form_instance, 'cleaned_data'): + if name in form_instance.cleaned_data: + data += unicode(form_instance.cleaned_data[name]) + else: + # Initial data passed into the constructor overrides defaults. + if name in form_instance.initial: + data += unicode(form_instance.initial[name]) + elif name in form_instance.fields and form_instance.fields[name].initial is not None: + data += unicode(form_instance.fields[name].initial) - secret = get_hexdigest('sha1', settings.SECRET_KEY, data) - return secret + 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'] - return make_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'] + return make_secret(form_instance) == secret diff --git a/paypal/standard/ipn/admin.py b/paypal/standard/ipn/admin.py index 173c97c..06f91e6 100644 --- a/paypal/standard/ipn/admin.py +++ b/paypal/standard/ipn/admin.py @@ -5,65 +5,65 @@ class PayPalIPNAdmin(admin.ModelAdmin): - date_hierarchy = 'payment_date' - fieldsets = ( - (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", - "auth_id" - ] - }), - ("Address", { - "description": "The address of the Buyer.", - 'classes': ('collapse',), - "fields": [ - "address_city", "address_country", "address_country_code", - "address_name", "address_state", "address_status", - "address_street", "address_zip" - ] - }), - ("Buyer", { - "description": "The information about the Buyer.", - 'classes': ('collapse',), - "fields": [ - "first_name", "last_name", "payer_business_name", "payer_email", - "payer_id", "payer_status", "contact_phone", "residence_country" - ] - }), - ("Seller", { - "description": "The information about the Seller.", - 'classes': ('collapse',), - "fields": [ - "business", "item_name", "item_number", "quantity", - "receiver_email", "receiver_id", "custom", "invoice", "memo" - ] - }), - ("Recurring", { - "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", - "next_payment_date" - ] - }), - ("Admin", { - "description": "Additional Info.", - "classes": ('collapse',), - "fields": [ - "test_ipn", "ipaddress", "query", "response", "flag_code", - "flag_info" - ] - }), - ) - list_display = [ - "__unicode__", "flag", "flag_info", "invoice", "custom", - "payment_status", "created_at" - ] - search_fields = ["txn_id", "recurring_payment_id"] + date_hierarchy = 'payment_date' + fieldsets = ( + (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", + "auth_id" + ] + }), + ("Address", { + "description": "The address of the Buyer.", + 'classes': ('collapse',), + "fields": [ + "address_city", "address_country", "address_country_code", + "address_name", "address_state", "address_status", + "address_street", "address_zip" + ] + }), + ("Buyer", { + "description": "The information about the Buyer.", + 'classes': ('collapse',), + "fields": [ + "first_name", "last_name", "payer_business_name", "payer_email", + "payer_id", "payer_status", "contact_phone", "residence_country" + ] + }), + ("Seller", { + "description": "The information about the Seller.", + 'classes': ('collapse',), + "fields": [ + "business", "item_name", "item_number", "quantity", + "receiver_email", "receiver_id", "custom", "invoice", "memo" + ] + }), + ("Recurring", { + "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", + "next_payment_date" + ] + }), + ("Admin", { + "description": "Additional Info.", + "classes": ('collapse',), + "fields": [ + "test_ipn", "ipaddress", "query", "response", "flag_code", + "flag_info" + ] + }), + ) + list_display = [ + "__unicode__", "flag", "flag_info", "invoice", "custom", + "payment_status", "created_at" + ] + search_fields = ["txn_id", "recurring_payment_id"] admin.site.register(PayPalIPN, PayPalIPNAdmin) \ No newline at end of file diff --git a/paypal/standard/ipn/forms.py b/paypal/standard/ipn/forms.py index 8df7327..2cb2ccd 100644 --- a/paypal/standard/ipn/forms.py +++ b/paypal/standard/ipn/forms.py @@ -5,12 +5,12 @@ 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 + """ + 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 diff --git a/paypal/standard/ipn/migrations/0001_first_migration.py b/paypal/standard/ipn/migrations/0001_first_migration.py index 54da912..7fe4162 100644 --- a/paypal/standard/ipn/migrations/0001_first_migration.py +++ b/paypal/standard/ipn/migrations/0001_first_migration.py @@ -3,242 +3,242 @@ from south.db import db from paypal.standard.ipn.models import * -class Migration: - def forwards(self, orm): - # Adding model 'PayPalIPN' - db.create_table('paypal_ipn', ( - ('id', models.AutoField(primary_key=True)), - ('business', models.CharField(max_length=127, blank=True)), - ('charset', models.CharField(max_length=32, blank=True)), - ('custom', models.CharField(max_length=255, blank=True)), - ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), - ('receiver_email', models.EmailField(max_length=127, blank=True)), - ('receiver_id', models.CharField(max_length=127, blank=True)), - ('residence_country', models.CharField(max_length=2, blank=True)), - ('test_ipn', models.BooleanField(default=False, blank=True)), - ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), - ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), - ('verify_sign', models.CharField(max_length=255, blank=True)), - ('address_country', models.CharField(max_length=64, blank=True)), - ('address_city', models.CharField(max_length=40, blank=True)), - ('address_country_code', models.CharField(max_length=64, blank=True)), - ('address_name', models.CharField(max_length=128, blank=True)), - ('address_state', models.CharField(max_length=40, blank=True)), - ('address_status', models.CharField(max_length=11, blank=True)), - ('address_street', models.CharField(max_length=200, blank=True)), - ('address_zip', models.CharField(max_length=20, blank=True)), - ('contact_phone', models.CharField(max_length=20, blank=True)), - ('first_name', models.CharField(max_length=64, blank=True)), - ('last_name', models.CharField(max_length=64, blank=True)), - ('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)), - ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=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)), - ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), - ('invoice', models.CharField(max_length=127, blank=True)), - ('item_name', models.CharField(max_length=127, blank=True)), - ('item_number', models.CharField(max_length=127, blank=True)), - ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), - ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('memo', models.CharField(max_length=255, blank=True)), - ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), - ('option_name1', models.CharField(max_length=64, blank=True)), - ('option_name2', models.CharField(max_length=64, blank=True)), - ('payer_status', models.CharField(max_length=10, blank=True)), - ('payment_date', models.DateTimeField(null=True, blank=True)), - ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('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)), - ('quantity', models.IntegerField(default=1, null=True, blank=True)), - ('reason_code', models.CharField(max_length=15, blank=True)), - ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('settle_currency', models.CharField(max_length=32, blank=True)), - ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('shipping_method', models.CharField(max_length=255, blank=True)), - ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('transaction_entity', models.CharField(max_length=7, blank=True)), - ('auction_buyer_id', models.CharField(max_length=64, blank=True)), - ('auction_closing_date', models.DateTimeField(null=True, blank=True)), - ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), - ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('next_payment_date', models.DateTimeField(null=True, blank=True)), - ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('payment_cycle', models.CharField(max_length=32, blank=True)), - ('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)), - ('profile_status', models.CharField(max_length=32, blank=True)), - ('recurring_payment_id', models.CharField(max_length=128, blank=True)), - ('rp_invoice_id', models.CharField(max_length=127, blank=True)), - ('time_created', models.DateTimeField(null=True, blank=True)), - ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('password', models.CharField(max_length=24, blank=True)), - ('period1', models.CharField(max_length=32, blank=True)), - ('period2', models.CharField(max_length=32, blank=True)), - ('period3', models.CharField(max_length=32, blank=True)), - ('reattempt', models.CharField(max_length=1, blank=True)), - ('recur_times', models.IntegerField(default=0, null=True, blank=True)), - ('recurring', models.CharField(max_length=1, blank=True)), - ('retry_at', models.DateTimeField(null=True, blank=True)), - ('subscr_date', models.DateTimeField(null=True, blank=True)), - ('subscr_effective', models.DateTimeField(null=True, blank=True)), - ('subscr_id', models.CharField(max_length=19, blank=True)), - ('username', models.CharField(max_length=64, blank=True)), - ('case_creation_date', models.DateTimeField(null=True, blank=True)), - ('case_id', models.CharField(max_length=14, blank=True)), - ('case_type', models.CharField(max_length=24, blank=True)), - ('receipt_id', models.CharField(max_length=64, blank=True)), - ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), - ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('transaction_subject', models.CharField(max_length=255, blank=True)), - ('ipaddress', models.IPAddressField(blank=True)), - ('flag', models.BooleanField(default=False, blank=True)), - ('flag_code', models.CharField(max_length=16, blank=True)), - ('flag_info', models.TextField(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)), - ('from_view', models.CharField(max_length=6, null=True, blank=True)), - )) - db.send_create_signal('ipn', ['PayPalIPN']) +class Migration: + def forwards(self, orm): + # Adding model 'PayPalIPN' + db.create_table('paypal_ipn', ( + ('id', models.AutoField(primary_key=True)), + ('business', models.CharField(max_length=127, blank=True)), + ('charset', models.CharField(max_length=32, blank=True)), + ('custom', models.CharField(max_length=255, blank=True)), + ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), + ('receiver_email', models.EmailField(max_length=127, blank=True)), + ('receiver_id', models.CharField(max_length=127, blank=True)), + ('residence_country', models.CharField(max_length=2, blank=True)), + ('test_ipn', models.BooleanField(default=False, blank=True)), + ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), + ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), + ('verify_sign', models.CharField(max_length=255, blank=True)), + ('address_country', models.CharField(max_length=64, blank=True)), + ('address_city', models.CharField(max_length=40, blank=True)), + ('address_country_code', models.CharField(max_length=64, blank=True)), + ('address_name', models.CharField(max_length=128, blank=True)), + ('address_state', models.CharField(max_length=40, blank=True)), + ('address_status', models.CharField(max_length=11, blank=True)), + ('address_street', models.CharField(max_length=200, blank=True)), + ('address_zip', models.CharField(max_length=20, blank=True)), + ('contact_phone', models.CharField(max_length=20, blank=True)), + ('first_name', models.CharField(max_length=64, blank=True)), + ('last_name', models.CharField(max_length=64, blank=True)), + ('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)), + ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=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)), + ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), + ('invoice', models.CharField(max_length=127, blank=True)), + ('item_name', models.CharField(max_length=127, blank=True)), + ('item_number', models.CharField(max_length=127, blank=True)), + ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), + ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('memo', models.CharField(max_length=255, blank=True)), + ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), + ('option_name1', models.CharField(max_length=64, blank=True)), + ('option_name2', models.CharField(max_length=64, blank=True)), + ('payer_status', models.CharField(max_length=10, blank=True)), + ('payment_date', models.DateTimeField(null=True, blank=True)), + ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('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)), + ('quantity', models.IntegerField(default=1, null=True, blank=True)), + ('reason_code', models.CharField(max_length=15, blank=True)), + ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('settle_currency', models.CharField(max_length=32, blank=True)), + ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('shipping_method', models.CharField(max_length=255, blank=True)), + ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('transaction_entity', models.CharField(max_length=7, blank=True)), + ('auction_buyer_id', models.CharField(max_length=64, blank=True)), + ('auction_closing_date', models.DateTimeField(null=True, blank=True)), + ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), + ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('next_payment_date', models.DateTimeField(null=True, blank=True)), + ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('payment_cycle', models.CharField(max_length=32, blank=True)), + ('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)), + ('profile_status', models.CharField(max_length=32, blank=True)), + ('recurring_payment_id', models.CharField(max_length=128, blank=True)), + ('rp_invoice_id', models.CharField(max_length=127, blank=True)), + ('time_created', models.DateTimeField(null=True, blank=True)), + ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('password', models.CharField(max_length=24, blank=True)), + ('period1', models.CharField(max_length=32, blank=True)), + ('period2', models.CharField(max_length=32, blank=True)), + ('period3', models.CharField(max_length=32, blank=True)), + ('reattempt', models.CharField(max_length=1, blank=True)), + ('recur_times', models.IntegerField(default=0, null=True, blank=True)), + ('recurring', models.CharField(max_length=1, blank=True)), + ('retry_at', models.DateTimeField(null=True, blank=True)), + ('subscr_date', models.DateTimeField(null=True, blank=True)), + ('subscr_effective', models.DateTimeField(null=True, blank=True)), + ('subscr_id', models.CharField(max_length=19, blank=True)), + ('username', models.CharField(max_length=64, blank=True)), + ('case_creation_date', models.DateTimeField(null=True, blank=True)), + ('case_id', models.CharField(max_length=14, blank=True)), + ('case_type', models.CharField(max_length=24, blank=True)), + ('receipt_id', models.CharField(max_length=64, blank=True)), + ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), + ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('transaction_subject', models.CharField(max_length=255, blank=True)), + ('ipaddress', models.IPAddressField(blank=True)), + ('flag', models.BooleanField(default=False, blank=True)), + ('flag_code', models.CharField(max_length=16, blank=True)), + ('flag_info', models.TextField(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)), + ('from_view', models.CharField(max_length=6, null=True, blank=True)), + )) + db.send_create_signal('ipn', ['PayPalIPN']) - def backwards(self, orm): - # Deleting model 'PayPalIPN' - db.delete_table('paypal_ipn') + def backwards(self, orm): + # Deleting model 'PayPalIPN' + db.delete_table('paypal_ipn') - models = { - 'ipn.paypalipn': { - 'Meta': {'db_table': '"paypal_ipn"'}, - 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), - 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), - 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), - 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), - 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), - 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), - 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': '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'}), - 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), - 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), - 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), - 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), - 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), - 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), - 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'flag_info': ('models.TextField', [], {'blank': 'True'}), - 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'id': ('models.AutoField', [], {'primary_key': 'True'}), - 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), - 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), - 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), - 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), - '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'}), - 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), - 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - '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'}), - 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - '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'}), - 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'query': ('models.TextField', [], {'blank': 'True'}), - 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), - 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), - 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), - 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), - 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), - 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), - 'response': ('models.TextField', [], {'blank': 'True'}), - 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), - 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), - 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), - 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), - 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), - 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) - } - } - complete_apps = ['ipn'] + models = { + 'ipn.paypalipn': { + 'Meta': {'db_table': '"paypal_ipn"'}, + 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), + 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': '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'}), + 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), + 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), + 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), + 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), + 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), + 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'flag_info': ('models.TextField', [], {'blank': 'True'}), + 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'id': ('models.AutoField', [], {'primary_key': 'True'}), + 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), + 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), + 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), + 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), + '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'}), + 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), + 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + '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'}), + 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + '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'}), + 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'query': ('models.TextField', [], {'blank': 'True'}), + 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), + 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), + 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'response': ('models.TextField', [], {'blank': 'True'}), + 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), + 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), + 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), + 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), + 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), + 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) + } + } + complete_apps = ['ipn'] diff --git a/paypal/standard/ipn/models.py b/paypal/standard/ipn/models.py index 9495463..cc9cb5a 100644 --- a/paypal/standard/ipn/models.py +++ b/paypal/standard/ipn/models.py @@ -6,49 +6,49 @@ class PayPalIPN(PayPalStandardBase): - """Logs PayPal IPN interactions.""" - format = u"" + """Logs PayPal IPN interactions.""" + format = u"" - class Meta: - db_table = "paypal_ipn" - verbose_name = "PayPal IPN" + class Meta: + db_table = "paypal_ipn" + verbose_name = "PayPal IPN" - 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: - if self.is_transaction(): - if self.flag: - payment_was_flagged.send(sender=self) - else: - payment_was_successful.send(sender=self) - # Recurring payment signals: - # XXX: Should these be merged with subscriptions? - elif self.is_recurring(): - if self.is_recurring_create(): - recurring_create.send(sender=self) - elif self.is_recurring_payment(): - recurring_payment.send(sender=self) - elif self.is_recurring_cancel(): - recurring_cancel.send(sender=self) - elif self.is_recurring_skipped(): - recurring_skipped.send(sender=self) - elif self.is_recurring_failed(): - recurring_failed.send(sender=self) - # Subscription signals: - else: - if self.is_subscription_cancellation(): - subscription_cancel.send(sender=self) - elif self.is_subscription_signup(): - subscription_signup.send(sender=self) - elif self.is_subscription_end_of_term(): - subscription_eot.send(sender=self) - elif self.is_subscription_modified(): - subscription_modify.send(sender=self) \ No newline at end of file + 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: + if self.is_transaction(): + if self.flag: + payment_was_flagged.send(sender=self) + else: + payment_was_successful.send(sender=self) + # Recurring payment signals: + # XXX: Should these be merged with subscriptions? + elif self.is_recurring(): + if self.is_recurring_create(): + recurring_create.send(sender=self) + elif self.is_recurring_payment(): + recurring_payment.send(sender=self) + elif self.is_recurring_cancel(): + recurring_cancel.send(sender=self) + elif self.is_recurring_skipped(): + recurring_skipped.send(sender=self) + elif self.is_recurring_failed(): + recurring_failed.send(sender=self) + # Subscription signals: + else: + if self.is_subscription_cancellation(): + subscription_cancel.send(sender=self) + elif self.is_subscription_signup(): + subscription_signup.send(sender=self) + elif self.is_subscription_end_of_term(): + subscription_eot.send(sender=self) + elif self.is_subscription_modified(): + subscription_modify.send(sender=self) \ No newline at end of file diff --git a/paypal/standard/ipn/tests/test_ipn.py b/paypal/standard/ipn/tests/test_ipn.py index 2db0893..44c9b54 100644 --- a/paypal/standard/ipn/tests/test_ipn.py +++ b/paypal/standard/ipn/tests/test_ipn.py @@ -6,220 +6,220 @@ from paypal.standard.models import ST_PP_CANCELLED from paypal.standard.ipn.models import PayPalIPN from paypal.standard.ipn.signals import (payment_was_successful, - payment_was_flagged, recurring_skipped, recurring_failed, - recurring_create, recurring_payment, recurring_cancel) + payment_was_flagged, recurring_skipped, recurring_failed, + recurring_create, recurring_payment, recurring_cancel) IPN_POST_PARAMS = { - "protection_eligibility": "Ineligible", - "last_name": "User", - "txn_id": "51403485VH153354B", - "receiver_email": settings.PAYPAL_RECEIVER_EMAIL, - "payment_status": "Completed", - "payment_gross": "10.00", - "tax": "0.00", - "residence_country": "US", - "invoice": "0004", - "payer_status": "verified", - "txn_type": "express_checkout", - "handling_amount": "0.00", - "payment_date": "23:04:06 Feb 02, 2009 PST", - "first_name": "Test", - "item_name": "", - "charset": "windows-1252", - "custom": "website_id=13&user_id=21", - "notify_version": "2.6", - "transaction_subject": "", - "test_ipn": "1", - "item_number": "", - "receiver_id": "258DLEHY2BDK6", - "payer_id": "BN5JZ2V7MLEV4", - "verify_sign": "An5ns1Kso7MWUdW4ErQKJJJ4qi4-AqdZy6dD.sGO3sDhTf1wAbuO2IZ7", - "payment_fee": "0.59", - "mc_fee": "0.59", - "mc_currency": "USD", - "shipping": "0.00", - "payer_email": "bishan_1233269544_per@gmail.com", - "payment_type": "instant", - "mc_gross": "10.00", - "quantity": "1", + "protection_eligibility": "Ineligible", + "last_name": "User", + "txn_id": "51403485VH153354B", + "receiver_email": settings.PAYPAL_RECEIVER_EMAIL, + "payment_status": "Completed", + "payment_gross": "10.00", + "tax": "0.00", + "residence_country": "US", + "invoice": "0004", + "payer_status": "verified", + "txn_type": "express_checkout", + "handling_amount": "0.00", + "payment_date": "23:04:06 Feb 02, 2009 PST", + "first_name": "Test", + "item_name": "", + "charset": "windows-1252", + "custom": "website_id=13&user_id=21", + "notify_version": "2.6", + "transaction_subject": "", + "test_ipn": "1", + "item_number": "", + "receiver_id": "258DLEHY2BDK6", + "payer_id": "BN5JZ2V7MLEV4", + "verify_sign": "An5ns1Kso7MWUdW4ErQKJJJ4qi4-AqdZy6dD.sGO3sDhTf1wAbuO2IZ7", + "payment_fee": "0.59", + "mc_fee": "0.59", + "mc_currency": "USD", + "shipping": "0.00", + "payer_email": "bishan_1233269544_per@gmail.com", + "payment_type": "instant", + "mc_gross": "10.00", + "quantity": "1", } -class IPNTest(TestCase): - urls = 'paypal.standard.ipn.tests.test_urls' - - def setUp(self): - self.old_debug = settings.DEBUG - settings.DEBUG = True - - # Monkey patch over PayPalIPN to make it get a VERFIED response. - 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 - - payment_was_successful.receivers = [] - payment_was_flagged.receivers = [] - recurring_skipped.receivers = [] - recurring_failed.receivers = [] - recurring_create.receivers = [] - recurring_payment.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_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 - - - 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(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) - - def test_failed_ipn(self): - PayPalIPN._postback = lambda self: "INVALID" - self.assertGotSignal(payment_was_flagged, True) - - def assertFlagged(self, updates, flag_info): - params = IPN_POST_PARAMS.copy() - params.update(updates) - response = self.client.post("/ipn/", params) - self.assertEqual(response.status_code, 200) - ipn_obj = PayPalIPN.objects.all()[0] - self.assertEqual(ipn_obj.flag, True) - self.assertEqual(ipn_obj.flag_info, flag_info) - - def test_incorrect_receiver_email(self): - update = {"receiver_email": "incorrect_email@someotherbusiness.com"} - flag_info = "Invalid receiver_email. (incorrect_email@someotherbusiness.com)" - self.assertFlagged(update, flag_info) - - 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() - params.update(update) - response = self.client.post("/ipn/", params) - self.assertEqual(response.status_code, 200) - ipn_obj = PayPalIPN.objects.all()[0] - self.assertEqual(ipn_obj.flag, False) - - - 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) - 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)") - - def test_recurring_payment_skipped_ipn(self): - update = { - "recurring_payment_id": "BN5JZ2V7MLEV4", - "txn_type": "recurring_payment_skipped", - "txn_id": "" - } - params = IPN_POST_PARAMS.copy() - params.update(update) - - self.assertGotSignal(recurring_skipped, False, params) - - def test_recurring_payment_failed_ipn(self): - update = { - "recurring_payment_id": "BN5JZ2V7MLEV4", - "txn_type": "recurring_payment_failed", - "txn_id": "" - } - params = IPN_POST_PARAMS.copy() - params.update(update) - - self.assertGotSignal(recurring_failed, False, params) - - def test_recurring_payment_create_ipn(self): - update = { - "recurring_payment_id": "BN5JZ2V7MLEV4", - "txn_type": "recurring_payment_profile_created", - "txn_id": "" - } - params = IPN_POST_PARAMS.copy() - params.update(update) - - self.assertGotSignal(recurring_create, False, params) - - def test_recurring_payment_cancel_ipn(self): - update = { - "recurring_payment_id": "BN5JZ2V7MLEV4", - "txn_type": "recurring_payment_profile_cancel", - "txn_id": "" - } - 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 - will never be sent because the paypal ipn - contains a txn_id, if this test failes you - might break some compatibility - """ - update = { - "recurring_payment_id": "BN5JZ2V7MLEV4", - "txn_type": "recurring_payment", - } - 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.assertFalse(self.got_signal) +class IPNTest(TestCase): + urls = 'paypal.standard.ipn.tests.test_urls' + + def setUp(self): + self.old_debug = settings.DEBUG + settings.DEBUG = True + + # Monkey patch over PayPalIPN to make it get a VERFIED response. + 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 + + payment_was_successful.receivers = [] + payment_was_flagged.receivers = [] + recurring_skipped.receivers = [] + recurring_failed.receivers = [] + recurring_create.receivers = [] + recurring_payment.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_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 + + + 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(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) + + def test_failed_ipn(self): + PayPalIPN._postback = lambda self: "INVALID" + self.assertGotSignal(payment_was_flagged, True) + + def assertFlagged(self, updates, flag_info): + params = IPN_POST_PARAMS.copy() + params.update(updates) + response = self.client.post("/ipn/", params) + self.assertEqual(response.status_code, 200) + ipn_obj = PayPalIPN.objects.all()[0] + self.assertEqual(ipn_obj.flag, True) + self.assertEqual(ipn_obj.flag_info, flag_info) + + def test_incorrect_receiver_email(self): + update = {"receiver_email": "incorrect_email@someotherbusiness.com"} + flag_info = "Invalid receiver_email. (incorrect_email@someotherbusiness.com)" + self.assertFlagged(update, flag_info) + + 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() + params.update(update) + response = self.client.post("/ipn/", params) + self.assertEqual(response.status_code, 200) + ipn_obj = PayPalIPN.objects.all()[0] + self.assertEqual(ipn_obj.flag, False) + + + 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) + 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)") + + def test_recurring_payment_skipped_ipn(self): + update = { + "recurring_payment_id": "BN5JZ2V7MLEV4", + "txn_type": "recurring_payment_skipped", + "txn_id": "" + } + params = IPN_POST_PARAMS.copy() + params.update(update) + + self.assertGotSignal(recurring_skipped, False, params) + + def test_recurring_payment_failed_ipn(self): + update = { + "recurring_payment_id": "BN5JZ2V7MLEV4", + "txn_type": "recurring_payment_failed", + "txn_id": "" + } + params = IPN_POST_PARAMS.copy() + params.update(update) + + self.assertGotSignal(recurring_failed, False, params) + + def test_recurring_payment_create_ipn(self): + update = { + "recurring_payment_id": "BN5JZ2V7MLEV4", + "txn_type": "recurring_payment_profile_created", + "txn_id": "" + } + params = IPN_POST_PARAMS.copy() + params.update(update) + + self.assertGotSignal(recurring_create, False, params) + + def test_recurring_payment_cancel_ipn(self): + update = { + "recurring_payment_id": "BN5JZ2V7MLEV4", + "txn_type": "recurring_payment_profile_cancel", + "txn_id": "" + } + 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 + will never be sent because the paypal ipn + contains a txn_id, if this test failes you + might break some compatibility + """ + update = { + "recurring_payment_id": "BN5JZ2V7MLEV4", + "txn_type": "recurring_payment", + } + 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.assertFalse(self.got_signal) diff --git a/paypal/standard/ipn/tests/test_urls.py b/paypal/standard/ipn/tests/test_urls.py index 55d7f07..329f860 100644 --- a/paypal/standard/ipn/tests/test_urls.py +++ b/paypal/standard/ipn/tests/test_urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * urlpatterns = patterns('paypal.standard.ipn.views', - (r'^ipn/$', 'ipn'), + (r'^ipn/$', 'ipn'), ) diff --git a/paypal/standard/ipn/urls.py b/paypal/standard/ipn/urls.py index 9de742b..aa8f0e5 100644 --- a/paypal/standard/ipn/urls.py +++ b/paypal/standard/ipn/urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * -urlpatterns = patterns('paypal.standard.ipn.views', - url(r'^$', 'ipn', name="paypal-ipn"), +urlpatterns = patterns('paypal.standard.ipn.views', + url(r'^$', 'ipn', name="paypal-ipn"), ) \ No newline at end of file diff --git a/paypal/standard/ipn/views.py b/paypal/standard/ipn/views.py index fb99967..bccf65a 100644 --- a/paypal/standard/ipn/views.py +++ b/paypal/standard/ipn/views.py @@ -10,52 +10,52 @@ @require_POST @csrf_exempt 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 - """ - #TODO: Clean up code so that we don't need to set None here and have a lot - # 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', - 'subscr_date', 'subscr_effective') - for date_field in date_fields: - if data.get(date_field) == 'N/A': - del data[date_field] + """ + 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 + """ + #TODO: Clean up code so that we don't need to set None here and have a lot + # 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', + 'subscr_date', 'subscr_effective') + for date_field in date_fields: + if data.get(date_field) == 'N/A': + del data[date_field] - form = PayPalIPNForm(data) - if form.is_valid(): - try: - #When commit = False, object is returned without saving to DB. - ipn_obj = form.save(commit = False) - except Exception, e: - flag = "Exception while processing. (%s)" % e - else: - flag = "Invalid form. (%s)" % form.errors + form = PayPalIPNForm(data) + if form.is_valid(): + try: + #When commit = False, object is returned without saving to DB. + 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) + if ipn_obj is None: + ipn_obj = PayPalIPN() + + #Set query params and sender's IP address + ipn_obj.initialize(request) - if flag is not None: - #We save errors in the flag field - ipn_obj.set_flag(flag) - else: - # Secrets should only be used over SSL. - if request.is_secure() and 'secret' in request.GET: - ipn_obj.verify_secret(form, request.GET['secret']) - else: - ipn_obj.verify(item_check_callable) + if flag is not None: + #We save errors in the flag field + ipn_obj.set_flag(flag) + else: + # Secrets should only be used over SSL. + if request.is_secure() and 'secret' in request.GET: + ipn_obj.verify_secret(form, request.GET['secret']) + else: + ipn_obj.verify(item_check_callable) - ipn_obj.save() - return HttpResponse("OKAY") + ipn_obj.save() + return HttpResponse("OKAY") diff --git a/paypal/standard/models.py b/paypal/standard/models.py index c07e2fd..ab3ebdc 100644 --- a/paypal/standard/models.py +++ b/paypal/standard/models.py @@ -20,299 +20,299 @@ ST_PP_UNCLEARED = 'Uncleared' try: - from idmapper.models import SharedMemoryModel as Model + from idmapper.models import SharedMemoryModel as Model except ImportError: - Model = models.Model + 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. - # FLAG_CODE_CHOICES = ( - # PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split() - PAYMENT_STATUS_CHOICES = (ST_PP_ACTIVE, ST_PP_CANCELLED, ST_PP_CLEARED, ST_PP_COMPLETED, ST_PP_DENIED, ST_PP_PAID, ST_PP_PENDING, ST_PP_PROCESSED, ST_PP_REFUSED, ST_PP_REVERSED, ST_PP_REWARDED, ST_PP_UNCLAIMED, ST_PP_UNCLEARED) - # AUTH_STATUS_CHOICES = "Completed Pending Voided".split() - # ADDRESS_STATUS_CHOICES = "confirmed unconfirmed".split() - # PAYER_STATUS_CHOICES = "verified / unverified".split() - # PAYMENT_TYPE_CHOICES = "echeck / instant.split() - # 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) - 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) - receiver_email = models.EmailField(max_length=127, blank=True) - receiver_id = models.CharField(max_length=127, blank=True) # 258DLEHY2BDK6 - residence_country = models.CharField(max_length=2, blank=True) - 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) - - # Buyer Information Variables - address_country = models.CharField(max_length=64, blank=True) - address_city = models.CharField(max_length=40, blank=True) - address_country_code = models.CharField(max_length=64, blank=True, help_text="ISO 3166") - address_name = models.CharField(max_length=128, blank=True) - address_state = models.CharField(max_length=40, blank=True) - address_status = models.CharField(max_length=11, blank=True) - address_street = models.CharField(max_length=200, blank=True) - address_zip = models.CharField(max_length=20, blank=True) - contact_phone = models.CharField(max_length=20, blank=True) - first_name = models.CharField(max_length=64, blank=True) - last_name = models.CharField(max_length=64, blank=True) - 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) - 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) - item_number = models.CharField(max_length=127, blank=True) - mc_currency = models.CharField(max_length=32, default="USD", blank=True) - mc_fee = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_handling = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - memo = models.CharField(max_length=255, blank=True) - num_cart_items = models.IntegerField(blank=True, default=0, null=True) - option_name1 = models.CharField(max_length=64, blank=True) - option_name2 = models.CharField(max_length=64, blank=True) - payer_status = models.CharField(max_length=10, blank=True) - payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") - payment_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - 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) - 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) - settle_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - settle_currency = models.CharField(max_length=32, blank=True) - shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - 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 - 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) - 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 - 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) - amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - mc_amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - password = models.CharField(max_length=24, blank=True) - period1 = models.CharField(max_length=32, blank=True) - period2 = models.CharField(max_length=32, blank=True) - period3 = models.CharField(max_length=32, blank=True) - reattempt = models.CharField(max_length=1, blank=True) - recur_times = models.IntegerField(blank=True, default=0, null=True) - recurring = models.CharField(max_length=1, blank=True) - retry_at = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") - subscr_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") - 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 - 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) + """Meta class for common variables shared by IPN and PDT: http://tinyurl.com/cuq6sj""" + # @@@ Might want to add all these one distant day. + # FLAG_CODE_CHOICES = ( + # PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split() + PAYMENT_STATUS_CHOICES = (ST_PP_ACTIVE, ST_PP_CANCELLED, ST_PP_CLEARED, ST_PP_COMPLETED, ST_PP_DENIED, ST_PP_PAID, ST_PP_PENDING, ST_PP_PROCESSED, ST_PP_REFUSED, ST_PP_REVERSED, ST_PP_REWARDED, ST_PP_UNCLAIMED, ST_PP_UNCLEARED) + # AUTH_STATUS_CHOICES = "Completed Pending Voided".split() + # ADDRESS_STATUS_CHOICES = "confirmed unconfirmed".split() + # PAYER_STATUS_CHOICES = "verified / unverified".split() + # PAYMENT_TYPE_CHOICES = "echeck / instant.split() + # 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) + 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) + receiver_email = models.EmailField(max_length=127, blank=True) + receiver_id = models.CharField(max_length=127, blank=True) # 258DLEHY2BDK6 + residence_country = models.CharField(max_length=2, blank=True) + 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) + + # Buyer Information Variables + address_country = models.CharField(max_length=64, blank=True) + address_city = models.CharField(max_length=40, blank=True) + address_country_code = models.CharField(max_length=64, blank=True, help_text="ISO 3166") + address_name = models.CharField(max_length=128, blank=True) + address_state = models.CharField(max_length=40, blank=True) + address_status = models.CharField(max_length=11, blank=True) + address_street = models.CharField(max_length=200, blank=True) + address_zip = models.CharField(max_length=20, blank=True) + contact_phone = models.CharField(max_length=20, blank=True) + first_name = models.CharField(max_length=64, blank=True) + last_name = models.CharField(max_length=64, blank=True) + 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) + 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) + item_number = models.CharField(max_length=127, blank=True) + mc_currency = models.CharField(max_length=32, default="USD", blank=True) + mc_fee = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_handling = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + memo = models.CharField(max_length=255, blank=True) + num_cart_items = models.IntegerField(blank=True, default=0, null=True) + option_name1 = models.CharField(max_length=64, blank=True) + option_name2 = models.CharField(max_length=64, blank=True) + payer_status = models.CharField(max_length=10, blank=True) + payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") + payment_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + 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) + 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) + settle_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + settle_currency = models.CharField(max_length=32, blank=True) + shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + 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 + 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) + 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 + 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) + amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + mc_amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + password = models.CharField(max_length=24, blank=True) + period1 = models.CharField(max_length=32, blank=True) + period2 = models.CharField(max_length=32, blank=True) + period3 = models.CharField(max_length=32, blank=True) + reattempt = models.CharField(max_length=1, blank=True) + recur_times = models.IntegerField(blank=True, default=0, null=True) + recurring = models.CharField(max_length=1, blank=True) + retry_at = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") + subscr_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") + 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 + 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) - # 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) - # mc_gross_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - # mc_handlingx = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - # payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") - # payment_status = models.CharField(max_length=9, blank=True) - # reason_code = models.CharField(max_length=15, blank=True) - # receiver_email_x = models.EmailField(max_length=127, blank=True) - # status_x = models.CharField(max_length=9, blank=True) - # unique_id_x = models.CharField(max_length=13, 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) + # 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) + # mc_gross_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + # mc_handlingx = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + # payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") + # payment_status = models.CharField(max_length=9, blank=True) + # reason_code = models.CharField(max_length=15, blank=True) + # receiver_email_x = models.EmailField(max_length=127, blank=True) + # 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. - ipaddress = models.IPAddressField(blank=True) - flag = models.BooleanField(default=False, blank=True) - flag_code = models.CharField(max_length=16, blank=True) - flag_info = models.TextField(blank=True) - query = models.TextField(blank=True) # What we sent to PayPal. - response = models.TextField(blank=True) # What we got back. - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) + # 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) + flag_info = models.TextField(blank=True) + query = models.TextField(blank=True) # What we sent to PayPal. + response = models.TextField(blank=True) # What we got back. + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) - # Where did it come from? - from_view = models.CharField(max_length=6, null=True, blank=True) + # Where did it come from? + from_view = models.CharField(max_length=6, null=True, blank=True) - class Meta: - abstract = True + class Meta: + abstract = True - def __unicode__(self): - if self.is_transaction(): - 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 __unicode__(self): + if self.is_transaction(): + 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" + 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" - def is_recurring_create(self): - return self.txn_type == "recurring_payment_profile_created" + def is_recurring_create(self): + return self.txn_type == "recurring_payment_profile_created" - 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 - that `mc_gross`, `mc_currency` `item_name` and `item_number` are all correct. + 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 + that `mc_gross`, `mc_currency` `item_name` and `item_number` are all correct. - """ - self.response = self._postback() - self._verify_postback() - if not self.flag: - if self.is_transaction(): - if self.payment_status not in self.PAYMENT_STATUS_CHOICES: - self.set_flag("Invalid payment_status. (%s)" % self.payment_status) - if duplicate_txn_id(self): - self.set_flag("Duplicate txn_id. (%s)" % self.txn_id) - if self.receiver_email != RECEIVER_EMAIL: - self.set_flag("Invalid receiver_email. (%s)" % self.receiver_email) - if callable(item_check_callable): - flag, reason = item_check_callable(self) - if flag: - self.set_flag(reason) - else: - # @@@ Run a different series of checks on recurring payments. - pass - - self.save() - self.send_signals() + """ + self.response = self._postback() + self._verify_postback() + if not self.flag: + if self.is_transaction(): + if self.payment_status not in self.PAYMENT_STATUS_CHOICES: + self.set_flag("Invalid payment_status. (%s)" % self.payment_status) + if duplicate_txn_id(self): + self.set_flag("Duplicate txn_id. (%s)" % self.txn_id) + if self.receiver_email != RECEIVER_EMAIL: + self.set_flag("Invalid receiver_email. (%s)" % self.receiver_email) + if callable(item_check_callable): + flag, reason = item_check_callable(self) + if flag: + self.set_flag(reason) + else: + # @@@ Run a different series of checks on recurring payments. + pass + + self.save() + self.send_signals() - def verify_secret(self, form_instance, secret): - """Verifies an IPN payment over SSL using EWP.""" - if not check_secret(form_instance, secret): - self.set_flag("Invalid secret. (%s)") % secret - self.save() - self.send_signals() + def verify_secret(self, form_instance, secret): + """Verifies an IPN payment over SSL using EWP.""" + if not check_secret(form_instance, secret): + self.set_flag("Invalid secret. (%s)") % secret + self.save() + self.send_signals() - def get_endpoint(self): - """Set Sandbox endpoint if the test variable is present.""" - if self.test_ipn: - return SANDBOX_POSTBACK_ENDPOINT - else: - return POSTBACK_ENDPOINT + def get_endpoint(self): + """Set Sandbox endpoint if the test variable is present.""" + if self.test_ipn: + return SANDBOX_POSTBACK_ENDPOINT + else: + return POSTBACK_ENDPOINT - def send_signals(self): - """Shout for the world to hear whether a txn was successful.""" + def send_signals(self): + """Shout for the world to hear whether a txn was successful.""" - # Don't do anything if we're not notifying! - if self.from_view != 'notify': - return + # Don't do anything if we're not notifying! + if self.from_view != 'notify': + return - # Transaction signals: - if self.is_transaction(): - if self.flag: - payment_was_flagged.send(sender=self) - else: - payment_was_successful.send(sender=self) - # Subscription signals: - else: - if self.is_subscription_cancellation(): - subscription_cancel.send(sender=self) - elif self.is_subscription_signup(): - subscription_signup.send(sender=self) - elif self.is_subscription_end_of_term(): - subscription_eot.send(sender=self) - elif self.is_subscription_modified(): - subscription_modify.send(sender=self) + # Transaction signals: + if self.is_transaction(): + if self.flag: + payment_was_flagged.send(sender=self) + else: + payment_was_successful.send(sender=self) + # Subscription signals: + else: + if self.is_subscription_cancellation(): + subscription_cancel.send(sender=self) + elif self.is_subscription_signup(): + subscription_signup.send(sender=self) + elif self.is_subscription_end_of_term(): + subscription_eot.send(sender=self) + elif self.is_subscription_modified(): + subscription_modify.send(sender=self) - def initialize(self, request): - """Store the data we'll need to make the postback from the request object.""" - self.query = getattr(request, request.method).urlencode() - self.ipaddress = request.META.get('REMOTE_ADDR', '') + def initialize(self, request): + """Store the data we'll need to make the postback from the request object.""" + self.query = getattr(request, request.method).urlencode() + self.ipaddress = request.META.get('REMOTE_ADDR', '') - 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 + 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 diff --git a/paypal/standard/pdt/admin.py b/paypal/standard/pdt/admin.py index d7f16cb..1890654 100644 --- a/paypal/standard/pdt/admin.py +++ b/paypal/standard/pdt/admin.py @@ -7,42 +7,42 @@ # ToDo: How similiar is this to PayPalIPNAdmin? Could we just inherit off one common admin model? class PayPalPDTAdmin(admin.ModelAdmin): - date_hierarchy = 'payment_date' - fieldsets = ( - (None, { - "fields": L("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 auth_id") - }), - ("Address", { - "description": "The address of the Buyer.", - 'classes': ('collapse',), - "fields": L("address_city address_country address_country_code address_name address_state address_status address_street address_zip") - }), - ("Buyer", { - "description": "The information about the Buyer.", - 'classes': ('collapse',), - "fields": L("first_name last_name payer_business_name payer_email payer_id payer_status contact_phone residence_country") - }), - ("Seller", { - "description": "The information about the Seller.", - 'classes': ('collapse',), - "fields": L("business item_name item_number quantity receiver_email receiver_id custom invoice memo") - }), - ("Subscriber", { - "description": "The information about the Subscription.", - 'classes': ('collapse',), - "fields": L("subscr_id subscr_date subscr_effective") - }), - ("Recurring", { - "description": "Information about recurring Payments.", - "classes": ("collapse",), - "fields": L("profile_status initial_payment_amount amount_per_cycle outstanding_balance period_type product_name product_type recurring_payment_id receipt_id next_payment_date") - }), - ("Admin", { - "description": "Additional Info.", - "classes": ('collapse',), - "fields": L("test_ipn ipaddress query flag_code flag_info") - }), - ) - list_display = L("__unicode__ flag invoice custom payment_status created_at") - search_fields = L("txn_id recurring_payment_id") + date_hierarchy = 'payment_date' + fieldsets = ( + (None, { + "fields": L("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 auth_id") + }), + ("Address", { + "description": "The address of the Buyer.", + 'classes': ('collapse',), + "fields": L("address_city address_country address_country_code address_name address_state address_status address_street address_zip") + }), + ("Buyer", { + "description": "The information about the Buyer.", + 'classes': ('collapse',), + "fields": L("first_name last_name payer_business_name payer_email payer_id payer_status contact_phone residence_country") + }), + ("Seller", { + "description": "The information about the Seller.", + 'classes': ('collapse',), + "fields": L("business item_name item_number quantity receiver_email receiver_id custom invoice memo") + }), + ("Subscriber", { + "description": "The information about the Subscription.", + 'classes': ('collapse',), + "fields": L("subscr_id subscr_date subscr_effective") + }), + ("Recurring", { + "description": "Information about recurring Payments.", + "classes": ("collapse",), + "fields": L("profile_status initial_payment_amount amount_per_cycle outstanding_balance period_type product_name product_type recurring_payment_id receipt_id next_payment_date") + }), + ("Admin", { + "description": "Additional Info.", + "classes": ('collapse',), + "fields": L("test_ipn ipaddress query flag_code flag_info") + }), + ) + list_display = L("__unicode__ flag invoice custom payment_status created_at") + search_fields = L("txn_id recurring_payment_id") admin.site.register(PayPalPDT, PayPalPDTAdmin) \ No newline at end of file diff --git a/paypal/standard/pdt/forms.py b/paypal/standard/pdt/forms.py index db6c4da..e86327d 100644 --- a/paypal/standard/pdt/forms.py +++ b/paypal/standard/pdt/forms.py @@ -5,5 +5,5 @@ class PayPalPDTForm(PayPalStandardBaseForm): - class Meta: - model = PayPalPDT \ No newline at end of file + class Meta: + model = PayPalPDT \ No newline at end of file diff --git a/paypal/standard/pdt/migrations/0001_first_migration.py b/paypal/standard/pdt/migrations/0001_first_migration.py index ab7b921..3928968 100644 --- a/paypal/standard/pdt/migrations/0001_first_migration.py +++ b/paypal/standard/pdt/migrations/0001_first_migration.py @@ -5,259 +5,259 @@ from paypal.standard.pdt.models import * class Migration: - - def forwards(self, orm): - - # Adding model 'PayPalPDT' - db.create_table('paypal_pdt', ( - ('id', models.AutoField(primary_key=True)), - ('business', models.CharField(max_length=127, blank=True)), - ('charset', models.CharField(max_length=32, blank=True)), - ('custom', models.CharField(max_length=255, blank=True)), - ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), - ('receiver_email', models.EmailField(max_length=127, blank=True)), - ('receiver_id', models.CharField(max_length=127, blank=True)), - ('residence_country', models.CharField(max_length=2, blank=True)), - ('test_ipn', models.BooleanField(default=False, blank=True)), - ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), - ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), - ('verify_sign', models.CharField(max_length=255, blank=True)), - ('address_country', models.CharField(max_length=64, blank=True)), - ('address_city', models.CharField(max_length=40, blank=True)), - ('address_country_code', models.CharField(max_length=64, blank=True)), - ('address_name', models.CharField(max_length=128, blank=True)), - ('address_state', models.CharField(max_length=40, blank=True)), - ('address_status', models.CharField(max_length=11, blank=True)), - ('address_street', models.CharField(max_length=200, blank=True)), - ('address_zip', models.CharField(max_length=20, blank=True)), - ('contact_phone', models.CharField(max_length=20, blank=True)), - ('first_name', models.CharField(max_length=64, blank=True)), - ('last_name', models.CharField(max_length=64, blank=True)), - ('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)), - ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=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)), - ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), - ('invoice', models.CharField(max_length=127, blank=True)), - ('item_name', models.CharField(max_length=127, blank=True)), - ('item_number', models.CharField(max_length=127, blank=True)), - ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), - ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('memo', models.CharField(max_length=255, blank=True)), - ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), - ('option_name1', models.CharField(max_length=64, blank=True)), - ('option_name2', models.CharField(max_length=64, blank=True)), - ('payer_status', models.CharField(max_length=10, blank=True)), - ('payment_date', models.DateTimeField(null=True, blank=True)), - ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('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)), - ('quantity', models.IntegerField(default=1, null=True, blank=True)), - ('reason_code', models.CharField(max_length=15, blank=True)), - ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('settle_currency', models.CharField(max_length=32, blank=True)), - ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('shipping_method', models.CharField(max_length=255, blank=True)), - ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('transaction_entity', models.CharField(max_length=7, blank=True)), - ('auction_buyer_id', models.CharField(max_length=64, blank=True)), - ('auction_closing_date', models.DateTimeField(null=True, blank=True)), - ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), - ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('next_payment_date', models.DateTimeField(null=True, blank=True)), - ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('payment_cycle', models.CharField(max_length=32, blank=True)), - ('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)), - ('profile_status', models.CharField(max_length=32, blank=True)), - ('recurring_payment_id', models.CharField(max_length=128, blank=True)), - ('rp_invoice_id', models.CharField(max_length=127, blank=True)), - ('time_created', models.DateTimeField(null=True, blank=True)), - ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('password', models.CharField(max_length=24, blank=True)), - ('period1', models.CharField(max_length=32, blank=True)), - ('period2', models.CharField(max_length=32, blank=True)), - ('period3', models.CharField(max_length=32, blank=True)), - ('reattempt', models.CharField(max_length=1, blank=True)), - ('recur_times', models.IntegerField(default=0, null=True, blank=True)), - ('recurring', models.CharField(max_length=1, blank=True)), - ('retry_at', models.DateTimeField(null=True, blank=True)), - ('subscr_date', models.DateTimeField(null=True, blank=True)), - ('subscr_effective', models.DateTimeField(null=True, blank=True)), - ('subscr_id', models.CharField(max_length=19, blank=True)), - ('username', models.CharField(max_length=64, blank=True)), - ('case_creation_date', models.DateTimeField(null=True, blank=True)), - ('case_id', models.CharField(max_length=14, blank=True)), - ('case_type', models.CharField(max_length=24, blank=True)), - ('receipt_id', models.CharField(max_length=64, blank=True)), - ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), - ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('transaction_subject', models.CharField(max_length=255, blank=True)), - ('ipaddress', models.IPAddressField(blank=True)), - ('flag', models.BooleanField(default=False, blank=True)), - ('flag_code', models.CharField(max_length=16, blank=True)), - ('flag_info', models.TextField(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)), - ('from_view', models.CharField(max_length=6, null=True, blank=True)), - ('amt', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), - ('cm', models.CharField(max_length=255, blank=True)), - ('sig', models.CharField(max_length=255, blank=True)), - ('tx', models.CharField(max_length=255, blank=True)), - ('st', models.CharField(max_length=32, blank=True)), - )) - db.send_create_signal('pdt', ['PayPalPDT']) - - - - def backwards(self, orm): - - # Deleting model 'PayPalPDT' - db.delete_table('paypal_pdt') - - - - models = { - 'pdt.paypalpdt': { - 'Meta': {'db_table': '"paypal_pdt"'}, - 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), - 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), - 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), - 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), - 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), - 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), - 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'amt': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': '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'}), - 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), - 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), - 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'cm': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), - 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), - 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), - 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), - 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'flag_info': ('models.TextField', [], {'blank': 'True'}), - 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), - 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'id': ('models.AutoField', [], {'primary_key': 'True'}), - 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), - 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), - 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), - 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), - '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'}), - 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), - 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - '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'}), - 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - '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'}), - 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), - 'query': ('models.TextField', [], {'blank': 'True'}), - 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), - 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), - 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), - 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), - 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), - 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), - 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), - 'response': ('models.TextField', [], {'blank': 'True'}), - 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), - 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'sig': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'st': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), - 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), - 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), - 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), - 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'tx': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), - 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), - 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), - 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), - 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) - } - } - - complete_apps = ['pdt'] + + def forwards(self, orm): + + # Adding model 'PayPalPDT' + db.create_table('paypal_pdt', ( + ('id', models.AutoField(primary_key=True)), + ('business', models.CharField(max_length=127, blank=True)), + ('charset', models.CharField(max_length=32, blank=True)), + ('custom', models.CharField(max_length=255, blank=True)), + ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), + ('receiver_email', models.EmailField(max_length=127, blank=True)), + ('receiver_id', models.CharField(max_length=127, blank=True)), + ('residence_country', models.CharField(max_length=2, blank=True)), + ('test_ipn', models.BooleanField(default=False, blank=True)), + ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), + ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), + ('verify_sign', models.CharField(max_length=255, blank=True)), + ('address_country', models.CharField(max_length=64, blank=True)), + ('address_city', models.CharField(max_length=40, blank=True)), + ('address_country_code', models.CharField(max_length=64, blank=True)), + ('address_name', models.CharField(max_length=128, blank=True)), + ('address_state', models.CharField(max_length=40, blank=True)), + ('address_status', models.CharField(max_length=11, blank=True)), + ('address_street', models.CharField(max_length=200, blank=True)), + ('address_zip', models.CharField(max_length=20, blank=True)), + ('contact_phone', models.CharField(max_length=20, blank=True)), + ('first_name', models.CharField(max_length=64, blank=True)), + ('last_name', models.CharField(max_length=64, blank=True)), + ('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)), + ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=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)), + ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), + ('invoice', models.CharField(max_length=127, blank=True)), + ('item_name', models.CharField(max_length=127, blank=True)), + ('item_number', models.CharField(max_length=127, blank=True)), + ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), + ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('memo', models.CharField(max_length=255, blank=True)), + ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), + ('option_name1', models.CharField(max_length=64, blank=True)), + ('option_name2', models.CharField(max_length=64, blank=True)), + ('payer_status', models.CharField(max_length=10, blank=True)), + ('payment_date', models.DateTimeField(null=True, blank=True)), + ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('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)), + ('quantity', models.IntegerField(default=1, null=True, blank=True)), + ('reason_code', models.CharField(max_length=15, blank=True)), + ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('settle_currency', models.CharField(max_length=32, blank=True)), + ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('shipping_method', models.CharField(max_length=255, blank=True)), + ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('transaction_entity', models.CharField(max_length=7, blank=True)), + ('auction_buyer_id', models.CharField(max_length=64, blank=True)), + ('auction_closing_date', models.DateTimeField(null=True, blank=True)), + ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), + ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('next_payment_date', models.DateTimeField(null=True, blank=True)), + ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('payment_cycle', models.CharField(max_length=32, blank=True)), + ('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)), + ('profile_status', models.CharField(max_length=32, blank=True)), + ('recurring_payment_id', models.CharField(max_length=128, blank=True)), + ('rp_invoice_id', models.CharField(max_length=127, blank=True)), + ('time_created', models.DateTimeField(null=True, blank=True)), + ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('password', models.CharField(max_length=24, blank=True)), + ('period1', models.CharField(max_length=32, blank=True)), + ('period2', models.CharField(max_length=32, blank=True)), + ('period3', models.CharField(max_length=32, blank=True)), + ('reattempt', models.CharField(max_length=1, blank=True)), + ('recur_times', models.IntegerField(default=0, null=True, blank=True)), + ('recurring', models.CharField(max_length=1, blank=True)), + ('retry_at', models.DateTimeField(null=True, blank=True)), + ('subscr_date', models.DateTimeField(null=True, blank=True)), + ('subscr_effective', models.DateTimeField(null=True, blank=True)), + ('subscr_id', models.CharField(max_length=19, blank=True)), + ('username', models.CharField(max_length=64, blank=True)), + ('case_creation_date', models.DateTimeField(null=True, blank=True)), + ('case_id', models.CharField(max_length=14, blank=True)), + ('case_type', models.CharField(max_length=24, blank=True)), + ('receipt_id', models.CharField(max_length=64, blank=True)), + ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), + ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('transaction_subject', models.CharField(max_length=255, blank=True)), + ('ipaddress', models.IPAddressField(blank=True)), + ('flag', models.BooleanField(default=False, blank=True)), + ('flag_code', models.CharField(max_length=16, blank=True)), + ('flag_info', models.TextField(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)), + ('from_view', models.CharField(max_length=6, null=True, blank=True)), + ('amt', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), + ('cm', models.CharField(max_length=255, blank=True)), + ('sig', models.CharField(max_length=255, blank=True)), + ('tx', models.CharField(max_length=255, blank=True)), + ('st', models.CharField(max_length=32, blank=True)), + )) + db.send_create_signal('pdt', ['PayPalPDT']) + + + + def backwards(self, orm): + + # Deleting model 'PayPalPDT' + db.delete_table('paypal_pdt') + + + + models = { + 'pdt.paypalpdt': { + 'Meta': {'db_table': '"paypal_pdt"'}, + 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), + 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'amt': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': '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'}), + 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), + 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), + 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'cm': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), + 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), + 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), + 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), + 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'flag_info': ('models.TextField', [], {'blank': 'True'}), + 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), + 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'id': ('models.AutoField', [], {'primary_key': 'True'}), + 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), + 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), + 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), + 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), + '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'}), + 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), + 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + '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'}), + 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + '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'}), + 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), + 'query': ('models.TextField', [], {'blank': 'True'}), + 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), + 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), + 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), + 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), + 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), + 'response': ('models.TextField', [], {'blank': 'True'}), + 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), + 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'sig': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'st': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), + 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), + 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), + 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'tx': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), + 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), + 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), + 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) + } + } + + complete_apps = ['pdt'] diff --git a/paypal/standard/pdt/models.py b/paypal/standard/pdt/models.py index 72e7d7d..d38011c 100644 --- a/paypal/standard/pdt/models.py +++ b/paypal/standard/pdt/models.py @@ -14,77 +14,77 @@ # if paypal.standard.pdt is in installed apps # ... then check for this setting in conf.py class PayPalSettingsError(Exception): - """Raised when settings are incorrect.""" + """Raised when settings are incorrect.""" try: - IDENTITY_TOKEN = settings.PAYPAL_IDENTITY_TOKEN + IDENTITY_TOKEN = settings.PAYPAL_IDENTITY_TOKEN except: - raise PayPalSettingsError("You must set PAYPAL_IDENTITY_TOKEN in settings.py. Get this token by enabling PDT in your PayPal account.") + raise PayPalSettingsError("You must set PAYPAL_IDENTITY_TOKEN in settings.py. Get this token by enabling PDT in your PayPal account.") class PayPalPDT(PayPalStandardBase): - format = u"" + format = u"" - amt = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) - cm = models.CharField(max_length=255, blank=True) - sig = models.CharField(max_length=255, blank=True) - tx = models.CharField(max_length=255, blank=True) - st = models.CharField(max_length=32, blank=True) + amt = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) + cm = models.CharField(max_length=255, blank=True) + sig = models.CharField(max_length=255, blank=True) + tx = models.CharField(max_length=255, blank=True) + st = models.CharField(max_length=32, blank=True) - class Meta: - db_table = "paypal_pdt" - verbose_name = "PayPal PDT" + class Meta: + db_table = "paypal_pdt" + verbose_name = "PayPal PDT" - 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 - result = False - response_list = self.response.split('\n') - response_dict = {} - for i, line in enumerate(response_list): - unquoted_line = unquote_plus(line).strip() - if i == 0: - self.st = unquoted_line - if self.st == "SUCCESS": - result = True - else: - if self.st != "SUCCESS": - self.set_flag(line) - break - try: - if not unquoted_line.startswith(' -'): - k, v = unquoted_line.split('=') - response_dict[k.strip()] = v.strip() - except ValueError, e: - pass + 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 + result = False + response_list = self.response.split('\n') + response_dict = {} + for i, line in enumerate(response_list): + unquoted_line = unquote_plus(line).strip() + if i == 0: + self.st = unquoted_line + if self.st == "SUCCESS": + result = True + else: + if self.st != "SUCCESS": + self.set_flag(line) + break + try: + if not unquoted_line.startswith(' -'): + k, v = unquoted_line.split('=') + response_dict[k.strip()] = v.strip() + except ValueError, e: + pass - qd = QueryDict('', mutable=True) - qd.update(response_dict) - 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) \ No newline at end of file + qd = QueryDict('', mutable=True) + qd.update(response_dict) + 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) \ No newline at end of file diff --git a/paypal/standard/pdt/tests/test_pdt.py b/paypal/standard/pdt/tests/test_pdt.py index e49e12b..7bf0ea1 100644 --- a/paypal/standard/pdt/tests/test_pdt.py +++ b/paypal/standard/pdt/tests/test_pdt.py @@ -12,108 +12,108 @@ 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', - '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'): - self.context_dict['txn_id'] = get_params.get('tx') - if get_params.has_key('amt'): - self.context_dict['mc_gross'] = get_params.get('amt') - if get_params.has_key('cm'): - 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 + + def __init__(self, update_context_dict={}): + 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'): + self.context_dict['txn_id'] = get_params.get('tx') + if get_params.has_key('amt'): + self.context_dict['mc_gross'] = get_params.get('amt') + if get_params.has_key('cm'): + 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'),] + urls = "paypal.standard.pdt.tests.test_urls" + 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"} - - # monkey patch the PayPalPDT._postback function - self.dpppdt = DummyPayPalPDT() - self.dpppdt.update_with_get_params(self.get_params) - PayPalPDT._postback = self.dpppdt._postback + 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"} + + # monkey patch the PayPalPDT._postback function + self.dpppdt = DummyPayPalPDT() + self.dpppdt.update_with_get_params(self.get_params) + PayPalPDT._postback = self.dpppdt._postback - def test_verify_postback(self): - dpppdt = DummyPayPalPDT() - paypal_response = dpppdt._postback() - assert('SUCCESS' in paypal_response) - self.assertEqual(len(PayPalPDT.objects.all()), 0) - pdt_obj = PayPalPDT() - pdt_obj.ipaddress = '127.0.0.1' - pdt_obj.response = paypal_response - pdt_obj._verify_postback() - self.assertEqual(len(PayPalPDT.objects.all()), 0) - self.assertEqual(pdt_obj.txn_id, '1ED550410S3402306') - - def test_pdt(self): - self.assertEqual(len(PayPalPDT.objects.all()), 0) - self.dpppdt.update_with_get_params(self.get_params) - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction complete', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 1) + def test_verify_postback(self): + dpppdt = DummyPayPalPDT() + paypal_response = dpppdt._postback() + assert('SUCCESS' in paypal_response) + self.assertEqual(len(PayPalPDT.objects.all()), 0) + pdt_obj = PayPalPDT() + pdt_obj.ipaddress = '127.0.0.1' + pdt_obj.response = paypal_response + pdt_obj._verify_postback() + self.assertEqual(len(PayPalPDT.objects.all()), 0) + self.assertEqual(pdt_obj.txn_id, '1ED550410S3402306') + + def test_pdt(self): + self.assertEqual(len(PayPalPDT.objects.all()), 0) + self.dpppdt.update_with_get_params(self.get_params) + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction complete', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 1) - def test_pdt_signals(self): - self.successful_pdt_fired = False - self.failed_pdt_fired = False - - def successful_pdt(sender, **kwargs): - self.successful_pdt_fired = True - pdt_successful.connect(successful_pdt) - - def failed_pdt(sender, **kwargs): - self.failed_pdt_fired = True - pdt_failed.connect(failed_pdt) - - self.assertEqual(len(PayPalPDT.objects.all()), 0) - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction complete', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 1) - self.assertTrue(self.successful_pdt_fired) - self.assertFalse(self.failed_pdt_fired) - pdt_obj = PayPalPDT.objects.all()[0] - self.assertEqual(pdt_obj.flag, False) - - def test_double_pdt_get(self): - self.assertEqual(len(PayPalPDT.objects.all()), 0) - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction complete', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 1) - pdt_obj = PayPalPDT.objects.all()[0] - self.assertEqual(pdt_obj.flag, False) - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction complete', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 1) # we don't create a new pdt - pdt_obj = PayPalPDT.objects.all()[0] - self.assertEqual(pdt_obj.flag, False) + def test_pdt_signals(self): + self.successful_pdt_fired = False + self.failed_pdt_fired = False + + def successful_pdt(sender, **kwargs): + self.successful_pdt_fired = True + pdt_successful.connect(successful_pdt) + + def failed_pdt(sender, **kwargs): + self.failed_pdt_fired = True + pdt_failed.connect(failed_pdt) + + self.assertEqual(len(PayPalPDT.objects.all()), 0) + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction complete', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 1) + self.assertTrue(self.successful_pdt_fired) + self.assertFalse(self.failed_pdt_fired) + pdt_obj = PayPalPDT.objects.all()[0] + self.assertEqual(pdt_obj.flag, False) + + def test_double_pdt_get(self): + self.assertEqual(len(PayPalPDT.objects.all()), 0) + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction complete', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 1) + pdt_obj = PayPalPDT.objects.all()[0] + self.assertEqual(pdt_obj.flag, False) + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction complete', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 1) # we don't create a new pdt + pdt_obj = PayPalPDT.objects.all()[0] + self.assertEqual(pdt_obj.flag, False) - def test_no_txn_id_in_pdt(self): - self.dpppdt.context_dict.pop('txn_id') - self.get_params={} - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction Failed', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 0) - - def test_custom_passthrough(self): - self.assertEqual(len(PayPalPDT.objects.all()), 0) - self.dpppdt.update_with_get_params(self.get_params) - paypal_response = self.client.get("/pdt/", self.get_params) - self.assertContains(paypal_response, 'Transaction complete', status_code=200) - self.assertEqual(len(PayPalPDT.objects.all()), 1) - pdt_obj = PayPalPDT.objects.all()[0] - self.assertEqual(pdt_obj.custom, self.get_params['cm'] ) \ No newline at end of file + def test_no_txn_id_in_pdt(self): + self.dpppdt.context_dict.pop('txn_id') + self.get_params={} + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction Failed', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 0) + + def test_custom_passthrough(self): + self.assertEqual(len(PayPalPDT.objects.all()), 0) + self.dpppdt.update_with_get_params(self.get_params) + paypal_response = self.client.get("/pdt/", self.get_params) + self.assertContains(paypal_response, 'Transaction complete', status_code=200) + self.assertEqual(len(PayPalPDT.objects.all()), 1) + pdt_obj = PayPalPDT.objects.all()[0] + self.assertEqual(pdt_obj.custom, self.get_params['cm'] ) \ No newline at end of file diff --git a/paypal/standard/pdt/tests/test_urls.py b/paypal/standard/pdt/tests/test_urls.py index 0eb164c..211f125 100644 --- a/paypal/standard/pdt/tests/test_urls.py +++ b/paypal/standard/pdt/tests/test_urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * urlpatterns = patterns('paypal.standard.pdt.views', - (r'^pdt/$', 'pdt'), + (r'^pdt/$', 'pdt'), ) diff --git a/paypal/standard/pdt/urls.py b/paypal/standard/pdt/urls.py index 9a088ff..e09a907 100644 --- a/paypal/standard/pdt/urls.py +++ b/paypal/standard/pdt/urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * urlpatterns = patterns('paypal.standard.pdt.views', - url(r'^$', 'pdt', name="paypal-pdt"), + url(r'^$', 'pdt', name="paypal-pdt"), ) \ No newline at end of file diff --git a/paypal/standard/pdt/views.py b/paypal/standard/pdt/views.py index 0993411..e45b269 100644 --- a/paypal/standard/pdt/views.py +++ b/paypal/standard/pdt/views.py @@ -9,42 +9,42 @@ @require_GET def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None): - """Payment data transfer implementation: http://tinyurl.com/c9jjmw""" - context = context or {} - pdt_obj = None - txn_id = request.GET.get('tx') - failed = False - if txn_id is not None: - # If an existing transaction with the id tx exists: use it - try: - pdt_obj = PayPalPDT.objects.get(txn_id=txn_id) - 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(): - try: - pdt_obj = form.save(commit=False) - except Exception, e: - error = repr(e) - failed = True - 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 + """Payment data transfer implementation: http://tinyurl.com/c9jjmw""" + context = context or {} + pdt_obj = None + txn_id = request.GET.get('tx') + failed = False + if txn_id is not None: + # If an existing transaction with the id tx exists: use it + try: + pdt_obj = PayPalPDT.objects.get(txn_id=txn_id) + 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(): + try: + pdt_obj = form.save(commit=False) + except Exception, e: + error = repr(e) + failed = True + 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)) \ No newline at end of file + context.update({"failed":failed, "pdt_obj":pdt_obj}) + return render_to_response(template, context, RequestContext(request)) \ No newline at end of file diff --git a/paypal/standard/widgets.py b/paypal/standard/widgets.py index 51aea94..408e470 100644 --- a/paypal/standard/widgets.py +++ b/paypal/standard/widgets.py @@ -7,25 +7,25 @@ class ValueHiddenInput(forms.HiddenInput): - """ - Widget that renders only if it has a value. - Used to remove unused fields from PayPal buttons. - """ - def render(self, name, value, attrs=None): - if value is None: - return u'' - else: - return super(ValueHiddenInput, self).render(name, value, attrs) + """ + Widget that renders only if it has a value. + Used to remove unused fields from PayPal buttons. + """ + def render(self, name, value, attrs=None): + if value is None: + return u'' + else: + return super(ValueHiddenInput, self).render(name, value, attrs) class ReservedValueHiddenInput(ValueHiddenInput): - """ - Overrides the default name attribute of the form. - Used for the PayPal `return` field. - """ - def render(self, name, value, attrs=None): - if value is None: - value = '' - final_attrs = self.build_attrs(attrs, type=self.input_type) - if value != '': - final_attrs['value'] = force_unicode(value) - return mark_safe(u'' % flatatt(final_attrs)) \ No newline at end of file + """ + Overrides the default name attribute of the form. + Used for the PayPal `return` field. + """ + def render(self, name, value, attrs=None): + if value is None: + value = '' + final_attrs = self.build_attrs(attrs, type=self.input_type) + if value != '': + final_attrs['value'] = force_unicode(value) + return mark_safe(u'' % flatatt(final_attrs)) \ No newline at end of file diff --git a/runtests.py b/runtests.py index dfe14e7..290d3de 100644 --- a/runtests.py +++ b/runtests.py @@ -7,34 +7,34 @@ from django.conf import settings if not settings.configured: - settings.configure( - ROOT_URLCONF='', - DATABASE_ENGINE='sqlite3', - PAYPAL_RECEIVER_EMAIL='test@example.com', - PAYPAL_TEST=True, - # Please dont make me create another test account and remove this from here :) - PAYPAL_WPP_USER='dcrame_1278645792_biz_api1.gmail.com', - PAYPAL_WPP_PASSWORD='1278645801', - PAYPAL_WPP_SIGNATURE='A4k1.O6xTyld5TiKeVmCuOgqzLRuAKuTtSG.7BD3R9E8SBa-J0pbUeYp', - INSTALLED_APPS=[ - 'paypal.pro', - 'paypal.standard', - 'paypal.standard.ipn', - # 'paypal.standard.pdt', # we need the PDT token - ] - ) + settings.configure( + ROOT_URLCONF='', + DATABASE_ENGINE='sqlite3', + PAYPAL_RECEIVER_EMAIL='test@example.com', + PAYPAL_TEST=True, + # Please dont make me create another test account and remove this from here :) + PAYPAL_WPP_USER='dcrame_1278645792_biz_api1.gmail.com', + PAYPAL_WPP_PASSWORD='1278645801', + PAYPAL_WPP_SIGNATURE='A4k1.O6xTyld5TiKeVmCuOgqzLRuAKuTtSG.7BD3R9E8SBa-J0pbUeYp', + INSTALLED_APPS=[ + 'paypal.pro', + 'paypal.standard', + 'paypal.standard.ipn', + # 'paypal.standard.pdt', # we need the PDT token + ] + ) from django.test.simple import run_tests def runtests(*test_args): - if not test_args: - test_args = ['pro', 'standard', 'ipn'] - parent = dirname(abspath(__file__)) - sys.path.insert(0, parent) - failures = run_tests(test_args, verbosity=1, interactive=True) - sys.exit(failures) + if not test_args: + test_args = ['pro', 'standard', 'ipn'] + parent = dirname(abspath(__file__)) + sys.path.insert(0, parent) + failures = run_tests(test_args, verbosity=1, interactive=True) + sys.exit(failures) if __name__ == '__main__': - runtests(*sys.argv[1:]) \ No newline at end of file + runtests(*sys.argv[1:]) \ No newline at end of file diff --git a/setup.py b/setup.py index 9988721..c84a599 100644 --- a/setup.py +++ b/setup.py @@ -5,24 +5,24 @@ import paypal setup( - name='django-paypal', - version=".".join(map(str, paypal.__version__)), - author='John Boxall', - author_email='john@handimobility.ca', - maintainer="David Cramer", - maintainer_email="dcramer@gmail.com", - url='http://github.com/johnboxall/django-paypal', - install_requires=[ - 'Django>=1.0' - ], - description = 'A pluggable Django application for integrating PayPal Payments Standard or Payments Pro', - packages=find_packages(), - include_package_data=True, - classifiers=[ - "Framework :: Django", - "Intended Audience :: Developers", - "Intended Audience :: System Administrators", - "Operating System :: OS Independent", - "Topic :: Software Development" - ], + name='django-paypal', + version=".".join(map(str, paypal.__version__)), + author='John Boxall', + author_email='john@handimobility.ca', + maintainer="David Cramer", + maintainer_email="dcramer@gmail.com", + url='http://github.com/johnboxall/django-paypal', + install_requires=[ + 'Django>=1.0' + ], + description = 'A pluggable Django application for integrating PayPal Payments Standard or Payments Pro', + packages=find_packages(), + include_package_data=True, + classifiers=[ + "Framework :: Django", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Operating System :: OS Independent", + "Topic :: Software Development" + ], ) From 92c62f31138e226ea31cfa4f109edca4b22948c5 Mon Sep 17 00:00:00 2001 From: Daniel Sokolowski Date: Mon, 1 Apr 2013 17:15:12 -0400 Subject: [PATCH 4/5] Added 'bn' button source referral/partner attribute support. --- paypal/standard/forms.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/paypal/standard/forms.py b/paypal/standard/forms.py index dcec094..fedd073 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -56,6 +56,11 @@ class PayPalPaymentsForm(forms.Form): SUBSCRIBE = 'subscribe' DONATE = 'donate' + # Referral code - please don't change if forking + # - https://cms.paypal.com/ca/cgi-bin/?cmd=_render-content&content_ID=ua/MerchBonus_full + # - https://www.paypal-marketing.com/emarketing/partner/es/portal/faq.page#7 + bn = forms.CharField(widget=ValueHiddenInput(), initial='DanolsWebEngineering_SP_PPS') + # Where the money goes. business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL) image_url = forms.CharField(widget=ValueHiddenInput()) From 9a484c9e3add33e266dfce3e83d990d22f5e2d52 Mon Sep 17 00:00:00 2001 From: Daniel Sokolowski Date: Mon, 1 Apr 2013 17:42:02 -0400 Subject: [PATCH 5/5] Added initial random UUID4 to 'invoice' PayPal attribute form. --- paypal/standard/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/paypal/standard/forms.py b/paypal/standard/forms.py index fedd073..a45b02f 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -8,6 +8,7 @@ from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, RECEIVER_EMAIL) import urllib +import uuid # 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box. # PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", "%H:%M:%S %b %d, %Y PST",) @@ -98,7 +99,7 @@ class PayPalPaymentsForm(forms.Form): return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name":"return"})) rm = forms.ChoiceField(widget=forms.HiddenInput(), initial=RM_CHOICES[0][0]) custom = forms.CharField(widget=ValueHiddenInput()) - invoice = forms.CharField(widget=ValueHiddenInput()) + invoice = forms.CharField(widget=ValueHiddenInput(), initial = lambda : uuid.uuid4()) # random UUID # Default fields. cmd = forms.ChoiceField(widget=forms.HiddenInput(), initial=CMD_CHOICES[0][0])