August 12, 2012

Templating the Commerce Order Completion Pane

I recently found myself working on a fairly simple Drupal Commerce site. There weren't a lot of tricks to it, just a few items and an "express" checkout. So I fired up the Commerce Kickstart profile and after a few small tweaks, had it just about right. As we approached the theme, it became obvious that the commerce completion pane was going to take a lot of love. The designers had given us a unique layout for how this page should appear, complete with order specific information displayed in a kaleidoscope of containers. This was going to take some markup, and my first thought was, "it sure would be nice to template this."

Turns out that's a fairly simple task thanks to hook_commerce_checkout_pane_info_alter. The function passes the variable $checkout_panes by reference which contains an array of checkout pane arrays. This function is going to allow us to redefine the function that the checkout completion pane is looking to for output. This will look something like:

function my_module_commerce_checkout_pane_info_alter(&$checkout_panes) {
  if(isset($checkout_panes['checkout_completion_message'])) {
    // forget the original include file                    
    // point at this module
    $checkout_panes['checkout_completion_message']['module'] = 'my_module';
    // register the new output function
    $checkout_panes['checkout_completion_message']['base'] = 'my_module_completion_message_pane';

The function name we passed above will have _checkout_form appended. We will need to declare a function to satisfy this and return our output.

function my_module_completion_message_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
  $pane_form = array();
  $pane_form['message'] = array('#markup' => /*markup*/);
  return $pane_form;

From here, we're basically home free. We simply need to replace /*markup*/ with the output we desire. Since we are looking to template this, we will need to register a theme implementation for it. The important thing to note is the $order variable passed to this function. We'll need that order information in our template, so we pass it along while laying out our hook_theme call.

function my_module_theme($existing, $type, $theme, $path) {
  return array(
    'my_module_completion' => array(
      // the template filename, .tpl.php will be appended
      'template' => 'my_module_completion',
      // include order as a variable in the template
      'variables' => array('order' => NULL),

Now that we have a theme implementation that is expecting an order variable, all that remains is to call it as the value for '#markup' in our output function above.

$pane_form['message'] = array('#markup' => theme('my_module_completion', array('order' => $order)));

From here simply create the my_module_completion.tpl.php file and theme away! The template will have our order information exposed as the php variable $order allowing us to output whatever order information we would like. This is probably a good time to note, that the $order variable very well may not be sufficient (it wasn't for us). It contains mostly keys to fetch other bits of data about the order. In our actual implementation we wound up loading the line item (our implementation only allowed one), and some customer information. This can easily be done from the output function you define in hook_commerce_checkout_pane_info_alter and passed to the template exactly as we did the order variable in this example.

I admit that this is bordering on being somewhat hackish. I don't think I would take this approach if the Commerce implementation was more complex. If that were the case a better approach may be to create a custom checkout pane with the output you would like, assign it to the appropriate checkout pages, and leave the poor checkout completion pane alone. However, in the interest of time and ease in satisfying a theme requirement for a relatively small site, this method worked like a charm. Further, I don't believe that anything has been obfuscated. With some light commenting it should be apparent to anyone who reviews this code, exactly what is going on. With that in mind, take this method as a handy trick if you need to quickly and drastically rework how the checkout completion pane is displayed.

Ryan Oles