There's a simple action to manage PayPal IPN notifications I developed for an Italian ISP (E4A) to handle VoIP recharges.
Why IPN is useful?
IPN is a very useful PayPal feature to allow automatic payments with immediate delivery of the chosen service/product. In my case a VoIP recharge, the user choses the amount, pays via PayPal and when PayPal notifies back to our servers the succesful payment, we automatically send the invoice and apply recharge to the VoIP account.
More informations about PayPal IPN: https://www.paypal.com/ipn
How IPN works
In order to make benefit of the IPN feature, you have to request payments from the users with a form like this:
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 | <table>
<tr>
<th>Accredito Ricarica</th>
<th>Costo Ricarica</th>
<th>Acquista</th>
</tr>
% for ricarica in h.ricariche_voip:
<td>${ricarica.accredito} Euro</td>
<td>${ricarica.costo} Euro</td>
<td>
<form action="https://${h.paypal_url}/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_xclick"/>
<input type="hidden" name="business" value="${h.paypal_business}"/>
<input type="hidden" name="item_name" value="Ricarica VoIP E4A ${ricarica.accredito} Euro"/>
<input type="hidden" name="item_number" value="${ricarica}"/>
<input type="hidden" name="amount" value="${ricarica.costo}"/>
<input type="hidden" name="shipping" value="0.00"/>
<input type="hidden" name="no_shipping" value="1"/>
<input type="hidden" name="no_note" value="1"/>
<input type="hidden" name="custom" value="${c.voipacct.whssid}"/>
<input type="hidden" name="currency_code" value="EUR"/>
<input type="hidden" name="tax" value="0.00"/>
<input type="hidden" name="lc" value="IT"/>
<input type="hidden" name="bn" value="PP-BuyNowBF"/>
<input type="hidden" name="charset" value="utf-8"/>
<input type="hidden" name="form-charset" value="utf-8"/>
<input type="hidden" name="return" value="${h.url_for(action='ricarica', qualified=True)}"/>
<input type="hidden" name="cancel_return" value="${h.url_for(action='ricarica', qualified=True)}"/>
<input type="hidden" name="notify_url" value="${h.url_for(action='ipn', qualified=True)}"/>
<input class="image" type="image" src="/pictures/paypal.png" border="0" name="submit" alt="PayPal" title="PayPal"/>
</form>
</td>
</tr>
%endfor
</table>
|
Most important field in this form for IPN is "notify_url", that's where PayPal servers notify the payment after successful transfer.
When this occurs, pylons receives a POST request directed toward this url with transaction data (variables reference: https://www.paypal.com/IntegrationCenter/ic_ipn-pdt-variable-reference.html
). In order to verify that this is a valid notification from PayPal, before continuing you should post data back to https://www.paypal.com/cgi-bin/webscr
and if response is "VERIFIED", you can safely continue with post-payment operations.
Code
e4acpanel/controller/voip.py
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 | from e4acpanel.lib.base import *
class VoipController(BaseController):
...
def ipn(self):
"PayPal Instant Payment Notification"
newpost = request.POST
newpost['cmd']=u'_notify-validate'
params = unicode_urlencode(newpost)
headers = { 'Content-type': 'application/x-www-form-urlencoded',
'Accept': 'text/plain',
'User-Agent': 'E4A Control Panel (http://ganimede.e4a.it)' }
conn = httplib.HTTPSConnection(h.paypal_url)
conn.request('POST', '/cgi-bin/webscr', params, headers)
response = conn.getresponse()
if not response.status == 200:
raise str(response.status) + ' ' + response.reason + ' while connecting to PayPal'
paymentStatus = response.read()
logFile = open('payments.log', 'a')
logFile.write('%s [%s] %s %s\n' % (
time.ctime(time.time()), params, paymentStatus, repr(newpost)))
logFile.close()
if paymentStatus != 'VERIFIED':
response.close()
raise 'IPN VoIP invalid'
if request.POST.get('business') != h.paypal_business:
response.close()
raise 'IPN VoIP invalid for ' + request.POST.get('business')
txn = request.POST.get('txn_id')
payer = request.POST.get('payer_email')
item_number = request.POST.get('item_number')
ammount = Decimal(request.POST.get('mc_gross'))
custom = request.POST.get('custom')
# send invoice and process recharge
# ...
response.close()
return 'thanks'
|
e4acpanel/lib/utils.py
Due to unicode.urlencode not managing unicode strings, I used a custom urlencode. I stole some code from Trac and adapted to work with Pylons:
1
2
3
4
5
6
7
8
9 | from urllib import urlencode
from paste.util.multidict import UnicodeMultiDict, MultiDict
def unicode_urlencode(params):
"""A unicode aware version of urllib.urlencode"""
if isinstance(params, dict) or isinstance(params, UnicodeMultiDict) or isinstance(params, MultiDict):
params = params.items()
return urlencode([(k, isinstance(v, unicode) and v.encode('utf-8') or v)
for k, v in params])
|
License