PayPal IPN on Rails on OS X: trips and ticks

Rails is a very popular platform thanks to its fantastic feature set which lets developers take advantage of all kinds of best-practice and take benefit from the learning and experience of lots of developers who have gone through the same development process time and time again. Rails offers a way to implement features found in just about every web application without needing to redesign, re-code, etc. We end up with feature rich web applications and confidence that we’ve used tried and tested design patterns and robust code.

Developing on a Mac is a pleasure, as long as you’re comfortable with the command line. Although I came to the Mac only a few months ago I’m already very comfortable with it, and feeling frustration when try to set up similar tasks through the equivalent command shell on my Windows machine.

PayPal’s popularity almost makes it the de-facto standard payment gateway. There’s a wealth of supporting reference material from PayPal and also from the hosts of developers who have been kept busy implementing PayPal’s Instant Payment Notification (IPN) as a payment option in their apps or websites.

It’s disappointing that there are so many hoops a developer must jump through to be able to use and test PayPal’s IPN when developing in Rails on a Mac, behind a firewall. Surely I’m not the first to come across this problem?

I’ve jumped through the hoops. It needed stamina and ingenuity, and a good deal of help from colleagues, but I have a working set-up which I feel the need to share for the benefit of anyone else falling foul of the trips. Lets get those ticks in boxes…

First step in getting all your ducks in a row is setting up a PayPal sandbox account, including getting a set of credentials to use in the development config of your app. This lets you perform transactions without incurring real-world charges. (Of course after you’ve completed your raft of sandbox checks it’s always a good idea to make sure that it all works in the real world too.)

Sign up for a developer account at developer.paypal.com and set up some test accounts. You’ll need at least two: one to act as the business selling stuff and another to act as your customer buying stuff. You might want more if you need to differentiate between customers, or if you’re working with more than one business, but two is the least you’ll get away with. The sandbox account has the same features as a regular business, so you can set it up in the same way as your real business. One important thing to note here is that your sandbox account needs to have its IPN handler URL set to your workstation rather then the live address you’ll be using after launch. e.g. http://workstation.example.com/ipn

Note that PayPal’s documentation is notoriously poor, vague or misleading. I find it annoying to have to download huge PDF documents only to find that their content is outdated. (I’ve made a promise to myself that, if ever I’m required to publish documentation on-line for an app I’ve built, I will make the content clear and concise and I will show version numbers and/or dates on everything, just to make sure my readers don’t feel I’ve wasted their time). No doubt buried somewhere among PayPal’s docs is a line that says their IPN simulator does not work on any port other than 80 (the simulator makes no attempt to warn when you select a non-standard port – it just fails with a vague or misleading test result).Set up your app to use the credentials you just got from PayPal. Use settings in /config/environments/devlopment.rb

  PAYPAL_WEBSCR = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
  PAYPAL_BUSINESS = 'seller_1234567890_biz@example.com'

Set up DNS to send a specific domain or subdomain, e.g. workstation.example.com to your IP address. You may need to arrange for your ISP to give you a fixed IP adress, or be prepared to update the settings whenever your IP adress changes. This will bring *all* web traffic from anywhere in the world including untold numbers of undesirables, to your door, so make sure you have appropriate protection in place.

Having just warned you about possible intrusion, and the need for a good firewall, now I’m asking you to set up your firewall to pass incoming traffic on port 80 to your development workstation – Sorry! You may find your router or firewall allows filtering so you could limit traffic to a whitelist, or block traffic from a blacklist if the level of unwanted requests becomes tiresome.

Set your workstation to accept that subdomain as its own
in ‘/private/etc/hosts’

127.0.0.1 workstation.example.com

The Rails development server (WebBrick by default) by convention uses port 3000, and while it is possible to have it run up on port 80 this brings other problems. If you have your system prefs to allow ‘web sharing’ OS X will run apache on port 80. You can stop it, but when you try to run your WebBrick server on port 80 you’ll see that OS X protects this ‘system level’ port and requires you to run it with superuser permissions. Even then, simply issuing your regular rails server start command through sudo brings further warnings about needing to have the complete rails environment, and all the required gems, installed on the superuser account. Nobody in their right mind would be comfortable doing all this as root.

We get around this problem by letting Apache continue to handle HTTP traffic on port 80 as before, but to hand off selected requests to our Rails app on port 3000…

Set apache to forward anything on the workstation subdomain to your rails app: in /private/etc/apache2/extra/httpd-vhosts.conf

NameVirtualHost 127.0.0.1:80

<VirtualHost 127.0.0.1:80>
  ServerName workstation.example.com
  ProxyPass / http://127.0.0.1:3000/
  ProxyPassReverse / http://127.0.0.1:3000/
  ProxyPreserveHost On
</VirtualHost>

Restart apache to get the new config.

Of course you need superuser permissions to change the apache config.

># sudo apachectl restart

Start your rails development server.

You can check that it’s working by browsing to http://workstation.example.com If all is well your Rails app will appear, rather than the Apache page.

># rails s

Log in to PayPal’s sandbox.

The sandbox uses a cookie so you need to log in to the sandbox using the browser you’re going to use to do your testing. That way when your app makes its call to the PayPal sandbox the cookies will get passed and PayPal will be up to speed. You can test your app as normal and when you’re ready to pay with PayPal your ‘buy now’ button should use the development values given for PAYPAL_WEBSCR and PAYPAL_BUSINESS. On arrival at PayPal’s site you need to log in as your test buyer and complete the transaction.

Alternatively you can use the simulator within the sandbox to have PayPal send a test IPN message. My latest bugbear is that the simulator doesn’t know anything about subscription transactions, so although I can check that generic IPN requests are carried and my app can act upon them, I need my own tool to simulate the kind of request payloads that my app needs to handle with regard to subscriptions.

Write a handler for incoming IPN requests. It’s probably a good idea to have a controller with actions to handle the IPN and also to act as a landing place for the PayPal cancellation and confirmation navigations. Although the logic is probably quite portable across applications I’ll leave the actual handler content as an exercise for the reader.

class PaypalController < ApplicationController
  def ipn
    ...
  end
  def pp_confirm
    ...
  end
  def pp_cancel
    ...
  end
end

So. All your ducks are in a row. You can work in the PayPal sandbox from the comfort of your development environment.

So we have PayPal sending its IPN request to a specific domain on the standard port (80), and we have the traffic to that domain delivered to our office. We’re bringing that traffic to our development workstation and we have Apache hand it off to our Rails app running WebBrick (or other – you choose).

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>