Fork me on GitHub

Samuel Simões

[Part 2] Rails with PayPal subscriptions guide

19/03/2015

In the first post of this series we have took brief look on the mediation objects and the PayPal subscription flow. Now we’ll understand how setup our app on the PayPal Sandbox network and take a look on the controller layer.

PayPal Sandbox Network

Fortunately, PayPal provides a sandbox network where we can create “fake accounts” and use it like real PayPal accounts (on the sandobox network). This will be extremely useful for testing the whole app.

Setting up

First of all you need a regular PayPal account, so, if you don’t have one create right now.

After this, access the PayPal Developer Area, there you have all tools to test your app’s PayPal things.

On the developer area you need create a new sandbox user account, this account must be BUSINESS type. The data for this account can be anything, you don’t even need a real email. This account will provides the credentials for our app.

After create your fake account get the credentials as shown below.

PayPal needs an endpoint on your app to notifies about the changes on your customer’s subscriptions, so we need register this URL endpoint on our PayPal fake account.

You need access your fake account on the sandbox network and register the endpoint, don’t worry, we’ll create this endpoint on our app soon, this endpoint will follow this structure: http://<app-host>/paypal/ipn_listener.html. Follow the steps below.

This endpoint need be public accessible from the internet, so you only can test the notification reception with your app online or using some kind of SSH tunnel.

You need configure the recurring gem with the PayPal credentials, I highly recomend you to store the PayPal credentials on our app with ENV vars as shown in .env.sample and config/initializers/paypal_recurring.rb.

The controller layer

Our app’s controller layer will make the whole mediation within our service objects explained on the first part of this series. It’s very complicated explain every implementation detail, so, I recommend you to read the controller and service objects carefully some times to get familiar with that and if you have any question you can comment below.

Plans controller

This controller basicaly renders the page with the available subscription plans on our app with a link to subscribe.

app/controllers/plans_controller.rb

class PlansController < ApplicationController
  def index
    @plans = Plan.all
  end
end

Subscriptions controller

This is the most important controller of our app. On index action, we show all created subscriptions and there we create the subscriptions. On create action we create a subscription record on regular Rails way and after this we redirect the user to checkout_url, this url is generated by the method on line 34~41, notice that we pass to the PaypalSubscription::ResourceFacade#checkout_url two URLs, the return_url and cancel_url, this two URL is used by PayPal’s payment page to redirect our costumer back to our app.

app/controllers/subscriptions_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class SubscriptionsController < ApplicationController
  def index
    @subscriptions = Subscription.all
  end

  def show
  end

  def create
    @subscription = Subscription.create(subscription_params)
    redirect_to checkout_url
  end

  def make_recurring
    if PaypalSubscription::RecurrenceCreator.create!(
        subscription: subscription,
        paypal_options: paypal_options.merge({
          payer_id: params[:PayerID],
          token: params[:token]
        })
      )
      redirect_to subscription_path(subscription),
        notice: I18n.t('flashes.subscription.successfully_created')
    end
  end

  def destroy
    @subscription.destroy
    redirect_to subscriptions_url, notice: 'Subscription was successfully destroyed.'
  end

  private

  def checkout_url
    PaypalSubscription::ResourceFacade.checkout_url(
      paypal_options.merge({
        return_url: make_recurring_subscription_url(subscription),
        cancel_url: subscription_url(subscription, aborting_operation: true)
      })
    )
  end

  def paypal_options
    @paypal_options ||=
      PaypalSubscription::DefaultOptions.for(subscription)
  end

  def subscription
    @subscription ||= Subscription.find(params[:id])
  end
  helper_method :subscription

  def subscription_params
    params.permit(:plan_id)
  end
end

We set on the PayPal redirect URL to redirect our customer back to our app on the page /subscriptions/<id>/make_recurring.

This after payment redirect will come with important subscription informations on query string, we’ll use the PayerID, which we’ll store on the paypal_payer_id column of our customer’s subscription record and token, which will be used on the internals of the paypal-recurring gem. The action responsible to this is the make_recurring (lines 14~25).

Take a look on the explanation of the PaypalSubscription::RecurrenceCreator, this is the service object that create the recurrence profile. If this object can do it, the customer is redirected to the subscription’s page with the flash message explaining that your subscription is created and we are expecting the PayPal confirms the successful payment (remember the endpoint http://<app-host>/paypal/ipn_listener.html, we gonna receive the notification there).

PayPal controller

This is the last one. This controller have only one action, the ipn_listener, this action is public exposed to receive PayPal payloads with informations about the subscriptions statuses created on our app. This endpoint doesn’t need render anything, only respond a HTTP 200 code, if the server returns whichever error code on this request, PayPal will try send the notification again later.

app/controllers/paypal_controller.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class PaypalController < ApplicationController
  protect_from_forgery except: [:ipn_listener]

  def ipn_listener
    if params[:txn_type].present?
      PaypalSubscription::NotificationHandler.resolve!(
        subscription: subscription,
        notification: notification
      )
    end

    render nothing: true
  end

  private

  def subscription
    @subscription ||=
      Subscription.find_by!(
        id: notification.reference,
        paypal_payer_id: notification.payer_id
      )
  end

  def notification
    @notification ||= PayPal::Recurring::Notification.new(params)
  end
end

On this action we verify if the param txn_type is present on the notification payload, the request that import to this endpoint must contains this param, otherwhise we can ignore.

On the #notification method we instantiate the recurring gem PayPal::Recurring::Notification object with the request payload, this notification object is a wrapper to received payload.

On the #subscription method (line 17~23) we search to the subscription record what the payload is referencing using the reference and payer_id attributes from the PayPal::Recurring::Notification instance.

Finally we pass to the PaypalSubscription::NotificationHandler the subscription model and the notification. This service object updates the subscription record according to the notification type (canceling, updating the “paid until” and so on).

P.S.: We need skip the Rails forgery protection (line 2) on this action once this request doesn’t returns the Rails verifier token.

Profit!

Phew, PayPal subscriptions is a large topic and I hope that this guide have helped you, is very complicated explain every detail of the implementation, but again, you can read and test the full app on the Github repository, I truly recommend you to read the commit messages, where I explain every creation step.

Cya!

Este post tem o "código aberto", caso encontre algo errado, desatualizado ou queira incorporar alguma coisa faça um pull request.

comments powered by Disqus

Samuel Simões ~ @samuelsimoes