Build an e-ticketing system with PayPal, Python and Google App Engine - Part 2

By Peter Georgeson

Overview

Part 1 of this series demonstrated the use of PayPal technologies, along with QR codes, to implement the server side of an e-ticketing system.

This part of the series implements the client side of the application. This involves giving an event holder the ability to validate ticket holders.

A recap

Part 1 of this series created a Google App Engine web application, which allowed users to purchase tickets to events. In return they would receive a QR code, or a six digit "password". The idea being that the purchaser brings along their QR code, or secret code, to gain entry to the event.

The client application needs to accept either a QR-code or a purchaser's secret code and then be able to validate it. A bonus would be to have the capacity to run the application offline.

This article demonstrates the client application as an iPhone app, however it could be easily extended to other smartphones. If the application did not use QR-scanning, it could be written as an HTML application. Since there is a QR-scanning requirement, the application must be a native application.

A problem with native apps is the effort involved in porting them to other platforms; this article demonstrates some methods for reducing the heartache involved in doing this.

Getting started

The completed iOS application is available in the GitHub repository, so if you'd like to play with the application download it from https://github.com/supernifty/paypal-eticketing/client.

iPhone application development requires a Mac and Xcode (generally). Xcode can be downloaded from the Mac App Store.

HTML as the presentation layer

This application uses HTML as its presentation layer. Although iOS comes with its own set of widgets, HTML is often an excellent choice for building the user interface. There are a number of advantages:

Some disadvantages of this approach are:

The application framework

To create an application using this approach, select "single view application" as your new project type in Xcode.

Drop a UIWebView component on to the main view and configure the main ViewController to be its delegate. The UIWebView also needs to be configured to be an outlet on the ViewController. The code for ViewController.h:


@interface ViewController : UIViewController<UIWebViewDelegate> {
    UIWebView *web;
}

@property (retain) IBOutlet UIWebView *web;

Add new blank files, main.htm, main.css and main.js. This application also includes the jQuery distributable. These files will form the bulk of the application. Place some HTML content into main.htm so that you can tell when the file loads into the UIWebView correctly.

Note that Xcode incorrectly places .js files in the "Compile Sources" build phase, which results in your JavaScript mysteriously failing. To resolve this, under Target → Build Phases, drag all JavaScript files to "Copy Bundle Resources".

The next step is to configure the UIWebView to load main.htm when the view loads. The following code is added to the ViewController.viewDidLoad method:


    [web setHidden:YES];
    NSString *path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"htm"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    [web setBackgroundColor:[UIColor clearColor]];
    [web loadData:data MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:[NSURL fileURLWithPath:path]];

The setHidden method prevents a blank screen from being displayed while the HTML is loading. Some operations in iOS are inexplicably slow.

To show the UIWebView content after the HTML has loaded, the webViewDidFinishLoad event is implemented:


- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [web setHidden:NO];
}

Now when the application is run, the contents of main.htm are displayed. We are ready to build our application interface using HTML.

The user interface

Using some basic HTML and CSS, a functional interface can be built. The following is required from the interface:



As mentioned earlier, the interface can be edited and built using a web browser. This is much faster than building the interface using the iOS simulator or an iOS device.

Server Functionality

Communication with the server takes place using AJAX. This is simple with jQuery. First though, the server needs to support AJAX requests. A new URL pattern was added to the server. This maps /api to the new class API.

The API class accepts AJAX requests encoded in JSON. The class finds the Item corresponding to the specified event name and returns the list of codes corresponding to the attendees of that event.


class API (RequestHandler):
  def post(self):
    req = simplejson.loads( self.request.get("json") )
    if req['command'] == 'load':
      # find event
      try:
        item = model.Item.all().filter( 'title =', req['title'] ).fetch(1)[0]
        # return list of attendee codes
        attendees = model.Purchase.all().filter( 'item =', item )
        result = []
        for attendee in attendees:
          if attendee.status == 'RETURNED' or attendee.status == 'COMPLETED':
            result.append( { 'code': attendee.code } )
        self.response.out.write( simplejson.dumps( { 'status': 'ok', 'result': result } ) )
      except IndexError:
        self.response.out.write( simplejson.dumps( { 'status': 'event not found' } ) )
    else:
      self.response.out.write( simplejson.dumps( { 'status': 'command not recognized' } ) )
The Python library simplejson is used to encode and decode Python objects to and from JSON. If the event exists a list of codes corresponding to paid attendees is returned.

Note that this method lacks authentication which means it shouldn't be used in production. In a more robust implementation, the user's credentials would be required before allowing them to download the complete list of attendee codes.

Client Functionality

The next step is to add behaviour to the user interface. Most of the application logic can be implemented in main.js, so in main.htm the JavaScript is initialized:

<script type="text/javascript" src="jQuery-1.7.2.min.js"></script>
<script type="text/javascript" src="main.js"></script>
<script type="text/javascript">
  $(window).load(eticketing.init);
</script>

An eticketing "class" is created and the init method configures the "Download" and "Validate" click events to link through to the appropriate implementations.


var eticketing = function() {
  var server = 'http://localhost:8088/api',
  ...
  function load_response(data) {
    if ( data['status'] == 'ok' ) {
      db = data['result'];
      set_status( db.length + ' ticket(s) downloaded' );
    }
    else {
      set_status( 'An error occurred: ' + data['status'] );
    }
  }

  function load() {
    var title = $('#title').val();
    set_status( 'Contacting server for "' + title + '"...' );
    $.ajax({
      type: 'POST',
      url: server,
      data: 'json=' + JSON.stringify( { 'command': 'load', 'title': title } ),
      success: load_response,
      error: load_error,
      dataType: 'json'
    });
  }
  ...
  return {
    init: function() {
      $('#download').on('click', load);
      ...
    },
  ...
  }
}();
The load method initiates an AJAX request to the newly created API class on the server with the entered title. load_response saves the result of this request so that later on the user will be able to validate the entered codes.

With a valid event and at least one attendee, the "Download" button is now operational.

Validation

We are now in a position to validate an entry code. The "Validate" button is linked to the validate method:


  function validate() {
    var candidate = $('#code').val();
    for ( attendee in db ) {
      if ( db[attendee].code == candidate ) {
        if ( db[attendee].attending == 'yes' ) {
          set_status( "This attendee has already been validated." );
          return;
        }
        else {
          set_status( "Code successfully validated." );
          db[attendee].attending = 'yes';
          attendees.append( candidate );
          return;
        }
      }
    }
    set_status( "Code '" + candidate + "' not found." );
  }
This method takes the entered code and compares it to every code in the downloaded list of codes. If there's a match then we record the successful validation of this code, enabling the detection of duplicate validation attempts with the same code.

Conclusion

This part of the tutorial demonstrated how to configure a basic web application to validate e-ticketing codes. In this example the web app was embedded into an iOS app. It could also operate as a standalone web page, usable on any web enabled mobile device.

AJAX was used to download all valid tickets and store them on the web app. This enables the app to run offline if required. It also means that we are guaranteed to get a fast response to validation requests.

In the next part of the tutorial the native functionality will be integrated into the iOS application. QR scanning will be added to the application, as well as a method for keeping the client application in sync with the server.

Further Details