Latest Version: 0.9.6.2
  Dashboard > Pylons Cookbook > ... > Recipes > PayPal Instant Payment Notification (IPN)
  Pylons Cookbook Log In | Sign Up   View a printable version of the current page.  
  PayPal Instant Payment Notification (IPN)
Added by Matteo Pillon, last edited by Matteo Pillon on Jul 16, 2008  (view change)
Labels: 
(None)

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

This is part of e4acpanel, the control panel of E4A ISP.

e4acpanel is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 2 of the License.

e4acpanel is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with e4acpanel.  If not, see <http://www.gnu.org/licenses/>.

Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators
Top