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

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.

Part 2 of this series implemented a basic iOS client using a pure solution based on HTML and JavaScript. The goal was to present a simple interface that could easily be implemented on other platforms such as Android and to enable a fallback to a basic web client.

One requirement of the client is to be able to scan QR-codes. This must be implemented natively and is demonstrated in this part of the tutorial. This article also demonstrates a solution for keeping the list of valid tickets synchronized across multiple clients.

Executing native code

Barcode scanning cannot be initiated on iOS from plain HTML/JavaScript. We need a method of executing native code from a web-based user interface. Fortunately, iOS provide a fairly simple way of doing this.

The UIWebViewDelegate interface includes the method shouldStartLoadWithRequest which enables any URL request in the UIWebView to be intercepted. A form is placed around the Scan QR code button with the following code:


  <form action="native:qr" method="get"><input type="submit" id="qr" value="Scan QR Code"/></form>

Now, mark the main ViewController class as implementing UIWebViewDelegate, then add an implementation for shouldStartLoadWithRequest to the class:


- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSLog(@"got request: %s", [[[request URL] absoluteString] UTF8String]  );
    if ( [[[request URL] absoluteString] hasPrefix:@"native:qr"]) {
        NSLog(@"got qr command");
        [self showScanner];
        return NO;
    }
    return YES;
}

When the user clicks "Scan QR Code", this method intercepts the request for the URL native:qr and showScanner will be executed. Note that this method returns NO, which prevents the browser from attempting to navigate to the requested URL.

Now that the request is being intercepted, the application needs to start up its barcode scanner.

QR code scanning libraries

There are a number of competing libraries available for iOS that provide the required QR-code scanning functionality. Some to consider:

For this example we use the ZBar library. For a paid application, or an application that will be listed on the app store, it is worth carefully considering the license agreement associated with each library and selecting the most appropriate option.

Adding the ZBar library to the project

Integrating ZBar and adding QR-code scanning functionality to the application is relatively painless. After downloading and extracting the ZBar iPhone SDK at http://zbar.sourceforge.net/iphone, full instructions are available at http://zbar.sourceforge.net/iphone/sdkdoc/install.html. However, the basic two steps are:

  1. Drag the ZBarSDK folder to your project:

    Project state after adding the ZBar library

  2. Add the required framework dependencies to your project. On XCode 4, frameworks are added via the "Build Phases" section of your target configuration. Make sure all the frameworks shown below are included in your project:

    Project state after adding the required frameworks

At this point, you should ensure that your project builds and links successfully. If not, refer to the ZBar SDK installation instructions.

Using the ZBar library

Code that uses the ZBar library requires the include:


#import "ZBarSDK.h"
Now the method showScanner, which is called when the user clicks "Scan QR Code", can be implemented to present the user with ZBar's view controller:

- (void) showScanner {
    // present a barcode reader that scans from the camera feed
    ZBarReaderViewController *reader = [ZBarReaderViewController new];
    reader.readerDelegate = self;
        
    ZBarImageScanner *scanner = reader.scanner;
    // disable rarely used I2/5 to improve performance
    [scanner setSymbology: ZBAR_I25
                    config: ZBAR_CFG_ENABLE
                    to: 0];
        
    // present and release the controller
    [self presentModalViewController:reader animated:YES];
}
This method creates a new ZBarReaderViewController, and sets this ViewController as its delegate. ViewController.h should be updated to reflect this:

...
@interface ViewController : UIViewController<UIWebViewDelegate, ZBarReaderDelegate> {
...
It is also possible to configure the scanner to only consider QR-codes, using the setSymbology method. This speeds up the scanning process and reduces the memory footprint of the application. However, this is not implemented here.

Finally, the ZBar view controller is presented to the user. A camera view is displayed and the user points it at the appropriate QR-code. ZBar continuously searches the camera input for a valid barcode. It will immediately call the delegate method imagePickerController when a valid barcode has been found.

The object of this method is to close the ZBar view controller and populate the code field on the user interface. The first step is to obtain the QR-code:


// get the decode results
id results = [info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results) {
    // grab the first barcode
    break;
}

The next step is to populate the user interface. To send data back to the user interface, iOS provides a handy method on UIWebView: stringByEvaluatingJavaScriptFromString, which allows arbitrary JavaScript to be executed on the UIWebView.


[web stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"eticketing.set_code( '%s' )", [symbol.data UTF8String]]];
A simple method is defined in the JavaScript to populate the appropriate HTML element:

set_code: function(code) {
  $('#code').val( code );
}

Finally, the view controller is dismissed and released:


[reader dismissModalViewControllerAnimated: YES];
[reader release];
This brings the user back to the UIWebView, with the field populated with the results of the QR-code.

The user can then click "Validate" to test the code and determine whether the user's ticket is valid: QR-code scanning has been implemented!

Server Synchronization

The implementation up to this point enables tickets to be validated without requiring an internet connection, after the initial download of valid tickets. In fact, as tickets are validated, the server has no idea which tickets have been used - all usage information is kept with the client.

Potentially, this could be a problem if more than one person was checking the tickets, each using the validation app independently. If the same ticket is presented to each client, there's a problem. Since the two apps are not communicating with the server, or each other, they would both successfully validate the same ticket, thus allowing multiple entries on the same ticket. Not good!

The solution is to have the client synchronize its validated tickets with the server, and to check with the server before validating a ticket. Of course this now requires a working internet connection.

Tracking validated tickets

Updating the client to validate tickets with the server is relatively painless. A checkbox was added to the HTML to enable either mode of operation. The JavaScript was updated such that if after first checking the local list of attendees, in this new mode the application will then notify the server and ask the server if the ticket is valid.


if ( $('#sync').attr('checked') ) {
  update_server( candidate );
  return;
}
else {
  set_status( "Code successfully validated." );
  db[attendee].attending = 'yes';
  return;
}

The update_server method passes JSON to the server of the form { 'command': 'validate', 'title': title, 'candidate': candidate }. Code was added to the server to check if this ticket is valid and to mark it as being validated:


class API:
  ...
  def post(self):
    ...
    elif req['command'] == 'validate':
      # find event
      try:
        item = model.Item.all().filter( 'title =', req['title'] ).fetch(1)[0]
        attendee = model.Purchase.all().filter( 'code =', req['candidate'] ).filter( 'item =', item ).fetch(1)[0]
        if attendee.status == 'RETURNED' or attendee.status == 'COMPLETED':
          attendee.status = 'ATTENDED'
          attendee.put()
          self.response.out.write( simplejson.dumps( { 'status': 'ok', 'candidate': req['candidate'] } ) )
        elif attendee.status == 'ATTENDED':
          self.response.out.write( simplejson.dumps( { 'status': 'ticket already validated' } ) )
        else:
          self.response.out.write( simplejson.dumps( { 'status': 'purchase process not completed' } ) )
      except IndexError:
        self.response.out.write( simplejson.dumps( { 'status': 'event or code not found' } ) )
This method first searches for the specified event and code, before checking its status. If it's a valid ticket, it is marked as being validated and a successful status is returned to the client.

The JavaScript method update_response handles the successful return of data from this call. If the server considers the code to be valid, the client marks the code locally as having been validated, before notifying the user. Otherwise, an error is displayed.


function update_response(data) {
  if ( data['status'] == 'ok' ) {
    for ( attendee in db ) {
      if ( db[attendee].code == data['candidate'] ) {
        db[attendee].attending = 'yes';
      }
    }
    set_status( "Code successfully validated." );
  }
  else {
    set_status( 'An error occurred: ' + data['status'] );
  }
}

With this in place, the app can now be used in either offline mode or "synchronized" mode, depending on the use case.

Conclusion

This set of tutorials demonstrated the steps involved in implementing a payment workflow for a specific use case - a ticketing system. We used PayPal as the payment provider, Google App Engine for the server-side requirement and an iOS application for the ticket validation process. QR-codes were used to help streamline the process.

The client side app was primarily written using HTML and JavaScript to maximize portability. The use of a QR-code is not a requirement; the system can fall back to simple text entry of a short code to complete validation.

This final part of this series demonstrated how to integrate ZBar into a simple iOS app, so that QR-codes could be used for the validation process. iOS provides a painless way to integrate native components with an HTML front-end.

The client app was written to be usable in either offline or online mode. Although the mechanism presented in this article could be extended and improved, it demonstrates how straight-forward this kind of enhancement can be.

Further Reading