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
|
Comments
By default, PayPal sends IPN notification encoded using windows-1252 - which means characters used outside of Europe will get lost or mangled.
You can tell PayPal to send IPN notifications in UTF8 instead. Here's the process:
If you don't do this, you'll need to tell Django to expect submissions in windows-1252, by setting request.encoding = 'windows-1252' at some point before the PayPal processing logic runs. It's a better idea to just tell PayPal to send UTF8 though.
#
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.
#
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!
#
does anyone managed to make express checkout work?
#
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:
Seemed to solve my issues..
#