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.
January 26th, 2008 at 01:58 AM
Thanks for this write up Cody, good stuff. Does this apply to the UK PayPal Express as well? It looks like it does, but I'm confused by the presence of ActiveMerchant::Billing::PayflowExpressUkGateway. Is that for a different PayPal product?
Thanks, Jordan
January 26th, 2008 at 05:31 PM
Jordan,
The
PayflowExpressUkGateway
, which uses the Payflow Pro API, is for use with PayPal Website Payments Pro UK. Using the Payflow API for both credit card payments and PayPal payments ensures that all transactions show up in the PayPal Manager interface. It is also simpler because you only need one set of credentials - your Payflow API credentials.You would use the
PayflowExressGateway
if you have a UK PayPal account, but you aren't using PayPal Website Payments Pro UK. In this case you wouldn't have Payflow credentials or access to the PayPal Manager interface. You would follow the instructions exactly as described in my article.January 28th, 2008 at 10:51 AM
Hi Cody, I just wanted to leave this note on here for any newbies like myself. For this to work without getting horrible errors you have to put this line at the top of the payments controller:
include ActiveMerchant::Billing
January 28th, 2008 at 11:36 AM
Cody, In relation to what Jordan wrote. I am using PayPal Website Payments Pro UK but for the time being only wish to use the express check-out option. Could you highlight what changes I would need to make to the code above in order to make it work?
January 29th, 2008 at 03:13 PM
Tom,
Thanks for the note. I added the inclusion of
ActiveMerchant::Billing
into the controller in the text.In order to use PayPal Express in the UK you shouldn't have to change anything but the currency. The normal PaypalExpress gateway has a default currency of USD. You can change that for your whole application by setting the following in the
config.after_initialize
block in yourdevelopment.rb
,test.rb
, andproduction.rb
files:You could also subclass the
PaypalExpressGateway
in your application and set the default currency there:Lastly, you can override the default currency by passing in the
:currency => 'GBP'
option in each interaction with the gateway.January 30th, 2008 at 07:24 PM
Thanks a lot for this tuto !!! Good job, it works perfectly.
February 4th, 2008 at 11:59 PM
Hi Cody, I'm just getting to grips with RoR myself and this is exactly the sort of tutorial I was looking for. Thanks for taking the time to write it in detail :-)
February 5th, 2008 at 08:56 PM
I'm hoping someone might know the answer to this question.
On the original index page I have a form with fields 'name' 'email' 'url' which I want to save in the database once the user has made payment. How would I go about passing these values to Paypal and then retrieving them on completion?
Thanks in advance for your help.
February 8th, 2008 at 12:30 PM
Lee,
You can store the information along with the token that you get from PayPal. Then when the customer returns from PayPal you can look that information up again because PayPal returns the token as a query parameter.
February 11th, 2008 at 02:30 PM
Cody,
This code does work brilliantly, however i was wondering if there is any possibility of a quick tutorial on how to use Paypal payments standard with Active Merchant. By all accounts it should be easy but there is little info on it and i am struggling to work it out!!
February 17th, 2008 at 08:46 AM
I got an error in confirm action:
NoMethodError in Payments#confirm
Showing payments/confirm.html.erb where line #4 raised:
You have a nil object when you didn't expect it! You might have expected an instance of ActiveRecord::Base. The error occurred while evaluating nil.[] Extracted source (around line #4):
1:
Please Confirm Your Payment Details
2: 3:How resolve it? Thanks
February 20th, 2008 at 07:46 AM
Hi,
Thank You
March 11th, 2008 at 12:35 AM
I have this error message on the ExpressCheckoutResponse of Paypal Sanbox after my purchase: "SOAP-ENV:Server: Internal error - Timeout processing request" But the test purchase, with this error message, is recorded in Paypal payments anyway!!
Can you help me to avoid this message from now on?
March 11th, 2008 at 12:27 PM
Your tuto is great, and works very well until a couple of weeks.
When I do the purchase with my Paypal Sandbox account, it doesn't success and i have this error message: " SOAP-ENV:Server: Internal error - Timeout processing request "
And when I check the purchase in the two test accounts, the transaction seems well done. So why have I this error message? Can you explain me this?
Thank you very much, sincerely,
March 18th, 2008 at 08:52 PM
Thank you very much, it is very good and helpfully article. ;)
March 30th, 2008 at 09:01 AM
Thanks, this plugin and tutorial are really helpful. It's a shame PayPal Express requires the user to have a PayPal account.
42 has a good tutorial that explains using PayPal Website Payments Standard: http://www.fortytwo.gr/blog/14/Using-Paypal-with-Rails
April 14th, 2008 at 12:02 AM
Your blog and writings have been very helpful, thanks! Providing a sample app was a nice touch, too.
Just wanted to mention that PayPal Express Checkout (PPEC) requires a merchant account (according to https://www.paypal.com/us/cgi-bin/webscr?cmd=_profile-comparison.) PayPal Website Payments Standard (PPWPS) does not.
For that reason, it might confuse some to say that "[PPEC] is a much more powerful alternative to [PPWPS]." This is true, but it depends what you mean by "alternative."
From my perspective, PPEC and PPWPS are rather different products, intended for a different types of customers -- and therefore not really "alternatives".
For someone building a young Web site that (1) doesn't want to setup a full merchant account, and (2) doesn't want to have the risk of holding credit card information, PPWPS makes a lot of sense. PPEC doesn't fit this kind of need.
Thanks, again, for sharing your work on ActiveMerchant!
April 15th, 2008 at 05:24 AM
Great write up.
The link to the PayPal Integration Guide towards the top is wrong. It should be: https://www.paypal.com/enUS/pdf/PPExpressCheckout_IntegrationGuide.pdf
I think you've got a stray space in there.
Sorry, comments are closed for this article.
Template design by Six Shooter Media.
Mephisto theme port by Stuart Loxton.