Custom Commerce Checkout Panes

Jason Yee

We've created a number of paid membership sites at Aten and as we've committed to creating new sites in Drupal 7, I was excited to take advantage of fieldable entities and the new Drupal Commerce system.

Advances in Drupal 7

Fieldable entities allow fields to be applied to nearly anything in Drupal 7, most notably, users. This is particularly important for membership sites, because it allows for an unlimited amount of user information to be stored: names, contact information, membership information - all the information an organization would need to manage relationships with their constituents and members. Similarly, Drupal Commerce has added a number of new features that have made CRM style functionality far easier. Foremost is the integration of Drupal Commerce with Rules, which allows membership purchase by the creation of a rule that runs on the "Completing the checkout process" event, checks for a condition that "Order contains a particular product" and runs the action "Add user role."

One of the most commonly requested features for membership sites is to include member profile information fields in the checkout process. Doing this allows organizations to encourage or require users to enter additional information beyond the standard billing information. Drupal Commerce makes this functionality easy to add with the checkout panes feature.

Creating custom checkout panes

To create a custom checkout pane, you'll first need to tell Drupal Commerce about your pane using hook_commerce_checkout_pane_info() in your module file:

/**
 * Implements hook_commerce_checkout_pane_info()
 */
function my_module_commerce_checkout_pane_info() {
  $panes['my_module'] = array(
    'title' => t('My Module Pane'),
    'page' => 'checkout',
    'weight' => 10,
    'file' => 'includes/my_module.checkout_pane.inc',
    'base' => 'my_module_pane',
  );
  return $panes;
}

The array key "my_module" is used as an identifier for your pane. The title is what is displayed in both the checkout panes settings page and to the user during checkout. The page and weight determine where you pane will appear by default, although you can reorder the pane using the checkout pane settings page drag and drop functionality. The file tells commerce where to find the appropriate pane functions and the base determines the base or root name for the pane functions. This is notable because it's possible to create multiple panes per module, so the pane system cannot assume your pane functions will be the same as your module name (i.e. standard hook_callback() naming conventions).

Now that Drupal Commerce knows about your pane, you'll need to create the pane in the file specified (includes/my_module.checkout_pane.inc). Let's start with a settings form, so that store administrators can customize the pane.

/**
 * Implements base_settings_form()
 */
function my_module_pane_settings_form($checkout_pane) {
  $form['my_module_pane_field'] = array(
    '#type' => 'textfield',
    '#title' => t('My Module Pane Field'),
    '#default_value' => variable_get('my_module_pane_field', ''),
  );
  return $form;
}

The base_settings_form() function receives information about itself through the $checkout_pane parameter and returns a standard Drupal form array. Note that you do not need to add a submit button, since Drupal Commerce will add one automatically. Also, you do not need to add a submit callback, since Drupal Commerce will automatically store your form values using the Drupal variable system.

Now that the backend configuration is handled, let's configure the customer facing part of the pane using base_checkout_form():

/**
 * Implements base_checkout_form()
 */
function my_module_pane_checkout_form($form, $form_state, $checkout_pane, $order) {
  $checkout_form['my_module_pane_field_display'] = array(
    '#markup' => variable_get('my_module_pane_field', ''),
  );
  $checkout_form['my_module_pane_field2'] = array(
    '#type' => 'textfield',
    '#title' => t('My Module Pane Field 2'),
  );
  return $checkout_form;
}

Just like base_settings_form(), base_checkout_form() receives information about itself from $checkout_pane, but since this is displayed during the actual checkout process, it also receives an $order parameter that contains information about the current customer's order. One important thing to note about orders is that they are entities, as are line items and products. This means that you may have to do a bit of work to trace an order back to the line items and back again to the products in order to get information. It also means that the current customer id can be found at $order->uid.

Also like base_settings_form(), base_checkout_form() returns a standard Drupal form array, however the values are not automatically saved by the Drupal variable system, because it's most likely that you'll want to do something more than simply saving the submitted values. base_checkout_form_submit() is the predefined submit callback:

/**
 * Implements base_checkout_form_submit()
 */
function my_module_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
  // do something here with 
  // $form_state['values']['my_module_pane_field2']
}

Since the pane system uses the standard Drupal form API, submitted values can be found in $form_state['values'] and you can do just about anything in the submit callback.

A world of pane

Using the checkout pane framework, we can see how it's easy to add form fields to the checkout process, then use the submitted values to update user account information, but that's only one way to use the pane framework. The Commerce Extra Panes module uses the pane system to display nodes (e.g. Terms & Conditions information) and the Commerce Coupon module adds coupon and discounts handling in panes. There are so many possibilities for Drupal Commerce checkout panes, so leave a comment below and tell us about your idea for a checkout pane.