#289 PayPal Recurring Billing pro
- Download:
- source codeProject Files in Zip (137 KB)
- mp4Full Size H.264 Video (60.2 MB)
- m4vSmaller H.264 Video (30 MB)
- webmFull Size VP8 Video (34 MB)
- ogvFull Size Theora Video (73.9 MB)
From episode 141[watch, read] a short series of episodes covered various aspects of using PayPal in Rails applications. One topic that wasn’t covered in that series was handling recurring payments. In this episode we’ll see what’s involved in adding this functionality to an existing application.
The application we’ll be working with is the one we used in the previous episode which sells llama kisses. The home screen is shown below.
The Llama Kisses application offers a number of plans, each of which has a monthly subscription. When we sign up for a plan we need to enter our credit card details but some customers may prefer to pay via PayPal rather than by entering their credit card details directly. This could be because they don’t have a credit card, because paying by credit card would incur hefty fees or because they simply don’t trust your site with their credit card information. We want to do as little as possible to discourage potential customers so we’ll add the option to pay through PayPal to our site.
Creating Test Accounts on PayPal
PayPal can be a little intimidating at first. It provides a large number of similar services and it can be difficult to know which one to choose. This page, for example, shows six different ways to handle recurring payments. As we’re already accepting credit card payments and we only want to use PayPal as an alternative payment method Express Checkout is the best choice for us.
The next choice we need to make is what we should use to integrate Express Checkout into our application. In episode 144 we used Active Merchant, but this is a little heavy for just adding simple PayPal integration and it doesn’t support recurring payments. There are some forks of Active Merchant have modified it to support recurring payment but we’re going to avoid Active Merchant completely.
Another option is to communicate directly with PayPal’s API. If you choose to do this then the Name-Value Pair API PDF document is worth reading before getting started as it has a good section on the Express Checkout API. Communication directly with the API means that your application doesn’t need any more external dependencies but it does mean much more work.
The solution we will be using in this episode is a little-known gem by Nando Vieira called PayPal-Recurring. This uses the Name-Value API and gives us a nice Ruby interface to interact with it. The gem’s source code is easy to read and well documented which means that if we ever need to work out how it’s talking to PayPal we can just read the relevant part of the source code.
Installing PayPal-Recurring
PayPal-Recurring is installed like most gems, by adding a reference to it in the application’s Gemfile
and then running bundle
.
source "http://rubygems.org" gem "rails", "3.1.1" gem "sqlite3" # Gems used only for assets and not required # in production environments by default. group :assets do gem "sass-rails", "~> 3.1.4" gem "coffee-rails", "~> 3.1.1" gem "uglifier", ">= 1.0.3" end gem "jquery-rails" gem "stripe" gem "paypal-recurring"
Setting up Test Accounts
PayPal-Recurring needs configuring before we can use it so we’ll create a new initializer file and put the configuration information there. Before we do, however, we’ll need to set up test accounts on PayPal’s sandbox. We already signed up for an account here so we can log in and set up the two test accounts we’ll need. Clicking “Create a preconfigured account” will take us to a page where we can create these.
First we’ll create an account for a buyer with the email address buyer@asciicasts.com
. We’ll need to select the “Buyer” account type, fill in the first part of the email address, and give them some money to play with. The form will contain a numeric password and we’ll need to copy this so that we can use it to log in as this user later.
For the seller we’ll use seller@asciicasts.com
, but instead of giving them the obvious “Seller” account type we’ll need to select “Website Payments Pro”. We don’t need to give the seller any money but again we should copy the numeric password that’s given.
Now we have accounts for both a buyer and a seller and PayPal will give each one a unique login email address as shown below.
This seller address isn’t the one we should use with PayPal-Recurring, however. To get this we need to click the “API Credentials” link in the left menu. This takes us to another page that will show us the credentials that we need to use.
We’ll use this information in our new initializer file to configure PayPal-Recurring.
PayPal::Recurring.configure do |config| config.sandbox = true config.username = "seller_1318669800_biz_api1.asciicasts.com" config.password = "1318669837" config.signature = "AFcWxV21C7fd0v3bYYYRCpSSRl31A1bAwEaPIqEwy9MooKcQYeYYQw0m" end
As this file contains sensitive information and we won’t use the same settings on the production server we should add this file to our .gitignore
file.
Adding Express Checkout to Our Application
There are a couple of ways that we can use Express Checkout to our application. We can use a checkout button, or we can use a pair of radio buttons that allow payment via either PayPal or credit card. We’re going to use a combination of these. To get started we’ll add the following code to our new subscription view, immediately above the email field.
<div class="field"> <%= radio_button_tag :pay_with, :card, true %> <%= label_tag :pay_with_card do %> <%= image_tag "visa.png" %> <%= image_tag "mastercard.png" %> <%= image_tag "discover.png" %> <%= image_tag "american_express.png" %> <%= image_tag "jcb.png" %> <% end %> <%= radio_button_tag :pay_with, :paypal %> <%= label_tag :pay_with_paypal do %> <%= image_tag "paypal.png" %> <% end %> </div> <div id="paypal_checkout" style="display:none"> <%= image_tag("https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif") %> </div>
This code will add two radio buttons to the page, one with images for various credit cards and one with a PayPal icon, and a PayPal checkout button which is styled to be hidden by default. When someone selects the PayPal button we want to show this button and hide the email and credit card fields and also the “submit” button as users paying by PayPal won’t need to fill these in. We’ll do this by wrapping these fields in a div
with an id
of billing_fields
so that we can find and modify them in our application’s JavaScript.
<div id="billing_fields"> <div class="field"> <%= f.label :email %> <%= f.text_field :email %> </div> <% if @subscription.stripe_card_token.present? %> Credit card has been provided. <% else %> <div class="field"> <%= label_tag :card_number, "Credit Card Number" %> <%= text_field_tag :card_number, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_code, "Security Code on Card (CVV)" %> <%= text_field_tag :card_code, nil, name: nil %> </div> <div class="field"> <%= label_tag :card_month, "Card Expiration" %> <%= select_month nil, {add_month_numbers: true}, {name: nil, id: "card_month"} %> <%= select_year nil, {start_year: Date.today.year, end_year: Date.today.year+15}, {name: nil, id: "card_year"} %> </div> <% end %> <div id="stripe_error"> <noscript>JavaScript is not enabled and is required for this form. First enable it in your web browser settings.</noscript> </div> <div class="actions"> <%= f.submit "Subscribe" %> </div> </div>
If we reload the form now we’ll see the new radio buttons.
Now our users can choose to pay by either credit card or by PayPal. Next we’ll write the CoffeeScript code that will show or hide the PayPal checkout button and the credit card fields depending on the payment option they choose. There is already some CoffeeScript code in this application related to this form and a setupForm function is called when the page’s DOM loads. We’ll modify this function so that it adds code to show the checkout button and hide the credit card fields when they choose to pay by PayPal and to hide the checkout and show the credit card fields when they click the credit card radio button.
setupForm: -> $("#pay_with_paypal").click -> $("#paypal_checkout").show() $("#billing_fields").hide() true $("#pay_with_card").click -> $("#paypal_checkout").hide() $("#billing_fields").show() true $("#new_subscription").submit -> $("input[type=submit]").attr("disabled", true) if $("#card_number").length subscription.processCard() false else true
When we reload the page again and choose to pay with PayPal the checkout button is shown and the credit card fields are hidden. When we click the credit card radio button the fields are shown again and the button is hidden.
The PayPal button isn’t actually linked to anything yet, but we’ll change that now. We’ll link the button to a paypal_checkout_path
that we haven’t created yet and pass in the id
of the selected plan as we’ll need to tell PayPal which plan a user is signing up for.
<div id="paypal_checkout" style="display:none"> <%= link_to image_tag("https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif"), paypal_checkout_path(:plan_id => @subscription.plan_id) %> </div>
We’ll create a paypal_checkout_path
in the routes file and point it to a new paypal_checkout
action in the SubscriptionsController
.
Saas::Application.routes.draw do root to: "plans#index" resources :subscriptions resources :plans get "paypal/checkout", to: "subscriptions#paypal_checkout" end
Note that we haven’t made the new route a named route. Rails will do this automatically for us based on the path we’ve specified.
In the SubscriptionsController
we can now write the new paypal_checkout
action.
def paypal_checkout plan = Plan.find(params[:plan_id]) ppr = PayPal::Recurring.new( return_url: new_subscription_url(:plan_id => plan.id), cancel_url: root_url, description: plan.name, amount: plan.price, currency: "USD" ) response = ppr.checkout if response.valid? redirect_to response.checkout_url else raise response.errors.inspect end end
In this action we find the plan with the plan_id
parameter than was passed in then create a new PayPal::Recurring
object to create a new payment. The parameters that this object takes are all fairly self-explanatory. We then call checkout
on that object to trigger the PayPal API and return a response. If the response is valid we’ll redirect to the response’s checkout_url
which is the PayPal URL where the user can sign in. If not we’ll raise
the errors so that we can see what’s gone wrong.
When we click the PayPal button on the new subscription form we’re now taken to the login page on the PayPal Sandbox site. We can see the name of the plan we’ve selected in the order summary section so it looks like our code is working.
We can log in to this page with the details from the buyer test account that we set up earlier. Doing this will take us to a page that lets us review the transaction we’re about to make. When we agree to it we’ll be taken back to our application’s new subscription page but with two new parameters in the URL, one called token
and one called PayerID
. We need these to finish the payment with PayPal so we’ll add new fields to the Subscription
model to store them. If the PayerID
parameter exists we’ll set the subscription’s payment_customer_token
to its value and store the temporary token as a paypal_payment_token
.
def new plan = Plan.find(params[:plan_id]) @subscription = plan.subscriptions.build if params[:PayerID] @subscription.paypal_customer_token = params[:PayerID] @subscription.paypal_payment_token = params[:token] end end
The paypal_payment_token
is only temporary so we’ll add it as a virtual attribute in our Subscription
model.
attr_accessor :stripe_card_token, :paypal_payment_token
The other parameter, the payment_customer_token
, is more permanent so we’ll store it in the subscriptions table. We’ll create a migration to add the new field and then run rake db:migrate
to add it to the database. While we’re doing this there’s another token that PayPal will give us as part of this process that we’ll need to store later. We’ll add this now too as the paypal_recurring_profile_token
.
$ rails g migration add_paypal_to_subscriptions paypal_customer_token:string paypal_recurring_profile_token:string
The paypal_customer_token
and paypal_payment_token
fields are now stored in a subscription object when the new subscription page is shown, but they need to be passed through the form when it’s POSTed back to create a new subscription. We’ll add two new hidden fields to the form to store these values so that they’re sent when the form is submitted.
<%= f.hidden_field :paypal_customer_token %> <%= f.hidden_field :paypal_payment_token %>
When the user returns to the subscriptions form after signing in through PayPal the credit card fields are shown again on the page. There is some logic in the view code already that hides these fields if a Stripe payment has already been made and we can expand this to also check to see if the PayPal token is present.
<% if @subscription.stripe_card_token.present? || @subscription.paypal_payment_token.present? %> Payment has been provided. Click &ldquo;Subscribe&rdquo; to complete the subscription. <% else %> <!-- Credit card fields omitted. --> <% end %>
When we reload the page now the credit card fields are hidden, but the payment type fields are still showing, even though we know that the user is paying by PayPal. We can use the same check to hide these but before we do we’ll tidy up the code a little by moving the logic into the model. We’ll create a new payment_provided?
method in the subscription model and use that in the view to determine whether these fields should be shown or not.
def payment_provided? stripe_card_token.present? || paypal_payment_token.present? end
We can also use this method to hide the payment options when the payment has been made.
<% unless payment_provided? %> <div class="field"> <%= radio_button_tag :pay_with, :card, true %> <%= label_tag :pay_with_card do %> <%= image_tag "visa.png" %> <%= image_tag "mastercard.png" %> <%= image_tag "discover.png" %> <%= image_tag "american_express.png" %> <%= image_tag "jcb.png" %> <% end %> <%= radio_button_tag :pay_with, :paypal %> <%= label_tag :pay_with_paypal do %> <%= image_tag "paypal.png" %> <% end %> </div> <% end %>
We can also use it tidy up the code that hides the credit card fields.
<% if @subscription.payment_provided? %> Payment has been provided. Click &ldquo;Subscribe&rdquo; to complete the subscription. <% else %> <!-- Credit card fields omitted. --> <% end %>
When we reload the page again the payment selection area is hidden and we only have to enter our email address and click “subscribe” to complete the subscription.
Users shouldn’t really have to enter even an email address. We can get this from the information that was supplied when they signed in to PayPal by using the temporary token that was returned to fetch the checkout details and therefore the email address.
def new plan = Plan.find(params[:plan_id]) @subscription = plan.subscriptions.build if params[:PayerID] @subscription.paypal_customer_token = params[:PayerID] @subscription.paypal_payment_token = params[:token] @subscription.email = PayPal::Recurring.new(token: params[:token]).checkout_details.email end end
The subscription form will now make a request to PayPal when it loads after returning from the PayPal site and fetch the email address that was used to log in.
The final step is to handle the payment processing when the user clicks “subscribe”. So far no actual payment has been made: the user has authorized a payment but we haven’t yet taken the money. We already have a save_with_payment
method inside the Subscription model which handles credit card payments through Stripe. We’ll modify this method so that it also handles PayPal payments. The code in save_with_payment
Stripe-specfic so we’ll rename it to save_with_stripe_payment
. We can then create a new, more generic, save_with_payment
method that will call either save_with_stripe_payment
or a new save_with_paypal_payment
method.
def save_with_payment if valid? if paypal_payment_token.present? save_with_paypal_payment else save_with_stripe_payment end end end
Next we’ll need to write this new save_with_paypal_payment
method. There are two things that need to happen to make a recurring payment. We need to request the first payment and then make a recurring profile so that payments will be made each month. The code to request the first payment looks something like this:
def save_with_paypal_payment ppr = PayPal::Recurring.new( token: paypal_payment_token, payer_id: paypal_customer_token, description: plan.name, amount: plan.price, currency: "USD" ) response = ppr.request_payment if response.errors.present? raise response.errors.inspect end # 2. Make recurring profile end
This code creates a new PayPal::Recurring
object, passing in some details such as the token
and payer_id
, then requests the payment. This is similar to the code we have in the paypal_checkout
method in the SubscriptionsController
. It’s important that the information that’s passed to PayPal matches up, especially the description
and amount
and so we’ll refactor it out into its own model so that we don’t have PayPal::Recurring
objects spread across our models and controllers.
Refactoring into the PaypalPayment class
We’ll refactor the code that creates PayPal::Recurring
objects out into a new PaypalPayment
class which we’ll place under app/models
. You might expect this class to belong under the /lib
directory as we’re not creating a new ActiveRecord model but models don’t always have to inherit from ActiveRecord. If a class represents information that’s specific to an application and our class does as it works mostly with a Subscription
, then this is the right place to put it. As we’ll be working with a subscription object we’ll pass one in to the initialize
method and assign it to an instance variable.
class PaypalPayment def initialize(subscription) @subscription = subscription end end
We can now start our refactoring. After we’ve finished we should have much cleaner code with most of the PayPal-specific logic in our new PaypalPayment
class.
The first change we’ll make is to move the code in the save_with_paypal_payment
method in the Subscription
model into a new make_recurring_payment
method in the PaypalPayment
class. We’ll need to create a new PaypalPayment
object more than once in this class so we’ll also create a paypal method to make this easier.
def paypal PaypalPayment.new(self) end def save_with_paypal_payment paypal.make_recurring end
The code we’ve removed from save_with_paypal_payment
will go into our new make_recurring_payment
method.
def make_recurring ppr = PayPal::Recurring.new( token: @subscription.paypal_payment_token, payer_id: @subscription.paypal_customer_token, description: @subscription.plan.name, amount: @subscription.plan.price, currency: "USD" ) response = ppr.prequest_payment if response.errors.present? raise response.errors.inspect end # 2. Make recurring profile end
Note that as we’re no longer in the Subscription
class we need to get the tokens and the plan from the @subscription
instance variable.
We can now extract any calls to PayPal::Recurring
from the SubscriptionController
and move them into our new PaypalPayment
class. We’ll start with this line in the new
action.
@subscription.email = PayPal::Recurring.new(token: params[:token]).checkout_details.email
We can replace it with this:
@subscription.email = @subscription.paypal.checkout_details.email
Note that instead of getting the token from the params
we now get it from the subscription
object. We’ll need to write the checkout_details
method now.
def checkout_details PayPal::Recurring.new(token: @subscription.paypal_payment_token).checkout_details end
We’re already seeing some duplication between the methods in the PaypalPayment
class but before we try to refactor this out we’ll get all of our mess into here and then clean it up. The next piece of code we’ll deal with is the SubscriptionsController’s paypal_checkout method, shown below.
def paypal_checkout plan = Plan.find(params[:plan_id]) ppr = PayPal::Recurring.new( return_url: new_subscription_url(:plan_id => plan.id), cancel_url: root_url, description: plan.name, amount: plan.price, currency: "USD" ) response = ppr.checkout if response.valid? redirect_to response.checkout_url else raise response.errors.inspect end end
We can extract most of this code out into the PaypalPayment
class but we’ll need a subscription
to pass in so we’ll build a new one from the plan
. The only information we need back from the new method we’ll write is the checkout URL so we’ll call our it checkout_url
.
There are a couple of pieces of information in the code above that we won’t be able to access from the model layer, namely the new_subscription_url
and the root_url
. We’ll pass these as parameters from the controller in an options
hash.
def paypal_checkout plan = Plan.find(params[:plan_id]) subscription = plan.subscriptions.build redirect_to subscription.paypal.checkout_url( return_url: new_subscription_url(:plan_id => plan.id), cancel_url: root_url) end
The new checkout_url
model will look like this:
def checkout_url(options) ppr = PayPal::Recurring.new( description: plan.name, amount: plan.price, currency: "USD" ).merge(options) response = ppr.checkout if response.valid? redirect_to response.checkout_url else raise response.errors.inspect end end
Tidying Up The Paypal Payment Class
We’ve moved all of the PayPal-related code into our PaypalPayment
class now but it’s a bit of a mess with a lot of duplication between its three main methods. To tidy it up we’ll create a new private process
method in the class. This method will take an argument that will be the method we want to call on the PayPal::Recurring
object and also a hash of options and it will return a response
object that we can call methods on if need be. Doing this will allow us to remove much of the duplication in the class. After we’ve done this the PaypalPayment
class will look like this:
class PaypalPayment def initialize(subscription) @subscription = subscription end def checkout_details process :checkout_details end def checkout_url(options) process(:checkout, options).checkout_url end def make_recurring process :request_payment # 2. Make recurring profile end private def process(action, options = {}) options = options.reverse_merge( token: @subscription.paypal_payment_token, payer_id: @subscription.paypal_customer_token, description: @subscription.plan.name, amount: @subscription.plan.price, currency: "USD" ) response = PayPal::Recurring.new(options).send(action) raise response.errors.inspect if response.errors.present? response end end
We’re supplying more information than we need for some of the PayPal actions, but that’s OK as the PayPal-Recurring gem will filter out the unnecessary ones.
Finishing the make_recurring Method
Now that we’re done with our refactoring we can focus again on the make_recurring
method as we still need to write the code to make a recurring profile. With the refactoring we’ve done this will be easy; all we need to do is call process
and pass it create_recurring_profile
as the action, along with a number of options that define how often the payment should be made.
def make_recurring process :request_payment process :create_recurring_profile, period: :monthly, frequency: 1, start_at: Time.zone.now end
If you’re ever curious as to what options you can pass to PayPal-Recurring and what they do you can find out by looking at the relevant source code file. Each option is listed there and is mapped the the equivalent option in the PayPal Name-Value Pair API.
We still need to finish the save_with_paypal
method in the Subscription model as we’re not storing the profile_id
that PayPal returns after we make a recurring profile. The make_recurring
method that’s called here returns a response
which contains a profile_id
and we need to store this in the database. When we created a migration for the paypal_payment_token
field earlier we also created a paypal_recurring_profile_token
field and we can store this value here.
def save_with_paypal_payment response = paypal.make_recurring self.paypal_recurring_profile_token = response.profile_id save! end
We now have all of the code needed to make recurring PayPal payments so let’s try it out and see if it works. If we select the “I’m in Heaven” plan from the home page then click the “PayPal” radio button on the subscription page and click the “Pay With PayPal” button we’ll be redirected to PayPal’s website. We can log in with the test username and password we created earlier and once we’ve logged in and confirmed that we want to make the payment we’ll be redirected back to our site to finish our subscription.
When we click the “Subscribe” button we’ll have successfully created a new subscription through PayPal.
That’s it for this episode on PayPal. It was longer than usual but it’s still not feature complete as we need to handle customers who have insufficient funds or who cancel their subscriptions. These may be covered in a future episode.