There is a lot of confusion over the myriad of services that PayPal offers for accepting PayPal payments. In reality there are two ways of accepting PayPal payments.
Website Payments Standard is the basic service PayPal offers for accepting PayPal payments. Website Payments Standard requires the creation of an HTML form that is posted to PayPal. The user is redirected to the PayPal website where they complete their payment. They are then redirected back to the seller's website at the end of the process where it is possible to display an order confirmation, or other information. Very basic, easy to setup, but also quite limited.
PayPal Express is a much more powerful alternative to PayPal Website Payments Standard. The downside is that it is slightly more complicated. However, I am going to show how easy it is to get PayPal Express working with ActiveMerchant. PayPal Express has several benefits including support for Authorization and Capture. Website Payments Standard also has support for Authorization and Capture, but the seller has to actually login to their PayPal account to capture the funds. PayPal Express also keeps control of the payment flow on your page, instead of PayPal's. The user is only redirected to PayPal briefly to confirm payment, and then they are sent back to the seller's page to complete the payment. There are several advantages to this, but I think the biggest is that you can rely on PayPal to provide address information for the buyer and then determine additional shipping or other charges to add to the total of the order.
Now that I've provided a bit of background on PayPal Express I'm going to show how simple it is to integrate into Rails application. The code is based on the integration of PayPal Express into Shopify. The applicable integration points from the PayPal Express Checkout Integration Guide will be noted throughout the example. If you like you can download the completed Sprockets application sprockets.tar.gz. You will need at least Rails 2.0.2 Gems to run the Sprockets application. You will only need to add your API credentials into the application, which is described in the example text.
First, in order to use the sandbox, you'll need a PayPal Sandbox buyer and seller to work with. See Sandbox Testing for more information on getting these accounts setup. The seller account must be either a Premier Account or Business Account in order to work with PayPal Express. Don't forget to activate the account using the Test Email link in Developer Central. Email isn't really sent in the sandbox. It just accumulates in the Developer Central inbox. Then you'll have to confirm your bank account using the Get Verified link while logged in as the test user.
Create a new Rails application. I am going to name my project Sprockets, which is what I'll be selling on my site:
$ rails sprockets
Now you can enter the project and install ActiveMerchant as a plugin:
$ cd sprockets
$ ./script/plugin install http://activemerchant.googlecode.com/svn/trunk/active_merchant
The first thing to do is to configure ActiveMerchant. By default ActiveMerchant uses the production environment of a payment gateway, so we have to force ActiveMerchant into test mode while we're running in the Rails development environment. This is easily accomplished by opening up config/environments/development.rb and adding the following code to the bottom of the file:
1 2 3 |
config.after_initialize do ActiveMerchant::Billing::Base.mode = :test end |
The preceding code ensures that the PayPal Sandbox will be used for the test payments, and waits until the entire application is loaded before doing the configuration.
The next thing to do is to login to the PayPal Sandbox and launch the sandbox for the seller account you previously created. Click the My Account link followed by the Profile _ link. Then click _API Access underneath Account Information. Select Request API signature and then click Agree and Submit. Record the API username, API Password, and Signature. We'll be using them shortly.
Now that all of the configuration has been completed we can move onto actually accepting payments. Let's generate a controller PaymentsController that will be used to accept the payments from PayPal.
$ ./script/generate controller payments index confirm complete
We created the controller with the action index, confirm, complete actions in the controller along with their associated views. Let's open up the index.html.erb template and add the PayPal Express logo that the customer will click to make payment.
The first thing we need to do is include the ActiveMerchant::Billing namespace into the controller so that we can access the classes from ActiveMerchant with less typing:
1 2 3 |
class PaymentsController < ApplicationController include ActiveMerchant::Billing end |
I'm assuming that you have created a common layout, such as application.html.erb to provide the required HTML structure for the page. The index action represents Integration Point 1a from the Integration Guide. index.html.erb looks as follows:
1 2 3 4 5 6 |
<h1>Purchase Sprockets</h1> <p>Thank you for your decision to purchase our sprockets.</p> <p>Your order total is $50.00</p> <p> <%= link_to image_tag('https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif'), :action => 'checkout' %> </p> |
Clicking the PayPal Express button do a GET request on the checkout action. The checkout action is where we setup the purchase with PayPal and get the token identifying the purchase from PayPal. We didn't generate the checkout action initially because it doesn't require a view. The checkout action represents Integration Point 1b from the Integration Guide. Define the checkout action as follows:
1 2 3 4 5 6 7 8 |
def checkout setup_response = gateway.setup_purchase(5000, :ip => request.remote_ip, :return_url => url_for(:action => 'confirm', :only_path => false), :cancel_return_url => url_for(:action => 'index', :only_path => false) ) redirect_to gateway.redirect_url_for(setup_response.token) end |
gateway is actually a method which constructs the PaypalExpressGateway if it hasn't been already:
1 2 3 4 5 6 7 8 |
private def gateway @gateway ||= PaypalExpressGateway.new( :login => 'API Login', :password => 'API Password', :signature => 'API Signature' ) end |
As you can see, we're constructing the gateway using the credentials provided to us earlier in the PayPal Sandbox and returning the new instance of the gateway.
The setup_purchase() method informs PayPal of the details of the purchase we're going to make and returns a token. 5000 is the amount in cents of the purchase, which is $50.00. ActiveMerchant only accepts amounts in cents. The customer's IP address, passed in using the :ip option, is a required option, and helps PayPal reduce fraud. The :return_url option is the absolute URL that the customer will be redirected to after their purchase, and :cancel_return_url is the URL the customer will return to if they cancel the purchase.
Finally, we redirect the customer to PayPal. The URL is constructed by passing the token returned in the setup_purchase() call to the redirect_url_for() method provided by the gateway.
Now if you load http://localhost:3000/payments in your browser and click the PayPal Express button you should be redirected to PayPal. You will need to be logged into the PayPal Developer Central at the time you click the button in order for the sandbox to load correctly. You can now log in to PayPal using the Sandbox buyer account that you created earlier. Once logged in you will see the details of the purchase. You'll also see the link to cancel the transaction below the Continue button. The time spent on PayPal's site represents Integration Point 2a and Integration Point 2b from the Integration Guide. Clicking the Continue button will redirect your browser back to the Sprockets application confirm action. The redirection back to our application represents Integration Point 2c from the Integration guide. We already have a confirm action, but it just displays the default message put in the template by the Rails generator.
Let's enhance the confirm action as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def confirm redirect_to :action => 'index' unless params[:token] details_response = gateway.details_for(params[:token]) if !details_response.success? @message = details_response.message render :action => 'error' return end @address = details_response.address end |
First, we request the details of the purchase from PayPal using the details_for() method, which takes the token param as its first argument. If we successful get the details for the purchase then we display the confirm.html.erb template. Otherwise, we render the the error.html.erb template. error.html.erb looks as follows:
1 2 |
<h1>Error</h1> <p>Unfortunately an error occurred: <%= @message %></p> |
The confirm.html.erb templates displays the customer's PayPal address and asks them if they want to complete the payment. Displaying the review page represents Integration Point 3a from the Integration guide. This is the customer's opportunity to review the details of their order, change shipping methods, etc. It looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<h1>Please Confirm Your Payment Details</h1> <table> <tr><td>Name</td><td><%= @address['name'] %></td></tr> <tr><td>Company</td><td><%= @address['company'] %></td></tr> <tr><td>Address 1</td><td><%= @address['address1'] %></td></tr> <tr><td>Address 2</td><td><%= @address['address2'] %></td></tr> <tr><td>City</td><td><%= @address['city'] %></td></tr> <tr><td>State</td><td><%= @address['state'] %></td></tr> <tr><td>Country</td><td><%= @address['country'] %></td></tr> <tr><td>Zip</td><td><%= @address['zip'] %></td></tr> </table> <% form_tag :action => 'complete', :token => params[:token], :payer_id => params[:PayerID] do %> <%= submit_tag 'Complete Payment' %> <% end %> |
When the customer clicks the Complete Payment button they will post their token, and PayerID, which were returned from PayPal, back to the complete action where we'll inform PayPal to complete the purchase. Completing the purchase represents Integration Point 3b from the Integration Guide. It looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def complete purchase = gateway.purchase(5000, :ip => request.remote_ip, :payer_id => params[:payer_id], :token => params[:token] ) if !purchase.success? @message = purchase.message render :action => 'error' return end end |
This looks similar to when we initially setup the purchase, but we're calling purchase() instead of setup_purchase(). If the purchase is successful then we render the complete.html.erb template. Displaying the final success message represents Integration Point 4 from the Integration Guide, which is the final step. It looks like:
<p>Thank you for your purchase</p> |
The entire flow of the purchase is very flexible. The initial amount of the setup_purchase() method doesn't even need to match the final amount sent in purchase(). This allows for the buyer to change their mind about their order details during the checkout flow in your site. This is useful for situations where the customer may want to add high priority shipping, a discount code, or gift wrapping while reviewing their order. There is a lot of data that can be provided to PayPal throughout the integration. Take a look at the Integration Guide for a complete overview of all that is possible.
Are you as tired as we were of loading 20+ different fixtures in each of your Rails test classes? We were, and we even added a method all_fixtures() to test_helper.rb to do the loading of all our fixtures for us.
Thankfully though, we don't need our own helper method anymore, as the Rails fixtures() method will now accept a symbol :all, which will instruct the test helper to load all of your fixtures automatically.
1 2 3 4 5 6 7 8 |
require File.dirname(__FILE__) + '/../test_helper' class ShopTest < Test::Unit::TestCase fixtures :all # Your tests here end |
As of Rails 1.2.3 this feature has not yet been merged from the trunk. This means that you'll either need to run Edge Rails from Subversion, or install the beta Rails gems as follows:
sudo gem install -s http://gems.rubyonrails.org rails -y
Happy testing!
Way back on June 16th, Tobi, Daniel, and I decided to enter the Rails Day 2006 programming contest. The contest was on June 17th. Somehow the theme became ninjas and Ninjistix was born.
A really good overview of Ninjistix and the rest of the Rails Day winners can be found on Evan Weaver's blog.
The day was really good. We started around 9am or so and worked until 12am that night. I can't really say that I did anything productive after around 9pm, but hey, we stuck it out to the end.
What I loved most about doing the project was the kind of discussions we had regarding the project. I remember asking "Ok, so add an attack method to the Ninja model?". Then later on I can recall Tobi announcing "I just added an RSS feed for the Ninja's kills". The bugs were also amusing: "Why does meditating cause the ninja to lose consciousness?" I don't remember hearing too much from Daniel, but that's because he was creating the killer UI that even included little Ninja attack and action icons.
So, the end result.... We won first prize for the most creative entry! Kudos to my teamates. I still can't believe how much work a team of 3 can do in one day. Also, congratulations to the other teams and thanks to the judges for judging the copious number of entries (> 100).