Digital Download Sales with PayPal IPN, Rails, and Refinery CMS

filecartWhile building an online store for one of my past projects using Rails and the Refinery CMS, I was presented with an interesting problem. I had a requirement to allow customers to purchase and download individual music tracks directly from an online store that also sells physical goods. On its surface, this sort of thing does not seem much different from selling tangible goods via the web: User selects items, places them in his/her cart, and checks out. After payment processing, the only thing different in the case of digital downloads would be in actually fulfilling the order.

The problem is not in getting the purchased tracks to the customer. It’s easy enough to email a link to the purchased files. However, a permanent link to your digital downloads would allow anyone to grab the files you’re trying to sell, for free. So, clearly a better solution is needed that will deny everyone but paying customers access to downloadable products.

A thought briefly occurred to me to just email the purchased tracks to the customer after a purchase was made. But that’s head-smackingly stupid for a number of reasons, not the least of which is the fact that many email providers place a size limit on attachments that would prevent this solution from working. Plus, what if a customer loses his emailed copy and wants to get the files he payed for a second time? Sure, you could tell him/her to just buy them again, but that’s not the kind of customer service that will engender loyalty and/or referrals.

A better solution would be to provide each customer with an access code that will allow him/her to access a download page containing all of the files purchased as part of a particular order. The Refinery CMS does a decent job of preventing unauthorized access to resources (our downloadable files, for example) that aren’t meant to be public, so a good amount of the work has already been done.  However, one requirement of this project introduced another problem: PayPal was being used for order processing. The issue with using a third-party payment processor like PayPal is that you can’t know for sure if a customer’s payment was processed successfully after being forwarded from the main store.

Fortunately, PayPal provides a service it calls Instant Payment Notification (IPN). Basically, it works like this:
1.) A customer is forwarded to PayPal from your website to complete his purchase.
2.) Paypal processes the payment, and then sends a notification to a listener you’ve set up on your site
3.) The listener verifies that the payment notification is valid by sending it, unaltered, to PayPal
4.) If the notification is legit, PayPal sends back a final, one-word response, either INVALID or VERIFIED.

The IPN also contains all the information forwarded to PayPal when transferring the user from your site, along with some details provided to PayPal, such as the customer’s email address.  So, if you set up your store correctly you should receive everything you need to fulfill the order.

So, armed with this information, the final process for digital download sales is as follows:

1.) Customer purchases files via PayPal
2.) PayPal sends an IPN to the website’s listener
3.) The listener verifies the IPN.
4.) If verified, the listener generates an access code and sends an email to the customer containing a link to a download page.

Setting up a listener is easy if you use ActiveMerchant, as it includes built in support for PayPal. Here’s an example using a simple five digit number for an access code (not the most secure thing in the world, you might want to use a longer string that can consist of the full gamut of alphanumeric characters). We hash the access code using MD5 and store the hash in the database.  Here’s an example controller for a PaymentNotification model:

class PaymentNotificationsController  [:create]

  def create
    notify = Paypal::Notification.new(request.raw_post)
    Rails.logger.info "In create"
    #Verify IPN with PayPal
    if notify.acknowledge
      Rails.logger.info "Acknowledge"
        if notify.complete?
          Rails.logger.info "payment complete"
          notification = PaymentNotification.create!(:params => params, 
            :cart_id => params[:invoice], :status => params[:payment_status], 
            :transaction_id => params[:txn_id])
          Cart.find(params[:invoice]).update_attribute(:purchased_at, 
            Time.now)
          #Generate access code
          random_number_generator = Random.new
          random = random_number_generator.rand(10000...99999)
          hashed_random = Digest::MD5.hexdigest(random.to_s)
          #store access code in database
          notification.update_attribute(:access_code, hashed_random)
          #Send Email notification
          OrderNotifier.received(notification, random).deliver
        else
          Rails.logger.error("Failed to verify Paypal's notification")
        end
    end
    render :nothing => true
  end
end

And if you’re curious about the PaymentNotification model itself, it’s pretty simple:


class PaymentNotification  :create
  validates_presence_of :cart_id, :on => :create
  validates_uniqueness_of :transaction_id
  validates_uniqueness_of :cart_id

  serialize :params
end

The data we need is pulled from the IPN itself (contained in :params). The cart_id is the id number of a cart in the site’s database that contains the customer’s order. This was forwarded to PayPal when the customer checked out and is returned as params[:invoice]. The :transaction_id (returned as params[:txn_id]) is a unique identifier assigned to the order by PayPal.

OrderNotifier is just a child of ActionMailer, and it’s used to send the transaction_id and access code to the customer:

class OrderNotifier  

  def received(notification, random)
    @notification = notification
    @code = random
    @greeting = "Hello"
    @cart = Cart.find(@notification.cart_id)
    mail :to => @notification.params[:payer_email]
    #mail :to => "nobody@nowhere.com"
  end
end

The email sent to the customer would contain a section that goes something like this:

To get your files, please visit the following link and enter your transaction ID and access code:
  http://www.example.com/carts/
  Transaction ID : 
  Access Code    :

When the customer visits the provided link, he’s presented with a form requesting the transaction ID and access code:

ddform

Entering the correct information would then present the user with a page containing links to the downloads he ordered. There are numerous variations on this basic system one could do. For example, with a few small modifications one could easily limit the number of times a user could download his/her files to, say, five. There are also third party solutions available to manage digital download sales, though I haven’t tried any yet.

Two Weeks with a Standing Desk

standing_desk2If many of the recent studies on the subject are to be believed, sitting all day is horrible for you. Regardless of how much we may exercise when we aren’t chained to our desks, sitting for long periods places us at greater risk for various cancers, obesity, and diabetes, not to mention neck and back problems.

Unfortunately for most of us, we don’t have much of a choice in the matter. I’m currently working a full time job that requires me to sit in a veal-fattening pen all day writing code. I also spend several hours a day at home, seated in front of my iMac, working on web development projects. I do my best to get up an move around as much as possible, but I’m definitely spending several hours a day more than I should with my butt parked in a chair.

There isn’t much I can do about my work environment at the office (Clearly, The Man is trying to kill me). At home, however, I figure I can do whatever I want (subject to the approval of my wife) so after reading about the new ‘standing desk’ fad in Silicon Valley, I decided to be Mr. Trendy and try it for myself.

My first thought was to price a few desks online, where I discovered much to my dismay that standing desks have a way of costing two to three times more than regular old sitting desks. I’m sure this is partly due to the new-found popularity of standing desks and partly due to the fact that standing desks either need to be customized to the height of the intended user (For some reason, human beings can vary quite a bit in the height department), or be made ‘adjustable’, which surely adds to manufacturing costs. I did find several good adjustable-height desks online, but the prices pretty much eliminated them from consideration.

Undeterred, I pulled out a piece of notebook paper and began sketching a design for a standing desk I could build myself. I took a few measurements, and decided on a height of 42″ for my desk (about an inch below my elbows). Then I realized another issue: While 42″ may be a good height for my keyboard and mouse, it would definitely be too low for my monitor.

According to OSHA’s ergonomic guidelines, the top of a monitor should be about at eye level. Personally, I can’t STAND that, but I’m a bit apple_iiodd in that I like my monitors to be set high. At the office, my monitor is set on top of a stack of books, and I still don’t think I have it high enough. I unconsciously tend to slouch in my chair until the center of the screen is at or slightly above eye level. It’s possible this is due to the fact that I spent the formative years of my computing experience as a child sitting in an adult-sized chair at an adult-sized desk, peering up at a tiny Apple monochrome monitor that sat perched atop an Apple ][ and a set of chunky Disk ][ drives. I chose not to fight my subconscious conditioning and decided to build a split-level desk, with my 27-inch iMac sitting on a second platform about 6 inches above the keyboard/mouse platform.

So, sketches and measurements in hand, I headed over to Lowe’s to buy supplies. The legs of the table consisted of four 48″ 2x4s and two 42″ 2x4s. I decided on a width of 30 inches and a depth of 24, so I bought a 72×12 piece of pine board, cutting it into two 30×12 pieces to serve as the desktops for my two platforms. For bracing, I bought a few 1x3s, cutting two 30″ pieces to brace the back, and four 24″ pieces (two for each side) to brace the sides. To support the back of the keyboard platform, I bought two metal L-brackets, attaching one side of each bracket to the front legs of the monitor platform, and the other side to the bottom of the pine board serving as the keyboard platform. I also bought a box of 2-inch wood screws, and some half-inch ones for attaching the L-brackets.
After putting it all together, the result was better than I expected. Kinda ugly, but sturdy:

I’ve got this set up in the little home office corner of my bedroom (my wife forbade me to put in the living room), and have been using it constantly for the last two weeks. I’ll admit it took a lot of getting used to, as after a few hours my feet started to burn and my calves started to ache. The discomfort faded as the weeks went by and now, I can say, I much prefer this to sitting in front of a desk for hours on end. Standing helps keep me alert and focused on the task at hand, and when I get into a flow, pounding out code or intently focused on a design, I hardly notice that I’ve been standing for a long period of time. I do, however, have a bar stool that I occasionally use when I’m too tired to stand after a hard leg workout.

standing_desk1

So, my recommendation: Get a standing desk. If you have the money to spend and are concerned about aesthetics, go out and buy one. If you’re cheap like me, build one; at least you’ll be able to try out the experience at a low cost and decide if you want to shell out the big money for a nicer desk later.