Tech Blog

Integrating Alma and PayPal with the Alma REST APIs

In this world of cloud systems and integrated user experiences, patrons expect to be able to perform transactions in a frictionless manner. And paying library fines is no exception. Currently, Alma provides out-of-the-box support for the WPM Education payment system in use by many UK institutions.  But the Alma APIs allow us the freedom to integrate with any payment system from a University portal application or even from Primo.

In this article, we’ll show an end-to-end flow from a sample portal application to PayPal, and then we’ll add the ability to offer a pay fines service from Primo as well. We’re using the sample student portal application introduced last year as a base, and we’ll add the new functionality there.

Overview

The general flow for integrating with any payment system is the same, so we’ll use PayPal in our example but other systems can be integrated as well.

The flow can be summarized as follows:

  1. A patron requests to view his fees on a student portal
  2. The portal queries Alma and displays a list of fees and the balance. The patron requests to pay his fees via PayPal
  3. The portal creates a transaction in PayPal for the relevant amount, then redirects the patron’s browser to PayPal for approval
  4. The patron logs into PayPal and approves the payment and payment method. PayPal redirects the browser back to the portal application
  5. The portal executes the payment in PayPal using the specified transaction code
  6. Once confirmation is received from PayPal, the portal posts the payment to Alma to be reflected in the patron’s account
  7. The portal displays a confirmation message and a zero balance

If a picture is worth a thousand words, the diagram below should be helpful.

About the App

Our original sample app was written in C#. We’ve since ported it to Ruby on Rails and implemented all of the original functionality, including:

  • Authentication with Google OAuth
  • Library card– displays some basic student information and allow users to update a few key data elements
  • Requests– shows a list of the student’s requests, and allow the user to cancel them

Fines List

We’ve added a link to our fines controller to the home page and the menu bar. Now we need to add the controller itself. We create a new fines_controller.rb in the controllers folder.

We’ll add a before action filter to our controller which will populate an object with the fines from Alma.

before_action :get_fines, only: [:index, :pay]

The private get fines method uses a helper method to call the Alma Get User Fines API and transform the JSON results into a Ruby object.

def get_fines
   @fines = alma_api_get("/users/#{current_user.uid}/fees")
end

The helper method uses the concise RestClient  library to make an HTTP GET call to the Alma API, passing in the Accept header to indicate our preference for JSON, and the Authorization header which includes the API key we issued from the Developer Network Dashboard.

def alma_api_get(uri)
  response =
      RestClient.get ENV['almaurl'] + uri,
      accept: :json,
      authorization: 'apikey ' + ENV['apikey']
   return JSON.parse(response.body)
end

Now that we have the list of fines, we want to render the index view (by default) and display the list of fines.

The fines index view simply loops through the fines to create a table. And at the end it displays a button which prompts the user to “Pay with PayPal.” This button links to the controller called “pay.”

<%= link_to 'Pay with PayPal', pay_fines_path, class: 'btn btn-default'%>

Pay with PayPal

We’re going to need three different routes for the Pay controller: one for the pay button we just created, one to handle the confirmation from PayPal, and the third we’ll use later when we integrate with Primo. So we add these lines to the routes.rb file:

resources :fines, only: [:index] do
   collection do
      get 'pay'
      get 'confirm'
      get 'validate'
   end
end

The Pay method is where we first communicate with PayPal. The new PayPal REST APIs are well documented and easy to use. The flow to create a payment is as follows:

  • Use the PayPal developer OAuth credentials to retrieve a token
  • Create a payment
  • Redirect the user to PayPal to authorize the payment
  • Execute the payment

The Pay method then retrieves the fines from Alma, calls the PayPal API to retrieve a token, and sends a create payment API call to PayPal for the amount specified by Alma. Then we redirect the user to the URL provided in the PayPal response.

def pay
   fines = get_fines()
   payment = Payment.new(fines["total_sum"], fines["currency"],
      request.base_url)
   token = get_token

   # store in session for later use
   session[:token] = token

   # send payment to PayPal
   transaction = send_payment(token, payment)

   # find approval URL to redirect to
   if transaction["state"] == "created"
      approval_url = transaction['links'].find {
         |key, val| key['rel'] == "approval_url"
      } ['href']
      redirect_to approval_url
   else
      flash[:alert] = "There was an error processing your payment."
      render :error and return
   end
end

We use a model called Payment to define the structure required in the PayPal API and create the required JSON automatically. (We could have hard-coded the JSON structure, but this seemed easier.)

The code for calling PayPal is located in the fines helper. It’s very similar to the code which we’ve written to make Alma API calls, using the Authorization header to pass the token to PayPal.

Approve in PayPal

Once we’ve redirected the user’s browser to PayPal, the user logs in to approve the payment. Of course, the user has the opportunity to cancel the payment as well. To facilitate this, we provided to PayPal two URLs when we created the payment, one to confirm the transaction and one to cancel it. For the confirmation URL, we provided /fines/confirm. For the cancel URL, we send the user back to the fines index page.

Execute the Transaction

In the confirm method of the fines controller, we use the payment ID and payer ID provided in the query string from PayPal to call the execute API. We receive a confirmation structure from PayPal, which we check to confirm the payment has been approved. If so, we need to close the loop by posting the payment to Alma so that it’s reflected in the patron’s account. To do that, we call the POST payment API, including the amount and the confirmation number from PayPal in the note.

Once the payment has been posted to Alma, we redirect the user back to the fines index with a message of thanks for the payment.

def confirm
   # Execute payment at PayPal
   payment = execute_payment(session[:token], params["paymentId"],
      params["PayerID"])

   if payment["state"] == "approved"
      amount = payment["transactions"][0]["amount"]["total"]
      params = {
         "op"                      => "pay",
         "amount"                  => amount,
         "method"                  => "ONLINE",
         "external_transaction_id" => params["paymentId"]
      }
      qs = params.collect {|key,value| "#{key}=#{value}"}.join "&"
      alma_api_post("/users/#{current_user.uid}/fees?#{qs}", nil)
      redirect_to fines_path,
         notice: "Your payment has been successfully processed " \
            "and #{amount} has been credited to your account. Thanks!"
   else
      flash[:alert] = "There was an error processing your payment."
      render :error and return
   end
end

We also can see that the transaction was posted to Alma and applied to all of the fees on the patron’s account. We even get a receipt email from Alma with the PayPal transaction number and a payment type of “Online”.

Integrating with Primo

We’ve successfully done an end-to-end payment flow with PayPal in a custom student portal. If the discovery tool in use has a library card section that displays fines, we might want to add a link from there to start the payment process.

The flow is essentially the same once the payment link is clicked, with one exception. When using the student portal, the patron was authenticated already, via Google OAuth in our example. When coming from the discovery system, we need to resolve the existing authentication or use a single sign on system to allow the patron to sign in again.

In our example, we’ll integrate the payment link with Primo. The process is as follows:

  1. Add a Pay Now link to the Fines & Fees page in the Primo My Library Card. The link will send the patron to our fines controller via PDS, which serves as the single sign on system.
  2. The fines controller will validate the PDS handle and retrieve the user information
  3. The payment process will continue as before

Pay Fines Link

To add a link to pay fines in Primo, we go into the back office to Advanced Configuration à All Mapping Tables. In the My Account Links table, edit the fines.payfeelink entry to point to the validation method we’ll build in the next section. This will cause the Pay Fines link to appear in Primo My Account, as displayed below.

In order to receive the PDS handle, the link should utilize the SSO functionality of PDS:

http://PDS-URL/pds?func=sso&url=VALIDATE-METHOD-URL

Validate PDS Handle

In order to validate the PDS handle, we’ve added a new “validate” method to the fines controller. The validate method calls the bor_info API in PDS to receive the patron information based on the PDS handle. The bor_info method is a URL which accepts a PDS handle in the querystring and returns an XML document. We then create a user object and populate it with the information we received from PDS.

Once validated, we redirect to the fines index screen to allow the patron to confirm the amount and launch the payment process.

def validate
   # called from Primo to resolve users from PDS handle
   redirect_to root_path and return unless params["pds_handle"]

   # Resolve PDS handle
   bor_info = get_pds_bor_info(params["pds_handle"])

   # Create user from bor_info  
   user = User.from_pds(bor_info)
   session[:user_id] = user.id
   session[:pds] = "true"

   redirect_to :action => "index"     
end

The only other change to the process is that we’ve added a “popup” layout that removes the menu. Then we add a method that selects the popup layout if we’ve authenticated via PDS.

class FinesController < ApplicationController
   layout :choose_layout

   def choose_layout
      "popup" if session[:pds]
   end

The process then continues exactly as before.

Conclusion

The Alma APIs are a powerful way to extend the functionality provided in Alma. In this example, we’ve integrated Alma with PayPal to allow patrons to pay their fines and fees directly from a university student portal or from within the library card in Primo.

The entire application created for this article is available in this Github repository.

Leave a Reply