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/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 e9992a5..a45b02f 100644 --- a/paypal/standard/forms.py +++ b/paypal/standard/forms.py @@ -5,215 +5,249 @@ from django.utils.safestring import mark_safe from paypal.standard.conf import * from paypal.standard.widgets import ValueHiddenInput, ReservedValueHiddenInput -from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, - RECEIVER_EMAIL) - +from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, + RECEIVER_EMAIL) +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",) # 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") - ) - SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping")) - 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) - - # 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"})) - 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(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' + + # 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()) + + # 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(), initial = lambda : uuid.uuid4()) # random UUID + + # 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(self): - return mark_safe(u"""
- %s - + + + 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, - (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 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_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" + ], )