Real-time auctions with HTML5, PayPal and Google App Engine - Part 2

By Peter Georgeson

Overview

This series of articles explores some interesting possibilities with

We develop a "real-time" auction page by taking advantage of some of the more recent web developments.

The first part of this tutorial described how to configure and deploy the sample application to Google App Engine, and how to use Google's Channel API to implement real-time web applications.

This is Part 2, which describes how to integrate payments into our real-time auction application with PayPal's Adaptive Payments API.

The sample application, including all source code, is available from GitHub. Download it and follow along.

The problem with auctions

An issue with auctions is that once the auction is over, you really want the final bidder to make the required payment. If the final bidder fails to make payment then potentially a lot of people have wasted their time bidding on an item that hasn't been sold. It also means the site or auctioneer has wasted valuable time holding an auction with no return.

Historically this has been a problem for both real and virtual auctions.

Ensuring the buyer pays

One solution to this problem is to force users to keep a "balance" on the auction site. A buyer can only bid up to their site balance on an auction, which then guarantees payment. The auction site holds the money in the buyer's "account" before the auction even begins. Using this scheme, buyers could top up their balance by depositing money using a standard PayPal payment button.

This is a simple solution, one that is employed by many gambling sites. However, it's not ideal.

These disadvantages and difficulties serve to deter buyers from using the site and from bidding. In today's world of online retail competitiveness, a solution that does not hinder the buyer is preferred.

Using PayPal's Adaptive Payments to ensure the buyer pays

PayPal's Adaptive Payments API offers a solution to this problem with the Preapproval API Operation. Once configured, the auction site has permission to withdraw up to a specified amount from a buyer, over a specified time period. This scheme effectively acts as a user balance, without the disadvantages associated with forcing the user to transfer actual money.

The auction site can guarantee payment without having to take the buyer's money until they make a purchase.

Setting up a preapproval

Configuring a preapproval is relatively simple. The process is identical to a normal payment, just with a few different parameters in the request.

A user can initiate a preapproval from the profile page. It is implemented at main.Profile.post().

When the user clicks the "Update" button, a paypal.Preapproval class is instantiated with the desired preapproved amount.

The class paypal.Preapproval implements the communication with PayPal using the following data structure:

  now = datetime.datetime.utcnow()
  expiry = now + datetime.timedelta( days=settings.PREAPPROVAL_PERIOD )
  data = {
    'endingDate': expiry.isoformat(),
    'startingDate': now.isoformat(),
    'maxTotalAmountOfAllPayments': '%.2f' % amount,
    'currencyCode': 'USD',
    'returnUrl': return_url,
    'cancelUrl': cancel_url,
    'requestEnvelope': { 'errorLanguage': 'en_US' },
  }

Compared to a normal payment, the main differences are that a preapproval also includes a startingDate, endingDate and maxTotalAmountOfAllPayments. Many other options are available, such as which days of the week are permissible and how many payments can be made. Refer to the Adaptive Payments API for details.

The starting date is set to now; the ending date is calculated from this date. maxTotalAmountOfAllPayments is set to the desired preapproval amount.

If the call to PayPal yields a preapprovalKey, the browser is redirected to PayPal to complete the process.

We include a "secret" in the return URL. This is a random string which is stored on the database. This prevents an attacker from going directly to the "success" page without going through PayPal's approval process. In main.Profile.post():


  item = model.Preapproval( user=user, status="NEW", secret=util.random_alnum(16), amount=int(amount*100) )

A successful transaction returns the user to main.Success. The application validates the transaction by checking the "secret" and verifying that this is not an old transaction. In main.Success.get():


def get(self, key, secret):
  item = model.Preapproval.get( key )
  ...
  if item.secret != secret:
    item.status_detail = 'Incorrect secret %s' % secret
    item.status = 'ERROR'
    item.put()
    self.error(501)
  ...

All going well, the user's profile is updated with the new preapproval balance, expiry date and preapproval key.

With the preapproval in place, we can now take user bids with the ability to receive payment from the buyer without requiring the buyer to explicitly authorize it.

Taking payment after winning an auction

The application only accepts a user bid if it is within their current preapproved limit. If a user needs to bid more, they can quickly and easily update their preapproved limit on their profile page. This enables the application to immediately take payment at the end of an auction, and enables a user to enjoy an uninterrupted bidding experience. It also provides the auction site with a high likelihood of being able to take payment from the winning bidder, with minimal effort from the buyer.

In Part 3 of this tutorial we intend to make this application suitable for mobile devices. Arranging payment details in advance is an advantage for mobile users - mobile applications typically have limited bandwidth, smaller screens and less patient users. Removing the requirement to manage the payment process during auctions removes another impediment to a user making a purchase.

Implementing a preapproved payment is easy. It is identical to a normal adaptive payment, the only difference being that the preapprovalKey must be included in the request. This is implemented in the sample application by the class paypal.PayWithPreapproval.

When an auction finishes, the application checks to see who has won the auction. A payment is then initiated using this user's preapproval key, in the method model.Item.settle():


  pay = paypal.PayWithPreapproval( amount=bid.amount_dollars(), preapproval_key=profile.preapproval_key )
  if pay.status() == 'COMPLETED':
    # update db
    profile.preapproval_amount -= bid.amount
    profile.save()
    self.status = 'SETTLED'
    self.save()
    logging.info( "settling transaction: done" )
    return True
  else:
    # something went wrong
    return False

Unlike a normal payment where the user is redirected to PayPal, for a preapproved payment the expectation is that the transaction will be completed without any further action required by the user. Hence the application checks for COMPLETED as the status returned from PayPal. This being the case, the item is marked as SETTLED and the buyer's preapproved balance is updated accordingly. The item has been sold successfully and can be delivered to the buyer.

If the transaction has not completed for whatever reason, the user is notified that there was a problem. Payment will need to be made separately. Although this scenario is not implemented in the sample application, a separate page could be provided to allow the user to manually make payment. Failing that, the item could be offered to the next highest bidder, or simply re-auctioned.

Other Considerations

Although the sample application demonstrates the fundamentals of using preapprovals to guarantee payment, there are some additional considerations not covered by this implementation.

Cancelling a Preapproval

According to the preapproval specification, preapprovals are "additive". If a user updates their preapproval balance when they already have an existing preapproval, they will end up with the sum of all existing preapprovals. This may not be desirable for the user.

The system should implement the CancelPreapproval operation when overriding an existing preapproval with a new one.

Special Permissions

Preapproval is a "special permission". If you intend to go live with a system that uses preapprovals, permission must be explicitly requested from PayPal when submitting an application.

Payment is not guaranteed

Although having a preapproval in place makes it likely that a payment will succeed, it can still fail for various reasons. A strategy for dealing with failed payments is required. An example strategy would be to offer a manual payment process, and if that fails, re-auction the item.

Conclusion

This part of the tutorial built upon the basic framework from Part 1 by integrating PayPal as the payment provider. The sample application uses the Preapproval API to enable the system to automatically take payment from the winning bidder. It also encourages a simplified bidding process to improve the application experience on mobile platforms. This is the focus of Part 3.

Part 3 will discuss a number of options for making a HTML-based payment application suitable for use on mobile platforms.

Further Details