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.
Figure 3-2. When the user clicks the PayPal button, an inline frame is spawned in the page. When the user clicks the Log In button, it spawns a new browser window that is used to initiate the login process. (See Steps 1–4 of Figure 3-1.)
Figure 3-3. Clicking the Login button from the inline frame spawns a new window so that the user can log in to PayPal and complete the purchase. At the discretion of the developer, the purchase details can be reviewed and approved at PayPal as opposed to back on your site, which is usually a more intuitive way to handle approval, given the in-context experience of the popup window. (See Steps 5-10 of Figure 3-1, noting that the Tweet Relevance flow shown here elects to take the “shortcut” approach and minimize the implementation burden.)
Figure 3-4. The minibrowser provides a streamlined user experience that exposes the same functionality that PayPal users have grown accustomed to.
Figure 3-6. After the user clicks the Close and Continue button on the popup window, the transaction completes, login tokens are credited to the account, and the user is returned to the Tweet Relevance login page.
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.
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'}
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)
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)
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.
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,)
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>
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.