leicester web designer blog

Closing Down - No Sale!

Fresh Web Services is closing down, effective 31/12/2019.

We'd like to thank all our customers for placing their trust in us & we hope that trust has been repaid.

Recently a client who runs a B2c and B2B Magento shop, asked if It was possible to limit the right to buy a product to certain customer groups.

With Magento it certainly is! And this is how we did it.


The Requirement - flexible right to buy permissions

This Magento client sells a range of products that they do not want members of the general public to be able to buy. In fact, only certain B2B customer groups should be able to buy these products. So, a “retail dealer” might be able to buy one kind of product, but a “distributor" might only be able to buy certain other products. The complication is that some customers are in reality both “retail dealers” and “distributors”. The desired outcome was a flexible system that allowed permissions to buy to be set at the product level - so at runtime we could determine if a particular customer had the right to buy this product, while also having the flexibility to change the permissions should the occasion arise.

The Solution - an observer database lookup

One issue with Magento open source is that its customer groups operate on a one-to-one basis - you can only be a member of one customer group. So, putting B2B customers into “dealer” or “distributor” customer groups would not provide the flexibility to allow some customers to span both groups for some products but be restricted from buying other products.

So, the issue for this customer, was how to allow only certain customers to buy a product based on the product permissions themselves? The solution was to write an extension that used Magento's event-observer architecture to lookup who could buy this product.

Product Attributes

A previous Magento agency had attempted to tackle this requirement by creating a 'yes/no' product attribute called “Saleable”. If a product was not “Saleable” the add to cart button was not rendered. The problem was that this solution took no account of different customer permissions - it was either on or off, on for all customers regardless of their customer group, or off for all customers, again regardless of their customer group.

Product Attributes and Customer Groups.

The solution we took was to add another product attribute and then map this attribute to the customer group a user belonged to. This required a lookup table that mapped product attribute values to customer groups. This meant we were able to say that customer groups A, B & D were able to buy this product if its attribute value = “ABD”, etc.

The issue was then how to perform the lookup at runtime without putting loads of business logic into Magento’s product view files.

Observer to the rescue.

Magento has the concept of event observers. When an event occurs, an observer can be created to observe and inject logic to determine outcomes. Joomla has a similar event model, and Drupal uses hooks in a similar fashion. So, in this instance I had to find the correct event to observe & then inject my logic to determine if this product was saleable to this user.

Here’s how we did it.

Customer and Product Attributes and the Look up Table

As mentioned above, each product has two attributes that we could use - Saleable & Sage Category (which is a value from their CRM system).

Customers also have an attribute called Customer Department - this provides the lookup between the product attribute Sage Category and the current customer.

Thus our extension creates a lookup table that maps the Sage Category to the customer department. As a Sage category has a one-to-many relationship to Customer Department, this allows us to say that this product can be bought by customers in any given customer group.

Our lookup table is very simple:

CREATE TABLE `{$this->getTable('saleable')}` (
`saleable_id`       int(11) unsigned NOT NULL auto_increment,
`cat`        int(11)   NOT NULL,
`cust_dept`      int(11) NOT NULL,
  PRIMARY KEY  (`saleable_id`)

Once this table has been populated, at runtime we can search this table to see if this customer with a cust_dept can be found in the saleable table.

The observer is pretty straightforward too. It checks if the current customer is logged in, and if so, what is their Customer Department. It passes that value to a sql query that checks the lookup table and returns a boolean that is placed in a session variable. The event it observes is "catalog_controller_product_view" - this event is tiggered everytime a product is called.

public function checkSaleable(Varien_Event_Observer $observer)
   $event  = $observer->getEvent();
   $method = $observer->getMethodInstance();
   $_canBuy = false;
            //is this product saleable?
            $saleable = Mage::registry('product')->getData('saleable');
            //if its saleable, then we don't need to do anything else
            $session = Mage::getSingleton('customer/session');
            // get the product sage category option id
            $value = Mage::registry('product')->getData('sku_sage_cat');
            //get the user
            $customer = $session->getCustomer();
            $_cust_dept = $customer->getData("cust_department");
            //query database with the cust_dept & product attribute
               $_canBuy = $this->getResult($value,$_cust_dept);
               $session->setData("canBuy", $_canBuy);
            catch( Exception $e ) {
               Mage::log( "FWS_Saleable observer checkSaleable failed: " . $e->getMessage() );
   return $_canBuy;
* Query database - will return 0|1
* @param $value - sku_sge_cat
* @param $_cust_dept - customer department
* @return boolean - $result
private function getResult($value,$_cust_dept){
   $resource = Mage::getSingleton('core/resource');
    * Retrieve the read connection
   $readConnection = $resource->getConnection('core_read');
    * Execute the query and store the result in $result
      $query = 'SELECT EXISTS(SELECT * FROM ' . $resource->getTableName('saleable') .' WHERE cat = '
         . (int)$value .' AND cust_dept = ' . (int)$_cust_dept .')';
   }catch( Exception $e ) {
      Mage::log( "FWS_Saleable observer getResult failed: " . $e->getMessage() );
   $result = $readConnection->fetchOne($query);
   return $result;

The last piece of the jigsaw is the product view page. Here we render the add to cart button if the user is allowed to buy the product. We get the customer’s permission from our session variable:

if(Mage::getSingleton('customer/session')->isLoggedIn()) {
   $session = Mage::getSingleton('customer/session');
   $_canBuy = $session->getData("canBuy");

Then we render the add to cart button based on this outcome.

<div class="add-to-box">
        <?php if($_product->isSaleable()): ?>
            <?php if($_product->getAttributeText('saleable') == 'Yes'   ):?>
                    <?php echo $this->getChildHtml('addtocart') ?>
            <?php elseif($_product->getAttributeText('saleable') == 'No' && $_canBuy ): ?>
                 <?php echo $this->getChildHtml('addtocart') ?>
             <?php elseif($_product->getAttributeText('saleable') == 'No' && !$_canBuy ): ?>
                 <?php echo $this->getLayout()->createBlock('cms/block')->setBlockId('find_dealer_block')->toHtml(); ?>
            <?php endif; ?>
        <?php else: ?>
            <button type="button" title="<?php echo $this->__('Enquire about this product') ?>" class="button" onclick="div_show()"><span><span><?php echo $this->__('Enquire about this product') ?></span></span></button>
        <?php endif; ?>
        <?php echo $this->getChildHtml('extra_buttons') ?>
<?php endif; ?>


What the above code does is check if the product is not Saleable. If that’s the case, can this user still buy it? If not render a CMS static block with some further information.

With this extension, which uses Magento’s event and observer architecture, our client is able to deploy flexible business rules to govern which user groups can buy a particular product. For this client, that has transformed their Magento shop into a true B2B platform, allowing them to sell directly to dealers and distributors without the risk of retail customers being able to bypass the distributors and dealer networks the client has spent two decades nurturing.

Do you have a Magento requirement? Please call us on 0116 279 3822 to have a chat.

Satisfied Clients

Fresh Web Services Ltd:
LCB Depot, 31 Rutland Street, Leicester. LE1 1RE
Phone: +44 (0)116 279 3822
Company No: 04716234
52.634568, -1.127919
Use of this website constitutes acceptance of the
Fresh Web Services Terms and Privacy Policy including cookie-use