I wrote recently on some [options](http://atastypixel.com/2009/12/19/store-options-for-the-indie-mac-developer/) I was considering for A Tasty Pixel’s [store](http://atastypixel.com/store). Rather than going with a service like Kagi or eSellerate, I decided to build the store upon Potion Factory’s [Potion Store](http://www.potionfactory.com/potionstore), which is free and open-source — the latter being a big draw, as it means infinite customisability. Potion Factory have done a wonderful thing for the indie Mac developer community! Together with PayPal’s ‘Website Payments Standard’ service, which offers quite reasonable fees, all my needs were addressed.
So, I thought I’d follow up by writing about my experiences putting it all together.
Step 1: Learning curve
Potion Store is written in Ruby on Rails. I’d only ever toyed with Ruby before, and had never played with Rails at all, so I decided to put some time in to getting a stronger handle on it before I dived in.
My first port of call was the [Ruby on Rails documentation page](http://rubyonrails.org/documentation), which led me to [why’s (poignant) guide to Ruby](http://mislav.uniqpath.com/poignant-guide/), the most astonishingly entertaining programming guide I’ve ever seen. It manages to be absurdly random and wandering, while firmly solidifying concepts and linking them with easy to remember mnemonics. Unlike that last sentence.
Very impressive, and well worth the read for the entertainment factor alone. It appears to be unfinished, so I’ll be keeping an eager eye out for the last chapters to find out how it ends.
Armed with that knowledge, albeit struggling slightly to rediscover my grip on reality, I took the Ruby on Rails [Getting Started](http://guides.rubyonrails.org/getting_started.html) tutorial, which takes you through setting up a basic project and putting a few things together. As a Mac developer, the concepts were comfortingly familiar, although Rails does introduce quite a good deal of [magic](http://guides.rubyonrails.org/active_record_querying.html), which starts off being quite challenging, and ends up being earth-shakingly fantastic.
Step 2: Setting up the test and development environment locally
I was pleasantly surprised to find that Rails applications are entirely self-contained for development purposes: A Rails project contains all the frameworks it needs, including a server, which can be started on the command line with script/server
.
I had some hassles setting up the development environment under Snow Leopard, however. I have a MySQL installation under Fink that I’ve kept around since Leopard, and it’s 32 bit. This causes trouble with the 64-bit Ruby installation, but the error messages one gets are rather unhelpful:
uninitialized constant MysqlCompat::MysqlRes
The easiest solution would be to download and install MySQL 64-bit, but being short on download quota (mobile broadband in a developing country), that wasn’t a great option.
The end solution was to run ruby in 32-bit mode, which means it works with the 32-bit MySQL libraries:
- Rename
ruby
in/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin
toruby.real
, then symlink/usr/bin/arch
toruby
. This causesarch
, the utility which lets you select the architecture to run an application under, to be called instead. - Create a property list (plist) called
environment.plist
in a folder in your home directory called.MacOSX
- If you use Property List Editor, add a string key called
ARCHPREFERENCE
, with a value ofruby:/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby.real:i386
- Otherwise, the file’s contents should be:
- If you use Property List Editor, add a string key called
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>ARCHPREFERENCE</key> <string>ruby:/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby.real:i386</string> </dict> </plist> |
- Log out, then log back in again, and all should be well
Step 3: Coding
By far the biggest time-consumer was implementing the features I needed into Potion Store:
- PayPal Website Payments Standard support
- Uses PayPal’s Payment Data Transfer feature to bring the customer back to the store and integrate with Google Analytics, and Instant Payment Notification (with all the proper verification) to actually process the order.
- Embeds all order information in PayPal item number, so no order caching is necessary (one caveat: As the coupons are embedded in the item number, which has a maximum length of 127 characters, coupon codes should be relatively short)
- Regional tax calculation and reporting
- Specify tax rates per country or per state in
config/store.yml
, and these will be selected automatically based on the customer’s location, obtained from their IP address. - Includes tax details in receipt
- Specify tax rates per country or per state in
- Multiple currency support, for adjusting price per-currency, tied to the customer’s location.
- Set default currency, and currency for admin reporting in
config/store.yml
- Reports in admin will be in the specified reporting currency; amounts converted automatically
- Automatically selects currency based on customer’s location (from IP address)
- Automatically looks up exchange rates using Xavier Media’s currency API
- Manually specify regional prices for products, or let the store convert from the price in the default currency automatically
- Set default currency, and currency for admin reporting in
There are a couple of minor improvements too; support for running from a sub-directory instead of the webroot, extra unit tests, some formatting/javascript improvements, lengthened serial number field, and some other bits and pieces.
I took the time to implement this in a reusable way, unit tests and all, so I’ve made my changes available. Hopefully they’ll make their way back into Potion Store.
You can access the [fork at Github](http://github.com/michaeltyson/potionstore).
Read more about the PayPal WPS feature in [paypal_wps.yml](http://github.com/michaeltyson/potionstore/blob/master/config/paypal_wps.yml), and see how tax and currency is configured in [store.yml](http://github.com/michaeltyson/potionstore/blob/master/config/store.yml).
To install it from scratch, get a fresh git clone: git clone http://github.com/michaeltyson/potionstore.git store
To switch your current repository to the fork, I think this should do it (I’m a git dilettante, but I found [this](http://snipplr.com/view/16263/switch-to-a-remote-github-branch-or-fork/):
# add reference to remote branch 'michaeltyson' git remote add michaeltyson git://github.com/michaeltyson/potionstore.git # add branch named (-b) 'michaeltyson' and change to it (checkout) git checkout -b michaeltyson # confirm that michaeltyson is active git branch # pull branch called 'master' from the remote reference called 'michaeltyson' git pull michaeltyson master |
Or, you can use git cherry-pick
.
Step 4: Deployment
My web hosting company is Site5, who have [excellent Rails support](http://www.site5.com/blog/programming/ruby-rails/how-to-deploy-phusion-passenger-to-a-subdirectory-routing-errors-and-restarting/20090414/). Once I discovered the existence of Phusion Passenger, everything went very smoothly.
The one caveat was that I wanted the store to be a sub-folder of the main site, not a separate subdomain. A little modification of Potion Store was necessary to make this work:
- Add
config.action_controller.relative_url_root = "/store"
toconfig/environment.rb
, inside theRails::Initializer.run do |config|
block. There are lots of resources out there that give conflicting instructions — this appears to be the proper way to configure Rails. - Fix all asset links (images, css, etc.) to use the proper access methods (
stylesheet_path
,image_path
, etc), so that they reference the right paths - Adjust the routing a little, within
config/routes.rb
:
Replace:
map.connect 'store', :controller => "store/order" map.connect '', :controller => "store/order" |
with:
map.root :controller => 'store/order' map.connect 'order/:action', :controller => 'store/order' map.connect 'notification/:action', :controller => 'store/notification' map.connect 'lost_license/:action', :controller => 'store/lost_license' map.connect 'products/:action', :controller => 'store/products' |
That should do it.
The rest of the setup process was:
- Used
rsync
to upload the store software into a folder underneath my home directory, out of the web root - Made a symlink from the
public
directory tostore
within the web root. - Edited the
.htaccess
file inpublic
to block everyone but me and the PayPal test server (216.113.191.33), based upon IP:
ErrorDocument 403 http://atastypixel.com/store-coming-soon Order deny,allow Deny from all Allow from 196.203.36. 216.113.191.33
- Set up [Passenger](http://www.site5.com/blog/programming/ruby-rails/how-to-deploy-phusion-passenger-to-a-subdirectory-routing-errors-and-restarting/20090414/) in the
.htaccess
:
PassengerEnabled on PassengerAppRoot /home/tysonid/webapps/atastypixel-store RailsEnv development
- Firmed up security a bit by adding HTTP authentication for the admin section, in
.htaccess
(this assumes an.htpasswd
file is already set up):
<Files admin> AuthUserFile /home/my-account/.htpasswd AuthName "Store Administration" AuthType Basic require valid-user </Files>
- Set up the database, and migrated –
rake db:migrate
from the command line located within the Potion Store folder. - Configured the PayPal account (further instructions available in
config/paypal_wps.yml
in my Potion Store modifications, which will hopefully be available soon):- Enabled Instant Payment Notification
- Enabled Payment Data Transfer
- Configured Australian GST tax settings
- Added balances for USD, AUD, EUR and GBP
Step 6: Registration and licensing
I opted to build upon [CocoaFob](http://www.turbodad.com/articles/48/cocoafob-released) for serial number generation and validation. CocoaFob is a freely available set of code snippets which perform DSA key generation/validation.
This works by using a private key — which you generate and keep secret — to sign a piece of text, such as a product code and registration name. The generated signature forms the basis of your serial number, and is used with your public key to verify that it was the private key (i.e., you) that made the signature.
Integration is fairly straightforward and well documented — including how to integrate into Potion Store. I made some fairly significant changes, partly due to my own obsessive-compulsive need for beautiful, clean code, and partly so that my system was unique.
There’s always a danger in using a well-known (especially open source) validation engine — if it’s cracked once, it’ll be cracked for all software that uses it. So, making it unique means a would-be cracker must analyse your engine individually. At least, that’s how I understand it.
One possible vulnerability that a DSA-based verification engine has is the potential for a cracker to replace the public key with their own, and thus be able to use their own private key to generate a signature whenever they want. To a certain extent, it’s impossible to completely guard against this — at least, not until we have code signing facilities — but it can be made difficult. Some notes:
- The first thing which is probably fairly obvious is to embed the public key in your app, not keep it as a separate file in the application’s resources.
- The second is to obfuscate it a little — at least by removing the obvious ‘BEGIN PUBLIC KEY…END PUBLIC KEY’ marker. The CocoaFob verifier class includes a utility method (
completePublicKeyPEM:
) to reattach this marker at run-time. - To make it a little harder for a would-be cracker, I used my private key to sign the public key, and included that signature, and an integrity check routine to verify the public key. This means that if the public key is replaced, an attacker would need to replace the signature as well — which means finding it.
- Finally, I included good and bad sample license keys, and added a check that each is correctly identified as good or bad.
Of course, this won’t make the system immune to mischief, and certainly doesn’t provide protection against code injection, but it makes things harder for a would-be attacker.
Finally, I didn’t want to mess about with having a serial number and a separate registration name, which is what the CocoaFob documentation suggests. Instead, I wrapped the registration name inside the serial number, so it’s all one, and easy to handle.
A little bit of interface programming in the application, and it was done.
Step 7: Testing
This required the setup of a [PayPal sandbox account](http://developer.paypal.com), and the creation of a test seller & test buyer. I ran through the purchase process a few times, and made sure everything behaved as expected.
Rails’ excellent unit testing framework came in very handy, to make sure each of the components functioned as planned.
For those using Site5, remember to touch app_path/tmp/restart.txt
after making any changes, in order to restart the application.
Step 8: Go live
When I was satisfied that everything was working properly, I removed the redirection from the .htaccess
, and opened up the store.
Summary
It took me about a week to get the store going, mostly due to the Ruby learning curve — one I’m happy to have climbed — but I’m happy with the result. Having an open source system is fantastic, as I’m able to have any functionality I can dream up, rather than be at the mercy of a service provider.
See it in action [here](http://atastypixel.com/store)
Glad to hear everything went smooth! Let me know if you run into any problems!
Hello Michael, I’m using also site5 .. but the only thing i can’t setup is the email sending after sucessfull order.. i tried delivery_method = :smtp also delivery_method = :sendmail but in vain. What is your config for the environment.rb .. Thank You !!
This is a great article !!!!
actually i succeed to send mail but from the admin/orders but there is no mail on successful order. i don’t have any idea where to look . I’m not in production (is it possible the emails on success to be sent only in production).
Hi Kevin – glad to hear you got it. And yes, your guess is right – test and development modes disable email, for some bizarre reason.
Hi Michael, Thank You for your answer .. And great article !
Godspeed !
Hi Michael,
I have everything working (i think) except, when I try to add an order manually from the admin page, I always get this error:
ActionView::TemplateError (You have a nil object when you didn’t expect it! You might have expected an instance of Array. The error occurred while evaluating nil.inject) on line #27 of app/views/admin/orders/_form.rhtml: 24: 25: 26: Currency: 27: @order.currency.code %> 28: 29: 30: Items:
Do you have any idea how i can fix this? also, one other question: I have no idea if my licence generator is working, as no emails are sent out in development mode…. is there a way I can test that? (sorry for the noobie question, but the docs are sparse)
Hi Russell,
Hmm, have you got at least the basic currency configured in store.yml? Something like:
Yes, that surprised me too – in development/test modes, no emails are sent. I’m not 100% how to make this happen, but a good place to start is making sure you’ve got
production.rb
‘sconfig.action_mailer.delivery_method
set to something like:sendmail
. If you can’t figure it out, the path of least resistance may be putting it into production mode.I fixed it!
…temporary blindness… I missed the log error directly before the error posted; and, my dsa key locations were defined wrongly in “envioronments.rb”
as soon as i fixed that up, it worked!
thanks so much for the paypal WPS support, it would have taken me weeks/months to figure out how to incorporate that myself.
Hey.
I am unable to create a order manually. Is there something wrong. I keep getting 500 Server Errors…
Please help :)
Cheers,
Thomas
Hi Thomas – you’ll need to find your server logs to determine what’s wrong; look in the logs folder beneath the potion store app.