Chapter 4. Building Payment Processors

The goal of this chapter is to extend the basic system we've designed so far with a more robust and flexible payment system. Currently we have a shopping cart design that is independent of our payment process. We convert the cart to a format that is compatible with the Google Checkout API and then we can submit our customer orders for payment. We will now extend this system to achieve several things:

  • Create a pluggable design so that payment systems can be reused, swapped out, or replaced
  • Migrate our simple Google Checkout processor to our new system
  • Discuss payment processors for Amazon FPS and PayPal services
  • Explore advanced Django and Python design concepts such as class-based generic views and callable objects

After we have built our generic payment system, implementing additional payment services such as Amazon and PayPal will be relatively straightforward.

An additional goal in this chapter is to build a system that is not constrained to the payment services mentioned, but could be extended as a basis for any web-based payment system that can handle shopping cart orders and form-based payment initiation. Using the design laid out in this chapter, new implementations for other third-party services should be relatively straightforward.

Building a generic payment processor app

We start by creating an empty framework to act as the skeleton of any payment processors we will eventually build. This design will assume that there are two key components to a payment processor:

  • Shopping cart translation
  • Checkout submission

The first step is always to convert the contents of our customer's shopping cart into a format that can be understood by the payment service. We handled this in Chapter 3, Handling Customers and Their Orders, using the Django template system to render an XML file that represented a customer order in an XML document the Google Checkout API could understand. Many web services use an XML format for transmitting e-commerce data and fortunately Django makes it very easy to generate these files.

Step two is to take our newly translated shopping cart data and walk the customer through a checkout process. Initially we'll render a confirmation page listing the contents of the shopping cart and the quantity and price for each item. If the user confirms this information, their order will be submitted to the payment service where they will complete the payment.

The process described in this book will complete the payment process through the service provider interface. Often these pages can be customized with a business logo and other design elements, but the interaction will be entirely handled by the web service provider. For most organizations this is a highly recommended practice.

Medium to small companies generally don't have the resources to commit to the security practices necessary to secure a checkout service running on their own site. Despite the inability to brand and control this portion of your sales process, it is still preferable for its enhanced security. It also provides peace of mind if there is a major breach or other crisis; your business will not be required to deal with it directly. You can rely on the payment service to fix the problem quickly, because it will affect all of their clients and their own bottom line. For this reason, as well, we will focus on the biggest, most reliable payment services.

Class-based views

The approach we will take to implement our payments framework involves a focus on reuse and simplification of code. As this code will generally live in the view-layer of our application, one excellent pattern to use here is the class-based view. Unlike functional views, class-based views allow the usual gains in robustness inherent in object-oriented programming.

Using this method, we will create a standard Python class to act as a superclass for specialized versions of our views. This parent class will implement all of the basic functions for the view, but avoids specific details related to our application. Application specific logic will be implemented in child classes using polymorphism to modify our parent's basic set of methods.

These classes become suitable for use as views in URL patterns once we make them callable. In Python, any class can be made "callable" by implementing a __call__ method. This allows object instances of the class to be called such as functions. The following code creates a callable object from a MyCallable class:

>>> obj = MyCallable(initial_data=[...])
>>> # call the object...
>>> result = obj()

Note that this is equivalent to the following:

>>> obj = MyCallable(initial_data=[...])
>>> result = obj.__call__()

The __call__ method is a Python special method that allows any object to be executed using the function syntax shown in the first example. In Python any object that can be called this way is generally referred to as callable or as a callable object. You can use Python's introspection to check whether an object you are working with is callable or not:

>>> if callable(obj): print "True!"

Implementing a callable object has many advantages over simply writing a method and referring to that method anywhere you need to obtain certain functionality from the object. It is a form of abstraction; you can write your code to expect a callable object and just call it. The implementation details, including the names of the methods that run, are up to the class.

In our specific example, Django URL patterns were designed to use functions as views. But really it is more accurate to say that they are designed to use callables. Thus by writing callable classes, we can construct object instances directly in the URL patterns. The classes themselves don't matter as long as they are callable. This is one of several examples that make Python a unique and interesting language to work with.

Implementing a checkout view base class

The base class for our class-based view checkout system will be called CheckoutView. All class-based generic views are going to share a basic set of requirements: be callable, store a template name, create a response to an incoming request that renders the template, and provide whatever extra functionality, such as form creation, that the view needs.

Some of this information, such as the template name and any form classes, can be stored as data attributes on the view. Others, such as rendering a request, will be implemented as a method. Anything that is possible with standard Django views can be implemented using a class-based design. For complex designs, the class-based solution will often be easier to read, manage, and reuse later.

Using function-based generic views, the typical usage pattern in Django is to include arguments from the URL pattern to specify the view behavior. For example, Django's built-in list_detail.object_list generic view takes several arguments: an object ID, a slug value, the queryset to filter, and so on. These are passed either from the URL itself, via a regular expression, or a dictionary of parameters defined in the URLs file. Either way, we have to write extra code.

Using class-based views, it makes more sense to take advantage of polymorphism and inheritance rather than passing arguments to a function. This simplifies our URL patterns and cleanly separates view code into a typical class hierarchy. This approach means we can avoid passing a lot of data from our URLs but still achieve the goal of code reuse.

There still may be data attributes we can treat as constructor parameters, the template name for example, but most of the data should exist as class attributes. An excellent demonstration of this is the extra_context dictionary. This is a dictionary of additional key-value pairs to be added to the template context when it is rendered. It can get unwieldy to define this dictionary in the urls.py file, but using a class-based view it becomes a simple matter of adding a data attribute to the class. When the view is rendered we can update the context with the value of self.extra_context.

Let's demonstrate these concepts by revealing our base CheckoutView class:

class CheckoutView(object):
    template = 'payments/checkout.html'
    extra_context = {}
    cart = None
    cart_class = Cart
    request = None
    form = None
    form_class = None

    def __init__(self, template=None, form_class=CheckoutForm,
                       context_class=RequestContext):
        self.context_class = context_class
        self.form_class = form_class
        if template is not None:
            self.template = template

    def __unicode__(self):
        return self.__class__.__name__

    def get_shopping_cart(self, request):
        self.cart = request.session.get('cart', None) or self.cart_class()

    def __call__(self, request):
        self.request = request
        return self.return_response()

    def return_response(self):
        self.form = self.form_class()
        context = {'cart': self.cart, 'form': self.form}
        context.update(self.get_extra_context())
        return render_to_response(self.template, context,   
                                  context_instance=self.context_class(self.request))

    def get_extra_context(self):
         return self.extra_context

Here are the key features of this class: we made it callable by defining a __call__ method, we store the view parameters as class attributes, and the generic base-class provides default values for some of these attributes.

Saving the order

As we will be processing payments using third-party payment processors, we need to save the order before sending it to the customer to make their payment. We also need to be able to create an order from our shopping cart object so that we can refer to it when we begin shipping after receiving payment.

Most payment processors allow us to add unique information specific to an order reference number for this purpose. When payment succeeds, we can update the status of the order in our system from 'NEW' to something meaningful like 'PAID'. Most payment processors will notify vendors via e-mail when an order has been paid and some even allow this to be automated. We will explore this topic more in Chapter 5, From Payment to Porch: An Order Pipeline.

Using the contents of the customer's shopping cart, we create an Order object from Chapter 3. This order will record all of the Products and quantities the customer is ordering. We save this prior to rendering the final checkout view from which the customer will navigate to the payment processor to complete payment.

We will implement this as a new method on our CheckoutView class called save_order. The save_order method is listed below:

def save_order(self):
    cart = self.get_shopping_cart()       
    make_order_from_cart(order, customer=self.request.user)

save_order creates an order by passing the current shopping cart to the make_order_from_cart function. A new order object is created every time the customer submits their cart for checkout, which leads to a common problem with shopping cart services: abandonment. A shopping cart is abandoned when a customer adds items, but never completes the checkout process.

The design above will save these abandoned carts as Order objects with status set to 'NEW'. We will address this abandonment issue in the next chapter, but saving these as Orders is not necessarily a bad thing. For example, it can be useful business knowledge to record what products are almost purchased, but given up at the last minute. There may be other analysis we can do as well. Maybe at peak traffic times, the server is responding too slowly and more shopping carts get abandoned out of user frustration. This would help make a business case for a faster server or other upgrades.

The make_order_from_cart function has been added to our shopping cart module from Chapter 3. Its code is listed below:

from coleman.orders.models import StatusCode

def get_status_code(name):
    code, created = StatusCode.objects.get_or_create(short_name=name)
    return code

def make_order_from_cart(cart, customer=None):
    '''
    Takes a shopping cart object and generates a new order object for the provided customer. If no customer is provided, an "anonymous" order is created.
    '''
    order = Order.objects.create(customer=customer,
    status_code=get_status_code('NEW'))
    for item in cart:
        total_price = item.product.get_price() * item.quantity
        ordered_item = ProductInOrder(
               order=order,
               product=item.product, 
               unit_price=item.product.get_price(),      
               total_price=total_price, 
               quantity=item.quantity)
        ordered_item.save()
    return order

We start by creating the Order object, then looping over each item in the shopping cart. Remember that the shopping cart stores Item objects, not Products directly. This helps us manage quantities and other information. We can access the actual Product object via the Item's product attribute. We do this to create our ProductInOrder intermediary model, which creates the list of Products in our Order. Refer to the design of this model in the previous chapter.

The save_order method is called from our return_response, which renders the final checkout view to the user. With our Order saved, we can begin building specialized checkout procedures for specific payment processors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset