Configuring with Nginx
Normally you can serve your applications directly to the web for tasks such as these, but some instances may require serving static files such as graphics or css. You can do this with circuits.web, but you can also use a webserver such as Nginx to serve both static files and your python applications. Primarily handy if you don’t wish link directly to a non-standard port.
A typical configuration for Nginx:
server { server_name apps.domain.com; root html/static_files; location / { try_files $uri $uri/ @app; } location @app { proxy_set_header X-Real-IP $remote_addr; proxy_pass http://unix:/opt/app/ipn/socket:/; # if you are not using a socket with your Python App use the line below instead # proxy_pass http://127.0.0.1:8000/; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } location ~ /\. { deny all; access_log off; log_not_found off; } }
The try_files directive is available on Nginx 0.7.26 and above. Its a very useful directive that allows you to test the request URI a number of ways before falling back to a catch-all location. With this a static file such as css, or javascript, or a folder such as /images, would be served directly by Nginx. Anything else not caught by location rules would be sent to @app to be proxied to your Python application.
You’ll also notice a few extras on the bottom of the server configuration. To cut down on access log clutter as well as erorr log clutter if you don’t have a favicon or robots.txt file ,you can specifically turn off the logging of those types of access. Also its always a good idea to deny access to any possible .hidden unix files that may exist in your document’s root path (such as an .htpasswd copied over from another host).
Uninterrupted Source
For your convenience, the complete example source has been pasted below in uninterrupted form.
#!/usr/bin/env python from time import time from urllib import urlencode from sqlite3 import connect, Error as sqerr from urllib2 import urlopen, Request from circuits.web import Controller, Server def verify_ipn(data): # prepares provided data set to inform PayPal we wish to validate the response data["cmd"] = "_notify-validate" params = urlencode(data) # sends the data and request to the PayPal Sandbox req = Request("""https://www.sandbox.paypal.com/cgi-bin/webscr""", params) req.add_header("Content-type", "application/x-www-form-urlencoded") # reads the response back from PayPal response = urlopen(req) status = response.read() # If not verified if not status == "VERIFIED": return False # if not the correct receiver ID if not data["receiver_id"] == "DDBSOMETHING4KE": return False # if not the correct currency if not data["mc_currency"] == "USD": return False # otherwise... return True class Root(Controller): # if the app will not be served at the root of the domain, uncomment the next line #channel = "/ipn" # index is invoked on the root path, or the designated channel URI def index(self, **data): # If there is no txn_id in the received arguments don't proceed if not "txn_id" in data: return "No Parameters" # Verify the data received with Paypal if not verify_ipn(data): return "Unable to Verify" # Suggested Check : check the item IDs and Prices to make sure they match with records # If verified, store desired information about the transaction reference = data["txn_id"] amount = data["mc_gross"] email = data["payer_email"] name = data["first_name"] + " " + data["last_name"] status = data["payment_status"] # Open a connection to a local SQLite database (use MySQLdb for MySQL, psycopg or PyGreSQL for PostgreSQL) conn = connect('db') curs = conn.cursor() try: curs.execute("""INSERT INTO ipn (id, purchased, txn, name, email, price, notes, status) VALUES (NULL, ?, ?, ?, ?, ?, NULL, ?)""", (time(), reference, name, email, amount, status,)) conn.commit() except sqerr, e: return "SQL Error: " + e.args[0] conn.close() # Alternatively you can generate license keys, email users login information # or setup accounts upon successful payment. The status will always be "Completed" on success. # Likewise you can revoke user access, if status is "Canceled", or another payment error. return "Success" def lookup(self, id): if not id: return ierr("No Transaction Provided") conn = connect('db') curs = conn.cursor() try: # Pulls a record from the database matching the transaction ID curs.execute("""SELECT name FROM ipn WHERE txn = ? LIMIT 1""", (id,)) row = curs.fetchone() ret = row[0] except sqerr, e: ret = ierr(e.args[0]) # The response will either by the name of the buyer, or a SQL error message return ret # Standard TCP method #(Server(("127.0.0.1", 9000)) + Root()).run() # Unix Socket Method - make sure webserver can read and write to the socket file (Server(("ipn.sock")) + Root()).run()
This is great! Thanks for this post. I am just starting python and this will help a lot.
Hello can I reference some of the content from this blog if I link back to you?
Sure, however I do not know what most of it has to do with free gift cards? (potential spam url was removed)
I highly recommend also using dcramer’s django-payapl branch:
http://github.com/dcramer/django-paypal
Course one would have to use django (which isn’t a bad thing if you’re already down that route).
I am tinkering with Paypal’s IPN, and I have a question about your code.
According to PayPal’s docs, the POST you send back to them must contain the variables in the exact same order as they were sent to you (with cmd=_notify-validate in the beginning).
In your code, you’re just adding a new item to the dictionary (thus you cannot predict in which order the final request will be formed). I suspect that in certain cases PayPal will refuse to accept your response, because the order of the values has changed.
Has that ever happened to you? Or are PayPal’s rules more relaxed than the docs say?
Has not happened to me, though one would assume that if unchanged the info goes right back to them in the same order. But I’ve never encountered that problem with either code.