Using the permissions API to visualize PayPal account usage

By Peter Georgeson

Overview

PayPal's Permissions API provides a programmatic method for performing operations on another user's PayPal account. The operations you can perform cover a variety of scenarios, ranging from retrieving activity summaries, right up to performing payments on their behalf.

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 tutorial covers the steps involved in building an application that uses the PayPal permissions API to build an account usage visualization tool.

Setup

The example application is built on Google App Engine and the source code is available on GitHub.

To configure the sample application to run locally:

  1. Sign up for Google App Engine;
  2. Download and install the AppEngine SDK for Python. Note that Python version 2.5 or newer is required. App Engine is not compatible with Python 3;
  3. You'll need PayPal sandbox credentials. If you don't already, sign up at https://developer.paypal.com/ and configure some test accounts.
  4. Configure your PayPal credentials in settings.py. To retrieve your credentials, log in to the PayPal sandbox and click on "API Credentials".

To run the sample application locally:

  1. Start up the Google App Engine Launcher: Windows and Mac come with an easy to use GUI;
  2. Add an existing application: File→Add Existing Application;
  3. Browse to the "app" directory in your local GitHub repository;
  4. Click "Run";
  5. Direct your web browser to http://127.0.0.1:8080/.
All going well, a basic front page will be displayed:

The following sections describe the steps involved in building a permissions based application.

Step 1: Determine required APIs

Typically, your application first requests permission to perform an operation on a user's account. After the account holder explicitly grants this permision, your application then executes the operation on the user's account.

The first step is to determine which API calls your application will need to execute on behalf of the user. Available API calls are listed in the PayPal Permissions API Documentation. Our sample application requires the TransactionSearch API call.

Step 2: Determine required permission groups

Rather than requesting a particular API call, you request a list of Permission Groups. Each API call has been placed in a group, and you need to specify which permission groups your application requires.

The groups are described in the PayPal Permissions API Documentation. Check the required API calls against the permission groups and make a list.

The TransactionSearch API call comes under the permissions group TRANSACTION_SEARCH. This is the only permission group that will be requested by the sample application.

In this application we only intend to make one API call against the user's account, and so only one permission group is required. In a more complicated scenario, multiple groups can be requested.

Step 3: Request permissions

The next step is to make the RequestPermission API call. You are asking the user to grant the desired permissions.

PayPal responds to this call with a URL which the application must redirect the user to. The user is asked to login and they will be asked if they wish to grant this permission.

A big advantage of this process is security. The user does not need to give up full control of their account and does not need to enter their password anywhere other than at PayPal.com.

In the sample app, the RequestPermision API call is initiated when the user clicks on Show Visualization. This generates a FORM POST, which is handled in main.py in Home.post:


def post(self):
  # start and redirect to paypal
  permission = paypal.RequestPermissions( "%sreturn" % self.request.uri, "TRANSACTION_SEARCH", self.request.remote_addr )
  if permission.ok():
    logging.debug( "next_url: " + permission.next_url() )
    self.redirect( permission.next_url() )
  ...

The RequestPermissions class has been added to the paypal module:


class RequestPermissions( object ):
  def __init__( self, return_url, permissions, remote_address ):
    headers = {
      ...
    }

    data = {
      'scope': permissions,
      'requestEnvelope': { 'errorLanguage': 'en_US' },
      'callback': return_url
    }

    self.raw_request = json.dumps(data)
    self.raw_response = url_request( "%s%s" % ( settings.PAYPAL_ENDPOINT, "RequestPermissions" ), data=self.raw_request, headers=headers ).content()
    logging.debug( "response was: %s" % self.raw_response )
    self.response = json.loads( self.raw_response )

  def ok( self ):
    return self.response.has_key( 'token' )

  def next_url( self ):
    return '%s?cmd=_grant-permission&request_token=%s' % ( settings.PAYPAL_PAYMENT_HOST, self.response['token'] )

This API call mirrors the Pay call - authentication details are specified in the HTTP headers and data is specified in the request body as a JSON object.

The important field is scope which must contain a comma separated list of the required permission groups. The other important field is return_url which tells PayPal where to send the user after they have granted the permissions.

If the RequestPermission call succeeds then the user will be directed to PayPal and asked to grant the requested permissions. Once they login and agree, PayPal will send them back to the specified return URL.

Step 4: Access permissions

The return to the application after granting permissions is handled by the main.Return class:


class Return(webapp.RequestHandler):
  def get(self):
    access = paypal.AccessPermissions( self.request.get( "request_token" ), self.request.get( "verification_code" ), self.request.remote_addr )
    if access.ok():
      ...

The redirected URL includes the parameters request_token and verification_code. These values are needed for the call to GetAccessToken, which is handled by the class paypal.AccessPermissions:


class AccessPermissions( object ):
  def __init__( self, token, verifier, remote_address ):
    headers = {
      ...
    }

    data = {
      'token': token,
      'verifier': verifier,
      'requestEnvelope': { 'errorLanguage': 'en_US' },
    }

    self.raw_request = json.dumps(data)
    self.raw_response = url_request( "%s%s" % ( settings.PAYPAL_ENDPOINT, "GetAccessToken" ), data=self.raw_request, headers=headers ).content()
    logging.debug( "response was: %s" % self.raw_response )
    self.response = json.loads( self.raw_response )

  def ok( self ):
    return self.response.has_key( 'token' )

  def token( self ):
    return self.response[ 'token' ]

  def token_secret( self ):
    return self.response[ 'tokenSecret' ]

The GetAccessToken call has the same structure as RequestPermissions. It requires the request_token and verification_code which signify that the user has granted the permission. It returns a token and a tokenSecret, which are required for the next step.

Step 4: Generate the authentication signature

Once the access tokens have been obtained, the signature can be generated. This is a hash combining a bunch of details including your password, a timestamp, and the tokens obtained earlier. The generated signature is included in the API call that we make on the user's behalf. It is considered proof that permission has been granted.


class Return(webapp.RequestHandler):
  def get(self):
      ...
      signature = paypal.AuthorizationSignature( access.token(), access.token_secret(), self.request.remote_addr )
      ...

When PayPal receives the API call, they generate their own hash and check that it matches the hash that you have generated. The hash generation is implemented in the paypal.AuthorizationSignature class:


class AuthorizationSignature( object ):
  '''generates the X-PP-AUTHORIZATION header'''
  def __init__( self, token, token_secret, parameters ):
    self.token = token
    self.timestamp = int( time.time() )
    self.key = "%s&%s" % ( settings.PAYPAL_PASSWORD, AuthorizationSignature.encode( token_secret ) )
    self.base = "%s&s" % ( 'POST', AuthorizationSignature.encode( settings.PAYPAL_API_ENDPOINT ) )
    self.params = "oauth_consumer_key=%s&oauth_signature_method=HMAC-SHA1&oauth_timestamp=%i&oauth_token=%s&oauth_version=1.0" % (
      settings.PAYPAL_USERID, self.timestamp, token )
    self.raw = "%s&%s" % ( self.base, AuthorizationSignature.encode( self.params ) )
    # sign
    self.hashed = hmac.new( self.key, self.raw, hashlib.sha1 )
    self.signed = base64.b64encode( self.hashed.digest() )
    logging.debug( "key: %s, params: %s, raw: %s, signed: %s, b64: %s" % ( self.key, self.params, self.raw, self.signed, self.signed ) )

  def signature( self ):
    return "timestamp=%i,token=%s,signature=%s" % ( self.timestamp, self.token, self.signed )

  ...

Implementing your own signature generator is not recommended; if your generated hash doesn't match, it is quite difficult to debug! Fortunately there are reference implementations for a number of languages - including Python, above!

This signed hash verifies that the request originates from your PayPal account, and that you have obtained the appropriate permissions to execute the operation on the target PayPal account.

Step 5: Make the API call

With the signature generated, we are now ready to execute the TransactionSearch operation on the target user's PayPal account. This is called from main.Return and is implemented by the paypal.TransactionSearch class:

class TransactionSearch( object ):
  def __init__( self, start_date, signature, remote_address ):
    headers = {
      'X-PP-AUTHORIZATION': signature,
    }

    data = {
      'METHOD': 'TransactionSearch',
      'VERSION': '74.0',
      'STARTDATE': start_date.isoformat(),
    }

    self.raw_request = urllib.urlencode( data )
    self.raw_response = url_request( "%s" % settings.PAYPAL_API_ENDPOINT, data=self.raw_request, headers=headers ).content()
    logging.debug( "response was: %s" % self.raw_response )
    self.response = cgi.parse_qs( self.raw_response ) # 2.5
    self.items = []
    self.count = 0
    while self.response.has_key( "L_TIMESTAMP%i" % self.count ):
      self.items.append( {
        'timestamp': self.safe_get( "L_TIMESTAMP%i" % self.count ),
        'timezone': self.safe_get( 'L_TIMEZONE%i' % self.count ),
        'type': self.safe_get( 'L_TYPE%i' % self.count ),
        'email': self.safe_get( 'L_EMAIL%i' % self.count ),
        'name': self.safe_get( 'L_NAME%i' % self.count ),
        'transaction_id': self.safe_get( 'L_TRANSACTIONID%i' % self.count ),
        'status': self.safe_get( 'L_STATUS%i' % self.count ),
        'amount': self.safe_get( 'L_AMT%i' % self.count ),
        'fee': self.safe_get( 'L_FEEAMT%i' % self.count ),
        'net_amount': self.safe_get( 'L_NETAMT%i' % self.count ),
      } )
      self.count += 1

  def ok( self ):
    return self.response.has_key( 'ACK' ) and self.response[ 'ACK' ][0] == 'Success'

  ...

The generated signature is added to the HTTP headers as X-PP-AUTHORIZATION. This provides all the required authentication information, including which account the operation will be executed against.

Otherwise it is a normal API call. This particular API method uses a different endpoint and uses NVP (name-value pairs) as the format for exchanging data. You can handle this format in Python with the urllib.urlencode method to encode NVP and the parse_qs method to decode NVP.

The only slight inconvenience with NVP is that is is unstructured, unlike JSON and XML. To handle the multiple records that are returned by this call (one for each transaction), the field names are appended with a record number. We convert this into a normal Python array.

Step 6: Profit!

The TransactionSearch method returns an array of transactions for the user - now they can be visualized or manipulated in some enlightening way.

In the sample application, each transaction is placed into a bucket based on the month of its occurrence and whether it is income or expenditure. This data is then used to generate a Google Chart:

Although this is a very simple visualization, of a not very active PayPal account, it provides some useful information. For instance, it is clear that March was the most active month for this PayPal account. Clearly there is potential for this to be expanded into a more complete visualization tool.

Conclusion

This tutorial covered the steps involved in requesting a permission and then executing an operation on another user's PayPal account.

The sample application requests permission to retrieve a user's transactions. Once given that permission, the application presents a simple visualization based on that data.

This article has demonstrated the potential of the Permissions API and hopefully provided a launching point for more interesting applications of the API.

Links and Further Information