Restricting Product Purchase to Authorised Users in Magento


I haven’t written about e-commerce for quite a while now. Magento has been keeping me busy and now I’ve a list of thoughts and insights that I hope to share in the coming weeks.

So first off, I’d like to describe a recent module I wrote that might seem reasonably complex. However, I’d like to show how easy it can be when taking advantage of Magento’s features.

The idea behind the module is that a customer may only purchase a particular product by entering a code. There’s 4 parts to the module:

  • The product view page where the customer can enter their code.
  • An override of the default Add to Cart functionality in Magento to validate the given Code
  • A section in the Magento backend for managing Codes.
  • An event listener to observe when a customer places an order so that the code can be marked as “used”.

The basics of the module can be setup using ModuleCreator (http://www.magentocommerce.com/magento-connect/Daniel+Nitz/extension/1108/modulecreator). The module doesn’t really install properly in Magento 1.4.x but I’ve used it enough times at this stage to be able to copy the files over manually and do some search and replace over them to get it the way I want it.

Once the files, are in place, the most important thing is to design the table structure in the database and write the SQL installation script. The fields I needed were:

  • code_id
  • country_id
  • customer_ip
  • valid_to
  • valid_from
  • used_timestamp
  • status

Once installed in Magento, you can start fixing up the grid and add/edit screens to show you the fields for your particular module (rather than the default ModuleCreator fields). Having useful field types in the grid is a topic I intend to return to in a future post.

After that, it’s just a matter of creating a test product and giving it a custom option where the customer can enter the code.

We then override the Add to Cart functionality. So I override /app/code/core/Mage/Checkout/Model/Cart.php. To do this I add the following lines to the config.xml file of my module:

<models>
	   ...
	   <checkout>
	      <rewrite>
	         <cart>SF9_Codes_Model_Cart</cart>
	      </rewrite>
	   </checkout>
	</models>

and I create my own Cart.php file

class SF9_Codes_Model_Cart extends Mage_Checkout_Model_Cart
{
     public function addProduct($product, $info=null){
        $code_title = Mage::getStoreConfig('codes/codes/custom_option_title', 1);
        $error_message = Mage::getStoreConfig('codes/codes/invalid_code_error_message', 1);
    
          $options = array();
         
          foreach ($product->getOptions() as $_option) {
               $options[$_option->getId()] = $_option->getTitle();
          }
         
          if(!in_array($code_title, $options)){
               parent::addProduct($product, $info);
               return;
          }         
         
          foreach($info['options'] as $key => $value){
               if(in_array($key, array_keys($options)) ){
                    $code = $value;
               }
          }         
    
          $timestamp = date('Y-m-d H:i:s');
         
          $codes = Mage::getModel('codes/codes')->getCollection()
                         ->addFieldToSelect('*')
                         ->addFieldToFilter('code', $code)
                         ->addFieldToFilter('status', 0)
                         ->addFieldToFilter('valid_from', array('date' => true, 'to' => $timestamp))
                         ->addFieldToFilter('valid_to', array('date' => true, 'from' => $timestamp))
                         ->setPageSize(1);
         
          if(count($codes) > 0){
               parent::addProduct($product, $info);    
          }else{
               Mage::throwException($error_message);
          }
         
     }
}

Finally, I create an observer for the order_placed event. To do this, I add the following lines to config.xml

<events>
	   <checkout_type_onepage_save_order_after>
	      <observers>
	         <sf9_codes_observer>
	            <type>singleton</type>
	            <class>SF9_Codes_Model_Observer</class>
	            <method>update_used_codes</method>
	         </sf9_codes_observer>
	      </observers>
	   </checkout_type_onepage_save_order_after>
	</events>

and create the following Observer.php file

class SF9_Codes_Model_Observer{
        
          //runs when order is saved in Onepage Checkout
          public function update_used_codes($observer){
               $event = $observer->getEvent();
               $order = $event->getOrder();
              
               $code_title = Mage::getStoreConfig('codes/codes/custom_option_title', 1);
              
               $items = $order->getAllItems();
               foreach($items as $item){
                    $_options = $item->getProductOptions();
                    foreach($_options['options'] as $_option){
                         $options[$_option['label']] = $_option['value'];
                    }
                    if(isset($options[$code_title])){
                         $code = $options[$code_title];
                         $codes = Mage::getModel('codes/codes')->getCollection()
                                             ->addFieldToSelect('*')
                                             ->addFieldToFilter('code', $code)
                                             ->setPageSize(1);
              
                         $update = $codes->getFirstItem();
                         $timestamp = date('Y-m-d H:i:s');                             
                         $update->setStatus(1)
                                   ->setUsername($order->getBillingAddress()->getName())
                                   ->setUserIp($_SERVER['REMOTE_ADDR'])
                                   ->setUsedTimestamp($timestamp)
                                   ->save();
                    }
               }
          }
     }

What’s nice about the module is how it exemplifies in a very succinct way the different ways to extend Magento’s core functionality. You can extend a core PHP file when you want to alter the default behaviour or create an observer that listens to one of the approximately 280 events that Magento fires off when you want to do something extra at a particular time (in this case, update the code’s status to “used” and write its “used timestamp”.

Now, let’s hope I can make blogging about Magento more of a habit!!

Alan Morkan
Alan Morkan |