Accepting and processing PayPal IPN messages (including using App Engine)

  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
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
"""
Classes for accepting PayPal's Instant Payment Notification messages in a 
Django application (or Django-on-App-Engine):

https://www.paypal.com/ipn

Usage:

from paypal import Endpoint  # Or AppEngineEndpoint as Endpoint

class MyEndPoint(Endpoint):
    def process(self, data):
        # Do something with valid data from PayPal - e-mail it to yourself,
        # stick it in a database, generate a license key and e-mail it to the
        # user... whatever
        
    def process_invalid(self, data):
        # Do something with invalid data (could be from anywhere) - you 
        # should probably log this somewhere

These methods can optionally return an HttpResponse - if they don't, a 
default response will be sent.

Then in urls.py:

    (r'^endpoint/$', MyEndPoint()),

"data" looks something like this:

{
    'business': 'your-business@example.com',
    'charset': 'windows-1252',
    'cmd': '_notify-validate',
    'first_name': 'S',
    'last_name': 'Willison',
    'mc_currency': 'GBP',
    'mc_fee': '0.01',
    'mc_gross': '0.01',
    'notify_version': '2.4',
    'payer_business_name': 'Example Ltd',
    'payer_email': 'payer@example.com',
    'payer_id': '5YKXXXXXX6',
    'payer_status': 'verified',
    'payment_date': '11:45:00 Aug 13, 2008 PDT',
    'payment_fee': '',
    'payment_gross': '',
    'payment_status': 'Completed',
    'payment_type': 'instant',
    'receiver_email': 'your-email@example.com',
    'receiver_id': 'CXZXXXXXQ',
    'residence_country': 'GB',
    'txn_id': '79F58253T2487374D',
    'txn_type': 'send_money',
    'verify_sign': 'AOH.JxXLRThnyE4toeuh-.oeurch23.QyBY-O1N'
}
"""
from django.http import HttpResponse
import urllib

class Endpoint:
    
    default_response_text = 'Nothing to see here'
    verify_url = "https://www.paypal.com/cgi-bin/webscr"
    
    def do_post(self, url, args):
        return urllib.urlopen(url, urllib.urlencode(args)).read()
    
    def verify(self, data):
        args = {
            'cmd': '_notify-validate',
        }
        args.update(data)
        return self.do_post(self.verify_url, args) == 'VERIFIED'
    
    def default_response(self):
        return HttpResponse(self.default_response_text)
    
    def __call__(self, request):
        r = None
        if request.method == 'POST':
            data = dict(request.POST.items())
            # We need to post that BACK to PayPal to confirm it
            if self.verify(data):
                r = self.process(data)
            else:
                r = self.process_invalid(data)
        if r:
            return r
        else:
            return self.default_response()
    
    def process(self, data):
        pass
    
    def process_invalid(self, data):
        pass

class AppEngineEndpoint(Endpoint):
    
    def do_post(self, url, args):
        from google.appengine.api import urlfetch
        return urlfetch.fetch(
            url = url,
            method = urlfetch.POST,
            payload = urllib.urlencode(args)
        ).content

More like this

  1. Commit on success unless managed decorator by Kronuz 4 years, 7 months ago
  2. Adding data in a transaction by hughdbrown 4 years, 9 months ago
  3. FormHandler - take the legwork out of form processing by zvoase 5 years, 6 months ago
  4. dumpdata/loaddata with MySQL and ForeignKeys (Revision 2) by cmgreen 6 years ago
  5. dumpdata/loaddata with MySQL and ForeignKeys by cmgreen 6 years, 3 months ago

Comments

simon (on August 22, 2008):

PayPal have an IPN simulation tool for testing, which you can use to send example requests to your endpoint URL:

https://developer.paypal.com/cgi-bin/devscr?cmd=_ipn-link-session

(You'll need to sign up for a PayPal developer account to use it)

Annoyingly, it doesn't look like it's possible to get that tool to send UTF8 rather than windows-1252.

#

pjs (on October 3, 2008):

Great snippet. Much cleaner than the paypalipn app I created.

One thing though, I think you should make the Endpoint class inherit 'object' at it's base..

class Endpoint(object):

So that when over writing methods like __init__ you can call super() without any problems.

For instance, I wanted to change the obj.verify_url when calling the class for testing purposes..

def __init__(self, *args, **kwargs): is_test = kwargs.pop('is_test', False) super(PaypalIPN, self).__init__(*args, **kwargs) if is_test: self.verify_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'

Without object as the base inheritance this raises a TypeError.

Thanks again!

#

aronchi (on November 11, 2008):

does anyone managed to make express checkout work?

#

pjs (on November 25, 2008):

I ran into Unicode errors when processing orders from some countries (Encode/Decode errors)..

I fixed it by replacing args.update(data) in the verify method with the following:

for k, v in data.items():
    args[k] = v.encode('utf-8')

Seemed to solve my issues..

#

davidfung (on July 17, 2013):

This snippet helps me to understand how IPN works, and eventually I am able to implement my own IPN handler (Django 1.6 on Python 2.6). Thank you.

One thing I am not sure is that how to make sure the cmd=_notify_validate is put in the beginning of the parameter list (as requested by PayPal) without the use of an OrderedDict. So I force it to the beginning by doing something similar to the following:

'cmd=_notify_validate&' + urllib.urlencode(data)

#

(Forgotten your password?)