How to set up order processing

How orders are processed differs for every shop. Some shops will process orders manually, using the dashboard to print picking slips and update orders once items have shipped. Others will use automated processes to send order details to fulfillment partners and pick up shipment and cancellation messages.

Oscar provides only a skeleton for building your order processing pipeline on top of. This page details how it works and how to build your order processing pipeline.

Structure

There are two relevant Oscar apps to order processing.

  • The checkout app is responsible for collecting the required shipping and payment information, taking payment in some sense and placing the order. It is not normally used to process the order in any sense. If your orders can be fulfilled immediately after being placed (eg digital products), it’s better to use a separate process (like a cronjob or celery task). That way, if the fulfilment work fails for some reason, it can be retried easily later. It’s also a neater decoupling of responsibilities.
  • The order app has a processing.py module which is intended to handle order processing tasks, such as items being cancelled, shipped or returned. More details below.

Modelling

Oscar models order processsing through events. There are three types to be aware of:

  • Shipping events. These correspond to some change in the location or fulfilment status of the order items. For instance, when items are shipped, returned or cancelled. For digital goods, this would cover when items are downloaded.
  • Payment events. These model each transaction that relates to an order. The payment model allows order lines to be linked to the payment event.
  • Communication events. These capture emails and other messages sent to the customer about a particular order. These aren’t a core part of order processing and are used more for audit and to ensure, for example, that only one order confirmation email is sent to a customer.

Event handling

Most Oscar shops will want to customise the EventHandler class from the order app. This class is intended to handle all events and perform the appropriate actions. The main public API is

class oscar.apps.order.processing.EventHandler(user=None)[source]

Handle requested order events.

This is an important class: it houses the core logic of your shop’s order processing pipeline.

handle_order_status_change(order, new_status, note_msg=None)[source]

Handle a requested order status change

This method is not normally called directly by client code. The main use-case is when an order is cancelled, which in some ways could be viewed as a shipping event affecting all lines.

handle_payment_event(order, event_type, amount, lines=None, line_quantities=None, **kwargs)[source]

Handle a payment event for a given order.

These should normally be called as part of handling a shipping event. It is rare to call to this method directly. It does make sense for refunds though where the payment event may be unrelated to a particular shipping event and doesn’t directly correspond to a set of lines.

handle_shipping_event(order, event_type, lines, line_quantities, **kwargs)[source]

Handle a shipping event for a given order.

This is most common entry point to this class - most of your order processing should be modelled around shipping events. Shipping events can be used to trigger payment and communication events.

You will generally want to override this method to implement the specifics of you order processing pipeline.

Many helper methods are also provided:

class oscar.apps.order.processing.EventHandler(user=None)[source]

Handle requested order events.

This is an important class: it houses the core logic of your shop’s order processing pipeline.

are_stock_allocations_available(lines, line_quantities)[source]

Check whether stock records still have enough stock to honour the requested allocations.

Lines whose product doesn’t track stock are disregarded, which means this method will return True if only non-stock-tracking-lines are passed. This means you can just throw all order lines to this method, without checking whether stock tracking is enabled or not. This is okay, as calling consume_stock_allocations() has no effect for non-stock-tracking lines.

calculate_payment_event_subtotal(event_type, lines, line_quantities)[source]

Calculate the total charge for the passed event type, lines and line quantities.

This takes into account the previous prices that have been charged for this event.

Note that shipping is not including in this subtotal. You need to subclass and extend this method if you want to include shipping costs.

cancel_stock_allocations(order, lines=None, line_quantities=None)[source]

Cancel the stock allocations for the passed lines.

If no lines/quantities are passed, do it for all lines.

consume_stock_allocations(order, lines=None, line_quantities=None)[source]

Consume the stock allocations for the passed lines.

If no lines/quantities are passed, do it for all lines.

have_lines_passed_shipping_event(order, lines, line_quantities, event_type)[source]

Test whether the passed lines and quantities have been through the specified shipping event.

This is useful for validating if certain shipping events are allowed (ie you can’t return something before it has shipped).

validate_shipping_event(order, event_type, lines, line_quantities, **kwargs)[source]

Test if the requested shipping event is permitted.

If not, raise InvalidShippingEvent

Most shops can handle all their order processing through shipping events, which may indirectly create payment events.

Customisation

Assuming your order processing involves an admin using the dashboard, then the normal customisation steps are as follows:

  1. Ensure your orders are created with the right default status.
  2. Override the order dashboard’s views and templates to provide the right interface for admins to update orders.
  3. Extend the EventHandler class to correctly handle shipping and payment events that are called from the dashboard order detail view.

It can be useful to use order and line statuses too. Oscar provides some helper methods to make this easier.

class oscar.apps.order.abstract_models.AbstractOrder(*args, **kwargs)[source]

The main order model

classmethod all_statuses()[source]

Return all possible statuses for an order

available_statuses()[source]

Return all possible statuses that this order can move to

pipeline = {'Being processed': ('Complete', 'Cancelled'), 'Cancelled': (), 'Complete': (), 'Pending': ('Being processed', 'Cancelled')}

Order status pipeline. This should be a dict where each (key, value) # – corresponds to a status and a list of possible statuses that can follow that one.

set_status(new_status)[source]

Set a new status for this order.

If the requested status is not valid, then InvalidOrderStatus is raised.

class oscar.apps.order.abstract_models.AbstractLine(*args, **kwargs)[source]

An order line

classmethod all_statuses()[source]

Return all possible statuses for an order line

available_statuses()[source]

Return all possible statuses that this order line can move to

pipeline = {}

Order status pipeline. This should be a dict where each (key, value) corresponds to a status and the possible statuses that can follow that one.

set_status(new_status)[source]

Set a new status for this line

If the requested status is not valid, then InvalidLineStatus is raised.

Example

Here is a reasonably common scenario for order processing. Note that some of the functionality described here is not in Oscar. However, Oscar provides the hook points to make implementing this workflow easy.

  • An order is placed and the customer’s bankcard is pre-authed for the order total. The new order has status ‘Pending’
  • An admin logs into the dashboard and views all new orders. They select the new order, retrieve the goods from the warehouse and get them ready to ship.
  • When all items are retrieved, they select all the lines from the order and hit a button saying ‘take payment’. This calls the handle_payment_event method of the EventHandler class which performs the settle transaction with the payment gateway and, if successful, creates a payment event against the order.
  • If payment is successful, the admin ships the goods and gets a tracking number from the courier service. They then select the shipped lines for the order and hit a button saying “mark as shipped”. This will show a form requesting a shipping number for the event. When this is entered, the handle_shipping_event method of the EventHandler class is called, which will update stock allocations and create a shipping event.