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 - 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);
}
}
?>


Thanks!! This saved me lots of time.
hi, thanks for this
i have use your solution for my site: http://www.one.nl/
i also need to know if you ever try to integrate a form within checkout pane?
i want to place a login form inside checkout_pane_customer
so basically instead of i send user to user/login page
they can just login through cart/checkout page
i have tried using thickbox module
however, this module did not support in case a user login with wrong username or password
so please share your knowledge if you have any solution for this problem
thank you very much
You could try the popups module (http://drupal.org/project/popups), though I think ubercart will update the correct user account if they enter in the same email address.
thank you for your suggestion
however my boss and project leader really want it to be a form next to fieldset with customer e-mail textfield
i have a solution before using javascript, appending the login form in the predefine area
but since without javascript it will not work anymore
so stella, if you know a solution, you will be a really live saver
thank you
Thanks for the code Stella. It works great on my local host site. I will be adding this soon to my live scooter production site.
Thanks
Works like a charm. For the record: to make this work, create a new module (mymodule.module with both the hooks and a mymodule.info, I gave it the dependancy 'uc_cart' since it is part of the cart-checkout).
Too bad the known-addresses feature doesn't work with this mod. For those who want to keep using it, it might be an option to replace the original uc_checkout_pane_billing() with the posted one.
As of Ubercart 2.3, there is now a new hook called hook_checkout_pane_alter() in which you can alter the information about the panes.
So now an implementation of hook_checkout_pane() is not needed anymore. However, the callback function from the example, 'mymodule_checkout_pane_mymodule_billing()', is still needed.
Example of implementing hook_checkout_pane_alter() which alters the billing pane:
<?php/**
* Implementation of hook_checkout_pane_alter().
* Alters billing pane
* @param array $panes
* @return void
*/
function mymodule_checkout_pane_alter(&$panes) {
foreach ($panes as $key => $pane) {
switch ($pane['id']) {
case 'billing':
$panes[$key]['callback'] = 'mymodule_checkout_pane_mymodule_billing';
break;
}
}
}
?>
Great! Thank you
Great! This helped me carry out exactly what I needed in a more future-proof fashion. I hope to write my own blog post when I get a minute, on the issues I've solved with help from both of you. Thanks.
~mccrodp
Anyone who is just looking for adding extra address fields to delivery and/or billing panes, try out the 2.x version of Extra Fields Checkout Pane (http://drupal.org/project/uc_extra_fields_pane). The 2.x version is still in development, but it might already fit your needs.
This is just what I need, but the one part missing is the exact places to put the code in the examples above. I would be forever in your debt if you clarified where these functions go. Thanks for taking the time to post this tutorial.
-
spike
tried MegaChriz's version of this with Ubercart 2.3> returns an error from the module
Parse error: syntax error, unexpected T_FOREACH in /Applications/MAMP/htdocs/xxxxxxx/sites/all/modules/myformmodule/mymodule.module on line 22
Hi I managed to get this to work but I don't understand how (and where) the additional information (custom field) is stored in the database.
Thanks
It ends up in the "data" column in the "uc_orders" table.
I have two panes with a salutation field :
<?php
/*
* alter the checkout pane, add salutation
*/
function uc_laulas_checkout_pane() {
// Replacement for standard billing address pane.
$panes[] = array(
'id' => 'uc_laulas_billing',
'callback' => 'uc_laulas_checkout_pane_uc_laulas_billing',
'title' => t('Billing Address'),
'desc' => t('Custom billing address fields.'),
'weight' => 4,
'process' => TRUE,
'collapsible' => TRUE,
);
// Replacement for standard delivery address pane.
$panes[] = array(
'id' => 'uc_laulas_delivery',
'callback' => 'uc_laulas_checkout_pane_uc_laulas_delivery',
'title' => t('Delivery Address'),
'desc' => t('Custom delivery address fields.'),
'weight' => 3,
'process' => TRUE,
'collapsible' => FALSE,
);
return $panes;
}
function uc_laulas_checkout_pane_uc_laulas_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']['uc_laulas_billing']['billing_country'])) {
$_POST['panes']['billing']['billing_country'] = $_POST['panes']['uc_laulas_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('Salutation'),
'#options' => array(t('Mr'), t('Mrs')),
'#required' => TRUE,
'#weight' => 0,
'#default_value' => $arg1->data['billing_title'],
);
$contents['contents']['copy_address'] = array(
'#type' => 'checkbox',
'#title' => t('My billing information is the same as my delivery information.'),
'#attributes' => array('onclick' => "uc_cart_copy_address(this.checked, 'uc_laulas_delivery', 'uc_laulas_billing');"),
);
// 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);
}
}
function uc_laulas_checkout_pane_uc_laulas_delivery($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']['uc_laulas_delivery']['delivery_country'])) {
$_POST['panes']['delivery']['delivery_country'] = $_POST['panes']['uc_laulas_delivery']['delivery_country'];
}
$contents = uc_checkout_pane_delivery($op, $arg1, $arg2);
// Add 'title' or 'salutation' to delivery address details.
$contents['contents']['delivery_title'] = array(
'#type' => 'select',
'#title' => t('Salutation'),
'#options' => array(t('Mr'), t('Mrs')),
'#required' => TRUE,
'#weight' => 0,
'#default_value' => $arg1->data['delivery_title'],
);
// Address history selector doesn't work for this solution, so remove it.
unset($contents['contents']['delivery_address_select']);
return $contents;
case 'review':
return uc_checkout_pane_delivery($op, $arg1, $arg2);
case 'process':
$arg1->delivery_title = $arg2['delivery_title']; // Save our custom field.
return uc_checkout_pane_delivery($op, $arg1, $arg2);
}
}
?>
in the data column I only see the info from the uc_coupon module
a:1:{s:6:"coupon";s:4:"TEST";}
regards
Hmmm you may also need the following if the above is not working:
<?php
/**
* Implements hook_order().
*/
function myorder_order($op, &$arg1, $arg2) {
switch ($op) {
case 'save':
$order_id = $arg1->order_id;
// Load up the existing data array.
$order = db_fetch_object(db_query("SELECT * FROM {uc_orders} WHERE order_id = %d", $order_id));
$data = unserialize($order->data);
$data['billing_title'] = (!empty($arg1->billing_title) ? $arg1->billing_title : $data['billing_title']);
// Save it again.
db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id);
}
}
?>
Thanx for your kind and quick support, this worked but then I ran into problems with the copy address checkbox which did not work because it needed the pane-id 'billing' and 'delivery'. I ended up installing the uc Extra Fields Pane 6.2 (http://drupal.org/project/uc_extra_fields_pane) which lets you insert Fields into the existing panes ( billing and delivery), works great for me.
Actually, on process you want to do
$arg1->data['delivery_title'] =It creates two billing panes for me. How to remove one
You can disable them via the Ubercart admin UI - so just disable the built-in one.
hy guys! it's all the day that i'm trying to custom my cart pane in the check-out area!
I have 2 attributes that i have to add to my pane. i try with the PANE_ALTER and CHECK_OUT_PANE. No results! with check_out pane i can just create a new pane but not override the old. ok no problem, i will throw away the old pane.
I create a new module, just like above. The program enter in the callback. here I have to put the code to create my pane. I put the same code that define the function uc_checkout_pane_cart (uc_cart_checkout_pane.inc, line 18) and i changed the value of $output (the content of the like i need for my custom table. but not! nothing change. somebody can help me please???? thanks!! i try also using pane_alter but whithout good results!
Stefano italy
I followed above, inclusive stella's comment from Wed, 2011-03-16 21:17, and verified the data is saved to DB. But how can I retrive this information on order management screen, print bill page etc.?
Best regards,
Sebastian
I implemented following funktion to make the title fields available in the order templates (uc_order-admin.tpl.php, uc_order-customer.tpl.php). This way the title get added to the head of adresses and are available as $mymodule_shipping_address and $mymodule_billing_address.
function mymodule_token_values($type, $object = NULL) {$values = array();
switch ($type) {
case 'order':
$order = $object;
$options = array('Frau', 'Herr', 'Mrs', 'Mr');
$values['mymodule-shipping-address'] = $options[$order->data['shipping_title']].'<br />'.uc_order_address($order, 'delivery');
$values['mymodule-billing-address'] = $options[$order->data['delivery_title']].'<br />'.uc_order_address($order, 'billing');
break;
}
return $values;
}
But still did not found way to imlement the title display on "Review order" screen (cart/checkout/review) and admin/store/orders/%.
To display the titles on cart/checkout/review you need to modify the 'review' section in mymodule_checkout_pane_mymodule_billing() / mymodule_checkout_pane_mymodule_shipping().
Mine looks like:
function lmymodule_checkout_pane_delivery($op, &$arg1, $arg2) {
require_once(drupal_get_path('module', 'uc_cart') . '/uc_cart_checkout_pane.inc');
switch ($op) {
[...]
case 'review':
$review[] = array('title' => t('Address'), 'data' => mymodule_uc_order_address($arg1, 'delivery', FALSE));
if (uc_address_field_enabled('phone') && !empty($arg1->delivery_phone)) {
$review[] = array('title' => t('Phone'), 'data' => check_plain($arg1->delivery_phone));
}
return $review;
[...]
}
And helper function to add the title to output:
function mymodule_uc_order_address(&$order, $type) {$options = array('Frau', 'Herr', 'Mrs', 'Mr');
return $options[$order->data[$type.'_title']].'<br />'.uc_order_address($order, $type);
}
You don't need to build a separate module anymore to add something like a title field to delivery/billing pane.
Extra Fields Pane 6.x-2.x can do that.
http://drupal.org/project/uc_extra_fields_pane
Thank you so soooooo much.