Using PayPal's Adaptive Payments and Google App Engine to build an online market with Python - Part 2

By Peter Georgeson

Overview

This multipart tutorial demonstrates how to build an online market application with Google App Engine (GAE), PayPal's Adaptive Payments and Python. This is Part 2 and covers:

The source code for the example application developed for this tutorial is hosted at GitHub.

To learn about configuring, installing and deploying the basic application, refer to Part 1 of this series.

Chained Payments

The first iteration of the store makes a direct payment to you, the store owner. However, PayPal's API enables you to send a payment to your supplier as part of the same transaction. In this "market" example, anyone can list an item for sale, so chained payments are a natural fit for managing this. The store owner can collect a commision on a payment, then pass the remaining amount to the seller.

Chained payment flow
The chained payment flow

Updating the application to use chained payments is straight-forward. First, the paypal.Pay class is updated to accept an extra parameter, specifying the secondary payment recipient.


  def __init__( self, amount, return_url, cancel_url, remote_address, secondary_receiver=None ):

Then, if this parameter is specified, the PayPal request is modified to include this as a secondary receiver, and the main email address marked as the primary receiver:


    if secondary_receiver == None: # simple payment
      data['receiverList'] = { 'receiver': [ { 'email': settings.PAYPAL_EMAIL, 'amount': '%f' % amount } ] }
    else: # chained
      commission = amount * settings.PAYPAL_COMMISSION
      data['receiverList'] = { 'receiver': [
          { 'email': settings.PAYPAL_EMAIL, 'amount': '%f' % amount, 'primary': 'true' },
          { 'email': secondary_receiver, 'amount': '%f' % ( amount - commission ), 'primary': 'false' },
        ]
      }

In settings.py, PAYPAL_COMMISSION is a new setting that defaults to 20%.

Finally, we need to pass the seller email address to this class. main.Pay.post needs to be updated:


    seller_paypal_email = util.paypal_email(item.owner)
    pay = paypal.Pay(
      item.price_dollars(),
      "%sreturn/%s/%s/" % (self.request.uri, purchase.key(), purchase.secret),
      "%scancel/%s/" % (self.request.uri, purchase.key()),
      self.request.remote_addr,
      seller_paypal_email
       )

User Profiles

The user authentication that comes with Google App Engine is based on Google accounts, however, a seller may wish to use a different PayPal email address. In order to accommodate this, user profiles were introduced. User profiles enable extra user-specific preferences to be associated with a user's account.

update a user's profile
Updating your user profile

In this case, we enable a user to set their PayPal email address. The new class is defined in model.py:


class Profile(db.Model):
  owner = db.UserProperty()
  paypal_email = db.EmailProperty()  # for payment

  @staticmethod
  def from_user( u ):
    return Profile.all().filter( "owner = ", u ).get()

In main.py, the new Profile class and the new template profile.htm manage the interface allowing a user to update their profile.

New user preferences can easily be added by updating model.UserProfile, main.Profile and the profile.htm template.

Permissions

Before going live with chained payments, extra permissions must be granted to your PayPal account. These should be requested when submitting your application for approval.

Testing in the sandbox

To test chained payments, add another seller account to your sandbox. Then update your seller profile on the application with this new email address.

Now when you buy an item from this seller, 20% of the amount will go to your primary PayPal account and 80% to this new account.

IPN

In the first iteration of this application we rely on the user to follow the link back to the application after making a purchase. We also embed a secret in the return URL to prevent a buyer from bypassing PayPal. This approach is not ideal because:

The recommended solution is IPN (Instant Payment Notification). When IPN is enabled, PayPal sends a notification to a URL of your choosing to indicate the status of a transaction. This is a guaranteed notification - PayPal implements a retry mechanism to ensure that your application is informed of a successful payment. This notification may occur some time after the user has completed the payment process.

Testing

An unfortunate property of IPN is that it requires a public facing URL, because PayPal needs to access it to send the notification. Thus localhost, and hence your local application, will not work with IPN. In order to test IPN, create a test app engine instance, in addition to your existing live instance. Just create another application instance at https://appengine.google.com/. Update app.yaml with the same ID and deploy it. This gives you two public facing applications.

As well as being able to test IPN, this gives you the ability to fully test a new version of your application before deploying it to your live instance. With App Engine, creating a staging server is that simple - one of the great advantages of cloud based deployment.

Implementation

IPN can be enabled by setting settings.USE_IPN to True. When set, ipnNotificationUrl is included in the PayPal payment request, and PayPal will attempt to notify the application when a transaction is complete.

The notification URL is defined in main.Buy:


ipn_url = "%s/ipn/%s/%s/" % ( self.request.host_url, purchase.key(), purchase.secret )

This URL pattern is implemented by main.IPN, which passes the request on to paypal.IPN for validation. IPN validation works by passing the entire request back to PayPal, which PayPal then verifies as being from them with the response VERIFIED.


    verify_request = urllib2.Request( "%s?cmd=_notify-validate" % settings.PAYPAL_PAYMENT_HOST, data=urllib.urlencode( request.POST.copy() ) ) 
    verify_response = urllib2.urlopen( verify_request )                                                 
    # check code 
    if verify_response.code != 200:                                                                     
      self.error = 'PayPal response code was %i' % verify_response.code                                 
      return 
    # check response                                                                                    
    raw_response = verify_response.read()                                                               
    if raw_response != 'VERIFIED':
      self.error = 'PayPal response was "%s"' % raw_response                                            
      return
    # check payment status                                                                              
    if request.get('status') != 'COMPLETED':                                                            
      self.error = 'PayPal status was "%s"' % request.get('status')                                     
      return
      
    (currency, amount) = request.get( "transaction[0].amount" ).split(' ')                              
    if currency != 'USD':
      self.error = 'Incorrect currency %s' % currency                                                   
      return

Once PayPal acknowledges that the notification is theirs, the application verifies that:

Now we can be certain that a buyer has made payment before fulfilling the order.

Embedded Payments

Embedded Payments is a relatively new feature available from PayPal which enables users to make purchases without ever leaving your website. The focus is on a quick checkout process and is targetted at micropayments and "in game" purchases.

To enable embedded payments, set settings.USE_EMBEDDED to True.

The purchase process changes slightly with embedded payments. In the traditional process:

For embedded payments, the process is completed without leaving the page, so the application needs to obtain a paykey token before the user clicks "Buy":

The main.Buy method is updated accordingly:

class Buy(webapp.RequestHandler):
  @login_required
    def get(self, key):
      ...
      if settings.USE_EMBEDDED:
        (ok, pay) = self.start_purchase( item )
        data['endpoint'] = settings.EMBEDDED_ENDPOINT
        data['paykey'] = pay.paykey()
        path = os.path.join(os.path.dirname(__file__), 'templates/buy_embedded.htm')

A consequence of this is that a paykey is requested every time a page containing the "Buy" button is rendered. It's not ideal for the application to be requesting paykeys for a user who is simply browsing the store - potentially this should be re-engineered into a "checkout" page to keep the application fast for the store pages.

The application uses the buy_embedded.htm template for embedded purchases so that some simple JavaScript can be added. This JavaScript adds an onclick event handler to the "Buy" button, which when triggered opens an iframe to guide the user through the payment process.

First time around, the in-page process does not appear much better than the traditional page-based process. However once a user is logged in, the purchase process becomes highly streamlined - just a single click in a lightbox. This is great for applications targetting "impulse" buys such as micropayments and in-game purchases.

embedded payment sample

Once the payment process has completed, the browser makes a request to either the Return URL or the Cancel URL. JavaScript needs to be added to these pages as well in order to close the lightbox. It's important that this JavaScript runs, otherwise the user can no longer interact with your application! For embedded payments the return URLs have been changed to render main_embedded.htm, which includes the required JavaScript.

Overall, embedded payments provide an excellent user experience, as long as everything works correctly. It requires JavaScript to be enabled in the browser - which is not the case for approximately 2% of US users.

For certain applications, it seems like the perfect solution. For instance, many online games already require JavaScript, and embedded payments can provide that immersive experience that game developers work so hard to achieve.

In the next part...

This part of the tutorial extended the basic application with:

Part 3 of this tutorial will include practical tips and considerations for hosting on the App Engine platform, as well as guidelines for transitioning this demonstration application to a live PayPal application.

Links and Further Information