Implementing a Digital Goods Checkout for Tweet Relevance

This section provides a general overview of an Express Checkout for Digital Goods user experience as implemented in Tweet Relevance before transitioning into a more detailed account of the changes to the previous chapter’s project code that are necessary in order to implement it.

If you’re familiar with the implementation details for an Express Checkout, you’ll find that implementing an Express Checkout for Digital Goods is almost identical. The primary differences are just that you pass in some different parameters to the core API operations (SetExpressCheckout, GetExpressCheckoutDetails, and DoExpressCheckoutPayment) and wire some JavaScript into your page. Aside from a marginal amount of learning curve that may be incurred by way of debugging common mistakes that can happen along the way, it’s really a pretty smooth transition. Figure 3-1 illustrates the checkout flow; it’s worth comparing this workflow diagram to Figure 2-6 to have a good, intuitive sense for how similar they are to one another.

The PayPal API operations that you’ll use in an Express Checkout for Digital Goods are the very same as a traditional Express Checkout. A few name-value pair parameters differ in the Express Checkout API operations, and there are just a few tweaks to templates that are necessary in order to wire up a JavaScript-based, in-context user experience. Before discussing the specific changes that need to be made to the previous chapter’s code, however, let’s first take a closer look at the Tweet Relevance user experience as integrated with an Express Checkout for Digital Goods payment flow, as illustrated in Figures 3-2 through 3-6.

Now that you have an understanding for the kind of in-context user experience that is possible with Express Checkout for Digital Goods and Tweet Relevance, let’s systematically look through the changes to the project code from the previous chapter to better understand the implementation details.

Perhaps the best way to navigate through the implementation details is to check out the code, explore it, and use a tool such as diff to identify itemized listings of the key changes, and it is highly recommended that you do so. (See Recommended Exercises.) A summary of these changes with relevant code snippets follows.

Product.py

Minimal changes are necessary to getProduct in order to return a simple data structure with appropriate details for a “bundle of virtual tokens” product:

class Product(object):

  @staticmethod
  def getProduct():
    return {'price' : 4.99, 'quantity' : 50, 'units' : 'login tokens'}
User.py

The User data model requires minimal changes so that it stores a simple counter of login requests versus an access start time and duration:

class User(db.Model):
  twitter_username = db.StringProperty(required=True)
  requests_remaining = db.IntegerProperty(required=True, default=25)
handlers/AppHandler.py

Minimal changes are necessary in order for creditUserAccount to inspect the User data model to confirm that login requests are remaining, as opposed to the calendar math that was previously involved:

def creditUserAccount(twitter_username, num_tokens):
  query = User.all().filter("twitter_username =", twitter_username)
  user = query.get()
  user.requests_remaining += num_tokens
  db.put(user)
handlers/PaymentHandler.py

Perhaps the most substantive change takes place in PaymentHandler. Recall that the logic for /set_ec sets up and initiates the checkout by executing SetExpressCheckout. Here, the name-value pair parameters passed in are updated to include several important (and required) items that specify the name, amount, and quantity of the item that is presented to the buyer. Additionally, an item category is specified that indicates that the product being sold is a digital good, and the return URL is also updated to a URL that initiates the DoExpressCheckoutPayment API and finalizes the transaction since we are now relying on PayPal to present the item details (effectively eliminating the need for GetExpressCheckoutDetails). Finally, a different redirect URL is returned (more on this in a moment). The updated code for setting up the payment transaction follows with these lines emphasized:

  def post(self, mode=""):

    if mode == "set_ec":

      sid = self.request.get("sid")
      user_info = memcache.get(sid)

      product = Product.getProduct()

      nvp_params = { 
              'L_PAYMENTREQUEST_0_NAME0' : str(product['quantity']) + ' ' + product['units'],
              'L_PAYMENTREQUEST_0_AMT0' : str(product['price']),
              'L_PAYMENTREQUEST_0_QTY0' : 1,
              'L_PAYMENTREQUEST_0_ITEMCATEGORY0' : 'Digital',

              'PAYMENTREQUEST_0_AMT' : str(product['price']),
              'RETURNURL' : self.request.host_url+"/do_ec_payment?sid="+sid,
              'CANCELURL': self.request.host_url+"/cancel_ec?sid="+sid
            }   

      response = EC.set_express_checkout(nvp_params)

      if response.status_code != 200:
        logging.error("Failure for SetExpressCheckout")

        template_values = { 
          'title' : 'Error',
          'operation' : 'SetExpressCheckout'
        }   
    
        path = os.path.join(os.path.dirname(__file__), '..', 'templates', 'unknown_error.html')
        return self.response.out.write(template.render(path, template_values))

      # The remainder of the transaction is completed in context

      parsed_qs = cgi.parse_qs(response.content)

      redirect_url = EC.generate_express_checkout_digital_goods_redirect_url(parsed_qs['TOKEN'][0])
      return self.redirect(redirect_url)

    else:
      logging.error("Unknown mode for POST request!")

The logic for /do_ec_payment likewise involves a minimal change so that the four L_PAYMENTREQUEST parameters that were mentioned can appear in the name-value pairs that are passed in with DoExpressCheckoutPayment. Perhaps the most important detail to call out with the addition of these parameters is that the appearance of Digital as the item category ensures that the improved digital goods rates in Table 3-1 apply; according to PayPal, the omission of this parameter may result in standard rates being applied.

paypal/products.py

An additional method is added to return a redirect URL specific to the in-context digital goods checkout experience. An optional query string parameter of useraction=commit may be added if payment details should be verified on PayPal’s site (as is implemented with the Tweet Relevance sample code in this chapter) as opposed to back on your site using GetExpressCheckoutDetails:

@staticmethod
def generate_express_checkout_digital_goods_redirect_url(token, commit=True):
    if commit:
        return "https://www.sandbox.paypal.com/incontext?token=%s&useraction=commit" % (token,)
    else:
        return "https://www.sandbox.paypal.com/incontext?token=%s" % (token,)
templates/checkout.html

The checkout template that features the familiar yellow “Checkout with PayPal” button requires minimal changes. An HTML ID is added to the main form’s Submit button so that clicks can be intercepted by some JavaScript code that PayPal provides to trigger an in-context checkout flow. A reference to the PayPal-provided JavaScript code along with the code to wire things together is also added to the bottom of the page:

<html>
  <head>
      <title>{{ title }}</title>
  </head>
  <body>
      <h1>{{ title }}</h1>
      <p>{{ msg }}</p>

      <form action='/set_ec' METHOD='POST'>
      <input type='image' name='submit' 
             id='submitButton' 
             src='https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif' 
             border='0' align='top' alt='Check out with PayPal'/>
      <input type='hidden' name='sid' value='{{ sid }}'/>
      </form>

      <script type="text/javascript" src="https://www.paypalobjects.com/js/external/dg.js"></script>
      <script> 
          var dg = new PAYPAL.apps.DGFlow({ 
              trigger: "submitButton"
          }); 
      </script>
  </body>
</html>
templates/successful_payment.html

Changes to the successful payment template are pretty straightforward. After a successful payment has been completed, the updated checkout flow provides an alert to the user that the transaction is successful and redirects to the main login page:

<html>
  <head>
      <title>{{ title }}</title>
  </head>
  <body>
      <h1>{{ title }}</h1>

      <script>
        alert('You just purchased {{ quantity }} {{ units }}. You may now login.');
        top.window.location = "/"; // Redirect to the main login page
      </script>
  </body>
</html>

To summarize, we’ve performed a very minor surgery on the sample code from the previous chapter on Express Checkout in order to implement an Express Checkout for Digital Goods payment flow. On the frontend and backend of the payment flow, a little bit of JavaScript was tactically injected to kick off and wrap up the in-context experience, and sandwiched in between were some updates to the application logic—most of which were related to changing the business model to support virtual tokens and setting up the call for SetExpressCheckout. In many regards, the implementation for the digital goods checkout actually seems a little bit simpler for both the developer and the buyer.

If you haven’t already done so, now would be the time to pull down the sample code for this chapter and work through it in more detail. If you sell digital goods, understand the fundamentals presented in this chapter, and have some basic web development skills, you are in a great position to implement an in-context payment experience for your consumers.