Prevent PO Boxes in shipping address field on Checkout

Prevent PO Boxes in shipping address field on Checkout © jechasteen@sxc.hu

We recently integrated a feature that would prevent users from creating an order with a PO Box inĀ Australia. Since we are already familiar with Australia Post shipping integration (and heavy modifications of it, I might add :)), the first thing that came to mind is to have a look if there’s some API or web interface that can be used to filter out post codes that are linked to PO Boxes. Many high volume addresses and/or PO Boxes in Australia have their own post code, so it looked like an obvious solution. It turns out that it’s not that simple or logical, since there are many edge cases where you can not reliably tell if you should filter the code out or not. There is a guide made by Fontis that shows how to validate the input fields in order to filter out PO Boxes. However, in our case we had some dynamic content loading with Temando shipping method integrated. This guide is intended as a more general solution that is compatible with Checkout page’s dynamic content and third party Shipping methods integration like Temando.

Compatibility issues

The first step we took to add this functionality is to add a regular expression validation check in the Customer/Model/Address/Abstract.php (like explained in the Fontis’ post). However, Temando’s dynamic content on the checkout refused to update itself and was throwing strange validation errors. We have identified that the source of the compatibility problem is not the regular expression itself, but rather its location and triggering method.

Solution

The solution is based on pretty much the same logic – regular expression check to filter out addresses that have PO boxes in them (not a prefect solution, but at the moment, that’s the only tool developers for Australian market have). The difference is in the approach. We will create an AJAX called server side validation that hooks only on the needed fields without interference (overriding) with Magento’s validation logic or any other custom modification, dynamically generated data or a third party shipping module. So, let’s start.

The first thing we’ll need is a module. You can make a blank module or use a “wrapper” kind of module that holds all those minor changes and fixes for the project you are working on (like I do). Define a new router in your module’s config.xml file:

<frontend>
   <routers>
      <poboxes>
         <use>standard</use>
         <args>
            <module>Namespace_Yourmodulename</module>
            <frontName>poboxes</frontName>
         </args>
      </poboxes>
   </routers>
</frontend>

This router is linked with the controller action responsible for validation, so your module will need a PoboxesController.php file as well:

class Namespace_Yourmodulename_PoboxesController extends Mage_Core_Controller_Front_Action
{
   public function indexAction()
   {
      $this->loadLayout();
      $this->renderLayout();
   }
 
   public function checkpoboxAction()
   {
      $street1 = $this->getRequest()->getParam('street1');
      $street2 = $this->getRequest()->getParam('street2');
      $response = '';
 
      if (preg_match("/p\.* *o\.* *box/i", street1) || preg_match("/p\.* *o\.* *box/i", $street2))
      {
         $response = Mage::helper('yourmodulehelper')->__('We do not ship to PO boxes.');
      }
      echo $response;
   }
}

This controller action, when called will read two parameters: street1 and street2. Those are your – street1 and street2 form elements on the checkout page :). The $response variable contains the HTML response: it’s empty if the validation passed and contains the error message if the validation fails. One important thing to remember here is to echo the response instead of using return. If you use the “return”, you’ll end up with a system.log full of “Headers already sent” errors.

We now need to connect the logic with the page itself. I suggest you to go to Magento’s admin area, turn the template path hints on and go to the Checkout page. eWave’s Temando module, for example, overrides the default .phtml files and uses its own. Depending on your configuration and/or other third party shipping modules, Magento may not use the default shipping.phtml and billing.phtml files on Checkout page and we need to edit those.

We have two options on the billing information step. One is to invalidate the PO box input directly, another one is to avoid the validation if customer decides to use different shipping address. In the second case, the validation will be done on the shipping step. I’ll post the second approach just as a showcase.

Find the “Continue” button in the billing.phtml file and notice that its onclick action is to call billing.save() function. We’ll inject our code here and use custom_billing() function instead of a default one. Your “Continue” button should look like this:

<button type="button" title="<?php echo $this->__('Continue') ?>" class="button" onclick="custom_billing()"><span><span><?php echo $this->__('Continue') ?></span></span></button>

Create the custom_billing() javaScript function at the bottom of your billing.phtml file:

	function custom_billing()
	{
		var poboxcheck = '<?php echo $this->getUrl("poboxes/poboxes/checkpobox")?>street1/'
							+ document.getElementById('billing:street1').value + '/street2/'
							+ document.getElementById('billing:street2').value;
			if (document.getElementById('billing:use_for_shipping_yes').checked == true)
			{
				new Ajax.Request(poboxcheck,
					{method:'get',
					 onSuccess: function (data){
					 	var response = data.responseText;
						response = response.strip();
							if (response !== '')
							{
								alert(response);
							}
							else
							{
								billing.save();
							}
					 }
 
					}
				);
			}
			else
			{
				billing.save();
			}
 
	}

Hold on… AJAX?! In my PHP? Well, I’d never… :)

Don’t be scared. It’s fairly simple, really. The first thing we’ve done is formed an URL to the validation controller action that we have already made. That URL is placed inside the “poboxcheck” variable. We have added the billing:street1 and billing:street2 field’s values to the URL and they will be passed to the controller action in a GET request. You can add any number of parameters to a controller action call using this method. The URL format is something like:

 your_contoller_action_URL/parameter_1_name/parameter_1_value/parameter_2_name/parameter_2_value/...

and so on. The AJAX request itself calls the provided URL from the “poboxcheck” variable and fetches response from the controller action. It is generally wise to strip() the response, since Magento sometimes adds empty spaces on the start of the response for some weird reason. If the response is empty, we will just call the default billing.save() function. If the response contains the validation error message, we will notify the customer by using alert() (or render some fancy lightbox message, or whatever your designer wants to implement :)). Since we have echoed the response in our controller, the AJAX request will get just a response message. If we returned the response instead, the function would send HTTP headers as well (which should be avoided).

The AJAX call is wrapped in an “if” statement that checks if the customer wants to use billing address as the shipping address. If the option is checked on the Checkout page, it will activate validation right away; if not, it will save the billing information and proceed to the shipping step. That is the only difference between the two approaches discussed earlier. If you want to trigger validation right away on the billing step, just remove this “if” statement and that’s it.

The exact same logic goes in the shipping.phtml file: replace the shipping.save() onclick method with custom_shipping() function, create the function, form an URL with parameters, call it with AJAX and process the response. It really looks the same as in billing step; you just have to make sure to pass the info to shipping.save() at the end (instead to billing.save()).

Clear the cache, including javaScrpt/CSS cache, from Magento’s admin area, and you now have an isolated validation method that will not clash with any third party module or dynamic data generated on the Checkout page.

Interested in hiring us?

Have a chat with us. You would be surprised how small changes can make your business even more successful.


7 comments

  1. Per my previous post, since a non-default address that uses a POBox breaks this (due to not updating the hidden #shipping:street1 field), I tried using the built in shipping.setAddress(val) method in a change() event for the select element. This works entirely as expected, however when you change the address, the setAddress() method fires and then resets the select’s selected option to “New Address” – the glitch is only visual, as the address that get’s used is the correct one, but this visual bug will confuse users.

    I’m not sure how to get around this without having to rewrite opcheckout.js. Any ideas?

    <script type="text/javascript">
        jQuery("#shipping-address-select").change(function(){
            var value = jQuery("#shipping-address-select option:selected").val();
            shipping.setAddress(value);
        });
    
        function check_pobox() {
            var street1 = jQuery('#shipping\\:street1').val();
            var street2 = jQuery('#shipping\\:street2').val();
    
            var controller = '<?php echo $this->getUrl("poboxfilter/index/index") ?>';
            var request = controller + 'street1/' + street1 + '/street2/' + street2;
    
            jQuery.ajax({
                type: "POST",
                url: request
            }).done(function(response){
                    if (response !== '') {
                        alert(response);
                    }
                    else {
                        shipping.save();
                    };
                });
    
            }
    </script>
  2. Probably need to modify to handle the situation when a user has additional addresses saved in their address book. If their PO Box address is not the default address, and they choose to select that address when choosing a shipping address, the #shipping:street1 and #shipping:street2 elements aren’t yet updated with the new address (I assume this happens somewhere in shipping.save() ).

  3. Matt,
    Your module is extending the sales_quote_address validate() function, which is in line with Fontis’ post (the only difference being that Fontis has implemented it in customer address validation instead of quote address validation).

    The reason for this blog post are compatibility issues of validation method used by Fontis (and your extension) with third party modules that load dynamic data on the checkout page. :)

  4. Lars,

    Frontend validation should be just the first step. Depending on the amount of orders and business methodology, the shop owner will decide if he needs some automated order filtering code implemented in the administrator’s area as well.

  5. So a clever user would just override the JS function custom_billing() with Firebug or something similar with function custom_billing() {biling_save()} and be done with it?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.