Paypal Instant Payment Notification (IPN)
If we put a Paypal button somewhere in a website, it is nice to know if the user got through the payment process properly and did not just canceled in the middle.
To achieve this, Paypal provides an asynchronous messaging service named Instant Payment Notification (IPN). Here is the process:
- we create a regular Paypal button and we add an extra hidden input named "custom" which value will be our tracking id (it can be the user id, or anything relevant to our case),
- when the user clicks on the button, he/she leaves our site and goes to Paypal, and once the payment is complete, Paypal notifies us by making a request to our IPN URL (this is an URL we declare in our Paypal settings),
- this request contains all the payment information including the custom variable (so we can know who paid what, how much, and store that in our system),
- to acknowledge the notification, we are supposed to do 2 things:
- reply with an empty content
- make a POST containing the very same information contained in the notification request plus "cmd=_notify-validate"
- and then Paypal answers to our POST by returning "VERIFIED".
How to do it with Rapido
Creating the Paypal button is easy, we just copy/paste the core provided by Paypal in a block (see the Rapido documentation to learn how to create blocks) and we add our "custom" variable:
pay.yaml:
elements:
trackingid:
type: BASIC
pay.py:
def trackingid(context):
currentUser = context.api.user.get_current()
return currentUser.getUserName() # or anything else relevant
pay.html:
<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="custom" value="{trackingid}"/>
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="XXXXXXXXXX">
<input type="image" src="https://www.sandbox.paypal.com/fr_FR/FR/i/btn/btn_paynowCC_LG.gif" border="0" name="submit" alt="PayPal, le réflexe sécurité pour payer en ligne">
<img alt="" border="0" src="https://www.sandbox.paypal.com/fr_FR/i/scr/pixel.gif" width="1" height="1">
</form>
Note: make sure we do not insert the full block form but just its children with our Diazo rule because we would get a form into a form, so the rule will be like that:
<after css:content="h1.documentFirstHeading">
<include href="@@rapido/myapp/block/pay"css:content-children="form[name='pay']"/>
</after>
Now let's see how to implement the listener that will get the Paypal notification. At some point, we will need to create a POST requests, so we need an extra library for that. We will use the well-known Python requests library, so we need to declare it safe for Rapido.
Hence we add the following somewhere in our custom theme module, for instance in __init__.py (or anywhere else):
import requests
from rapido.core import app
app.safe_modules.requests = requests
Warning: allowing to use a module like Requests in Rapido can have consequences regarding security, so if you do not control who is allowed to create Rapido apps (by default, only managers), do not do it.
And now, we create a block named ipn, containing an element named listener, the IPN URL we will declare to Paypal will be:
http://my.server.com/@@rapido/myapp/block/ipn/listener
Here is the implementation:
ipn.yaml:
elements:
listener:
type: BASIC
ipn.py:
def listener(context):
trackingid = context.request.form['custom']
paypal_params = {'cmd': '_notify-validate'}
# collect all the Paypal params
for key in context.request.form:
paypal_params[key] = context.request.form[key]
# acknowledge with a POST
req = context.modules.requests.post(
'https://www.sandbox.paypal.com/cgi-bin/webscr',
data=paypal_params)
# check if everthing is ok
if req.text == "VERIFIED":
mark_payment_has_done(trackingid)
# return an empty answer to the Paypal original request
return ''
That's it, that's just 10 lines of code (of course we will need to implement mark_payment_has_done() depending on our case inb order to persist in a record that the payment is done and verified).