Enterprise Drupal: Ubercart eCommerce Addresses and Location Module

Mar 01, 2010
Eric Aguayo

In Drupal, the Location module is used to handle everything related to locations or addresses whether it is to associate locations with users or nodes. Ubercart ecommerce module, is used to handle website payments, and is widely used due to its ease of usability. The biggest business benefit is to provide an integration of both of these modules for a seamless ecommerce/payment processing experience.

The Ubercart module suite purpose is to provide a complete built-in solution to handle website payments and e-commerce solutions, which means that is probable that you not need to download any other plug-in module for Ubercart to handle a specific functionality. This solution might seem better for the Drupal user since the user might not have to install additional software to handle specific functionality as it might be provided one of the Ubercart built-in modules. However this represents some drawbacks, one of them is that if there is a bugfix on one of the Ubercart built-in modules we might need to wait until the next realease of the whole Ubercart module suite to get the module update and the other is that there might be some functionality that is being provided by other modules, such as the billing address. The Ubercart Drupal module has its own way to handle billing addresses, however there is a more specialized module to handle such things such as the Location module mentioned before.


Fortunately, Drupal flexibility through the hook system allows us to provide an integration between the Ubercart billing addresses and the location module by developing a custom module. Suppose that a client request is to give the users the ability to set the billing address (which is handled by Ubercart) as the same as the profile address (which is handled by the Location module). For this, we are going to include the location data attached to a node/user into the billing address of the recurring fees checkout page. First we need to understand some differences between ubercart and location module addresses storage. Ubercart store the province/state data by a province id called zone id which can be retrieved by the {uc_zones} table. The location module on the other hand retrieves the province/state data by returning the state code, which corresponds to the zone_code on the {uc_zones} table for Ubercart. Based on this, we write the following function to perform the conversion between the zone code to the zone id.

<?php
  
function uc_location_address_get_zone_id($zone_code)
  {
      
$query 'SELECT zone_id FROM {uc_zones} WHERE zone_code = "%s"';
      
$result db_query($query$zone_code);
      
$zone_id db_result($result);
      return 
$zone_id;
  }
?>



Something similar happens with the country where we can have the following function to perform the conversion. This is only necessary if we are using the country field for the billing address.

<?php
  
function uc_location_address_get_country_id($country_code)
  {
      
$country_code strtoupper($country_code);
      
$query 'SELECT country_id FROM {uc_countries} WHERE country_iso_code_2 = "%s"';
      
$result db_query($query$country_code);
      
$country_id db_result($result);
      return 
$country_id;
  }
?>


Now, we need to include this data as part of the checkout form so we can display the data on the ubercart fields. We are going to include this information in hidden fields so they can be optionally included as the billing address information. We can do this through the Drupal hook_form_alter which can be implemented in a module called uc_location_address

<?php
  
/**
   * Implementation of hook_form_alter
   */
  
function uc_location_address_form_alter(&$form$form_state$form_id)
  {
      global 
$user;
      
// We want to execute the following code only when the checkout form is loaded
      
if ($form_id == 'uc_cart_checkout_form') {
          
// We add a javascript code to optionally include the user/node address
          // as part of the billing address
          
drupal_add_js(drupal_get_path('module''uc_location_address') .
 
'/uc_location_address.js');
          
          
// We load the node or user containing the address we want to display
          // in this case we are using a node that can be added through the content
          // profile module.
          // For a user object (usually loaded through user_load function),
          // locations are tipically retrieved from $account->location
          
$profile_node node_load(array('uid' => $user->uid'type' => 'profile'));
          
          
// We add this information as part of the billing pane of checkout form.
          // It can be added directly to the fields but we are adding to different fields
          // so user can optionally choose to use the user address as billing addresses
          // which are not necessary the same
          
$form['panes']['billing']['profile_node']['street'] = array('#type' => 'hidden''#value' => $profile_node->locations[0]['street'], );
          
$form['panes']['billing']['profile_node']['additional'] = array('#type' => 'hidden''#value' => $profile_node->locations[0]['additional'], );
          
$form['panes']['billing']['profile_node']['city'] = array('#type' => 'hidden''#value' => $profile_node->locations[0]['city'], );
          
$form['panes']['billing']['profile_node']['province'] = array('#type' => 'hidden''#value' =>
uc_location_address_get_zone_id($profile_node->locations[0]['province']), );
          
$form['panes']['billing']['profile_node']['country'] = array('#type' => 'hidden''#value' =>
uc_location_address_get_country_id($profile_node->locations[0]['country']), );
          
$form['panes']['billing']['profile_node']['postal_code'] = array('#type' => 'hidden''#value' => $profile_node->locations[0]['postal_code'], );
          
          
// Finally, in our case we wanted to override the billing address select drop down
          // by a checkbox to indicate that we want the billing address to be the same address
          // as in profile information.
          // We also tie to a javascript function triggered on the onchage event.
          
$form['panes']['billing']['billing_address_select'] = array('#type' => 'checkbox''#description' =>
t('Billing Address same as profile information'), '#attributes' =>
  array(
'onchange' => 'uc_location_address_select();'), );
      }
  }
?>


Finally we add some javascript code to the uc_location_address.js file to handle the dynamic fill of the fields.

function uc_location_address_select()
  {
      
// check if the option has been selected, if so copy all fields values
      
if ($("#edit-panes-billing-billing-address-select") . attr("checked") == true) {
          $(
"#edit-panes-billing-billing-street1") . val($("#edit-panes-billing-profile-node-street") . val());
          $(
"#edit-panes-billing-billing-street2") . val($("#edit-panes-billing-profile-node-additional") . val());
          $(
"#edit-panes-billing-billing-city") . val($("#edit-panes-billing-profile-node-city") . val());
          $(
"#edit-panes-billing-billing-zone") . val($("#edit-panes-billing-profile-node-province") . val());
          $(
"#edit-panes-billing-billing-country") . val($("#edit-panes-billing-profile-node-country") . val());
          $(
"#edit-panes-billing-billing-postal-code") . val($("#edit-panes-billing-profile-node-postal-code") . val());
          
// prevent the fields from being edited
          
$("#edit-panes-billing-billing-street1") . attr("disabled"true);
          $(
"#edit-panes-billing-billing-street2") . attr("disabled"true);
          $(
"#edit-panes-billing-billing-city") . attr("disabled"true);
          $(
"#edit-panes-billing-billing-zone") . attr("disabled"true);
          $(
"#edit-panes-billing-billing-country") . attr("disabled"true);
          $(
"#edit-panes-billing-billing-postal-code") . attr("disabled"true);
      } else {
          
// In case the option has been deselected, clear and enable the fields
          
$("#edit-panes-billing-billing-street1") . val("");
          $(
"#edit-panes-billing-billing-street2") . val("");
          $(
"#edit-panes-billing-billing-city") . val("");
          $(
"#edit-panes-billing-billing-zone") . val("");
          $(
"#edit-panes-billing-billing-country") . val("");
          $(
"#edit-panes-billing-billing-postal-code") . val("");
          $(
"#edit-panes-billing-billing-street1") . removeAttr("disabled");
          $(
"#edit-panes-billing-billing-street2") . removeAttr("disabled");
          $(
"#edit-panes-billing-billing-city") . removeAttr("disabled");
          $(
"#edit-panes-billing-billing-zone") . removeAttr("disabled");
          $(
"#edit-panes-billing-billing-country") . removeAttr("disabled");
          $(
"#edit-panes-billing-billing-postal-code") . removeAttr("disabled");
      }
  }


Conclusions and Recommendations


We can provide integration between contributed and core modules through the Drupal hook system without the need of hacking into the core functionality of the modules. It is important that contributed module developers leverage the functionallity of existing Drupal modules so the code and/or data duplication is reduced and extra effort in integrating solutions is avoided.

The code presented here provides a very specific functionality which can help to solve a very specific case but does not work as a general drupal module or provide a seamless integration between Ubercart addresses and Location module. However, I believe this can be the start of a more general module which can be further uploaded to the community. I have already filled a feature request on the issue queue of the Ubercart module http://drupal.org/node/520484 which stills active but hopefully some day a location integration module will be added to the Ubercart modules suite.