Using the PayPal preapproval API for goal realization

By Peter Georgeson

Overview

PayPal's preapproval API enables you to set up an agreement with a sender that allows you to make payments on their behalf.

The API provides significant flexibility for customizing this agreement. A number of constraints are allowed, including:

Preapprovals are often used when an ongoing cost is involved in a transaction - such as a monthly subscription. The payment can be made automatically without the sender having to explicitly approve the payment each time.

Another application of preapprovals is the ability to "auto-recharge" a user's balance when it gets too low. If the user has agreed to a preapproval then this can happen automatically for the user.

Achieving your Resolutions

This article demonstrates another application for the preapproval API - as a motivational tool. For example, imagine that you have decided to write a book, perhaps as a New Year's resolution. Unfortunately, you're having trouble staying motivated. Many potentially great books never get written as they require sustained effort. This example application aims to rectify this with the use of the preapproval API.

In this application, we allow the user to set a writing target, and to specify a financial penalty if this target is not reached. Once the preapproval is configured and approved, the application can check periodically to see if the user has reached their goal or alternatively, has failed to keep up with their specified target. If they fail to reach their goal then the motivational financial penalty is applied.

With the help of the preapproval API, we can enable authors to maintain their motivation and perhaps ensure that the next Ernest Hemingway sees his book finished and published.

Getting Started

The sample code demonstrating this application of preapprovals is available on GitHub at https://github.com/supernifty/great-motivator.

The application is built on Google App Engine in Python so if you want to run the application, you will need to sign up to App Engine and install the runtime. Details are available at https://appengine.google.com/. Once the runtime is installed, elect to "Add Existing Application" and open the "app" directory in the example project.

The application also requires PayPal credentials to be specified in settings.py. Sign up to PayPal's sandbox site at https://developer.paypal.com/ and create buyer and seller test accounts.

To run the application, start the application using the Google App Engine Launcher, then browse to http://127.0.0.1:8080/.

Setting Goals

In order to track a user's goals and preapproval details, a Profile model class is added to model.py:


class Profile( db.Model ):
  owner = db.UserProperty()

  preapproval_amount = db.IntegerProperty() # cents
  preapproval_expiry = db.DateTimeProperty()
  preapproval_key = db.StringProperty()

  goal_active = db.BooleanProperty()
  goal_name = db.StringProperty()
  goal_count = db.IntegerProperty()
  goal_date = db.DateTimeProperty()

  current_count = db.IntegerProperty()
  words = db.TextProperty()
If no goal is active then the user is prompted to select a goal.

The goal page is a simple HTML FORM. The user has the opportunity to define their writing goal in terms of words and due date, along with the financial penalty if this is not achieved.

This form submission is handled by Goal.post in main.py. The user's specified goal is first saved to their profile:


class Goal(webapp.RequestHandler):
  ...
  def post(self):
    ...
    # update goal details
    profile.goal_name = self.request.get( "name" )
    profile.goal_count = int(self.request.get( "count" ))
    ( m, d, y ) = [ int( x ) for x in self.request.get( "date" ).split( '/' ) ]
    profile.goal_date = datetime.datetime( year=y, month=m, day=d )
    profile.save()

The next step is to gain preapproval for the user, so that if the user fails to achieve their goal then the specified amount can be deducted from their account without any further interaction from them. A Preapproval object is first created to track the preapproval transaction.


  item = model.Preapproval( user=user, status="NEW", secret=util.random_alnum(16), amount=int(amount*100) )
  item.put()
We then initiate the preapproval by using the helper class paypal.Preapproval:

  preapproval = paypal.Preapproval(
    amount=amount,
    return_url="%s/success/%s/%s/" % ( self.request.uri, item.key(), item.secret ),
    cancel_url=self.request.uri,
    remote_address=self.request.remote_addr )

The constructor for this class contacts PayPal and obtains a preapproval key. This key is used to create the PayPal URL that the user is redirected to:


  def next_url( self ):
    return '%s?cmd=_ap-preapproval&preapprovalkey=%s' % ( settings.PAYPAL_PAYMENT_HOST, self.response['preapprovalKey'] )
In main.Goal.post:

  self.redirect( preapproval.next_url() ) # go to paypal

The user is presented with the login page for PayPal. After logging in, they are asked to grant the preapproval request.


Note that the period for the preapproval is displayed - we set the period based on PREAPPROVAL_PERIOD defined in settings.py. In a more robust implementation, we would ensure that the goal expiry date lies within the PREAPPROVAL_PERIOD. Otherwise, the application may need to ask for preapproval again.

By default, the preapproval amount is not displayed - this can be enabled through the API call if you want to show the user that the most we can take from their account is equal to the amount that they have specified.

All going well, the user is returned to the specified return_url. In this application, the return to the application after approval is handled by main.Success. This class checks that the correct key and secret have been specified in the URL, then the user's profile is updated. The user's goal is marked as active.

Achieving goals

Now that the user has an active goal, the home page displays the status of this goal.

Some simple code enables the user to add text to their masterpiece via the textarea field, while a word count is maintained. This is implemented under main.Home:


  def post(self):
    ...
    # count words
    words = self.request.get( "words" )
    count = len( re.split( '\s+', words ) )

    # add to words
    if profile.words == None:
      profile.words = ''
    profile.words += "<hr/>" + words
    if profile.current_count == None:
      profile.current_count = 0
    profile.current_count += count
    profile.save()
All entered words are stored so that they can later be printed as a manuscript, while a running total of the word count is maintained and displayed prominently on the main home page.

Checking Goals

Now that the user can create goals and make progress towards achieving these goals, the system needs to check this progress and make use of the motivating penalty if necessary.

In the sample application, this check can be initiated manually by browsing to http://127.0.0.1:8080/check. The model.Profile.check_expired() method finds profiles that require action:


    candidates = Profile.all().filter( 'goal_active =', True ).filter( 'goal_date <', datetime.datetime.now() )
    total = 0
    failed = 0
    for candidate in candidates:
      if candidate.current_count < candidate.goal_count: # didn't make it
        failed += 1
        # take preapproval amount
        logging.info( "settling transaction..." )
        pay = paypal.PayWithPreapproval( amount=candidate.amount_dollars(), preapproval_key=candidate.preapproval_key )
        if pay.status() == 'COMPLETED':
          logging.info( "settling transaction: done" )
        else:
          logging.info( "settling transaction: failed" )

      candidate.goal_active = False
      candidate.save()
      total += 1

    return ( failed, total )

This method first finds all profiles that both have an active goal and has passed its expiry date. Each of these profiles is then checked to see if the goal was successfully achieved.

If the current word count is less than the target word count then the user has not achieved their goal. The system will attempt to take payment using the preapproval key.

The helper class paypal.PayWithPreapproval is used to achieve this. A JSON structure is constructed which includes the amount to pay and the preapproval key:


    data = {
      'actionType': 'PAY',
      'amount': '%.2f' % amount,
      'preapprovalKey': preapproval_key,
      'currencyCode': 'USD',
      'returnUrl': 'http://dummy',
      'cancelUrl': 'http://dummy',
      'requestEnvelope': { 'errorLanguage': 'en_US' },
      'receiverList': { 'receiver': [ { 'email': settings.PAYPAL_EMAIL, 'amount': '%.2f' % amount } ] }
    }
This is a direct request from server to server and does not involve interaction with the user or with their browser. As a result, the returnUrl and cancelUrl fields are irrelevant - however, these fields must still be set to something that looks like a valid URL.

Note that Google App Engine by default has quite a short timeout when making a URL fetch. This particular request timed out frequently during testing.

Fortunately, the default timeout for app engine can be increased with the following line:


urlfetch.set_default_fetch_deadline(60)

All going well, the user's account will be debited and the goal is then set to inactive. Now the user can sign up to start on their next masterpiece.

Conclusion

PayPal's Preapproval API allows a sender to grant permission enabling payments to be taken from their account in a transparent and flexible way. Preapprovals have a number of applications which typically revolve around managing ongoing transaction costs or recurring payments.

This article demonstrated how the preapproval process works with an application that motivates writers to complete their literary masterpiece. Perhaps this will inspire you to find other applications of the preapproval API.

Note that the sample application is not ready for production use and is a simple demonstration of how the API works and how an application might be built around it.

Further Details