Using the permissions API to visualize PayPal account usage - part 2

By Peter Georgeson

Overview

PayPal's Permissions API provides a programmatic method for performing operations on another user's PayPal account. The Permissions API enables operations to be performed on a user's account without requiring full control of their account, or the user's login details.

This opens up some interesting application possibilities while maintaining the security of a user's account.

Part 1 of this series explained how to setup a PayPal application on Google App Engine that utilized the permissions API to provide some simple account analytics.

This is part 2 and builds on the existing account visualization application by adding some new analysis functionality and by demonstrating how to use some more of the available API calls.

Setup

If you haven't already, it is recommended that you peruse part 1 of this series, which covers the application setup in detail. In brief:

  1. This is a Google App Engine application - download and install app engine
  2. You'll need PayPal sandbox credentials. If you don't already, sign up at https://developer.paypal.com/ and configure some test accounts.
  3. Download the sample application from GitHub
  4. Update settings.py with the correct PayPal settings
  5. Start up the Google App Engine Launcher: Windows and Mac come with an easy to use GUI;
  6. Add an existing application: File→Add Existing Application;
  7. Browse to the "app" directory in your local GitHub repository;
  8. Click "Run";
  9. Direct your web browser to http://127.0.0.1:8080/.

All going well, a basic front page will be displayed:

Homepage screenshot
Figure 1: The application's home page

More account analysis

The basic application demonstrates the permissions API concept. It presents a simple visualization of monthly incomings and outgoings based on the net_amount field returned by the TransactionSearch API call. However, the PayPal interface provides a great deal more information - even the basic TransactionSearch API call returns a wealth of information that is suitable for analysis and visualization, including:

Using the counterparty name, it is relatively simple to break transactions down into an account's main sources of income and expenditure.

Pie chart
Figure 2: Find your important clients

In main.Return a simple dictionary can be used to track amounts against different counterparties:


groups = {}
groups['names'] = defaultdict(float)
for i in tx.items:
  amount = float( i[ 'net_amount' ] )
  ...
  groups['names'][i['name']] += float( i['net_amount'] )

Then the amounts are separated into those with positive values (income) and negative values (expenditure):


for k in groups['names']:
  amt = groups['names'][k]
  if amt > 0:
    y_list.append( k )
    y_value.append( groups['names'][k] )
    ...
  else:
    x_list.append( k )
    x_value.append( -groups['names'][k] )
    ...

Now that we have a breakdown we use Google Charts to build a suitable chart. Google's Chart API is very simple to use. The income pie chart is generated by a one-line URL:


http://chart.apis.google.com/chart?chs=300x300&cht=p&chd=t:{{ yv|join:"," }}&chdl={{ yl|join:"|" }}&chds=0,{{ ymax }}"

The TransactionSearch results provide many opportunities for analysis, however, if this level of detail isn't sufficient, it is possible to obtain even more detailed transaction information using the GetTransactionDetails call. Refer to the documentation for details.

Requesting more permissions

It is relatively straight-forward to request multiple permissions. In this section, we request the user's account balance. By combining our existing transaction data with the user's account balance, we can visualize the account balance over time. This involves a new API call, GetBalance, and requires a new permission: ACCOUNT_BALANCE.

First, the permissions request is modified to ask for multiple permissions. The permissions parameter is simply updated to become a list:


paypal.RequestPermissions( "%sreturn" % self.request.uri, ["TRANSACTION_SEARCH", "ACCOUNT_BALANCE"], self.request.remote_addr )

The new permission is reflected at PayPal when the user is asked to approve the additional permission:

PayPal screenshot
Figure 3: Additional permission request shows up at PayPal

The new class added to paypal.py - GetBalance - follows a similar structure to the TransactionSearch class and returns the user's balance in their primary currency.

This piece of information is then combined with the transaction details retrieved earlier to build the user's balance history:


current = float(balance.items[0]['amount'])
balances = [ current, ]
for i in tx.items: # newest to oldest
  current -= float(i['net_amount'])
  balances.insert(0, current)
We start with the last known balance and work backwards through the transactions to calculate the user's balance over time.

This data is plugged in to a Google chart to produce the balance history visualization:

Balance history
Figure 4: Balance History Visualization

Caching the authorization signature

In the first version of this web application, the user returns to the website from PayPal, and the signature is then generated from the returned request token and verification code:


access = paypal.AccessPermissions( self.request.get( "request_token" ), self.request.get( "verification_code" ), self.request.remote_addr )
if access.ok():
  signature = paypal.AuthorizationSignature( access.token(), access.token_secret(), self.request.remote_addr )

This request handler then uses the calculated signature to make the required privileged API calls.

The problem with this approach is that the request token expires after about 15 minutes. This means that if you simply refresh this page, or return to it, the request token will expire relatively quickly, and the attempt to get a new token and token secret will fail. This means that every 15 minutes, the user will need to visit PayPal and grant your application permissions again.

Sometimes this may be what you want - perhaps you are requesting some high-risk permissions that you are happy to have time out 15 minutes after the user has granted them. However, there are also applications where you would like to allow the user to stay "logged in". For instance, in this application, you may want to allow the user to manipulate and refresh their analytics page without having to revisit PayPal every 15 minutes.

In any case, a better implementation enables the webapp to control the length of the user's session, instead of relying on the lifespan of the request token.

The calculated signature does not expire, so a better approach is to calculate the signature and store it. By associating the signature with the user's browser, the user can remain logged in.

The simplest way to do this is with a browser cookie.

Rather than place the actual signature in the browser cookie, the signature is stored on the server and a unique ID is provided to the browser as cookie data. The mapping between unique ID and calculated signature occurs on the app engine datastore. This avoids exposing the somewhat sensitive signature and keeps the data transfer between browser and server to a minimum. More data could be associated with the user if desired.

The datastore mapping is defined in model.py:


class Session(db.Model):
  session = db.StringProperty()
  signature = db.TextProperty()

Next, the main.Return class is modified to save the signature to the datastore and generate a cookie for the user:


signature = paypal.AuthorizationSignature( access.token(), access.token_secret(), self.request.remote_addr )
...
target = model.Session()
target.session = uuid.uuid4().hex
target.signature = signature.signature()
target.put()

On App Engine, cookies need to be set by directly adding the relevant header to the response:


self.response.headers.add_header(
  'Set-Cookie',
  'session=%s; expires=Fri, 31-Dec-2020 23:59:59 GMT' \
  % target.session.encode())

Finally, the browser is redirected to the analysis page:


self.redirect( "/analysis" )

In main.Analysis, we retrieve the signature from the cookie, by first retrieving the session ID from the cookie, and extracting the signature from the datastore.


  target = model.Session.all().filter( "session = ", self.request.cookies["session"] ).get()
  ...
  signature = target.signature

Now all the API calls can happen as normal and the analysis page can be built. The difference now is that as long as the browser cookie remains valid, the user can return to this analysis page at any time, without having to go through the process of granting permission to the application every time.

The transaction data is still retrieved from PayPal every time - the analysis page will continue to be up to date and no PayPal data has been cached. All that is stored on the web app is the calculated PayPal signature.

This improvement opens up a number of opportunities to extend the application further. Clearly, using a "browser cookie" could be expanded to be a more formal login system on the website, allowing users to configure preferences specific to the application.

Storing the PayPal signature also creates the possibility of accessing a user's account and acting on their behalf when they are not present. For instance, the application could be extended to provide a monitoring service that notifies the account holder when interesting events occur on their account. For example, with the authorization signature, the application could check the user's balance periodically and send an email notification if the balance falls below a specified threshold.

Conclusion

PayPal's Permissions API provides some interesting opportunities for operating on a user's behalf, without the user having to provide full access to their account. This example application provides some useful account analytics and could easily be expanded further, while only requiring read-only access to the user's PayPal account. The user can be confident that the application - even if it was completely compromised - could not also compromise their PayPal account.

Hopefully this series of articles has demonstrated some important aspects of the Permissions API and provided potential ideas and applications that utilize this functionality.

Links and Further Information