4 Kasım 2013 Pazartesi

Paypal ipn with django

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. 192.168.1.1
    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 192.168.2.102:80
  3. Change 192.168.2.102:80 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. 

UPDATE

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. 
 

2 yorum: