Like every other chapter in this book, let’s take the Tweet
Relevance sample code from Appendix A
and use the PayPal product at hand, DoDirectPayment
in this
case, to implement a payment experience. Although an integration with
DoDirectPayment
as part of a Website Payments Pro
integration normally requires additional integration with Express
Checkout according to PayPal’s terms of service, we’ll focus solely on
integrating DoDirectPayment
in this chapter to maintain
maximal focus. (Recall that Express Checkout is discussed at length in
Chapters 2 and 3.) A recommended exercise for the
seriously interested reader, as presented in the final section of this
chapter, is to integrate Express Checkout into this chapter’s sample
project.
It may be helpful to review Implementing a Checkout Experience for Tweet Relevance to better understand some of the various payment mechanisms that could be viable for a service like Tweet Relevance if you have not done so already. The remainder of this chapter assumes familiarity with the options as presented in that section and implements the “subscription model,” which was covered in detail in Chapter 2 and used again in Chapter 4.
The first step to integrating DoDirectPayment
or any
other payment mechanism is to map a URL into the main application as a
means of handling a payment experience. Since integrating
DoDirectPayment
requires only a single call to PayPal, the
addition of a /do_direct_payment
URL that’s serviced by a
PaymentHandler
class is the only API-level addition to the
application that is necessary. A basic template that we’ll add to
collect payment information from the user once their free trial of the
service expires will pass the information to
/do_direct_payment
, which is the entry point for the
payment process. The following
list itemizes the key changes to the project for integrating DoDirectPayment
in a file-by-file
fashion.
Example 5-3 illustrates the entry point into
the web application. Note that there’s only one PaymentHandler
URL.
Example 5-3. main.py
from google.appengine.ext import webapp from google.appengine.ext.webapp import util # Logic for implementing DoDirectPayment from handlers.PaymentHandler import PaymentHandler # Logic for the app itself from handlers.AppHandler import AppHandler # Logic for interacting with Twitter's API and serving up data, etc. def main(): application = webapp.WSGIApplication([ # PaymentHandler URLs ('/(do_direct_payment)', PaymentHandler), # AppHandler URLs ('/(app)', AppHandler), ('/(data)', AppHandler), ('/(login)', AppHandler), ('/', AppHandler) ], debug=True) util.run_wsgi_app(application) if __name__ == '__main__': main()
A standard form that collects payment information, as shown
in Figure 5-4, passes this information
through to /do_direct_payment
as a POST request,
which is serviced by PaymentHandler
, as shown in
Example 5-5. In a
nutshell, PaymentHandler
validates the payment
information, passes it through to DoDirectPayment
,
and credits the user’s account if the payment action was
successful. Otherwise, it displays an error message. The trivial
Product
class that’s referenced by
PaymentHandler
is shown in Example 5-4.
Example 5-4. Product.py
# The Product class provides product details. # A more flexible product line could be managed in a database class Product(object): @staticmethod def getProduct(): return {'price' : 9.99, 'quantity' : 30, 'units' : 'days'}
Example 5-5. handlers/PaymentHandler.py
# PaymentHandler provides logic for interacting wtih PayPal's Website Payments Pro product import os import cgi from google.appengine.ext import webapp from google.appengine.api import memcache from google.appengine.ext.webapp import template import logging from paypal.products import DirectPayment as DP from Product import Product from handlers.AppHandler import AppHandler class PaymentHandler(webapp.RequestHandler): def post(self, mode=""): if mode == "do_direct_payment": # To be on the safe side, filter through a pre-defined list of fields # to pass through to DoDirectPayment. i.e. prevent the client from # potentially overriding IPADDRESS, AMT, etc. valid_fields = [ 'FIRSTNAME', 'LASTNAME', 'STREET', 'CITY', 'STATE', 'ZIP', 'COUNTRYCODE', 'CREDITCARDTYPE', 'ACCT', 'EXPDATE', 'CVV2', ] product = Product.getProduct() nvp_params = {'AMT' : str(product['price']), 'IPADDRESS' : self.request.remote_addr} for field in valid_fields: nvp_params[field] = self.request.get(field) response = DP.do_direct_payment(nvp_params) if response.status_code != 200: logging.error("Failure for DoDirectPayment") template_values = { 'title' : 'Error', 'operation' : 'DoDirectPayment' } path = os.path.join(os.path.dirname(__file__), '..', 'templates', 'unknown_error.html') return self.response.out.write(template.render(path, template_values)) # Ensure that the payment was successful parsed_qs = cgi.parse_qs(response.content) if parsed_qs['ACK'][0] != 'Success': logging.error("Unsuccessful DoDirectPayment") template_values = { 'title' : 'Error', 'details' : parsed_qs['L_LONGMESSAGE0'][0] } path = os.path.join(os.path.dirname(__file__), '..', 'templates', 'unsuccessful_payment.html') return self.response.out.write(template.render(path, template_values)) # Credit the user's account user_info = memcache.get(self.request.get("sid")) twitter_username = user_info['username'] product = Product.getProduct() AppHandler.creditUserAccount(twitter_username, product['quantity']) template_values = { 'title' : 'Successful Payment', 'quantity' : product['quantity'], 'units' : product['units'] } path = os.path.join(os.path.dirname(__file__), '..', 'templates', 'successful_payment.html') self.response.out.write(template.render(path, template_values)) else: logging.error("Unknown mode for POST request!")
The final piece of substance to completing the discussion on
a DoDirectPayment
integration involves the
DirectPayment
class that’s referenced in
PaymentHandler
. Basically, this class is just a thin
abstraction around the DoDirectPayment
API call and
follows along with the same pattern used for the
paypal
package in earlier chapters. In short, it
combines the 3-Token credentials with any other keyword parameters
passed into the do_direct_payment
method and executes
a DoDirectPayment
API call.
Example 5-6. paypal/products.py
from google.appengine.api import urlfetch import urllib import cgi import paypal_config class DirectPayment(object): @staticmethod def _api_call(nvp_params): params = nvp_params.copy() # copy to avoid mutating nvp_params with update() params.update(paypal_config.nvp_params) # update with 3 token credentials and api version response = urlfetch.fetch( paypal_config.sandbox_api_url, payload=urllib.urlencode(params), method=urlfetch.POST, validate_certificate=True, deadline=10 # seconds ) if response.status_code != 200: decoded_url = cgi.parse_qs(result.content) for (k,v) in decoded_url.items(): logging.error('%s=%s' % (k,v[0],)) raise Exception(str(response.status_code)) return response @staticmethod def do_direct_payment(nvp_params): nvp_params.update(METHOD='DoDirectPayment') nvp_params.update(PAYMENTACTION='Sale') return DirectPayment._api_call(nvp_params)
Prior chapters, which covered products explicitly involving PayPal
as an intermediating party in the user experience of the application,
required more than a single API call to PayPal due to very the nature of
setting up a payment, redirecting the customer to PayPal (to avoid
having you directly handle sensitive account information), and ensuring
the payment was processed before crediting a user’s account. Because
DoDirectPayment
gives you the ability handle payment
account information directly, some of the implementation details are a
bit simpler because of the streamlined user experience. Just remember
that with the power to handle sensitive account information directly
comes great responsibility and (potentially) great liability as it
relates to maintaining PCI compliance and safeguarding financial
records.