Home » Odeon Blogs » Nai Chng »

Django PayPal Step-by-Step Integration

Django PayPal Step-by-Step Integration

Is this your first time integrating Paypal with Django? Can't get the signals to work? Don't know how to test django-paypal on your local machine? Hopefully, this step-by-step blog post will address all the above mentioned problems and more. This tutorial will cover only Paypal Standard IPN although most of the initial steps are applicable for the other payments methods as well.

1. Install django-paypal

There are a couple of version floating around namely: 
Cramer's is a fork from Boxall's version so it's more update. For this tutorial, we will be using Boxall because we found Boxall's first.
  1. git clone git://github.com/johnboxall/django-paypal.git paypal

2. Create a PayPal developer account

  1. Goto https://developer.paypal.com and sign up.
  2. Once you're in, create a preconfigured account
  3. Do this twice, one for account type Buyer and one for account type Seller
  4. Your dashboard should then look like this:

3. Modify settings.py file

  1. For the PAYPAL_RECEIVER_EMAIL, set this to the business account email you created in step 2. In this example, it is naiyun_1311845021_biz@od-eon.com
  2. After you have entered this, run syncdb to create the paypal tables
  1. # settings.py
  2. ...
  3. INSTALLED_APPS = (... 'paypal.standard.ipn', ...)
  4. ...
  5. PAYPAL_RECEIVER_EMAIL = "naiyun_1311845021_biz@od-eon.com" #change this to yours

4. Create a url

# urls.py

  1. from django.conf.urls.defaults import patterns, include, url
  2. urlpatterns = patterns('',
  3. # Examples:
  4. url(r'^$', 'cat.homepage.views.home', name='home'),
  5. url(r'^paypal/$', 'cat.homepage.views.paypal', name='paypal'),
  6. url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
  7. url(r'^admin/', include(admin.site.urls)),
  8. )
  9. urlpatterns += patterns('',
  10. (r'^something/hard/to/guess/', include('paypal.standard.ipn.urls')),
  11. )
  1. Replace cat.homepage with your project.appname

5. Create a view and template

  1. # views.py
  2. ...
  3. from paypal.standard.forms import PayPalPaymentsForm
  4. from django.conf import settings
  5. from django.core.urlresolvers import reverse
  6. def paypal(request):
  7. # What you want the button to do.
  8. paypal_dict = {
  9. "business": settings.PAYPAL_RECEIVER_EMAIL,
  10. "amount": "1.00",
  11. "item_name": "name of the item",
  12. "invoice": "unique-invoice-id",
  13. "notify_url": "%s%s" % (settings.SITE_NAME, reverse('paypal-ipn')),
  14. "return_url": "http://www.example.com/your-return-location/"
  15. "cancel_return": "http://www.example.com/your-cancel-location/",
  16. }
  17. # Create the instance.
  18. form = PayPalPaymentsForm(initial=paypal_dict)
  19. context = {"form": form.sandbox()}
  20. return render_to_response("paypal.html", context)
  21. # paypal.html
  22. {{ form }}
  1. Don't worry about what to put for settings.SITE_NAME first, we'll get to that in a bit
  2. The notify_url is the url that PayPal will try to send a POST request
  3. return_url is the page that Paypal redirects you to when the transaction is complete
  4. The form key in context renders a PayPal button. Right now we're using form.sandbox() to initiate the test process. Change this to form.render() to send the user to the real PayPal site
  5. You can add a "custom" key and value to the paypal_dict if you want to add a custom field later on
  6. If you're doing this test multiple times, you might run into an error message given by Paypal that says something like: This invoice has already been paid. For more information, please contact the merchant. To get around this, just change the invoice value everytime you try to make a purchase.

6. Now we fix the SITE_NAME

Sign up for an account at www.dyndns.com
  1. Add a new hostname. For service type, choose Host with IP address. Next, click on the link to populate the IP Address
  2. You should have something that looks like this:
  3. Add the Hostname to your settings.py file as SITE_NAME like so
  1. # settings.py
  2. ...
  3. SITE_NAME = 'http://nai.dyndns-free.com'
  4. ...

7. Run Django development server on your local IP

  1. For this to work, you need to configure your router to open port 80. The normal process is roughly like this:
    1. Goto your router IP e.g.
    2. Look for firewall or port forwarding
    3. Create an exception called 'django' or whatever with port 80 open
    4. Add this exception to the list of allowed applications that is tied to your computer
    5. Save
  2. Now, run Django's development server using sudo /etc/init.d/apache2 stop; sudo ./manage.py runserver
  3. Change to your own IP address which you can locate by running ifconfig in bash. Retain port 80
  4. If all goes well, you should be able to open up in your browser the hostname you created at dyndns. In my case, nai.dyndns-free.com/paypal looks like this:

8. What goes on under the hood?

When someone uses this button to buy something PayPal makes a HTTP POST to your "notify_url" which comes as part of the Paypal package. PayPal calls this Instant Payment Notification (IPN). The view paypal.standard.ipn.views.ipn handles IPN processing. We have already set the notify_url in step 4 as: urlpatterns += patterns('',(r'^something/hard/to/guess/', include('paypal.standard.ipn.urls')),)

When the notify_url is called, the ipn view is executed as shown below. You will need to include @csrf_exempt in the view as well

  1. @require_POST
  2. @csrf_exempt
  3. def ipn(request, item_check_callable=None):
  4. """
  5. PayPal IPN endpoint (notify_url).
  6. Used by both PayPal Payments Pro and Payments Standard to confirm transactions.
  7. http://tinyurl.com/d9vu9d
  8. PayPal IPN Simulator:
  9. https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session
  10. """
  11. flag = None
  12. ipn_obj = None
  13. form = PayPalIPNForm(request.POST)
  14. if form.is_valid():
  15. try:
  16. ipn_obj = form.save(commit=False)
  17. except Exception, e:
  18. flag = "Exception while processing. (%s)" % e
  19. else:
  20. flag = "Invalid form. (%s)" % form.errors
  21. if ipn_obj is None:
  22. ipn_obj = PayPalIPN()
  23. ipn_obj.initialize(request)
  24. if flag is not None:
  25. ipn_obj.set_flag(flag)
  26. else:
  27. # Secrets should only be used over SSL.
  28. if request.is_secure() and 'secret' in request.GET:
  29. ipn_obj.verify_secret(form, request.GET['secret'])
  30. else:
  31. try:
  32. ipn_obj.verify(item_check_callable)
  33. except Exception, e:
  34. flag = "Exception while processing. (%s)" % e
  35. ipn_obj.save()
  36. return HttpResponse("OKAY")

Just to elaborate a bit on the view, this line here ipn_obj.verify(item_check_callable) is the part that will invoke the sending of the signals. The verify() method can be found in paypal/models/standard/models.py which in turn calls the send_signals() method which can be found in paypal/models/standard/ipn/models.py

9. Final Stretch

So now we need to set up something to receive these signals. This can live anywhere in the project. The examples use models, so lets go with that.

  1. # models.py
  2. ...
  3. from paypal.standard.ipn.signals import payment_was_successful
  4. def show_me_the_money(sender, **kwargs):
  5. ipn_obj = sender
  6. # Undertake some action depending upon `ipn_obj`.
  7. if ipn_obj.custom == "Upgrade all users!":
  8. Users.objects.update(paid=True)
  9. print __file__,1, 'This works'
  10. payment_was_successful.connect(show_me_the_money)
  1. If everything works ok when we click the buy now button, console should print the line 'This works'
  2. So, go back to your domain/paypal page. Click on the buy button link, it should re-direct you to the Paypal sandbox page. Log in using the personal account you created in step 2. Mine is naiyun_1311850509_per@od-eon.com
  3. Go through the buying process and if everything works, you should see something like this in your console
  1. [05/Aug/2011 03:06:50] "GET /paypal/ HTTP/1.1" 200 1075
  2. /home/nai/GitProjects/cat/homepage/models.pyc 1 This works
  3. [05/Aug/2011 03:08:02] "POST /something/hard/to/guess/ HTTP/1.0" 200 4

Hope this tutorial has been helpful. If you have any questions, do leave them in the comments and I 'll do my best to answer them. 


Just in case anyone is running into DB related problems, django-paypal uses South to handle it's model creation. So running ./manage.py syncdb will *not* create the Paypal related tables. 

Category: Django

Tagged as: django django-paypal


  1. matt short on Sep 09, 2011 - 8:36 said:

    great tut, im just struggling with a single point in the signal part.

    whats the ipn_obj.custom attribute? where does it come from and why are you using "Upgrade All Users"?

  2. matt short on Sep 09, 2011 - 8:39 said:

    i think in part 8 youve got an extra models as second part of the urls too.

  3. Hi Matt, Glad you found it helpful.

    The ipn_obj.custom attribute actually comes from step 5. In addition to the keys provided by Paypal, there is one extra one called 'custom' which as you might guess allows you to pass your own variable. In this example, the custom value is 'Upgrade All Users' which was accidentally omitted from step 5. Apologies for that!

  4. Hi Nai,
    Thanks for this post --- it's been a great help. However, I'm not able to get the notification callbacks working properly. Am I right in thinking that when ipn(request, item_check_callable) is called, item_check_callable should be bound to the signal handler show_me_the_money? When I run the program, it's bound to None. The overall behavior I'm trying to fix is that execution gets as far as "ipn_obj.verify(item_check_callable)", then hangs---the PayPal testbed eventually complains that the connection was broken.

  5. Stanwin on Mar 20, 2012 - 22:15 said:

    Hi Nai Ching,

    Great Tutorial! however i went through the process and managed to buy something but i am getting this error.

    CSRF token missing or incorrect.

    Do i need to have a CSRF token inserted somewhere?

  6. @Stanwin - see the Django docs for CSRF handling (https://docs.djangoproject.com/en/dev/ref/contrib/csrf/). Are you using the older version of django-paypal? I grabbed the dcramer fork and it seemed to handle the csrf token in {{ form.render }}

  7. Scott Lobdell on Aug 06, 2012 - 20:27 said:

    Thank you!

  8. Thank you very much for the manual, I clarified much

  9. Will on Mar 28, 2013 - 11:55 said:

    This was really useful! I got stuck with local testing. The paypal IPN simulator only sends through to ports 80 or 443. Don't try and do things with :8000 etc when using it and try 443 if 80 isnt working (my ISP was preventing 80)

  10. AttributeError: 'Settings' object has no attribute 'PAYPAL_RECEIVER_EMAIL'

    I cloned the repo at the same level where i have my settings file.

    for eg:
    |- settings.py (file)
    |- paypal (dir)

  11. Adrien on Aug 28, 2013 - 8:36 said:

    Hey there!
    Your post was really helpful! But is it still valid though? It is dated from 2011...

    Anyway, step6 implies to create an account and this website isn't free... Do you have an other suggestion? I still don't manage to test it... :(

    Moreover, I don't understand the function show_me_the_money
    Once the payment is succesfull, I would like to add some "fake" money on a user account. At which step are we sending this information? (the user object with its account attribute I want to credit) In the context of the button views?

    Thanks a lot if you can answer some of my questions, great job!


  12. @Adrien

    This article is based on working with Paypal's Classic APIs.

    As long as Paypal's Classic APIs are still maintained by Paypal (they are), I don't see why it's not valid.

    However, with the release of Paypal's new REST SDK in 2013 (this year), that might be what you want to look at instead.

    See http://developer.paypal.com for information on the REST API.

  13. yen on Sep 25, 2013 - 11:47 said:

    Sorry I meant to say "REST API" not "REST SDK"

  14. I followed the tutorial but my ipn is getting flagged before it makes it to the payment_successful signal. Under the PayPalIpn model, the flag info field reads:

    Invalid form - no charset passed, can't decode

    This is coming from ipn/views.py where apparently charset is not being found in the ipn message sent by paypal. Any suggestions? I am sending the ipn via the ipn simulator on the paypal developer site.

  15. René Fleschenberg on Apr 04, 2014 - 3:46 said:

    +1, having the same problem as Jason.

Leave a Comment :




Page generated in: 0.90s