├── __init__.py ├── pro ├── __init__.py ├── admin.py ├── templates │ └── pro │ │ ├── confirm.html │ │ └── payment.html ├── signals.py ├── forms.py ├── creditcard.py ├── models.py ├── tests.py ├── helpers.py ├── views.py └── fields.py ├── standard ├── __init__.py ├── ipn │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_first_migration.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_urls.py │ │ └── test_ipn.py │ ├── urls.py │ ├── templates │ │ └── ipn │ │ │ ├── paypal.html │ │ │ ├── ipn.html │ │ │ └── ipn_test.html │ ├── forms.py │ ├── signals.py │ ├── views.py │ ├── models.py │ └── admin.py ├── pdt │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_first_migration.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_urls.py │ │ ├── templates │ │ │ └── pdt │ │ │ │ └── pdt.html │ │ └── test_pdt.py │ ├── urls.py │ ├── forms.py │ ├── templates │ │ └── pdt │ │ │ ├── pdt.html │ │ │ └── test_pdt_response.html │ ├── signals.py │ ├── views.py │ ├── admin.py │ └── models.py ├── conf.py ├── widgets.py ├── helpers.py ├── forms.py └── models.py ├── .gitignore └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pro/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/ipn/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/pdt/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/ipn/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/pdt/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /standard/ipn/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from test_ipn import * -------------------------------------------------------------------------------- /standard/pdt/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from test_pdt import * -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .svn 3 | .project 4 | .pydevproject 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **django-paypal is maintained at: https://github.com/spookylukey/django-paypal** 2 | 3 | This fork is no longer maintained. 4 | -------------------------------------------------------------------------------- /standard/ipn/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('paypal.standard.ipn.views', 4 | (r'^ipn/$', 'ipn'), 5 | ) 6 | -------------------------------------------------------------------------------- /standard/pdt/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('paypal.standard.pdt.views', 4 | (r'^pdt/$', 'pdt'), 5 | ) 6 | -------------------------------------------------------------------------------- /standard/pdt/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('paypal.standard.pdt.views', 4 | url(r'^$', 'pdt', name="paypal-pdt"), 5 | ) -------------------------------------------------------------------------------- /standard/ipn/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | urlpatterns = patterns('paypal.standard.ipn.views', 4 | url(r'^$', 'ipn', name="paypal-ipn"), 5 | ) -------------------------------------------------------------------------------- /standard/ipn/templates/ipn/paypal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ form.sandbox }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /standard/pdt/forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from paypal.standard.forms import PayPalStandardBaseForm 4 | from paypal.standard.pdt.models import PayPalPDT 5 | 6 | 7 | class PayPalPDTForm(PayPalStandardBaseForm): 8 | class Meta: 9 | model = PayPalPDT -------------------------------------------------------------------------------- /pro/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from string import split as L 4 | from django.contrib import admin 5 | from paypal.pro.models import PayPalNVP 6 | 7 | 8 | class PayPalNVPAdmin(admin.ModelAdmin): 9 | list_display = L("user method flag flag_code created_at") 10 | admin.site.register(PayPalNVP, PayPalNVPAdmin) 11 | -------------------------------------------------------------------------------- /pro/templates/pro/confirm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | {{ form.as_table }} 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /standard/ipn/templates/ipn/ipn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | {{ form.as_p }} 11 | 12 | 15 | 16 | 17 | 18 |
13 | 14 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /standard/ipn/forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from paypal.standard.forms import PayPalStandardBaseForm 4 | from paypal.standard.ipn.models import PayPalIPN 5 | 6 | 7 | class PayPalIPNForm(PayPalStandardBaseForm): 8 | """ 9 | Form used to receive and record PayPal IPN notifications. 10 | 11 | PayPal IPN test tool: 12 | https://developer.paypal.com/us/cgi-bin/devscr?cmd=_tools-session 13 | """ 14 | class Meta: 15 | model = PayPalIPN 16 | 17 | -------------------------------------------------------------------------------- /pro/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | """ 4 | These signals are different from IPN signals in that they are sent the second 5 | the payment is failed or succeeds and come with the `item` object passed to 6 | PayPalPro rather than an IPN object. 7 | 8 | ### SENDER is the item? is that right??? 9 | 10 | """ 11 | 12 | # Sent when a payment is successfully processed. 13 | payment_was_successful = Signal() #providing_args=["item"]) 14 | 15 | # Sent when a payment is flagged. 16 | payment_was_flagged = Signal() #providing_args=["item"]) 17 | -------------------------------------------------------------------------------- /pro/templates/pro/payment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | {% if errors %}{% endif %} 12 | 13 | {{ form.as_table }} 14 | 15 | 16 |
{{ errors }}
Pay by PayPal
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /standard/pdt/tests/templates/pdt/pdt.html: -------------------------------------------------------------------------------- 1 | {% ifequal pdt_obj.st 'SUCCESS' %} 2 |

Transaction complete

3 |

Thank you for your payment

4 |

Please print this page for your records

5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
Payer:{{ pdt_obj.first_name }} {{ pdt_obj.last_name }}
Payer Email:{{ pdt_obj.payer_email }}
Amount:{{ pdt_obj.mc_currency }} {{ pdt_obj.mc_gross }}
Reference:{{ pdt_obj.txn_id }}
14 |
15 | 16 | {% else %} 17 |

Transaction Failed

18 |

Sorry transaction failed, please try a different form of payment

19 | {% endifequal %} -------------------------------------------------------------------------------- /standard/pdt/templates/pdt/pdt.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 | {% ifequal pdt_obj.st 'SUCCESS' %} 5 |

Transaction complete

6 |

Thank you for your payment

7 |

Please print this page for your records

8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
Payer:{{ pdt_obj.first_name }} {{ pdt_obj.last_name }}
Payer Email:{{ pdt_obj.payer_email }}
Amount:{{ pdt_obj.mc_currency }} {{ pdt_obj.mc_gross }}
Reference:{{ pdt_obj.txn_id }}
17 |
18 | 19 | {% else %} 20 |

Transaction Failed

21 |

Sorry transaction failed, please try a different form of payment

22 | {% endifequal %} 23 | 24 | {% endblock %} 25 | 26 | 27 | -------------------------------------------------------------------------------- /standard/conf.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | class PayPalSettingsError(Exception): 4 | """Raised when settings be bad.""" 5 | 6 | 7 | TEST = getattr(settings, "PAYPAL_TEST", True) 8 | 9 | 10 | RECEIVER_EMAIL = settings.PAYPAL_RECEIVER_EMAIL 11 | 12 | 13 | # API Endpoints. 14 | POSTBACK_ENDPOINT = "https://www.paypal.com/cgi-bin/webscr" 15 | SANDBOX_POSTBACK_ENDPOINT = "https://www.sandbox.paypal.com/cgi-bin/webscr" 16 | 17 | # Images 18 | IMAGE = getattr(settings, "PAYPAL_IMAGE", "http://images.paypal.com/images/x-click-but01.gif") 19 | SUBSCRIPTION_IMAGE = "https://www.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif" 20 | SANDBOX_IMAGE = getattr(settings, "PAYPAL_SANDBOX_IMAGE", "https://www.sandbox.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif") 21 | SUBSCRIPTION_SANDBOX_IMAGE = "https://www.sandbox.paypal.com/en_US/i/btn/btn_subscribeCC_LG.gif" -------------------------------------------------------------------------------- /standard/ipn/signals.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems. 3 | If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers: 4 | http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave 5 | 6 | """ 7 | from django.dispatch import Signal 8 | 9 | # Sent when a payment is successfully processed. 10 | payment_was_successful = Signal() 11 | 12 | # Sent when a payment is flagged. 13 | payment_was_flagged = Signal() 14 | 15 | # Sent when a subscription was cancelled. 16 | subscription_cancel = Signal() 17 | 18 | # Sent when a subscription expires. 19 | subscription_eot = Signal() 20 | 21 | # Sent when a subscription was modified. 22 | subscription_modify = Signal() 23 | 24 | # Sent when a subscription is created. 25 | subscription_signup = Signal() -------------------------------------------------------------------------------- /standard/pdt/signals.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note that sometimes you will get duplicate signals emitted, depending on configuration of your systems. 3 | If you do encounter this, you will need to add the "dispatch_uid" to your connect handlers: 4 | http://code.djangoproject.com/wiki/Signals#Helppost_saveseemstobeemittedtwiceforeachsave 5 | 6 | """ 7 | from django.dispatch import Signal 8 | 9 | # Sent when a payment is successfully processed. 10 | pdt_successful = Signal() 11 | 12 | # Sent when a payment is flagged. 13 | pdt_failed = Signal() 14 | 15 | # # Sent when a subscription was cancelled. 16 | # subscription_cancel = Signal() 17 | # 18 | # # Sent when a subscription expires. 19 | # subscription_eot = Signal() 20 | # 21 | # # Sent when a subscription was modified. 22 | # subscription_modify = Signal() 23 | # 24 | # # Sent when a subscription ends. 25 | # subscription_signup = Signal() -------------------------------------------------------------------------------- /standard/pdt/templates/pdt/test_pdt_response.html: -------------------------------------------------------------------------------- 1 | {{st}} 2 | mc_gross={{mc_gross}} 3 | invoice=66 4 | settle_amount=289.83 5 | protection_eligibility=Ineligible 6 | payer_id=8MZ9FQTSAMUPJ 7 | tax=0.00 8 | payment_date=04%3A53%3A52+Apr+12%2C+2009+PDT 9 | payment_status=Completed 10 | charset=windows-1252 11 | first_name=Test 12 | mc_fee=6.88 13 | exchange_rate=1.32876 14 | settle_currency=USD 15 | custom={{custom}} 16 | payer_status=verified 17 | business={{business}} 18 | quantity=1 19 | payer_email=buyer_1239119200_per%40yoursite.com 20 | txn_id={{txn_id}} 21 | payment_type=instant 22 | last_name=User 23 | receiver_email={{business}} 24 | payment_fee= 25 | receiver_id=746LDC2EQAP4W 26 | txn_type=web_accept 27 | item_name=Advertising+Campaign%3A+1+Month+%28225.00%29 28 | mc_currency=EUR 29 | item_number= 30 | residence_country=US 31 | handling_amount=0.00 32 | transaction_subject={{custom}} 33 | payment_gross= 34 | shipping=0.00 35 | - -------------------------------------------------------------------------------- /standard/widgets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django import forms 4 | from django.forms.util import flatatt 5 | from django.utils.safestring import mark_safe 6 | from django.utils.encoding import force_unicode 7 | 8 | 9 | class ValueHiddenInput(forms.HiddenInput): 10 | """ 11 | Widget that renders only if it has a value. 12 | Used to remove unused fields from PayPal buttons. 13 | """ 14 | def render(self, name, value, attrs=None): 15 | if value is None: 16 | return u'' 17 | else: 18 | return super(ValueHiddenInput, self).render(name, value, attrs) 19 | 20 | class ReservedValueHiddenInput(ValueHiddenInput): 21 | """ 22 | Overrides the default name attribute of the form. 23 | Used for the PayPal `return` field. 24 | """ 25 | def render(self, name, value, attrs=None): 26 | if value is None: 27 | value = '' 28 | final_attrs = self.build_attrs(attrs, type=self.input_type) 29 | if value != '': 30 | final_attrs['value'] = force_unicode(value) 31 | return mark_safe(u'' % flatatt(final_attrs)) -------------------------------------------------------------------------------- /standard/ipn/views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.http import HttpResponse 4 | from django.views.decorators.http import require_POST 5 | from paypal.standard.ipn.forms import PayPalIPNForm 6 | from paypal.standard.ipn.models import PayPalIPN 7 | 8 | 9 | @require_POST 10 | def ipn(request, item_check_callable=None): 11 | """ 12 | PayPal IPN endpoint (notify_url). 13 | Used by both PayPal Payments Pro and Payments Standard to confirm transactions. 14 | http://tinyurl.com/d9vu9d 15 | 16 | PayPal IPN Simulator: 17 | https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session 18 | """ 19 | flag = None 20 | ipn_obj = None 21 | form = PayPalIPNForm(request.POST) 22 | if form.is_valid(): 23 | try: 24 | ipn_obj = form.save(commit=False) 25 | except Exception, e: 26 | flag = "Exception while processing. (%s)" % e 27 | else: 28 | flag = "Invalid form. (%s)" % form.errors 29 | 30 | if ipn_obj is None: 31 | ipn_obj = PayPalIPN() 32 | 33 | ipn_obj.initialize(request) 34 | 35 | if flag is not None: 36 | ipn_obj.set_flag(flag) 37 | else: 38 | # Secrets should only be used over SSL. 39 | if request.is_secure() and 'secret' in request.GET: 40 | ipn_obj.verify_secret(form, request.GET['secret']) 41 | else: 42 | ipn_obj.verify(item_check_callable) 43 | 44 | ipn_obj.save() 45 | return HttpResponse("OKAY") -------------------------------------------------------------------------------- /standard/ipn/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import urllib2 4 | from paypal.standard.models import PayPalStandardBase 5 | from paypal.standard.ipn.signals import * 6 | 7 | 8 | class PayPalIPN(PayPalStandardBase): 9 | """Logs PayPal IPN interactions.""" 10 | format = u"" 11 | 12 | class Meta: 13 | db_table = "paypal_ipn" 14 | verbose_name = "PayPal IPN" 15 | 16 | def _postback(self): 17 | """Perform PayPal Postback validation.""" 18 | return urllib2.urlopen(self.get_endpoint(), "cmd=_notify-validate&%s" % self.query).read() 19 | 20 | def _verify_postback(self): 21 | if self.response != "VERIFIED": 22 | self.set_flag("Invalid postback. (%s)" % self.response) 23 | 24 | def send_signals(self): 25 | """Shout for the world to hear whether a txn was successful.""" 26 | # Transaction signals: 27 | if self.is_transaction(): 28 | if self.flag: 29 | payment_was_flagged.send(sender=self) 30 | else: 31 | payment_was_successful.send(sender=self) 32 | # Subscription signals: 33 | else: 34 | if self.is_subscription_cancellation(): 35 | subscription_cancel.send(sender=self) 36 | elif self.is_subscription_signup(): 37 | subscription_signup.send(sender=self) 38 | elif self.is_subscription_end_of_term(): 39 | subscription_eot.send(sender=self) 40 | elif self.is_subscription_modified(): 41 | subscription_modify.send(sender=self) -------------------------------------------------------------------------------- /pro/forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django import forms 4 | 5 | from paypal.pro.fields import CreditCardField, CreditCardExpiryField, CreditCardCVV2Field, CountryField 6 | 7 | 8 | class PaymentForm(forms.Form): 9 | """Form used to process direct payments.""" 10 | firstname = forms.CharField(255, label="First Name") 11 | lastname = forms.CharField(255, label="Last Name") 12 | street = forms.CharField(255, label="Street Address") 13 | city = forms.CharField(255, label="City") 14 | state = forms.CharField(255, label="State") 15 | countrycode = CountryField(label="Country", initial="US") 16 | zip = forms.CharField(32, label="Postal / Zip Code") 17 | acct = CreditCardField(label="Credit Card Number") 18 | expdate = CreditCardExpiryField(label="Expiration Date") 19 | cvv2 = CreditCardCVV2Field(label="Card Security Code") 20 | 21 | def process(self, request, item): 22 | """Process a PayPal direct payment.""" 23 | from paypal.pro.helpers import PayPalWPP 24 | wpp = PayPalWPP(request) 25 | params = self.cleaned_data 26 | params['creditcardtype'] = self.fields['acct'].card_type 27 | params['expdate'] = self.cleaned_data['expdate'].strftime("%m%Y") 28 | params['ipaddress'] = request.META.get("REMOTE_ADDR", "") 29 | params.update(item) 30 | 31 | # Create single payment: 32 | if 'billingperiod' not in params: 33 | response = wpp.doDirectPayment(params) 34 | 35 | # Create recurring payment: 36 | else: 37 | response = wpp.createRecurringPaymentsProfile(params, direct=True) 38 | 39 | return response 40 | 41 | 42 | class ConfirmForm(forms.Form): 43 | """Hidden form used by ExpressPay flow to keep track of payer information.""" 44 | token = forms.CharField(max_length=255, widget=forms.HiddenInput()) 45 | PayerID = forms.CharField(max_length=255, widget=forms.HiddenInput()) -------------------------------------------------------------------------------- /standard/pdt/views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.template import RequestContext 4 | from django.shortcuts import render_to_response 5 | from django.views.decorators.http import require_GET 6 | from paypal.standard.pdt.models import PayPalPDT 7 | from paypal.standard.pdt.forms import PayPalPDTForm 8 | 9 | 10 | @require_GET 11 | def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None): 12 | """Payment data transfer implementation: http://tinyurl.com/c9jjmw""" 13 | context = context or {} 14 | pdt_obj = None 15 | txn_id = request.GET.get('tx') 16 | failed = False 17 | if txn_id is not None: 18 | # If an existing transaction with the id tx exists: use it 19 | try: 20 | pdt_obj = PayPalPDT.objects.get(txn_id=txn_id) 21 | except PayPalPDT.DoesNotExist: 22 | # This is a new transaction so we continue processing PDT request 23 | pass 24 | 25 | if pdt_obj is None: 26 | form = PayPalPDTForm(request.GET) 27 | if form.is_valid(): 28 | try: 29 | pdt_obj = form.save(commit=False) 30 | except Exception, e: 31 | error = repr(e) 32 | failed = True 33 | else: 34 | error = form.errors 35 | failed = True 36 | 37 | if failed: 38 | pdt_obj = PayPalPDT() 39 | pdt_obj.set_flag("Invalid form. %s" % error) 40 | 41 | pdt_obj.initialize(request) 42 | 43 | if not failed: 44 | # The PDT object gets saved during verify 45 | pdt_obj.verify(item_check_callable) 46 | else: 47 | pass # we ignore any PDT requests that don't have a transaction id 48 | 49 | context.update({"failed":failed, "pdt_obj":pdt_obj}) 50 | return render_to_response(template, context, RequestContext(request)) -------------------------------------------------------------------------------- /standard/helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.conf import settings 4 | 5 | 6 | def duplicate_txn_id(ipn_obj): 7 | """Returns True if a record with this transaction id exists.""" 8 | return ipn_obj._default_manager.filter(txn_id=ipn_obj.txn_id).count() > 0 9 | 10 | def make_secret(form_instance, secret_fields=None): 11 | """ 12 | Returns a secret for use in a EWP form or an IPN verification based on a 13 | selection of variables in params. Should only be used with SSL. 14 | 15 | """ 16 | # @@@ Moved here as temporary fix to avoid dependancy on auth.models. 17 | from django.contrib.auth.models import get_hexdigest 18 | # @@@ amount is mc_gross on the IPN - where should mapping logic go? 19 | # @@@ amount / mc_gross is not nessecarily returned as it was sent - how to use it? 10.00 vs. 10.0 20 | # @@@ the secret should be based on the invoice or custom fields as well - otherwise its always the same. 21 | 22 | # Build the secret with fields availible in both PaymentForm and the IPN. Order matters. 23 | if secret_fields is None: 24 | secret_fields = ['business', 'item_name'] 25 | 26 | data = "" 27 | for name in secret_fields: 28 | if hasattr(form_instance, 'cleaned_data'): 29 | if name in form_instance.cleaned_data: 30 | data += unicode(form_instance.cleaned_data[name]) 31 | else: 32 | # Initial data passed into the constructor overrides defaults. 33 | if name in form_instance.initial: 34 | data += unicode(form_instance.initial[name]) 35 | elif name in form_instance.fields and form_instance.fields[name].initial is not None: 36 | data += unicode(form_instance.fields[name].initial) 37 | 38 | secret = get_hexdigest('sha1', settings.SECRET_KEY, data) 39 | return secret 40 | 41 | def check_secret(form_instance, secret): 42 | """ 43 | Returns true if received `secret` matches expected secret for form_instance. 44 | Used to verify IPN. 45 | 46 | """ 47 | # @@@ add invoice & custom 48 | # secret_fields = ['business', 'item_name'] 49 | return make_secret(form_instance) == secret -------------------------------------------------------------------------------- /standard/ipn/templates/ipn/ipn_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /standard/pdt/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from string import split as L 4 | from django.contrib import admin 5 | from paypal.standard.pdt.models import PayPalPDT 6 | 7 | 8 | # ToDo: How similiar is this to PayPalIPNAdmin? Could we just inherit off one common admin model? 9 | class PayPalPDTAdmin(admin.ModelAdmin): 10 | date_hierarchy = 'payment_date' 11 | fieldsets = ( 12 | (None, { 13 | "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") 14 | }), 15 | ("Address", { 16 | "description": "The address of the Buyer.", 17 | 'classes': ('collapse',), 18 | "fields": L("address_city address_country address_country_code address_name address_state address_status address_street address_zip") 19 | }), 20 | ("Buyer", { 21 | "description": "The information about the Buyer.", 22 | 'classes': ('collapse',), 23 | "fields": L("first_name last_name payer_business_name payer_email payer_id payer_status contact_phone residence_country") 24 | }), 25 | ("Seller", { 26 | "description": "The information about the Seller.", 27 | 'classes': ('collapse',), 28 | "fields": L("business item_name item_number quantity receiver_email receiver_id custom invoice memo") 29 | }), 30 | ("Subscriber", { 31 | "description": "The information about the Subscription.", 32 | 'classes': ('collapse',), 33 | "fields": L("subscr_id subscr_date subscr_effective") 34 | }), 35 | ("Recurring", { 36 | "description": "Information about recurring Payments.", 37 | "classes": ("collapse",), 38 | "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") 39 | }), 40 | ("Admin", { 41 | "description": "Additional Info.", 42 | "classes": ('collapse',), 43 | "fields": L("test_ipn ipaddress query flag_code flag_info") 44 | }), 45 | ) 46 | list_display = L("__unicode__ flag invoice custom payment_status created_at") 47 | search_fields = L("txn_id recurring_payment_id") 48 | admin.site.register(PayPalPDT, PayPalPDTAdmin) -------------------------------------------------------------------------------- /pro/creditcard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Adapted from: 5 | - http://www.djangosnippets.org/snippets/764/ 6 | - http://www.satchmoproject.com/trac/browser/satchmo/trunk/satchmo/apps/satchmo_utils/views.py 7 | - http://tinyurl.com/shoppify-credit-cards 8 | """ 9 | import re 10 | 11 | 12 | # Well known card regular expressions. 13 | CARDS = { 14 | 'Visa': re.compile(r"^4\d{12}(\d{3})?$"), 15 | 'Mastercard': re.compile(r"(5[1-5]\d{4}|677189)\d{10}$"), 16 | 'Dinersclub': re.compile(r"^3(0[0-5]|[68]\d)\d{11}"), 17 | 'Amex': re.compile("^3[47]\d{13}$"), 18 | 'Discover': re.compile("^(6011|65\d{2})\d{12}$"), 19 | } 20 | 21 | # Well known test numbers 22 | TEST_NUMBERS = [ 23 | "378282246310005", "371449635398431", "378734493671000", "30569309025904", 24 | "38520000023237", "6011111111111117", "6011000990139424", "555555555554444", 25 | "5105105105105100", "4111111111111111", "4012888888881881", "4222222222222" 26 | ] 27 | 28 | def verify_credit_card(number): 29 | """Returns the card type for given card number or None if invalid.""" 30 | return CreditCard(number).verify() 31 | 32 | class CreditCard(object): 33 | def __init__(self, number): 34 | self.number = number 35 | 36 | def is_number(self): 37 | """True if there is at least one digit in number.""" 38 | self.number = re.sub(r'[^\d]', '', self.number) 39 | return self.number.isdigit() 40 | 41 | def is_mod10(self): 42 | """Returns True if number is valid according to mod10.""" 43 | double = 0 44 | total = 0 45 | for i in range(len(self.number) - 1, -1, -1): 46 | for c in str((double + 1) * int(self.number[i])): 47 | total = total + int(c) 48 | double = (double + 1) % 2 49 | return (total % 10) == 0 50 | 51 | def is_test(self): 52 | """Returns True if number is a test card number.""" 53 | return self.number in TEST_NUMBERS 54 | 55 | def get_type(self): 56 | """Return the type if it matches one of the cards.""" 57 | for card, pattern in CARDS.iteritems(): 58 | if pattern.match(self.number): 59 | return card 60 | return None 61 | 62 | def verify(self): 63 | """Returns the card type if valid else None.""" 64 | if self.is_number() and not self.is_test() and self.is_mod10(): 65 | return self.get_type() 66 | return None -------------------------------------------------------------------------------- /standard/ipn/admin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.contrib import admin 4 | from paypal.standard.ipn.models import PayPalIPN 5 | 6 | 7 | class PayPalIPNAdmin(admin.ModelAdmin): 8 | date_hierarchy = 'payment_date' 9 | fieldsets = ( 10 | (None, { 11 | "fields": [ 12 | "flag", "txn_id", "txn_type", "payment_status", "payment_date", 13 | "transaction_entity", "reason_code", "pending_reason", 14 | "mc_gross", "mc_fee", "auth_status", "auth_amount", "auth_exp", 15 | "auth_id" 16 | ] 17 | }), 18 | ("Address", { 19 | "description": "The address of the Buyer.", 20 | 'classes': ('collapse',), 21 | "fields": [ 22 | "address_city", "address_country", "address_country_code", 23 | "address_name", "address_state", "address_status", 24 | "address_street", "address_zip" 25 | ] 26 | }), 27 | ("Buyer", { 28 | "description": "The information about the Buyer.", 29 | 'classes': ('collapse',), 30 | "fields": [ 31 | "first_name", "last_name", "payer_business_name", "payer_email", 32 | "payer_id", "payer_status", "contact_phone", "residence_country" 33 | ] 34 | }), 35 | ("Seller", { 36 | "description": "The information about the Seller.", 37 | 'classes': ('collapse',), 38 | "fields": [ 39 | "business", "item_name", "item_number", "quantity", 40 | "receiver_email", "receiver_id", "custom", "invoice", "memo" 41 | ] 42 | }), 43 | ("Recurring", { 44 | "description": "Information about recurring Payments.", 45 | "classes": ("collapse",), 46 | "fields": [ 47 | "profile_status", "initial_payment_amount", "amount_per_cycle", 48 | "outstanding_balance", "period_type", "product_name", 49 | "product_type", "recurring_payment_id", "receipt_id", 50 | "next_payment_date" 51 | ] 52 | }), 53 | ("Admin", { 54 | "description": "Additional Info.", 55 | "classes": ('collapse',), 56 | "fields": [ 57 | "test_ipn", "ipaddress", "query", "response", "flag_code", 58 | "flag_info" 59 | ] 60 | }), 61 | ) 62 | list_display = [ 63 | "__unicode__", "flag", "flag_info", "invoice", "custom", 64 | "payment_status", "created_at" 65 | ] 66 | search_fields = ["txn_id", "recurring_payment_id"] 67 | 68 | 69 | admin.site.register(PayPalIPN, PayPalIPNAdmin) -------------------------------------------------------------------------------- /standard/pdt/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from urllib import unquote_plus 4 | import urllib2 5 | from django.db import models 6 | from django.conf import settings 7 | from django.http import QueryDict 8 | from django.utils.http import urlencode 9 | from paypal.standard.models import PayPalStandardBase 10 | from paypal.standard.conf import POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT 11 | from paypal.standard.pdt.signals import pdt_successful, pdt_failed 12 | 13 | # ### Todo: Move this logic to conf.py: 14 | # if paypal.standard.pdt is in installed apps 15 | # ... then check for this setting in conf.py 16 | class PayPalSettingsError(Exception): 17 | """Raised when settings are incorrect.""" 18 | 19 | try: 20 | IDENTITY_TOKEN = settings.PAYPAL_IDENTITY_TOKEN 21 | except: 22 | raise PayPalSettingsError("You must set PAYPAL_IDENTITY_TOKEN in settings.py. Get this token by enabling PDT in your PayPal account.") 23 | 24 | 25 | class PayPalPDT(PayPalStandardBase): 26 | format = u"" 27 | 28 | amt = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 29 | cm = models.CharField(max_length=255, blank=True) 30 | sig = models.CharField(max_length=255, blank=True) 31 | tx = models.CharField(max_length=255, blank=True) 32 | st = models.CharField(max_length=32, blank=True) 33 | 34 | class Meta: 35 | db_table = "paypal_pdt" 36 | verbose_name = "PayPal PDT" 37 | 38 | def _postback(self): 39 | """ 40 | Perform PayPal PDT Postback validation. 41 | Sends the transaction ID and business token to PayPal which responses with 42 | SUCCESS or FAILED. 43 | 44 | """ 45 | postback_dict = dict(cmd="_notify-synch", at=IDENTITY_TOKEN, tx=self.tx) 46 | postback_params = urlencode(postback_dict) 47 | return urllib2.urlopen(self.get_endpoint(), postback_params).read() 48 | 49 | def get_endpoint(self): 50 | """Use the sandbox when in DEBUG mode as we don't have a test_ipn variable in pdt.""" 51 | if settings.DEBUG: 52 | return SANDBOX_POSTBACK_ENDPOINT 53 | else: 54 | return POSTBACK_ENDPOINT 55 | 56 | def _verify_postback(self): 57 | # ### Now we don't really care what result was, just whether a flag was set or not. 58 | from paypal.standard.pdt.forms import PayPalPDTForm 59 | result = False 60 | response_list = self.response.split('\n') 61 | response_dict = {} 62 | for i, line in enumerate(response_list): 63 | unquoted_line = unquote_plus(line).strip() 64 | if i == 0: 65 | self.st = unquoted_line 66 | if self.st == "SUCCESS": 67 | result = True 68 | else: 69 | if self.st != "SUCCESS": 70 | self.set_flag(line) 71 | break 72 | try: 73 | if not unquoted_line.startswith(' -'): 74 | k, v = unquoted_line.split('=') 75 | response_dict[k.strip()] = v.strip() 76 | except ValueError, e: 77 | pass 78 | 79 | qd = QueryDict('', mutable=True) 80 | qd.update(response_dict) 81 | qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info)) 82 | pdt_form = PayPalPDTForm(qd, instance=self) 83 | pdt_form.save(commit=False) 84 | 85 | def send_signals(self): 86 | # Send the PDT signals... 87 | if self.flag: 88 | pdt_failed.send(sender=self) 89 | else: 90 | pdt_successful.send(sender=self) -------------------------------------------------------------------------------- /standard/ipn/tests/test_ipn.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import HttpResponse 3 | from django.test import TestCase 4 | from django.test.client import Client 5 | 6 | from paypal.standard.ipn.models import PayPalIPN 7 | from paypal.standard.ipn.signals import (payment_was_successful, 8 | payment_was_flagged) 9 | 10 | 11 | IPN_POST_PARAMS = { 12 | "protection_eligibility": "Ineligible", 13 | "last_name": "User", 14 | "txn_id": "51403485VH153354B", 15 | "receiver_email": settings.PAYPAL_RECEIVER_EMAIL, 16 | "payment_status": "Completed", 17 | "payment_gross": "10.00", 18 | "tax": "0.00", 19 | "residence_country": "US", 20 | "invoice": "0004", 21 | "payer_status": "verified", 22 | "txn_type": "express_checkout", 23 | "handling_amount": "0.00", 24 | "payment_date": "23:04:06 Feb 02, 2009 PST", 25 | "first_name": "Test", 26 | "item_name": "", 27 | "charset": "windows-1252", 28 | "custom": "website_id=13&user_id=21", 29 | "notify_version": "2.6", 30 | "transaction_subject": "", 31 | "test_ipn": "1", 32 | "item_number": "", 33 | "receiver_id": "258DLEHY2BDK6", 34 | "payer_id": "BN5JZ2V7MLEV4", 35 | "verify_sign": "An5ns1Kso7MWUdW4ErQKJJJ4qi4-AqdZy6dD.sGO3sDhTf1wAbuO2IZ7", 36 | "payment_fee": "0.59", 37 | "mc_fee": "0.59", 38 | "mc_currency": "USD", 39 | "shipping": "0.00", 40 | "payer_email": "bishan_1233269544_per@gmail.com", 41 | "payment_type": "instant", 42 | "mc_gross": "10.00", 43 | "quantity": "1", 44 | } 45 | 46 | 47 | class IPNTest(TestCase): 48 | urls = 'paypal.standard.ipn.tests.test_urls' 49 | 50 | def setUp(self): 51 | self.old_debug = settings.DEBUG 52 | settings.DEBUG = True 53 | 54 | # Monkey patch over PayPalIPN to make it get a VERFIED response. 55 | self.old_postback = PayPalIPN._postback 56 | PayPalIPN._postback = lambda self: "VERIFIED" 57 | 58 | def tearDown(self): 59 | settings.DEBUG = self.old_debug 60 | PayPalIPN._postback = self.old_postback 61 | 62 | def assertGotSignal(self, signal, flagged): 63 | # Check the signal was sent. These get lost if they don't reference self. 64 | self.got_signal = False 65 | self.signal_obj = None 66 | 67 | def handle_signal(sender, **kwargs): 68 | self.got_signal = True 69 | self.signal_obj = sender 70 | signal.connect(handle_signal) 71 | 72 | response = self.client.post("/ipn/", IPN_POST_PARAMS) 73 | self.assertEqual(response.status_code, 200) 74 | ipns = PayPalIPN.objects.all() 75 | self.assertEqual(len(ipns), 1) 76 | ipn_obj = ipns[0] 77 | self.assertEqual(ipn_obj.flag, flagged) 78 | 79 | self.assertTrue(self.got_signal) 80 | self.assertEqual(self.signal_obj, ipn_obj) 81 | 82 | def test_correct_ipn(self): 83 | self.assertGotSignal(payment_was_successful, False) 84 | 85 | def test_failed_ipn(self): 86 | PayPalIPN._postback = lambda self: "INVALID" 87 | self.assertGotSignal(payment_was_flagged, True) 88 | 89 | def assertFlagged(self, updates, flag_info): 90 | params = IPN_POST_PARAMS.copy() 91 | params.update(updates) 92 | response = self.client.post("/ipn/", params) 93 | self.assertEqual(response.status_code, 200) 94 | ipn_obj = PayPalIPN.objects.all()[0] 95 | self.assertEqual(ipn_obj.flag, True) 96 | self.assertEqual(ipn_obj.flag_info, flag_info) 97 | 98 | def test_incorrect_receiver_email(self): 99 | update = {"receiver_email": "incorrect_email@someotherbusiness.com"} 100 | flag_info = "Invalid receiver_email. (incorrect_email@someotherbusiness.com)" 101 | self.assertFlagged(update, flag_info) 102 | 103 | def test_invalid_payment_status(self): 104 | update = {"payment_status": "Failed"} 105 | flag_info = "Invalid payment_status. (Failed)" 106 | self.assertFlagged(update, flag_info) 107 | 108 | def test_duplicate_txn_id(self): 109 | self.client.post("/ipn/", IPN_POST_PARAMS) 110 | self.client.post("/ipn/", IPN_POST_PARAMS) 111 | self.assertEqual(len(PayPalIPN.objects.all()), 2) 112 | ipn_obj = PayPalIPN.objects.order_by('-created_at')[1] 113 | self.assertEqual(ipn_obj.flag, True) 114 | self.assertEqual(ipn_obj.flag_info, "Duplicate txn_id. (51403485VH153354B)") -------------------------------------------------------------------------------- /pro/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from string import split as L 4 | from django.db import models 5 | from django.utils.http import urlencode 6 | from django.forms.models import model_to_dict 7 | from django.contrib.auth.models import User 8 | 9 | 10 | class PayPalNVP(models.Model): 11 | """Record of a NVP interaction with PayPal.""" 12 | TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # 2009-02-03T17:47:41Z 13 | RESTRICTED_FIELDS = L("expdate cvv2 acct") 14 | ADMIN_FIELDS = L("id user flag flag_code flag_info query response created_at updated_at ") 15 | ITEM_FIELDS = L("amt custom invnum") 16 | DIRECT_FIELDS = L("firstname lastname street city state countrycode zip") 17 | 18 | # Response fields 19 | method = models.CharField(max_length=64, blank=True) 20 | ack = models.CharField(max_length=32, blank=True) 21 | profilestatus = models.CharField(max_length=32, blank=True) 22 | timestamp = models.DateTimeField(blank=True, null=True) 23 | profileid = models.CharField(max_length=32, blank=True) # I-E596DFUSD882 24 | profilereference = models.CharField(max_length=128, blank=True) # PROFILEREFERENCE 25 | correlationid = models.CharField(max_length=32, blank=True) # 25b380cda7a21 26 | token = models.CharField(max_length=64, blank=True) 27 | payerid = models.CharField(max_length=64, blank=True) 28 | 29 | # Transaction Fields 30 | firstname = models.CharField("First Name", max_length=255, blank=True) 31 | lastname = models.CharField("Last Name", max_length=255, blank=True) 32 | street = models.CharField("Street Address", max_length=255, blank=True) 33 | city = models.CharField("City", max_length=255, blank=True) 34 | state = models.CharField("State", max_length=255, blank=True) 35 | countrycode = models.CharField("Country", max_length=2,blank=True) 36 | zip = models.CharField("Postal / Zip Code", max_length=32, blank=True) 37 | 38 | # Custom fields 39 | invnum = models.CharField(max_length=255, blank=True) 40 | custom = models.CharField(max_length=255, blank=True) 41 | 42 | # Admin fields 43 | user = models.ForeignKey(User, blank=True, null=True) 44 | flag = models.BooleanField(default=False, blank=True) 45 | flag_code = models.CharField(max_length=32, blank=True) 46 | flag_info = models.TextField(blank=True) 47 | ipaddress = models.IPAddressField(blank=True) 48 | query = models.TextField(blank=True) 49 | response = models.TextField(blank=True) 50 | created_at = models.DateTimeField(auto_now_add=True) 51 | updated_at = models.DateTimeField(auto_now=True) 52 | 53 | class Meta: 54 | db_table = "paypal_nvp" 55 | verbose_name = "PayPal NVP" 56 | 57 | def init(self, request, paypal_request, paypal_response): 58 | """Initialize a PayPalNVP instance from a HttpRequest.""" 59 | self.ipaddress = request.META.get('REMOTE_ADDR', '') 60 | if hasattr(request, "user") and request.user.is_authenticated(): 61 | self.user = request.user 62 | 63 | # No storing credit card info. 64 | query_data = dict((k,v) for k, v in paypal_request.iteritems() if k not in self.RESTRICTED_FIELDS) 65 | self.query = urlencode(query_data) 66 | self.response = urlencode(paypal_response) 67 | 68 | # Was there a flag on the play? 69 | ack = paypal_response.get('ack', False) 70 | if ack != "Success": 71 | if ack == "SuccessWithWarning": 72 | self.flag_info = paypal_response.get('l_longmessage0', '') 73 | else: 74 | self.set_flag(paypal_response.get('l_longmessage0', ''), paypal_response.get('l_errorcode', '')) 75 | 76 | def set_flag(self, info, code=None): 77 | """Flag this instance for investigation.""" 78 | self.flag = True 79 | self.flag_info += info 80 | if code is not None: 81 | self.flag_code = code 82 | 83 | def process(self, request, item): 84 | """Do a direct payment.""" 85 | from paypal.pro.helpers import PayPalWPP 86 | wpp = PayPalWPP(request) 87 | 88 | # Change the model information into a dict that PayPal can understand. 89 | params = model_to_dict(self, exclude=self.ADMIN_FIELDS) 90 | params['acct'] = self.acct 91 | params['creditcardtype'] = self.creditcardtype 92 | params['expdate'] = self.expdate 93 | params['cvv2'] = self.cvv2 94 | params.update(item) 95 | 96 | # Create recurring payment: 97 | if 'billingperiod' in params: 98 | return wpp.createRecurringPaymentsProfile(params, direct=True) 99 | # Create single payment: 100 | else: 101 | return wpp.doDirectPayment(params) 102 | -------------------------------------------------------------------------------- /pro/tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | from django.conf import settings 4 | from django.core.handlers.wsgi import WSGIRequest 5 | from django.forms import ValidationError 6 | from django.http import QueryDict 7 | from django.test import TestCase 8 | from django.test.client import Client 9 | 10 | from paypal.pro.fields import CreditCardField 11 | from paypal.pro.helpers import PayPalWPP, PayPalError 12 | 13 | 14 | class RequestFactory(Client): 15 | # Used to generate request objects. 16 | def request(self, **request): 17 | environ = { 18 | 'HTTP_COOKIE': self.cookies, 19 | 'PATH_INFO': '/', 20 | 'QUERY_STRING': '', 21 | 'REQUEST_METHOD': 'GET', 22 | 'SCRIPT_NAME': '', 23 | 'SERVER_NAME': 'testserver', 24 | 'SERVER_PORT': 80, 25 | 'SERVER_PROTOCOL': 'HTTP/1.1', 26 | } 27 | environ.update(self.defaults) 28 | environ.update(request) 29 | return WSGIRequest(environ) 30 | 31 | RF = RequestFactory() 32 | REQUEST = RF.get("/pay/", REMOTE_ADDR="127.0.0.1:8000") 33 | 34 | 35 | class DummyPayPalWPP(PayPalWPP): 36 | pass 37 | # """Dummy class for testing PayPalWPP.""" 38 | # responses = { 39 | # # @@@ Need some reals data here. 40 | # "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=""", 41 | # } 42 | # 43 | # def _request(self, data): 44 | # return self.responses["DoDirectPayment"] 45 | 46 | 47 | class CreditCardFieldTest(TestCase): 48 | def testCreditCardField(self): 49 | field = CreditCardField() 50 | field.clean('4797503429879309') 51 | self.assertEquals(field.card_type, "Visa") 52 | self.assertRaises(ValidationError, CreditCardField().clean, '1234567890123455') 53 | 54 | 55 | class PayPalWPPTest(TestCase): 56 | def setUp(self): 57 | 58 | # Avoding blasting real requests at PayPal. 59 | self.old_debug = settings.DEBUG 60 | settings.DEBUG = True 61 | 62 | self.item = { 63 | 'amt': '9.95', 64 | 'inv': 'inv', 65 | 'custom': 'custom', 66 | 'next': 'http://www.example.com/next/', 67 | 'returnurl': 'http://www.example.com/pay/', 68 | 'cancelurl': 'http://www.example.com/cancel/' 69 | } 70 | self.wpp = DummyPayPalWPP(REQUEST) 71 | 72 | def tearDown(self): 73 | settings.DEBUG = self.old_debug 74 | 75 | def test_doDirectPayment_missing_params(self): 76 | data = {'firstname': 'Chewbacca'} 77 | self.assertRaises(PayPalError, self.wpp.doDirectPayment, data) 78 | 79 | def test_doDirectPayment_valid(self): 80 | data = { 81 | 'firstname': 'Brave', 82 | 'lastname': 'Star', 83 | 'street': '1 Main St', 84 | 'city': u'San Jos\xe9', 85 | 'state': 'CA', 86 | 'countrycode': 'US', 87 | 'zip': '95131', 88 | 'expdate': '012019', 89 | 'cvv2': '037', 90 | 'acct': '4797503429879309', 91 | 'creditcardtype': 'visa', 92 | 'ipaddress': '10.0.1.199',} 93 | data.update(self.item) 94 | self.assertTrue(self.wpp.doDirectPayment(data)) 95 | 96 | def test_doDirectPayment_invalid(self): 97 | data = { 98 | 'firstname': 'Epic', 99 | 'lastname': 'Fail', 100 | 'street': '100 Georgia St', 101 | 'city': 'Vancouver', 102 | 'state': 'BC', 103 | 'countrycode': 'CA', 104 | 'zip': 'V6V 1V1', 105 | 'expdate': '012019', 106 | 'cvv2': '999', 107 | 'acct': '1234567890', 108 | 'creditcardtype': 'visa', 109 | 'ipaddress': '10.0.1.199',} 110 | data.update(self.item) 111 | self.assertFalse(self.wpp.doDirectPayment(data)) 112 | 113 | def test_setExpressCheckout(self): 114 | # We'll have to stub out tests for doExpressCheckoutPayment and friends 115 | # because they're behind paypal's doors. 116 | nvp_obj = self.wpp.setExpressCheckout(self.item) 117 | self.assertTrue(nvp_obj.ack == "Success") 118 | 119 | 120 | ### DoExpressCheckoutPayment 121 | # PayPal Request: 122 | # {'amt': '10.00', 123 | # 'cancelurl': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname', 124 | # 'custom': u'website_id=480&cname=1', 125 | # 'inv': u'website-480-cname', 126 | # 'method': 'DoExpressCheckoutPayment', 127 | # 'next': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname', 128 | # 'payerid': u'BN5JZ2V7MLEV4', 129 | # 'paymentaction': 'Sale', 130 | # 'returnurl': u'http://xxx.xxx.xxx.xxx/deploy/480/upgrade/?upgrade=cname', 131 | # 'token': u'EC-6HW17184NE0084127'} 132 | # 133 | # PayPal Response: 134 | # {'ack': 'Success', 135 | # 'amt': '10.00', 136 | # 'build': '848077', 137 | # 'correlationid': '375f4773c3d34', 138 | # 'currencycode': 'USD', 139 | # 'feeamt': '0.59', 140 | # 'ordertime': '2009-03-04T20:56:08Z', 141 | # 'paymentstatus': 'Completed', 142 | # 'paymenttype': 'instant', 143 | # 'pendingreason': 'None', 144 | # 'reasoncode': 'None', 145 | # 'taxamt': '0.00', 146 | # 'timestamp': '2009-03-04T20:56:09Z', 147 | # 'token': 'EC-6HW17184NE0084127', 148 | # 'transactionid': '3TG42202A7335864V', 149 | # 'transactiontype': 'expresscheckout', 150 | # 'version': '54.0'} -------------------------------------------------------------------------------- /standard/pdt/tests/test_pdt.py: -------------------------------------------------------------------------------- 1 | """ 2 | run this with ./manage.py test website 3 | see http://www.djangoproject.com/documentation/testing/ for details 4 | """ 5 | import os 6 | from django.conf import settings 7 | from django.shortcuts import render_to_response 8 | from django.test import TestCase 9 | from paypal.standard.pdt.forms import PayPalPDTForm 10 | from paypal.standard.pdt.models import PayPalPDT 11 | from paypal.standard.pdt.signals import pdt_successful, pdt_failed 12 | 13 | 14 | class DummyPayPalPDT(object): 15 | 16 | def __init__(self, update_context_dict={}): 17 | self.context_dict = {'st': 'SUCCESS', 'custom':'cb736658-3aad-4694-956f-d0aeade80194', 18 | 'txn_id':'1ED550410S3402306', 'mc_gross': '225.00', 19 | 'business': settings.PAYPAL_RECEIVER_EMAIL, 'error': 'Error code: 1234'} 20 | 21 | self.context_dict.update(update_context_dict) 22 | 23 | def update_with_get_params(self, get_params): 24 | if get_params.has_key('tx'): 25 | self.context_dict['txn_id'] = get_params.get('tx') 26 | if get_params.has_key('amt'): 27 | self.context_dict['mc_gross'] = get_params.get('amt') 28 | if get_params.has_key('cm'): 29 | self.context_dict['custom'] = get_params.get('cm') 30 | 31 | def _postback(self, test=True): 32 | """Perform a Fake PayPal PDT Postback request.""" 33 | # @@@ would be cool if this could live in the test templates dir... 34 | return render_to_response("pdt/test_pdt_response.html", self.context_dict).content 35 | 36 | class PDTTest(TestCase): 37 | urls = "paypal.standard.pdt.tests.test_urls" 38 | template_dirs = [os.path.join(os.path.dirname(__file__), 'templates'),] 39 | 40 | def setUp(self): 41 | # set up some dummy PDT get parameters 42 | self.get_params = {"tx":"4WJ86550014687441", "st":"Completed", "amt":"225.00", "cc":"EUR", 43 | "cm":"a3e192b8-8fea-4a86-b2e8-d5bf502e36be", "item_number":"", 44 | "sig":"blahblahblah"} 45 | 46 | # monkey patch the PayPalPDT._postback function 47 | self.dpppdt = DummyPayPalPDT() 48 | self.dpppdt.update_with_get_params(self.get_params) 49 | PayPalPDT._postback = self.dpppdt._postback 50 | 51 | def test_verify_postback(self): 52 | dpppdt = DummyPayPalPDT() 53 | paypal_response = dpppdt._postback() 54 | assert('SUCCESS' in paypal_response) 55 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 56 | pdt_obj = PayPalPDT() 57 | pdt_obj.ipaddress = '127.0.0.1' 58 | pdt_obj.response = paypal_response 59 | pdt_obj._verify_postback() 60 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 61 | self.assertEqual(pdt_obj.txn_id, '1ED550410S3402306') 62 | 63 | def test_pdt(self): 64 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 65 | self.dpppdt.update_with_get_params(self.get_params) 66 | paypal_response = self.client.get("/pdt/", self.get_params) 67 | self.assertContains(paypal_response, 'Transaction complete', status_code=200) 68 | self.assertEqual(len(PayPalPDT.objects.all()), 1) 69 | 70 | def test_pdt_signals(self): 71 | self.successful_pdt_fired = False 72 | self.failed_pdt_fired = False 73 | 74 | def successful_pdt(sender, **kwargs): 75 | self.successful_pdt_fired = True 76 | pdt_successful.connect(successful_pdt) 77 | 78 | def failed_pdt(sender, **kwargs): 79 | self.failed_pdt_fired = True 80 | pdt_failed.connect(failed_pdt) 81 | 82 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 83 | paypal_response = self.client.get("/pdt/", self.get_params) 84 | self.assertContains(paypal_response, 'Transaction complete', status_code=200) 85 | self.assertEqual(len(PayPalPDT.objects.all()), 1) 86 | self.assertTrue(self.successful_pdt_fired) 87 | self.assertFalse(self.failed_pdt_fired) 88 | pdt_obj = PayPalPDT.objects.all()[0] 89 | self.assertEqual(pdt_obj.flag, False) 90 | 91 | def test_double_pdt_get(self): 92 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 93 | paypal_response = self.client.get("/pdt/", self.get_params) 94 | self.assertContains(paypal_response, 'Transaction complete', status_code=200) 95 | self.assertEqual(len(PayPalPDT.objects.all()), 1) 96 | pdt_obj = PayPalPDT.objects.all()[0] 97 | self.assertEqual(pdt_obj.flag, False) 98 | paypal_response = self.client.get("/pdt/", self.get_params) 99 | self.assertContains(paypal_response, 'Transaction complete', status_code=200) 100 | self.assertEqual(len(PayPalPDT.objects.all()), 1) # we don't create a new pdt 101 | pdt_obj = PayPalPDT.objects.all()[0] 102 | self.assertEqual(pdt_obj.flag, False) 103 | 104 | def test_no_txn_id_in_pdt(self): 105 | self.dpppdt.context_dict.pop('txn_id') 106 | self.get_params={} 107 | paypal_response = self.client.get("/pdt/", self.get_params) 108 | self.assertContains(paypal_response, 'Transaction Failed', status_code=200) 109 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 110 | 111 | def test_custom_passthrough(self): 112 | self.assertEqual(len(PayPalPDT.objects.all()), 0) 113 | self.dpppdt.update_with_get_params(self.get_params) 114 | paypal_response = self.client.get("/pdt/", self.get_params) 115 | self.assertContains(paypal_response, 'Transaction complete', status_code=200) 116 | self.assertEqual(len(PayPalPDT.objects.all()), 1) 117 | pdt_obj = PayPalPDT.objects.all()[0] 118 | self.assertEqual(pdt_obj.custom, self.get_params['cm'] ) -------------------------------------------------------------------------------- /pro/helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | import pprint 5 | import time 6 | import urllib 7 | import urllib2 8 | 9 | from django.conf import settings 10 | from django.forms.models import fields_for_model 11 | from django.utils.datastructures import MergeDict 12 | from django.utils.http import urlencode 13 | 14 | from paypal.pro.models import PayPalNVP, L 15 | 16 | 17 | TEST = settings.PAYPAL_TEST 18 | USER = settings.PAYPAL_WPP_USER 19 | PASSWORD = settings.PAYPAL_WPP_PASSWORD 20 | SIGNATURE = settings.PAYPAL_WPP_SIGNATURE 21 | VERSION = 54.0 22 | BASE_PARAMS = dict(USER=USER , PWD=PASSWORD, SIGNATURE=SIGNATURE, VERSION=VERSION) 23 | ENDPOINT = "https://api-3t.paypal.com/nvp" 24 | SANDBOX_ENDPOINT = "https://api-3t.sandbox.paypal.com/nvp" 25 | NVP_FIELDS = fields_for_model(PayPalNVP).keys() 26 | 27 | 28 | def paypal_time(time_obj=None): 29 | """Returns a time suitable for PayPal time fields.""" 30 | if time_obj is None: 31 | time_obj = time.gmtime() 32 | return time.strftime(PayPalNVP.TIMESTAMP_FORMAT, time_obj) 33 | 34 | def paypaltime2datetime(s): 35 | """Convert a PayPal time string to a DateTime.""" 36 | return datetime.datetime(*(time.strptime(s, PayPalNVP.TIMESTAMP_FORMAT)[:6])) 37 | 38 | 39 | class PayPalError(TypeError): 40 | """Error thrown when something be wrong.""" 41 | 42 | 43 | class PayPalWPP(object): 44 | """ 45 | Wrapper class for the PayPal Website Payments Pro. 46 | 47 | Website Payments Pro Integration Guide: 48 | https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_WPP_IntegrationGuide.pdf 49 | 50 | Name-Value Pair API Developer Guide and Reference: 51 | https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_NVPAPI_DeveloperGuide.pdf 52 | """ 53 | def __init__(self, request, params=BASE_PARAMS): 54 | """Required - USER / PWD / SIGNATURE / VERSION""" 55 | self.request = request 56 | if TEST: 57 | self.endpoint = SANDBOX_ENDPOINT 58 | else: 59 | self.endpoint = ENDPOINT 60 | self.signature_values = params 61 | self.signature = urlencode(self.signature_values) + "&" 62 | 63 | def doDirectPayment(self, params): 64 | """Call PayPal DoDirectPayment method.""" 65 | defaults = {"method": "DoDirectPayment", "paymentaction": "Sale"} 66 | required = L("creditcardtype acct expdate cvv2 ipaddress firstname lastname street city state countrycode zip amt") 67 | nvp_obj = self._fetch(params, required, defaults) 68 | # @@@ Could check cvv2match / avscode are both 'X' or '0' 69 | # qd = django.http.QueryDict(nvp_obj.response) 70 | # if qd.get('cvv2match') not in ['X', '0']: 71 | # nvp_obj.set_flag("Invalid cvv2match: %s" % qd.get('cvv2match') 72 | # if qd.get('avscode') not in ['X', '0']: 73 | # nvp_obj.set_flag("Invalid avscode: %s" % qd.get('avscode') 74 | return not nvp_obj.flag 75 | 76 | def setExpressCheckout(self, params): 77 | """ 78 | Initiates an Express Checkout transaction. 79 | Optionally, the SetExpressCheckout API operation can set up billing agreements for 80 | reference transactions and recurring payments. 81 | Returns a NVP instance - check for token and payerid to continue! 82 | """ 83 | if self._is_recurring(params): 84 | params = self._recurring_setExpressCheckout_adapter(params) 85 | 86 | defaults = {"method": "SetExpressCheckout", "noshipping": 1} 87 | required = L("returnurl cancelurl amt") 88 | return self._fetch(params, required, defaults) 89 | 90 | def doExpressCheckoutPayment(self, params): 91 | """ 92 | Check the dude out: 93 | """ 94 | defaults = {"method": "DoExpressCheckoutPayment", "paymentaction": "Sale"} 95 | required =L("returnurl cancelurl amt token payerid") 96 | nvp_obj = self._fetch(params, required, defaults) 97 | return not nvp_obj.flag 98 | 99 | def createRecurringPaymentsProfile(self, params, direct=False): 100 | """ 101 | Set direct to True to indicate that this is being called as a directPayment. 102 | Returns True PayPal successfully creates the profile otherwise False. 103 | """ 104 | defaults = {"method": "CreateRecurringPaymentsProfile"} 105 | required = L("profilestartdate billingperiod billingfrequency amt") 106 | 107 | # Direct payments require CC data 108 | if direct: 109 | required + L("creditcardtype acct expdate firstname lastname") 110 | else: 111 | required + L("token payerid") 112 | 113 | nvp_obj = self._fetch(params, required, defaults) 114 | 115 | # Flag if profile_type != ActiveProfile 116 | return not nvp_obj.flag 117 | 118 | def getExpressCheckoutDetails(self, params): 119 | raise NotImplementedError 120 | 121 | def setCustomerBillingAgreement(self, params): 122 | raise DeprecationWarning 123 | 124 | def getTransactionDetails(self, params): 125 | raise NotImplementedError 126 | 127 | def massPay(self, params): 128 | raise NotImplementedError 129 | 130 | def getRecurringPaymentsProfileDetails(self, params): 131 | raise NotImplementedError 132 | 133 | def updateRecurringPaymentsProfile(self, params): 134 | raise NotImplementedError 135 | 136 | def billOutstandingAmount(self, params): 137 | raise NotImplementedError 138 | 139 | def manangeRecurringPaymentsProfileStatus(self, params): 140 | raise NotImplementedError 141 | 142 | def refundTransaction(self, params): 143 | raise NotImplementedError 144 | 145 | def _is_recurring(self, params): 146 | """Returns True if the item passed is a recurring transaction.""" 147 | return 'billingfrequency' in params 148 | 149 | def _recurring_setExpressCheckout_adapter(self, params): 150 | """ 151 | The recurring payment interface to SEC is different than the recurring payment 152 | interface to ECP. This adapts a normal call to look like a SEC call. 153 | """ 154 | params['l_billingtype0'] = "RecurringPayments" 155 | params['l_billingagreementdescription0'] = params['desc'] 156 | 157 | REMOVE = L("billingfrequency billingperiod profilestartdate desc") 158 | for k in params.keys(): 159 | if k in REMOVE: 160 | del params[k] 161 | 162 | return params 163 | 164 | def _fetch(self, params, required, defaults): 165 | """Make the NVP request and store the response.""" 166 | defaults.update(params) 167 | pp_params = self._check_and_update_params(required, defaults) 168 | pp_string = self.signature + urlencode(pp_params) 169 | response = self._request(pp_string) 170 | response_params = self._parse_response(response) 171 | 172 | if settings.DEBUG: 173 | print 'PayPal Request:' 174 | pprint.pprint(defaults) 175 | print '\nPayPal Response:' 176 | pprint.pprint(response_params) 177 | 178 | # Gather all NVP parameters to pass to a new instance. 179 | nvp_params = {} 180 | for k, v in MergeDict(defaults, response_params).items(): 181 | if k in NVP_FIELDS: 182 | nvp_params[k] = v 183 | 184 | # PayPal timestamp has to be formatted. 185 | if 'timestamp' in nvp_params: 186 | nvp_params['timestamp'] = paypaltime2datetime(nvp_params['timestamp']) 187 | 188 | nvp_obj = PayPalNVP(**nvp_params) 189 | nvp_obj.init(self.request, params, response_params) 190 | nvp_obj.save() 191 | return nvp_obj 192 | 193 | def _request(self, data): 194 | """Moved out to make testing easier.""" 195 | return urllib2.urlopen(self.endpoint, data).read() 196 | 197 | def _check_and_update_params(self, required, params): 198 | """ 199 | Ensure all required parameters were passed to the API call and format 200 | them correctly. 201 | """ 202 | for r in required: 203 | if r not in params: 204 | raise PayPalError("Missing required param: %s" % r) 205 | 206 | # Upper case all the parameters for PayPal. 207 | return (dict((k.upper(), v) for k, v in params.iteritems())) 208 | 209 | def _parse_response(self, response): 210 | """Turn the PayPal response into a dict""" 211 | response_tokens = {} 212 | for kv in response.split('&'): 213 | key, value = kv.split("=") 214 | response_tokens[key.lower()] = urllib.unquote(value) 215 | return response_tokens -------------------------------------------------------------------------------- /pro/views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.template import RequestContext 4 | from django.shortcuts import render_to_response 5 | from django.http import HttpResponseRedirect 6 | from django.utils.http import urlencode 7 | 8 | from paypal.pro.forms import PaymentForm, ConfirmForm 9 | from paypal.pro.models import PayPalNVP 10 | from paypal.pro.helpers import PayPalWPP, TEST 11 | from paypal.pro.signals import payment_was_successful, payment_was_flagged 12 | 13 | 14 | # PayPal Edit IPN URL: 15 | # https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_profile-ipn-notify 16 | EXPRESS_ENDPOINT = "https://www.paypal.com/webscr?cmd=_express-checkout&%s" 17 | SANDBOX_EXPRESS_ENDPOINT = "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&%s" 18 | 19 | 20 | class PayPalPro(object): 21 | """ 22 | This class-based view takes care of PayPal WebsitePaymentsPro (WPP). 23 | PayPalPro has two separate flows - DirectPayment and ExpressPayFlow. In 24 | DirectPayment the user buys on your site. In ExpressPayFlow the user is 25 | direct to PayPal to confirm their purchase. PayPalPro implements both 26 | flows. To it create an instance using the these parameters: 27 | 28 | item: a dictionary that holds information about the item being purchased. 29 | 30 | For single item purchase (pay once): 31 | 32 | Required Keys: 33 | * amt: Float amount of the item. 34 | 35 | Optional Keys: 36 | * custom: You can set this to help you identify a transaction. 37 | * invnum: Unique ID that identifies this transaction. 38 | 39 | For recurring billing: 40 | 41 | Required Keys: 42 | * amt: Float amount for each billing cycle. 43 | * billingperiod: String unit of measure for the billing cycle (Day|Week|SemiMonth|Month|Year) 44 | * billingfrequency: Integer number of periods that make up a cycle. 45 | * profilestartdate: The date to begin billing. "2008-08-05T17:00:00Z" UTC/GMT 46 | * desc: Description of what you're billing for. 47 | 48 | Optional Keys: 49 | * trialbillingperiod: String unit of measure for trial cycle (Day|Week|SemiMonth|Month|Year) 50 | * trialbillingfrequency: Integer # of periods in a cycle. 51 | * trialamt: Float amount to bill for the trial period. 52 | * trialtotalbillingcycles: Integer # of cycles for the trial payment period. 53 | * failedinitamtaction: set to continue on failure (ContinueOnFailure / CancelOnFailure) 54 | * maxfailedpayments: number of payments before profile is suspended. 55 | * autobilloutamt: automatically bill outstanding amount. 56 | * subscribername: Full name of the person who paid. 57 | * profilereference: Unique reference or invoice number. 58 | * taxamt: How much tax. 59 | * initamt: Initial non-recurring payment due upon creation. 60 | * currencycode: defaults to USD 61 | * + a bunch of shipping fields 62 | 63 | payment_form_cls: form class that will be used to display the payment form. 64 | It should inherit from `paypal.pro.forms.PaymentForm` if you're adding more. 65 | 66 | payment_template: template used to ask the dude for monies. To comply with 67 | PayPal standards it must include a link to PayPal Express Checkout. 68 | 69 | confirm_form_cls: form class that will be used to display the confirmation form. 70 | It should inherit from `paypal.pro.forms.ConfirmForm`. It is only used in the Express flow. 71 | 72 | success_url / fail_url: URLs to be redirected to when the payment successful or fails. 73 | """ 74 | errors = { 75 | "processing": "There was an error processing your payment. Check your information and try again.", 76 | "form": "Please correct the errors below and try again.", 77 | "paypal": "There was a problem contacting PayPal. Please try again later." 78 | } 79 | 80 | def __init__(self, item=None, payment_form_cls=PaymentForm, 81 | payment_template="pro/payment.html", confirm_form_cls=ConfirmForm, 82 | confirm_template="pro/confirm.html", success_url="?success", 83 | fail_url=None, context=None, form_context_name="form"): 84 | self.item = item 85 | self.payment_form_cls = payment_form_cls 86 | self.payment_template = payment_template 87 | self.confirm_form_cls = confirm_form_cls 88 | self.confirm_template = confirm_template 89 | self.success_url = success_url 90 | self.fail_url = fail_url 91 | self.context = context or {} 92 | self.form_context_name = form_context_name 93 | 94 | def __call__(self, request): 95 | """Return the appropriate response for the state of the transaction.""" 96 | self.request = request 97 | if request.method == "GET": 98 | if self.should_redirect_to_express(): 99 | return self.redirect_to_express() 100 | elif self.should_render_confirm_form(): 101 | return self.render_confirm_form() 102 | elif self.should_render_payment_form(): 103 | return self.render_payment_form() 104 | else: 105 | if self.should_validate_confirm_form(): 106 | return self.validate_confirm_form() 107 | elif self.should_validate_payment_form(): 108 | return self.validate_payment_form() 109 | 110 | # Default to the rendering the payment form. 111 | return self.render_payment_form() 112 | 113 | def is_recurring(self): 114 | return self.item is not None and 'billingperiod' in self.item 115 | 116 | def should_redirect_to_express(self): 117 | return 'express' in self.request.GET 118 | 119 | def should_render_confirm_form(self): 120 | return 'token' in self.request.GET and 'PayerID' in self.request.GET 121 | 122 | def should_render_payment_form(self): 123 | return True 124 | 125 | def should_validate_confirm_form(self): 126 | return 'token' in self.request.POST and 'PayerID' in self.request.POST 127 | 128 | def should_validate_payment_form(self): 129 | return True 130 | 131 | def render_payment_form(self): 132 | """Display the DirectPayment for entering payment information.""" 133 | self.context[self.form_context_name] = self.payment_form_cls() 134 | return render_to_response(self.payment_template, self.context, RequestContext(self.request)) 135 | 136 | def validate_payment_form(self): 137 | """Try to validate and then process the DirectPayment form.""" 138 | form = self.payment_form_cls(self.request.POST) 139 | if form.is_valid(): 140 | success = form.process(self.request, self.item) 141 | if success: 142 | payment_was_successful.send(sender=self.item) 143 | return HttpResponseRedirect(self.success_url) 144 | else: 145 | self.context['errors'] = self.errors['processing'] 146 | 147 | self.context[self.form_context_name] = form 148 | self.context.setdefault("errors", self.errors['form']) 149 | return render_to_response(self.payment_template, self.context, RequestContext(self.request)) 150 | 151 | def get_endpoint(self): 152 | if TEST: 153 | return SANDBOX_EXPRESS_ENDPOINT 154 | else: 155 | return EXPRESS_ENDPOINT 156 | 157 | def redirect_to_express(self): 158 | """ 159 | First step of ExpressCheckout. Redirect the request to PayPal using the 160 | data returned from setExpressCheckout. 161 | """ 162 | wpp = PayPalWPP(self.request) 163 | nvp_obj = wpp.setExpressCheckout(self.item) 164 | if not nvp_obj.flag: 165 | pp_params = dict(token=nvp_obj.token, AMT=self.item['amt'], 166 | RETURNURL=self.item['returnurl'], 167 | CANCELURL=self.item['cancelurl']) 168 | pp_url = self.get_endpoint() % urlencode(pp_params) 169 | return HttpResponseRedirect(pp_url) 170 | else: 171 | self.context['errors'] = self.errors['paypal'] 172 | return self.render_payment_form() 173 | 174 | def render_confirm_form(self): 175 | """ 176 | Second step of ExpressCheckout. Display an order confirmation form which 177 | contains hidden fields with the token / PayerID from PayPal. 178 | """ 179 | initial = dict(token=self.request.GET['token'], PayerID=self.request.GET['PayerID']) 180 | self.context[self.form_context_name] = self.confirm_form_cls(initial=initial) 181 | return render_to_response(self.confirm_template, self.context, RequestContext(self.request)) 182 | 183 | def validate_confirm_form(self): 184 | """ 185 | Third and final step of ExpressCheckout. Request has pressed the confirmation but 186 | and we can send the final confirmation to PayPal using the data from the POST'ed form. 187 | """ 188 | wpp = PayPalWPP(self.request) 189 | pp_data = dict(token=self.request.POST['token'], payerid=self.request.POST['PayerID']) 190 | self.item.update(pp_data) 191 | 192 | # @@@ This check and call could be moved into PayPalWPP. 193 | if self.is_recurring(): 194 | success = wpp.createRecurringPaymentsProfile(self.item) 195 | else: 196 | success = wpp.doExpressCheckoutPayment(self.item) 197 | 198 | if success: 199 | payment_was_successful.send(sender=self.item) 200 | return HttpResponseRedirect(self.success_url) 201 | else: 202 | self.context['errors'] = self.errors['processing'] 203 | return self.render_payment_form() 204 | -------------------------------------------------------------------------------- /standard/forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django import forms 4 | from django.conf import settings 5 | from django.utils.safestring import mark_safe 6 | from paypal.standard.conf import * 7 | from paypal.standard.widgets import ValueHiddenInput, ReservedValueHiddenInput 8 | from paypal.standard.conf import (POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT, 9 | RECEIVER_EMAIL) 10 | 11 | 12 | # 20:18:05 Jan 30, 2009 PST - PST timezone support is not included out of the box. 13 | # PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", "%H:%M:%S %b %d, %Y PST",) 14 | # PayPal dates have been spotted in the wild with these formats, beware! 15 | PAYPAL_DATE_FORMAT = ("%H:%M:%S %b. %d, %Y PST", 16 | "%H:%M:%S %b. %d, %Y PDT", 17 | "%H:%M:%S %b %d, %Y PST", 18 | "%H:%M:%S %b %d, %Y PDT",) 19 | 20 | class PayPalPaymentsForm(forms.Form): 21 | """ 22 | Creates a PayPal Payments Standard "Buy It Now" button, configured for a 23 | selling a single item with no shipping. 24 | 25 | For a full overview of all the fields you can set (there is a lot!) see: 26 | http://tinyurl.com/pps-integration 27 | 28 | Usage: 29 | >>> f = PayPalPaymentsForm(initial={'item_name':'Widget 001', ...}) 30 | >>> f.render() 31 | u'
...' 32 | 33 | """ 34 | CMD_CHOICES = ( 35 | ("_xclick", "Buy now or Donations"), 36 | ("_cart", "Shopping cart"), 37 | ("_xclick-subscriptions", "Subscribe") 38 | ) 39 | SHIPPING_CHOICES = ((1, "No shipping"), (0, "Shipping")) 40 | NO_NOTE_CHOICES = ((1, "No Note"), (0, "Include Note")) 41 | RECURRING_PAYMENT_CHOICES = ( 42 | (1, "Subscription Payments Recur"), 43 | (0, "Subscription payments do not recur") 44 | ) 45 | REATTEMPT_ON_FAIL_CHOICES = ( 46 | (1, "reattempt billing on Failure"), 47 | (0, "Do Not reattempt on failure") 48 | ) 49 | 50 | # Where the money goes. 51 | business = forms.CharField(widget=ValueHiddenInput(), initial=RECEIVER_EMAIL) 52 | 53 | # Item information. 54 | amount = forms.IntegerField(widget=ValueHiddenInput()) 55 | item_name = forms.CharField(widget=ValueHiddenInput()) 56 | item_number = forms.CharField(widget=ValueHiddenInput()) 57 | quantity = forms.CharField(widget=ValueHiddenInput()) 58 | 59 | # Subscription Related. 60 | a1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Price 61 | p1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 Duration 62 | t1 = forms.CharField(widget=ValueHiddenInput()) # Trial 1 unit of Duration, default to Month 63 | a2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Price 64 | p2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 Duration 65 | t2 = forms.CharField(widget=ValueHiddenInput()) # Trial 2 unit of Duration, default to Month 66 | a3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Price 67 | p3 = forms.CharField(widget=ValueHiddenInput()) # Subscription Duration 68 | t3 = forms.CharField(widget=ValueHiddenInput()) # Subscription unit of Duration, default to Month 69 | src = forms.CharField(widget=ValueHiddenInput()) # Is billing recurring? default to yes 70 | sra = forms.CharField(widget=ValueHiddenInput()) # Reattempt billing on failed cc transaction 71 | no_note = forms.CharField(widget=ValueHiddenInput()) 72 | # Can be either 1 or 2. 1 = modify or allow new subscription creation, 2 = modify only 73 | modify = forms.IntegerField(widget=ValueHiddenInput()) # Are we modifying an existing subscription? 74 | 75 | # Localization / PayPal Setup 76 | lc = forms.CharField(widget=ValueHiddenInput()) 77 | page_style = forms.CharField(widget=ValueHiddenInput()) 78 | cbt = forms.CharField(widget=ValueHiddenInput()) 79 | 80 | # IPN control. 81 | notify_url = forms.CharField(widget=ValueHiddenInput()) 82 | cancel_return = forms.CharField(widget=ValueHiddenInput()) 83 | return_url = forms.CharField(widget=ReservedValueHiddenInput(attrs={"name":"return"})) 84 | custom = forms.CharField(widget=ValueHiddenInput()) 85 | invoice = forms.CharField(widget=ValueHiddenInput()) 86 | 87 | # Default fields. 88 | cmd = forms.ChoiceField(widget=forms.HiddenInput(), initial=CMD_CHOICES[0][0]) 89 | charset = forms.CharField(widget=forms.HiddenInput(), initial="utf-8") 90 | currency_code = forms.CharField(widget=forms.HiddenInput(), initial="USD") 91 | no_shipping = forms.ChoiceField(widget=forms.HiddenInput(), choices=SHIPPING_CHOICES, 92 | initial=SHIPPING_CHOICES[0][0]) 93 | 94 | def __init__(self, button_type="buy", *args, **kwargs): 95 | super(PayPalPaymentsForm, self).__init__(*args, **kwargs) 96 | self.button_type = button_type 97 | 98 | def render(self): 99 | return mark_safe(u""" 100 | %s 101 | 102 |
""" % (POSTBACK_ENDPOINT, self.as_p(), self.get_image())) 103 | 104 | 105 | def sandbox(self): 106 | return mark_safe(u"""
107 | %s 108 | 109 |
""" % (SANDBOX_POSTBACK_ENDPOINT, self.as_p(), self.get_image())) 110 | 111 | def get_image(self): 112 | return { 113 | (True, True): SUBSCRIPTION_SANDBOX_IMAGE, 114 | (True, False): SANDBOX_IMAGE, 115 | (False, True): SUBSCRIPTION_IMAGE, 116 | (False, False): IMAGE 117 | }[TEST, self.is_subscription()] 118 | 119 | def is_transaction(self): 120 | return self.button_type == "buy" 121 | 122 | def is_subscription(self): 123 | return self.button_type == "subscribe" 124 | 125 | 126 | class PayPalEncryptedPaymentsForm(PayPalPaymentsForm): 127 | """ 128 | Creates a PayPal Encrypted Payments "Buy It Now" button. 129 | Requires the M2Crypto package. 130 | 131 | Based on example at: 132 | http://blog.mauveweb.co.uk/2007/10/10/paypal-with-django/ 133 | 134 | """ 135 | def _encrypt(self): 136 | """Use your key thing to encrypt things.""" 137 | from M2Crypto import BIO, SMIME, X509 138 | # @@@ Could we move this to conf.py? 139 | CERT = settings.PAYPAL_PRIVATE_CERT 140 | PUB_CERT = settings.PAYPAL_PUBLIC_CERT 141 | PAYPAL_CERT = settings.PAYPAL_CERT 142 | CERT_ID = settings.PAYPAL_CERT_ID 143 | 144 | # Iterate through the fields and pull out the ones that have a value. 145 | plaintext = 'cert_id=%s\n' % CERT_ID 146 | for name, field in self.fields.iteritems(): 147 | value = None 148 | if name in self.initial: 149 | value = self.initial[name] 150 | elif field.initial is not None: 151 | value = field.initial 152 | if value is not None: 153 | # @@@ Make this less hackish and put it in the widget. 154 | if name == "return_url": 155 | name = "return" 156 | plaintext += u'%s=%s\n' % (name, value) 157 | plaintext = plaintext.encode('utf-8') 158 | 159 | # Begin crypto weirdness. 160 | s = SMIME.SMIME() 161 | s.load_key_bio(BIO.openfile(CERT), BIO.openfile(PUB_CERT)) 162 | p7 = s.sign(BIO.MemoryBuffer(plaintext), flags=SMIME.PKCS7_BINARY) 163 | x509 = X509.load_cert_bio(BIO.openfile(settings.PAYPAL_CERT)) 164 | sk = X509.X509_Stack() 165 | sk.push(x509) 166 | s.set_x509_stack(sk) 167 | s.set_cipher(SMIME.Cipher('des_ede3_cbc')) 168 | tmp = BIO.MemoryBuffer() 169 | p7.write_der(tmp) 170 | p7 = s.encrypt(tmp, flags=SMIME.PKCS7_BINARY) 171 | out = BIO.MemoryBuffer() 172 | p7.write(out) 173 | return out.read() 174 | 175 | def as_p(self): 176 | return mark_safe(u""" 177 | 178 | 179 | """ % self._encrypt()) 180 | 181 | 182 | class PayPalSharedSecretEncryptedPaymentsForm(PayPalEncryptedPaymentsForm): 183 | """ 184 | Creates a PayPal Encrypted Payments "Buy It Now" button with a Shared Secret. 185 | Shared secrets should only be used when your IPN endpoint is on HTTPS. 186 | 187 | Adds a secret to the notify_url based on the contents of the form. 188 | 189 | """ 190 | def __init__(self, *args, **kwargs): 191 | "Make the secret from the form initial data and slip it into the form." 192 | from paypal.standard.helpers import make_secret 193 | super(PayPalSharedSecretEncryptedPaymentsForm, self).__init__(self, *args, **kwargs) 194 | # @@@ Attach the secret parameter in a way that is safe for other query params. 195 | secret_param = "?secret=%s" % make_secret(self) 196 | # Initial data used in form construction overrides defaults 197 | if 'notify_url' in self.initial: 198 | self.initial['notify_url'] += secret_param 199 | else: 200 | self.fields['notify_url'].initial += secret_param 201 | 202 | 203 | class PayPalStandardBaseForm(forms.ModelForm): 204 | """Form used to receive and record PayPal IPN/PDT.""" 205 | # PayPal dates have non-standard formats. 206 | time_created = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) 207 | payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) 208 | next_payment_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) 209 | subscr_date = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) 210 | subscr_effective = forms.DateTimeField(required=False, input_formats=PAYPAL_DATE_FORMAT) -------------------------------------------------------------------------------- /pro/fields.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from calendar import monthrange 4 | from datetime import date 5 | 6 | from django.db import models 7 | from django import forms 8 | from django.utils.translation import ugettext as _ 9 | 10 | from paypal.pro.creditcard import verify_credit_card 11 | 12 | 13 | class CreditCardField(forms.CharField): 14 | """Form field for checking out a credit card.""" 15 | def __init__(self, *args, **kwargs): 16 | kwargs.setdefault('max_length', 20) 17 | super(CreditCardField, self).__init__(*args, **kwargs) 18 | 19 | def clean(self, value): 20 | """Raises a ValidationError if the card is not valid and stashes card type.""" 21 | self.card_type = verify_credit_card(value) 22 | if self.card_type is None: 23 | raise forms.ValidationError("Invalid credit card number.") 24 | return value 25 | 26 | 27 | # Credit Card Expiry Fields from: 28 | # http://www.djangosnippets.org/snippets/907/ 29 | class CreditCardExpiryWidget(forms.MultiWidget): 30 | """MultiWidget for representing credit card expiry date.""" 31 | def decompress(self, value): 32 | if value: 33 | return [value.month, value.year] 34 | else: 35 | return [None, None] 36 | 37 | def format_output(self, rendered_widgets): 38 | html = u' / '.join(rendered_widgets) 39 | return u'%s' % html 40 | 41 | class CreditCardExpiryField(forms.MultiValueField): 42 | EXP_MONTH = [(x, x) for x in xrange(1, 13)] 43 | EXP_YEAR = [(x, x) for x in xrange(date.today().year, date.today().year + 15)] 44 | 45 | default_error_messages = { 46 | 'invalid_month': u'Enter a valid month.', 47 | 'invalid_year': u'Enter a valid year.', 48 | } 49 | 50 | def __init__(self, *args, **kwargs): 51 | errors = self.default_error_messages.copy() 52 | if 'error_messages' in kwargs: 53 | errors.update(kwargs['error_messages']) 54 | 55 | fields = ( 56 | forms.ChoiceField(choices=self.EXP_MONTH, error_messages={'invalid': errors['invalid_month']}), 57 | forms.ChoiceField(choices=self.EXP_YEAR, error_messages={'invalid': errors['invalid_year']}), 58 | ) 59 | 60 | super(CreditCardExpiryField, self).__init__(fields, *args, **kwargs) 61 | self.widget = CreditCardExpiryWidget(widgets=[fields[0].widget, fields[1].widget]) 62 | 63 | def clean(self, value): 64 | exp = super(CreditCardExpiryField, self).clean(value) 65 | if date.today() > exp: 66 | raise forms.ValidationError("The expiration date you entered is in the past.") 67 | return exp 68 | 69 | def compress(self, data_list): 70 | if data_list: 71 | if data_list[1] in forms.fields.EMPTY_VALUES: 72 | error = self.error_messages['invalid_year'] 73 | raise forms.ValidationError(error) 74 | if data_list[0] in forms.fields.EMPTY_VALUES: 75 | error = self.error_messages['invalid_month'] 76 | raise forms.ValidationError(error) 77 | year = int(data_list[1]) 78 | month = int(data_list[0]) 79 | # find last day of the month 80 | day = monthrange(year, month)[1] 81 | return date(year, month, day) 82 | return None 83 | 84 | 85 | class CreditCardCVV2Field(forms.CharField): 86 | def __init__(self, *args, **kwargs): 87 | kwargs.setdefault('max_length', 4) 88 | super(CreditCardCVV2Field, self).__init__(*args, **kwargs) 89 | 90 | 91 | # Country Field from: 92 | # http://www.djangosnippets.org/snippets/494/ 93 | # http://xml.coverpages.org/country3166.html 94 | COUNTRIES = ( 95 | ('US', _('United States of America')), 96 | ('CA', _('Canada')), 97 | ('AD', _('Andorra')), 98 | ('AE', _('United Arab Emirates')), 99 | ('AF', _('Afghanistan')), 100 | ('AG', _('Antigua & Barbuda')), 101 | ('AI', _('Anguilla')), 102 | ('AL', _('Albania')), 103 | ('AM', _('Armenia')), 104 | ('AN', _('Netherlands Antilles')), 105 | ('AO', _('Angola')), 106 | ('AQ', _('Antarctica')), 107 | ('AR', _('Argentina')), 108 | ('AS', _('American Samoa')), 109 | ('AT', _('Austria')), 110 | ('AU', _('Australia')), 111 | ('AW', _('Aruba')), 112 | ('AZ', _('Azerbaijan')), 113 | ('BA', _('Bosnia and Herzegovina')), 114 | ('BB', _('Barbados')), 115 | ('BD', _('Bangladesh')), 116 | ('BE', _('Belgium')), 117 | ('BF', _('Burkina Faso')), 118 | ('BG', _('Bulgaria')), 119 | ('BH', _('Bahrain')), 120 | ('BI', _('Burundi')), 121 | ('BJ', _('Benin')), 122 | ('BM', _('Bermuda')), 123 | ('BN', _('Brunei Darussalam')), 124 | ('BO', _('Bolivia')), 125 | ('BR', _('Brazil')), 126 | ('BS', _('Bahama')), 127 | ('BT', _('Bhutan')), 128 | ('BV', _('Bouvet Island')), 129 | ('BW', _('Botswana')), 130 | ('BY', _('Belarus')), 131 | ('BZ', _('Belize')), 132 | ('CC', _('Cocos (Keeling) Islands')), 133 | ('CF', _('Central African Republic')), 134 | ('CG', _('Congo')), 135 | ('CH', _('Switzerland')), 136 | ('CI', _('Ivory Coast')), 137 | ('CK', _('Cook Iislands')), 138 | ('CL', _('Chile')), 139 | ('CM', _('Cameroon')), 140 | ('CN', _('China')), 141 | ('CO', _('Colombia')), 142 | ('CR', _('Costa Rica')), 143 | ('CU', _('Cuba')), 144 | ('CV', _('Cape Verde')), 145 | ('CX', _('Christmas Island')), 146 | ('CY', _('Cyprus')), 147 | ('CZ', _('Czech Republic')), 148 | ('DE', _('Germany')), 149 | ('DJ', _('Djibouti')), 150 | ('DK', _('Denmark')), 151 | ('DM', _('Dominica')), 152 | ('DO', _('Dominican Republic')), 153 | ('DZ', _('Algeria')), 154 | ('EC', _('Ecuador')), 155 | ('EE', _('Estonia')), 156 | ('EG', _('Egypt')), 157 | ('EH', _('Western Sahara')), 158 | ('ER', _('Eritrea')), 159 | ('ES', _('Spain')), 160 | ('ET', _('Ethiopia')), 161 | ('FI', _('Finland')), 162 | ('FJ', _('Fiji')), 163 | ('FK', _('Falkland Islands (Malvinas)')), 164 | ('FM', _('Micronesia')), 165 | ('FO', _('Faroe Islands')), 166 | ('FR', _('France')), 167 | ('FX', _('France, Metropolitan')), 168 | ('GA', _('Gabon')), 169 | ('GB', _('United Kingdom (Great Britain)')), 170 | ('GD', _('Grenada')), 171 | ('GE', _('Georgia')), 172 | ('GF', _('French Guiana')), 173 | ('GH', _('Ghana')), 174 | ('GI', _('Gibraltar')), 175 | ('GL', _('Greenland')), 176 | ('GM', _('Gambia')), 177 | ('GN', _('Guinea')), 178 | ('GP', _('Guadeloupe')), 179 | ('GQ', _('Equatorial Guinea')), 180 | ('GR', _('Greece')), 181 | ('GS', _('South Georgia and the South Sandwich Islands')), 182 | ('GT', _('Guatemala')), 183 | ('GU', _('Guam')), 184 | ('GW', _('Guinea-Bissau')), 185 | ('GY', _('Guyana')), 186 | ('HK', _('Hong Kong')), 187 | ('HM', _('Heard & McDonald Islands')), 188 | ('HN', _('Honduras')), 189 | ('HR', _('Croatia')), 190 | ('HT', _('Haiti')), 191 | ('HU', _('Hungary')), 192 | ('ID', _('Indonesia')), 193 | ('IE', _('Ireland')), 194 | ('IL', _('Israel')), 195 | ('IN', _('India')), 196 | ('IO', _('British Indian Ocean Territory')), 197 | ('IQ', _('Iraq')), 198 | ('IR', _('Islamic Republic of Iran')), 199 | ('IS', _('Iceland')), 200 | ('IT', _('Italy')), 201 | ('JM', _('Jamaica')), 202 | ('JO', _('Jordan')), 203 | ('JP', _('Japan')), 204 | ('KE', _('Kenya')), 205 | ('KG', _('Kyrgyzstan')), 206 | ('KH', _('Cambodia')), 207 | ('KI', _('Kiribati')), 208 | ('KM', _('Comoros')), 209 | ('KN', _('St. Kitts and Nevis')), 210 | ('KP', _('Korea, Democratic People\'s Republic of')), 211 | ('KR', _('Korea, Republic of')), 212 | ('KW', _('Kuwait')), 213 | ('KY', _('Cayman Islands')), 214 | ('KZ', _('Kazakhstan')), 215 | ('LA', _('Lao People\'s Democratic Republic')), 216 | ('LB', _('Lebanon')), 217 | ('LC', _('Saint Lucia')), 218 | ('LI', _('Liechtenstein')), 219 | ('LK', _('Sri Lanka')), 220 | ('LR', _('Liberia')), 221 | ('LS', _('Lesotho')), 222 | ('LT', _('Lithuania')), 223 | ('LU', _('Luxembourg')), 224 | ('LV', _('Latvia')), 225 | ('LY', _('Libyan Arab Jamahiriya')), 226 | ('MA', _('Morocco')), 227 | ('MC', _('Monaco')), 228 | ('MD', _('Moldova, Republic of')), 229 | ('MG', _('Madagascar')), 230 | ('MH', _('Marshall Islands')), 231 | ('ML', _('Mali')), 232 | ('MN', _('Mongolia')), 233 | ('MM', _('Myanmar')), 234 | ('MO', _('Macau')), 235 | ('MP', _('Northern Mariana Islands')), 236 | ('MQ', _('Martinique')), 237 | ('MR', _('Mauritania')), 238 | ('MS', _('Monserrat')), 239 | ('MT', _('Malta')), 240 | ('MU', _('Mauritius')), 241 | ('MV', _('Maldives')), 242 | ('MW', _('Malawi')), 243 | ('MX', _('Mexico')), 244 | ('MY', _('Malaysia')), 245 | ('MZ', _('Mozambique')), 246 | ('NA', _('Namibia')), 247 | ('NC', _('New Caledonia')), 248 | ('NE', _('Niger')), 249 | ('NF', _('Norfolk Island')), 250 | ('NG', _('Nigeria')), 251 | ('NI', _('Nicaragua')), 252 | ('NL', _('Netherlands')), 253 | ('NO', _('Norway')), 254 | ('NP', _('Nepal')), 255 | ('NR', _('Nauru')), 256 | ('NU', _('Niue')), 257 | ('NZ', _('New Zealand')), 258 | ('OM', _('Oman')), 259 | ('PA', _('Panama')), 260 | ('PE', _('Peru')), 261 | ('PF', _('French Polynesia')), 262 | ('PG', _('Papua New Guinea')), 263 | ('PH', _('Philippines')), 264 | ('PK', _('Pakistan')), 265 | ('PL', _('Poland')), 266 | ('PM', _('St. Pierre & Miquelon')), 267 | ('PN', _('Pitcairn')), 268 | ('PR', _('Puerto Rico')), 269 | ('PT', _('Portugal')), 270 | ('PW', _('Palau')), 271 | ('PY', _('Paraguay')), 272 | ('QA', _('Qatar')), 273 | ('RE', _('Reunion')), 274 | ('RO', _('Romania')), 275 | ('RU', _('Russian Federation')), 276 | ('RW', _('Rwanda')), 277 | ('SA', _('Saudi Arabia')), 278 | ('SB', _('Solomon Islands')), 279 | ('SC', _('Seychelles')), 280 | ('SD', _('Sudan')), 281 | ('SE', _('Sweden')), 282 | ('SG', _('Singapore')), 283 | ('SH', _('St. Helena')), 284 | ('SI', _('Slovenia')), 285 | ('SJ', _('Svalbard & Jan Mayen Islands')), 286 | ('SK', _('Slovakia')), 287 | ('SL', _('Sierra Leone')), 288 | ('SM', _('San Marino')), 289 | ('SN', _('Senegal')), 290 | ('SO', _('Somalia')), 291 | ('SR', _('Suriname')), 292 | ('ST', _('Sao Tome & Principe')), 293 | ('SV', _('El Salvador')), 294 | ('SY', _('Syrian Arab Republic')), 295 | ('SZ', _('Swaziland')), 296 | ('TC', _('Turks & Caicos Islands')), 297 | ('TD', _('Chad')), 298 | ('TF', _('French Southern Territories')), 299 | ('TG', _('Togo')), 300 | ('TH', _('Thailand')), 301 | ('TJ', _('Tajikistan')), 302 | ('TK', _('Tokelau')), 303 | ('TM', _('Turkmenistan')), 304 | ('TN', _('Tunisia')), 305 | ('TO', _('Tonga')), 306 | ('TP', _('East Timor')), 307 | ('TR', _('Turkey')), 308 | ('TT', _('Trinidad & Tobago')), 309 | ('TV', _('Tuvalu')), 310 | ('TW', _('Taiwan, Province of China')), 311 | ('TZ', _('Tanzania, United Republic of')), 312 | ('UA', _('Ukraine')), 313 | ('UG', _('Uganda')), 314 | ('UM', _('United States Minor Outlying Islands')), 315 | ('UY', _('Uruguay')), 316 | ('UZ', _('Uzbekistan')), 317 | ('VA', _('Vatican City State (Holy See)')), 318 | ('VC', _('St. Vincent & the Grenadines')), 319 | ('VE', _('Venezuela')), 320 | ('VG', _('British Virgin Islands')), 321 | ('VI', _('United States Virgin Islands')), 322 | ('VN', _('Viet Nam')), 323 | ('VU', _('Vanuatu')), 324 | ('WF', _('Wallis & Futuna Islands')), 325 | ('WS', _('Samoa')), 326 | ('YE', _('Yemen')), 327 | ('YT', _('Mayotte')), 328 | ('YU', _('Yugoslavia')), 329 | ('ZA', _('South Africa')), 330 | ('ZM', _('Zambia')), 331 | ('ZR', _('Zaire')), 332 | ('ZW', _('Zimbabwe')), 333 | ('ZZ', _('Unknown or unspecified country')), 334 | ) 335 | 336 | class CountryField(forms.ChoiceField): 337 | def __init__(self, *args, **kwargs): 338 | kwargs.setdefault('choices', COUNTRIES) 339 | super(CountryField, self).__init__(*args, **kwargs) -------------------------------------------------------------------------------- /standard/models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from django.db import models 4 | from django.conf import settings 5 | from paypal.standard.helpers import duplicate_txn_id, check_secret 6 | from paypal.standard.conf import RECEIVER_EMAIL, POSTBACK_ENDPOINT, SANDBOX_POSTBACK_ENDPOINT 7 | 8 | 9 | class PayPalStandardBase(models.Model): 10 | """Meta class for common variables shared by IPN and PDT: http://tinyurl.com/cuq6sj""" 11 | # @@@ Might want to add all these one distant day. 12 | # FLAG_CODE_CHOICES = ( 13 | # PAYMENT_STATUS_CHOICES = "Canceled_ Reversal Completed Denied Expired Failed Pending Processed Refunded Reversed Voided".split() 14 | # AUTH_STATUS_CHOICES = "Completed Pending Voided".split() 15 | # ADDRESS_STATUS_CHOICES = "confirmed unconfirmed".split() 16 | # PAYER_STATUS_CHOICES = "verified / unverified".split() 17 | # PAYMENT_TYPE_CHOICES = "echeck / instant.split() 18 | # PENDING_REASON = "address authorization echeck intl multi-currency unilateral upgrade verify other".split() 19 | # REASON_CODE = "chargeback guarantee buyer_complaint refund other".split() 20 | # TRANSACTION_ENTITY_CHOICES = "auth reauth order payment".split() 21 | 22 | # Transaction and Notification-Related Variables 23 | business = models.CharField(max_length=127, blank=True, help_text="Email where the money was sent.") 24 | charset=models.CharField(max_length=32, blank=True) 25 | custom = models.CharField(max_length=255, blank=True) 26 | notify_version = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 27 | parent_txn_id = models.CharField("Parent Transaction ID", max_length=19, blank=True) 28 | receiver_email = models.EmailField(max_length=127, blank=True) 29 | receiver_id = models.CharField(max_length=127, blank=True) # 258DLEHY2BDK6 30 | residence_country = models.CharField(max_length=2, blank=True) 31 | test_ipn = models.BooleanField(default=False, blank=True) 32 | txn_id = models.CharField("Transaction ID", max_length=19, blank=True, help_text="PayPal transaction ID.") 33 | txn_type = models.CharField("Transaction Type", max_length=128, blank=True, help_text="PayPal transaction type.") 34 | verify_sign = models.CharField(max_length=255, blank=True) 35 | 36 | # Buyer Information Variables 37 | address_country = models.CharField(max_length=64, blank=True) 38 | address_city = models.CharField(max_length=40, blank=True) 39 | address_country_code = models.CharField(max_length=64, blank=True, help_text="ISO 3166") 40 | address_name = models.CharField(max_length=128, blank=True) 41 | address_state = models.CharField(max_length=40, blank=True) 42 | address_status = models.CharField(max_length=11, blank=True) 43 | address_street = models.CharField(max_length=200, blank=True) 44 | address_zip = models.CharField(max_length=20, blank=True) 45 | contact_phone = models.CharField(max_length=20, blank=True) 46 | first_name = models.CharField(max_length=64, blank=True) 47 | last_name = models.CharField(max_length=64, blank=True) 48 | payer_business_name = models.CharField(max_length=127, blank=True) 49 | payer_email = models.CharField(max_length=127, blank=True) 50 | payer_id = models.CharField(max_length=13, blank=True) 51 | 52 | # Payment Information Variables 53 | auth_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 54 | auth_exp = models.CharField(max_length=28, blank=True) 55 | auth_id = models.CharField(max_length=19, blank=True) 56 | auth_status = models.CharField(max_length=9, blank=True) 57 | exchange_rate = models.DecimalField(max_digits=64, decimal_places=16, default=0, blank=True, null=True) 58 | invoice = models.CharField(max_length=127, blank=True) 59 | item_name = models.CharField(max_length=127, blank=True) 60 | item_number = models.CharField(max_length=127, blank=True) 61 | mc_currency = models.CharField(max_length=32, default="USD", blank=True) 62 | mc_fee = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 63 | mc_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 64 | mc_handling = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 65 | mc_shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 66 | memo = models.CharField(max_length=255, blank=True) 67 | num_cart_items = models.IntegerField(blank=True, default=0, null=True) 68 | option_name1 = models.CharField(max_length=64, blank=True) 69 | option_name2 = models.CharField(max_length=64, blank=True) 70 | payer_status = models.CharField(max_length=10, blank=True) 71 | payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 72 | payment_gross = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 73 | payment_status = models.CharField(max_length=9, blank=True) 74 | payment_type = models.CharField(max_length=7, blank=True) 75 | pending_reason = models.CharField(max_length=14, blank=True) 76 | protection_eligibility=models.CharField(max_length=32, blank=True) 77 | quantity = models.IntegerField(blank=True, default=1, null=True) 78 | reason_code = models.CharField(max_length=15, blank=True) 79 | remaining_settle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 80 | settle_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 81 | settle_currency = models.CharField(max_length=32, blank=True) 82 | shipping = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 83 | shipping_method = models.CharField(max_length=255, blank=True) 84 | tax = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 85 | transaction_entity = models.CharField(max_length=7, blank=True) 86 | 87 | # Auction Variables 88 | auction_buyer_id = models.CharField(max_length=64, blank=True) 89 | auction_closing_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 90 | auction_multi_item = models.IntegerField(blank=True, default=0, null=True) 91 | for_auction = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 92 | 93 | # Recurring Payments Variables 94 | amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 95 | amount_per_cycle = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 96 | initial_payment_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 97 | next_payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 98 | outstanding_balance = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 99 | payment_cycle= models.CharField(max_length=32, blank=True) #Monthly 100 | period_type = models.CharField(max_length=32, blank=True) 101 | product_name = models.CharField(max_length=128, blank=True) 102 | product_type= models.CharField(max_length=128, blank=True) 103 | profile_status = models.CharField(max_length=32, blank=True) 104 | recurring_payment_id = models.CharField(max_length=128, blank=True) # I-FA4XVST722B9 105 | rp_invoice_id= models.CharField(max_length=127, blank=True) # 1335-7816-2936-1451 106 | time_created = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 107 | 108 | # Subscription Variables 109 | amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 110 | amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 111 | amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 112 | mc_amount1 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 113 | mc_amount2 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 114 | mc_amount3 = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 115 | password = models.CharField(max_length=24, blank=True) 116 | period1 = models.CharField(max_length=32, blank=True) 117 | period2 = models.CharField(max_length=32, blank=True) 118 | period3 = models.CharField(max_length=32, blank=True) 119 | reattempt = models.CharField(max_length=1, blank=True) 120 | recur_times = models.IntegerField(blank=True, default=0, null=True) 121 | recurring = models.CharField(max_length=1, blank=True) 122 | retry_at = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 123 | subscr_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 124 | subscr_effective = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 125 | subscr_id = models.CharField(max_length=19, blank=True) 126 | username = models.CharField(max_length=64, blank=True) 127 | 128 | # Dispute Resolution Variables 129 | case_creation_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 130 | case_id = models.CharField(max_length=14, blank=True) 131 | case_type = models.CharField(max_length=24, blank=True) 132 | 133 | # Variables not categorized 134 | receipt_id= models.CharField(max_length=64, blank=True) # 1335-7816-2936-1451 135 | currency_code = models.CharField(max_length=32, default="USD", blank=True) 136 | handling_amount = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 137 | transaction_subject = models.CharField(max_length=255, blank=True) 138 | 139 | # @@@ Mass Pay Variables (Not Implemented, needs a separate model, for each transaction x) 140 | # fraud_managment_pending_filters_x = models.CharField(max_length=255, blank=True) 141 | # option_selection1_x = models.CharField(max_length=200, blank=True) 142 | # option_selection2_x = models.CharField(max_length=200, blank=True) 143 | # masspay_txn_id_x = models.CharField(max_length=19, blank=True) 144 | # mc_currency_x = models.CharField(max_length=32, default="USD", blank=True) 145 | # mc_fee_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 146 | # mc_gross_x = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 147 | # mc_handlingx = models.DecimalField(max_digits=64, decimal_places=2, default=0, blank=True, null=True) 148 | # payment_date = models.DateTimeField(blank=True, null=True, help_text="HH:MM:SS DD Mmm YY, YYYY PST") 149 | # payment_status = models.CharField(max_length=9, blank=True) 150 | # reason_code = models.CharField(max_length=15, blank=True) 151 | # receiver_email_x = models.EmailField(max_length=127, blank=True) 152 | # status_x = models.CharField(max_length=9, blank=True) 153 | # unique_id_x = models.CharField(max_length=13, blank=True) 154 | 155 | # Non-PayPal Variables - full IPN/PDT query and time fields. 156 | ipaddress = models.IPAddressField(blank=True) 157 | flag = models.BooleanField(default=False, blank=True) 158 | flag_code = models.CharField(max_length=16, blank=True) 159 | flag_info = models.TextField(blank=True) 160 | query = models.TextField(blank=True) # What we sent to PayPal. 161 | response = models.TextField(blank=True) # What we got back. 162 | created_at = models.DateTimeField(auto_now_add=True) 163 | updated_at = models.DateTimeField(auto_now=True) 164 | 165 | class Meta: 166 | abstract = True 167 | 168 | def __unicode__(self): 169 | if self.is_transaction(): 170 | return self.format % ("Transaction", self.txn_id) 171 | else: 172 | return self.format % ("Recurring", self.recurring_payment_id) 173 | 174 | def is_transaction(self): 175 | return len(self.txn_id) > 0 176 | 177 | def is_recurring(self): 178 | return len(self.recurring_payment_id) > 0 179 | 180 | def is_subscription_cancellation(self): 181 | return self.txn_type == "subscr_cancel" 182 | 183 | def is_subscription_end_of_term(self): 184 | return self.txn_type == "subscr_eot" 185 | 186 | def is_subscription_modified(self): 187 | return self.txn_type == "subscr_modify" 188 | 189 | def is_subscription_signup(self): 190 | return self.txn_type == "subscr_signup" 191 | 192 | def set_flag(self, info, code=None): 193 | """Sets a flag on the transaction and also sets a reason.""" 194 | self.flag = True 195 | self.flag_info += info 196 | if code is not None: 197 | self.flag_code = code 198 | 199 | def verify(self, item_check_callable=None): 200 | """ 201 | Verifies an IPN and a PDT. 202 | Checks for obvious signs of weirdness in the payment and flags appropriately. 203 | 204 | Provide a callable that takes an instance of this class as a parameter and returns 205 | a tuple (False, None) if the item is valid. Should return (True, "reason") if the 206 | item isn't valid. Strange but backward compatible :) This function should check 207 | that `mc_gross`, `mc_currency` `item_name` and `item_number` are all correct. 208 | 209 | """ 210 | self.response = self._postback() 211 | self._verify_postback() 212 | if not self.flag: 213 | if self.is_transaction(): 214 | if self.payment_status != "Completed": 215 | self.set_flag("Invalid payment_status. (%s)" % self.payment_status) 216 | if duplicate_txn_id(self): 217 | self.set_flag("Duplicate txn_id. (%s)" % self.txn_id) 218 | if self.receiver_email != RECEIVER_EMAIL: 219 | self.set_flag("Invalid receiver_email. (%s)" % self.receiver_email) 220 | if callable(item_check_callable): 221 | flag, reason = item_check_callable(self) 222 | if flag: 223 | self.set_flag(reason) 224 | else: 225 | # @@@ Run a different series of checks on recurring payments. 226 | pass 227 | 228 | self.save() 229 | self.send_signals() 230 | 231 | def verify_secret(self, form_instance, secret): 232 | """Verifies an IPN payment over SSL using EWP.""" 233 | if not check_secret(form_instance, secret): 234 | self.set_flag("Invalid secret. (%s)") % secret 235 | self.save() 236 | self.send_signals() 237 | 238 | def get_endpoint(self): 239 | """Set Sandbox endpoint if the test variable is present.""" 240 | if self.test_ipn: 241 | return SANDBOX_POSTBACK_ENDPOINT 242 | else: 243 | return POSTBACK_ENDPOINT 244 | 245 | def initialize(self, request): 246 | """Store the data we'll need to make the postback from the request object.""" 247 | self.query = getattr(request, request.method).urlencode() 248 | self.ipaddress = request.META.get('REMOTE_ADDR', '') 249 | 250 | def send_signals(self): 251 | """After a transaction is completed use this to send success/fail signals""" 252 | raise NotImplementedError 253 | 254 | def _postback(self): 255 | """Perform postback to PayPal and store the response in self.response.""" 256 | raise NotImplementedError 257 | 258 | def _verify_postback(self): 259 | """Check self.response is valid andcall self.set_flag if there is an error.""" 260 | raise NotImplementedError -------------------------------------------------------------------------------- /standard/ipn/migrations/0001_first_migration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | from south.db import db 4 | from paypal.standard.ipn.models import * 5 | 6 | 7 | class Migration: 8 | def forwards(self, orm): 9 | # Adding model 'PayPalIPN' 10 | db.create_table('paypal_ipn', ( 11 | ('id', models.AutoField(primary_key=True)), 12 | ('business', models.CharField(max_length=127, blank=True)), 13 | ('charset', models.CharField(max_length=32, blank=True)), 14 | ('custom', models.CharField(max_length=255, blank=True)), 15 | ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 16 | ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), 17 | ('receiver_email', models.EmailField(max_length=127, blank=True)), 18 | ('receiver_id', models.CharField(max_length=127, blank=True)), 19 | ('residence_country', models.CharField(max_length=2, blank=True)), 20 | ('test_ipn', models.BooleanField(default=False, blank=True)), 21 | ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), 22 | ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), 23 | ('verify_sign', models.CharField(max_length=255, blank=True)), 24 | ('address_country', models.CharField(max_length=64, blank=True)), 25 | ('address_city', models.CharField(max_length=40, blank=True)), 26 | ('address_country_code', models.CharField(max_length=64, blank=True)), 27 | ('address_name', models.CharField(max_length=128, blank=True)), 28 | ('address_state', models.CharField(max_length=40, blank=True)), 29 | ('address_status', models.CharField(max_length=11, blank=True)), 30 | ('address_street', models.CharField(max_length=200, blank=True)), 31 | ('address_zip', models.CharField(max_length=20, blank=True)), 32 | ('contact_phone', models.CharField(max_length=20, blank=True)), 33 | ('first_name', models.CharField(max_length=64, blank=True)), 34 | ('last_name', models.CharField(max_length=64, blank=True)), 35 | ('payer_business_name', models.CharField(max_length=127, blank=True)), 36 | ('payer_email', models.CharField(max_length=127, blank=True)), 37 | ('payer_id', models.CharField(max_length=13, blank=True)), 38 | ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 39 | ('auth_exp', models.CharField(max_length=28, blank=True)), 40 | ('auth_id', models.CharField(max_length=19, blank=True)), 41 | ('auth_status', models.CharField(max_length=9, blank=True)), 42 | ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), 43 | ('invoice', models.CharField(max_length=127, blank=True)), 44 | ('item_name', models.CharField(max_length=127, blank=True)), 45 | ('item_number', models.CharField(max_length=127, blank=True)), 46 | ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), 47 | ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 48 | ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 49 | ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 50 | ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 51 | ('memo', models.CharField(max_length=255, blank=True)), 52 | ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), 53 | ('option_name1', models.CharField(max_length=64, blank=True)), 54 | ('option_name2', models.CharField(max_length=64, blank=True)), 55 | ('payer_status', models.CharField(max_length=10, blank=True)), 56 | ('payment_date', models.DateTimeField(null=True, blank=True)), 57 | ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 58 | ('payment_status', models.CharField(max_length=9, blank=True)), 59 | ('payment_type', models.CharField(max_length=7, blank=True)), 60 | ('pending_reason', models.CharField(max_length=14, blank=True)), 61 | ('protection_eligibility', models.CharField(max_length=32, blank=True)), 62 | ('quantity', models.IntegerField(default=1, null=True, blank=True)), 63 | ('reason_code', models.CharField(max_length=15, blank=True)), 64 | ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 65 | ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 66 | ('settle_currency', models.CharField(max_length=32, blank=True)), 67 | ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 68 | ('shipping_method', models.CharField(max_length=255, blank=True)), 69 | ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 70 | ('transaction_entity', models.CharField(max_length=7, blank=True)), 71 | ('auction_buyer_id', models.CharField(max_length=64, blank=True)), 72 | ('auction_closing_date', models.DateTimeField(null=True, blank=True)), 73 | ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), 74 | ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 75 | ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 76 | ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 77 | ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 78 | ('next_payment_date', models.DateTimeField(null=True, blank=True)), 79 | ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 80 | ('payment_cycle', models.CharField(max_length=32, blank=True)), 81 | ('period_type', models.CharField(max_length=32, blank=True)), 82 | ('product_name', models.CharField(max_length=128, blank=True)), 83 | ('product_type', models.CharField(max_length=128, blank=True)), 84 | ('profile_status', models.CharField(max_length=32, blank=True)), 85 | ('recurring_payment_id', models.CharField(max_length=128, blank=True)), 86 | ('rp_invoice_id', models.CharField(max_length=127, blank=True)), 87 | ('time_created', models.DateTimeField(null=True, blank=True)), 88 | ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 89 | ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 90 | ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 91 | ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 92 | ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 93 | ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 94 | ('password', models.CharField(max_length=24, blank=True)), 95 | ('period1', models.CharField(max_length=32, blank=True)), 96 | ('period2', models.CharField(max_length=32, blank=True)), 97 | ('period3', models.CharField(max_length=32, blank=True)), 98 | ('reattempt', models.CharField(max_length=1, blank=True)), 99 | ('recur_times', models.IntegerField(default=0, null=True, blank=True)), 100 | ('recurring', models.CharField(max_length=1, blank=True)), 101 | ('retry_at', models.DateTimeField(null=True, blank=True)), 102 | ('subscr_date', models.DateTimeField(null=True, blank=True)), 103 | ('subscr_effective', models.DateTimeField(null=True, blank=True)), 104 | ('subscr_id', models.CharField(max_length=19, blank=True)), 105 | ('username', models.CharField(max_length=64, blank=True)), 106 | ('case_creation_date', models.DateTimeField(null=True, blank=True)), 107 | ('case_id', models.CharField(max_length=14, blank=True)), 108 | ('case_type', models.CharField(max_length=24, blank=True)), 109 | ('receipt_id', models.CharField(max_length=64, blank=True)), 110 | ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), 111 | ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 112 | ('transaction_subject', models.CharField(max_length=255, blank=True)), 113 | ('ipaddress', models.IPAddressField(blank=True)), 114 | ('flag', models.BooleanField(default=False, blank=True)), 115 | ('flag_code', models.CharField(max_length=16, blank=True)), 116 | ('flag_info', models.TextField(blank=True)), 117 | ('query', models.TextField(blank=True)), 118 | ('response', models.TextField(blank=True)), 119 | ('created_at', models.DateTimeField(auto_now_add=True)), 120 | ('updated_at', models.DateTimeField(auto_now=True)), 121 | ('from_view', models.CharField(max_length=6, null=True, blank=True)), 122 | )) 123 | db.send_create_signal('ipn', ['PayPalIPN']) 124 | 125 | def backwards(self, orm): 126 | # Deleting model 'PayPalIPN' 127 | db.delete_table('paypal_ipn') 128 | 129 | 130 | models = { 131 | 'ipn.paypalipn': { 132 | 'Meta': {'db_table': '"paypal_ipn"'}, 133 | 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), 134 | 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 135 | 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 136 | 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 137 | 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), 138 | 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), 139 | 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), 140 | 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), 141 | 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 142 | 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 143 | 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 144 | 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 145 | 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 146 | 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 147 | 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 148 | 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 149 | 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 150 | 'auth_exp': ('models.CharField', [], {'max_length': '28', 'blank': 'True'}), 151 | 'auth_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), 152 | 'auth_status': ('models.CharField', [], {'max_length': '9', 'blank': 'True'}), 153 | 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 154 | 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 155 | 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), 156 | 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), 157 | 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 158 | 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), 159 | 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), 160 | 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), 161 | 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 162 | 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), 163 | 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 164 | 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), 165 | 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), 166 | 'flag_info': ('models.TextField', [], {'blank': 'True'}), 167 | 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 168 | 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), 169 | 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 170 | 'id': ('models.AutoField', [], {'primary_key': 'True'}), 171 | 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 172 | 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 173 | 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), 174 | 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 175 | 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 176 | 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 177 | 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 178 | 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 179 | 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 180 | 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), 181 | 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 182 | 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 183 | 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 184 | 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 185 | 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 186 | 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 187 | 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 188 | 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 189 | 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 190 | 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 191 | 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 192 | 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), 193 | 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), 194 | 'payer_business_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 195 | 'payer_email': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 196 | 'payer_id': ('models.CharField', [], {'max_length': '13', 'blank': 'True'}), 197 | 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), 198 | 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 199 | 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 200 | 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 201 | 'payment_status': ('models.CharField', [], {'max_length': '9', 'blank': 'True'}), 202 | 'payment_type': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), 203 | 'pending_reason': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), 204 | 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 205 | 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 206 | 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 207 | 'period_type': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 208 | 'product_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 209 | 'product_type': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 210 | 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 211 | 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 212 | 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), 213 | 'query': ('models.TextField', [], {'blank': 'True'}), 214 | 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), 215 | 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), 216 | 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 217 | 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), 218 | 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 219 | 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 220 | 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), 221 | 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 222 | 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 223 | 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), 224 | 'response': ('models.TextField', [], {'blank': 'True'}), 225 | 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 226 | 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 227 | 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 228 | 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 229 | 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 230 | 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 231 | 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 232 | 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 233 | 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), 234 | 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 235 | 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), 236 | 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 237 | 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), 238 | 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 239 | 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), 240 | 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), 241 | 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), 242 | 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 243 | 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) 244 | } 245 | } 246 | 247 | complete_apps = ['ipn'] -------------------------------------------------------------------------------- /standard/pdt/migrations/0001_first_migration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from south.db import db 4 | from django.db import models 5 | from paypal.standard.pdt.models import * 6 | 7 | class Migration: 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'PayPalPDT' 12 | db.create_table('paypal_pdt', ( 13 | ('id', models.AutoField(primary_key=True)), 14 | ('business', models.CharField(max_length=127, blank=True)), 15 | ('charset', models.CharField(max_length=32, blank=True)), 16 | ('custom', models.CharField(max_length=255, blank=True)), 17 | ('notify_version', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 18 | ('parent_txn_id', models.CharField("Parent Transaction ID", max_length=19, blank=True)), 19 | ('receiver_email', models.EmailField(max_length=127, blank=True)), 20 | ('receiver_id', models.CharField(max_length=127, blank=True)), 21 | ('residence_country', models.CharField(max_length=2, blank=True)), 22 | ('test_ipn', models.BooleanField(default=False, blank=True)), 23 | ('txn_id', models.CharField("Transaction ID", max_length=19, blank=True)), 24 | ('txn_type', models.CharField("Transaction Type", max_length=128, blank=True)), 25 | ('verify_sign', models.CharField(max_length=255, blank=True)), 26 | ('address_country', models.CharField(max_length=64, blank=True)), 27 | ('address_city', models.CharField(max_length=40, blank=True)), 28 | ('address_country_code', models.CharField(max_length=64, blank=True)), 29 | ('address_name', models.CharField(max_length=128, blank=True)), 30 | ('address_state', models.CharField(max_length=40, blank=True)), 31 | ('address_status', models.CharField(max_length=11, blank=True)), 32 | ('address_street', models.CharField(max_length=200, blank=True)), 33 | ('address_zip', models.CharField(max_length=20, blank=True)), 34 | ('contact_phone', models.CharField(max_length=20, blank=True)), 35 | ('first_name', models.CharField(max_length=64, blank=True)), 36 | ('last_name', models.CharField(max_length=64, blank=True)), 37 | ('payer_business_name', models.CharField(max_length=127, blank=True)), 38 | ('payer_email', models.CharField(max_length=127, blank=True)), 39 | ('payer_id', models.CharField(max_length=13, blank=True)), 40 | ('auth_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 41 | ('auth_exp', models.CharField(max_length=28, blank=True)), 42 | ('auth_id', models.CharField(max_length=19, blank=True)), 43 | ('auth_status', models.CharField(max_length=9, blank=True)), 44 | ('exchange_rate', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=16, blank=True)), 45 | ('invoice', models.CharField(max_length=127, blank=True)), 46 | ('item_name', models.CharField(max_length=127, blank=True)), 47 | ('item_number', models.CharField(max_length=127, blank=True)), 48 | ('mc_currency', models.CharField(default='USD', max_length=32, blank=True)), 49 | ('mc_fee', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 50 | ('mc_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 51 | ('mc_handling', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 52 | ('mc_shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 53 | ('memo', models.CharField(max_length=255, blank=True)), 54 | ('num_cart_items', models.IntegerField(default=0, null=True, blank=True)), 55 | ('option_name1', models.CharField(max_length=64, blank=True)), 56 | ('option_name2', models.CharField(max_length=64, blank=True)), 57 | ('payer_status', models.CharField(max_length=10, blank=True)), 58 | ('payment_date', models.DateTimeField(null=True, blank=True)), 59 | ('payment_gross', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 60 | ('payment_status', models.CharField(max_length=9, blank=True)), 61 | ('payment_type', models.CharField(max_length=7, blank=True)), 62 | ('pending_reason', models.CharField(max_length=14, blank=True)), 63 | ('protection_eligibility', models.CharField(max_length=32, blank=True)), 64 | ('quantity', models.IntegerField(default=1, null=True, blank=True)), 65 | ('reason_code', models.CharField(max_length=15, blank=True)), 66 | ('remaining_settle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 67 | ('settle_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 68 | ('settle_currency', models.CharField(max_length=32, blank=True)), 69 | ('shipping', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 70 | ('shipping_method', models.CharField(max_length=255, blank=True)), 71 | ('tax', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 72 | ('transaction_entity', models.CharField(max_length=7, blank=True)), 73 | ('auction_buyer_id', models.CharField(max_length=64, blank=True)), 74 | ('auction_closing_date', models.DateTimeField(null=True, blank=True)), 75 | ('auction_multi_item', models.IntegerField(default=0, null=True, blank=True)), 76 | ('for_auction', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 77 | ('amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 78 | ('amount_per_cycle', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 79 | ('initial_payment_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 80 | ('next_payment_date', models.DateTimeField(null=True, blank=True)), 81 | ('outstanding_balance', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 82 | ('payment_cycle', models.CharField(max_length=32, blank=True)), 83 | ('period_type', models.CharField(max_length=32, blank=True)), 84 | ('product_name', models.CharField(max_length=128, blank=True)), 85 | ('product_type', models.CharField(max_length=128, blank=True)), 86 | ('profile_status', models.CharField(max_length=32, blank=True)), 87 | ('recurring_payment_id', models.CharField(max_length=128, blank=True)), 88 | ('rp_invoice_id', models.CharField(max_length=127, blank=True)), 89 | ('time_created', models.DateTimeField(null=True, blank=True)), 90 | ('amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 91 | ('amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 92 | ('amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 93 | ('mc_amount1', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 94 | ('mc_amount2', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 95 | ('mc_amount3', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 96 | ('password', models.CharField(max_length=24, blank=True)), 97 | ('period1', models.CharField(max_length=32, blank=True)), 98 | ('period2', models.CharField(max_length=32, blank=True)), 99 | ('period3', models.CharField(max_length=32, blank=True)), 100 | ('reattempt', models.CharField(max_length=1, blank=True)), 101 | ('recur_times', models.IntegerField(default=0, null=True, blank=True)), 102 | ('recurring', models.CharField(max_length=1, blank=True)), 103 | ('retry_at', models.DateTimeField(null=True, blank=True)), 104 | ('subscr_date', models.DateTimeField(null=True, blank=True)), 105 | ('subscr_effective', models.DateTimeField(null=True, blank=True)), 106 | ('subscr_id', models.CharField(max_length=19, blank=True)), 107 | ('username', models.CharField(max_length=64, blank=True)), 108 | ('case_creation_date', models.DateTimeField(null=True, blank=True)), 109 | ('case_id', models.CharField(max_length=14, blank=True)), 110 | ('case_type', models.CharField(max_length=24, blank=True)), 111 | ('receipt_id', models.CharField(max_length=64, blank=True)), 112 | ('currency_code', models.CharField(default='USD', max_length=32, blank=True)), 113 | ('handling_amount', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 114 | ('transaction_subject', models.CharField(max_length=255, blank=True)), 115 | ('ipaddress', models.IPAddressField(blank=True)), 116 | ('flag', models.BooleanField(default=False, blank=True)), 117 | ('flag_code', models.CharField(max_length=16, blank=True)), 118 | ('flag_info', models.TextField(blank=True)), 119 | ('query', models.TextField(blank=True)), 120 | ('response', models.TextField(blank=True)), 121 | ('created_at', models.DateTimeField(auto_now_add=True)), 122 | ('updated_at', models.DateTimeField(auto_now=True)), 123 | ('from_view', models.CharField(max_length=6, null=True, blank=True)), 124 | ('amt', models.DecimalField(default=0, null=True, max_digits=64, decimal_places=2, blank=True)), 125 | ('cm', models.CharField(max_length=255, blank=True)), 126 | ('sig', models.CharField(max_length=255, blank=True)), 127 | ('tx', models.CharField(max_length=255, blank=True)), 128 | ('st', models.CharField(max_length=32, blank=True)), 129 | )) 130 | db.send_create_signal('pdt', ['PayPalPDT']) 131 | 132 | 133 | 134 | def backwards(self, orm): 135 | 136 | # Deleting model 'PayPalPDT' 137 | db.delete_table('paypal_pdt') 138 | 139 | 140 | 141 | models = { 142 | 'pdt.paypalpdt': { 143 | 'Meta': {'db_table': '"paypal_pdt"'}, 144 | 'address_city': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), 145 | 'address_country': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 146 | 'address_country_code': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 147 | 'address_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 148 | 'address_state': ('models.CharField', [], {'max_length': '40', 'blank': 'True'}), 149 | 'address_status': ('models.CharField', [], {'max_length': '11', 'blank': 'True'}), 150 | 'address_street': ('models.CharField', [], {'max_length': '200', 'blank': 'True'}), 151 | 'address_zip': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), 152 | 'amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 153 | 'amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 154 | 'amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 155 | 'amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 156 | 'amount_per_cycle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 157 | 'amt': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 158 | 'auction_buyer_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 159 | 'auction_closing_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 160 | 'auction_multi_item': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 161 | 'auth_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 162 | 'auth_exp': ('models.CharField', [], {'max_length': '28', 'blank': 'True'}), 163 | 'auth_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), 164 | 'auth_status': ('models.CharField', [], {'max_length': '9', 'blank': 'True'}), 165 | 'business': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 166 | 'case_creation_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 167 | 'case_id': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), 168 | 'case_type': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), 169 | 'charset': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 170 | 'cm': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 171 | 'contact_phone': ('models.CharField', [], {'max_length': '20', 'blank': 'True'}), 172 | 'created_at': ('models.DateTimeField', [], {'auto_now_add': 'True'}), 173 | 'currency_code': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), 174 | 'custom': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 175 | 'exchange_rate': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}), 176 | 'first_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 177 | 'flag': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), 178 | 'flag_code': ('models.CharField', [], {'max_length': '16', 'blank': 'True'}), 179 | 'flag_info': ('models.TextField', [], {'blank': 'True'}), 180 | 'for_auction': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 181 | 'from_view': ('models.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}), 182 | 'handling_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 183 | 'id': ('models.AutoField', [], {'primary_key': 'True'}), 184 | 'initial_payment_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 185 | 'invoice': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 186 | 'ipaddress': ('models.IPAddressField', [], {'blank': 'True'}), 187 | 'item_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 188 | 'item_number': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 189 | 'last_name': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 190 | 'mc_amount1': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 191 | 'mc_amount2': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 192 | 'mc_amount3': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 193 | 'mc_currency': ('models.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}), 194 | 'mc_fee': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 195 | 'mc_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 196 | 'mc_handling': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 197 | 'mc_shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 198 | 'memo': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 199 | 'next_payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 200 | 'notify_version': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 201 | 'num_cart_items': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 202 | 'option_name1': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 203 | 'option_name2': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 204 | 'outstanding_balance': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 205 | 'parent_txn_id': ('models.CharField', ['"Parent Transaction ID"'], {'max_length': '19', 'blank': 'True'}), 206 | 'password': ('models.CharField', [], {'max_length': '24', 'blank': 'True'}), 207 | 'payer_business_name': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 208 | 'payer_email': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 209 | 'payer_id': ('models.CharField', [], {'max_length': '13', 'blank': 'True'}), 210 | 'payer_status': ('models.CharField', [], {'max_length': '10', 'blank': 'True'}), 211 | 'payment_cycle': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 212 | 'payment_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 213 | 'payment_gross': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 214 | 'payment_status': ('models.CharField', [], {'max_length': '9', 'blank': 'True'}), 215 | 'payment_type': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), 216 | 'pending_reason': ('models.CharField', [], {'max_length': '14', 'blank': 'True'}), 217 | 'period1': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 218 | 'period2': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 219 | 'period3': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 220 | 'period_type': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 221 | 'product_name': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 222 | 'product_type': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 223 | 'profile_status': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 224 | 'protection_eligibility': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 225 | 'quantity': ('models.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}), 226 | 'query': ('models.TextField', [], {'blank': 'True'}), 227 | 'reason_code': ('models.CharField', [], {'max_length': '15', 'blank': 'True'}), 228 | 'reattempt': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), 229 | 'receipt_id': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 230 | 'receiver_email': ('models.EmailField', [], {'max_length': '127', 'blank': 'True'}), 231 | 'receiver_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 232 | 'recur_times': ('models.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}), 233 | 'recurring': ('models.CharField', [], {'max_length': '1', 'blank': 'True'}), 234 | 'recurring_payment_id': ('models.CharField', [], {'max_length': '128', 'blank': 'True'}), 235 | 'remaining_settle': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 236 | 'residence_country': ('models.CharField', [], {'max_length': '2', 'blank': 'True'}), 237 | 'response': ('models.TextField', [], {'blank': 'True'}), 238 | 'retry_at': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 239 | 'rp_invoice_id': ('models.CharField', [], {'max_length': '127', 'blank': 'True'}), 240 | 'settle_amount': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 241 | 'settle_currency': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 242 | 'shipping': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 243 | 'shipping_method': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 244 | 'sig': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 245 | 'st': ('models.CharField', [], {'max_length': '32', 'blank': 'True'}), 246 | 'subscr_date': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 247 | 'subscr_effective': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 248 | 'subscr_id': ('models.CharField', [], {'max_length': '19', 'blank': 'True'}), 249 | 'tax': ('models.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}), 250 | 'test_ipn': ('models.BooleanField', [], {'default': 'False', 'blank': 'True'}), 251 | 'time_created': ('models.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 252 | 'transaction_entity': ('models.CharField', [], {'max_length': '7', 'blank': 'True'}), 253 | 'transaction_subject': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 254 | 'tx': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}), 255 | 'txn_id': ('models.CharField', ['"Transaction ID"'], {'max_length': '19', 'blank': 'True'}), 256 | 'txn_type': ('models.CharField', ['"Transaction Type"'], {'max_length': '128', 'blank': 'True'}), 257 | 'updated_at': ('models.DateTimeField', [], {'auto_now': 'True'}), 258 | 'username': ('models.CharField', [], {'max_length': '64', 'blank': 'True'}), 259 | 'verify_sign': ('models.CharField', [], {'max_length': '255', 'blank': 'True'}) 260 | } 261 | } 262 | 263 | complete_apps = ['pdt'] 264 | --------------------------------------------------------------------------------