Ubercart: modifying checkout panes
The Ubercart module is one of the best e-commerce options for Drupal currently. It is very user friendly and highly flexible with administrators having control over the product catalogue, payment gateways and email notifications. Site administrators also have control over which checkout panes are displayed during checkout and the order in which they appear. However, as I found out recently, while it is easy to control checkout pane visibility, and even add your own, there's no simple way of modifying the forms contained within a checkout pane. This article will cover one solution on how to overcome this.
For a site I'm working on, I needed to be able to add a new field to contain the user's title (Mr, Mrs, Miss, etc) to the "billing information" ubercart checkout pane. I first implemented hook_form_alter()
to add the field and set a custom submit handler. However, while I was able to modify the form, the submit function was never called and I wasn't able to save the value of the new field to the customer's order. This is because the ubercart checkout panes don't fully utilise the FAPI.
My solution was to create a new checkout pane and to entirely recreate the billing pane by pulling in the pane contents from the ubercart module, adding my own changes and returning the merged version.
So first I created a new checkout pane by implementing the hook, hook_checkout_pane()
:
<?php
function mymodule_checkout_pane() {
// Replacement for standard billing address pane.
$panes[] = array(
'id' => 'mymodule_billing',
'callback' => 'mymodule_checkout_pane_mymodule_billing',
'title' => t('Billing Address'),
'desc' => t('Custom billing address fields.'),
'weight' => 2,
'process' => TRUE,
'collapsible' => FALSE,
);
return $panes;
}
?>
Each pane needs a unique id. I originally used the same id as the ubercart billing information pane in the hope that I could override it, but that didn't work unfortunately. The other two fields to pay particular attention to are callback
- the function to call to build the pane and process it, and process</code> - this should be set to TRUE, so your callback function is called with the 'process' operation.
For the callback function, I didn't want to just copy and paste in Ubercart's
uc_checkout_pane_billing()
function as then I wouldn't be able to avail of any modifications made to the original "billing information" pane by simply upgrading the module - I would have to modify my custom module each time. So for each operation, my checkout pane calls Ubercart's one, makes my custom changes and then returns the merged result. The final result is as follows:
<?php
function mymodule_checkout_pane_mymodule_billing($op, &$arg1, $arg2) {
require_once(drupal_get_path('module', 'uc_cart') . '/uc_cart_checkout_pane.inc');
switch ($op) {
case 'view':
// This is needed to avoid 'an illegal choice has been made' error.
if (isset($_POST['panes']['mymodule_billing']['billing_country'])) {
$_POST['panes']['billing']['billing_country'] = $_POST['panes']['mymodule_billing']['billing_country'];
}
$contents = uc_checkout_pane_billing($op, $arg1, $arg2);
// Add 'title' or 'salutation' to billing address details.
$contents['contents']['billing_title'] = array(
'#type' => 'select',
'#title' => t('Title'),
'#options' => array('Mr', 'Mrs', 'Ms', 'Miss', 'Dr', 'Fr', 'Rev', 'Sr'),
'#required' => TRUE,
'#weight' => 0,
'#default_value' => $arg1->data['billing_title'],
);
// Address history selector doesn't work for this solution, so remove it.
unset($contents['contents']['billing_address_select']);
return $contents;
case 'review':
return uc_checkout_pane_billing($op, $arg1, $arg2);
case 'process':
$arg1->billing_title = $arg2['billing_title']; // Save our custom field.
return uc_checkout_pane_billing($op, $arg1, $arg2);
}
}
?>
Stella Power Managing Director
As well as being the founder and managing director of Annertech, Stella is one of the best known Drupal contributors in the world.