PayPal Express Payments with ActiveMerchant

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.

PayPal Website Payments Standard

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

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.

18 Responses to “PayPal Express Payments with ActiveMerchant”

  1. Jordan Elver Says:

    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

  2. Cody Fauser Says:

    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.

  3. Tom Says:

    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

  4. Tom Says:

    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?

  5. Cody Fauser Says:

    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 your development.rb, test.rb, and production.rb files:

    
    
    ActiveMerchant::Billing::PaypalExpressGateway.default_currency = 'GBP'

    You could also subclass the PaypalExpressGateway in your application and set the default currency there:

    1
    2
    3
    
    class PaypalExpressUkGateway < ActiveMerchant::Billing::PaypalExpressGateway
      self.default_currency = 'GBP'
    end

    Lastly, you can override the default currency by passing in the :currency => 'GBP' option in each interaction with the gateway.

  6. Romain Says:

    Thanks a lot for this tuto !!! Good job, it works perfectly.

  7. Lee Says:

    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 :-)

  8. Lee Says:

    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.

  9. Cody Fauser Says:

    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.

  10. Tom Says:

    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!!

  11. Martius Says:

    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: 4: 5: 6: 7:

    How resolve it? Thanks

  12. Srinivasan Says:

    Hi,

    Thank You

  13. Seb the Monster Says:

    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?

  14. Sebastien Says:

    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,

  15. Robin Bortlik Says:

    Thank you very much, it is very good and helpfully article. ;)

  16. Peter Says:

    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

  17. David James Says:

    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!

  18. Dave Says:

    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.

  19. Sorry, comments are closed for this article.

    Name<%= @address['name'] %>
    Company<%= @address['company'] %>
    Address 1<%= @address['address1'] %>
    Address 2<%= @address['address2'] %>