Calculate Shipping Based on Subtotal Drupal 7

  • Posted on: 15 August 2017
  • By: jmu

Recently, I am struggling how to calculate a shipping fee based on the subtotal instead of the total (total might include tax, default shipping fee and so on). I found a sandbox module called Commerce Order Sub-total. The rule condition part works perfectly for me. However, this module only provides one rule action which returns "NUMBER" instead of "DECIMAL". In order to implement "calculate a shipping fee based on the subtotal", I need to use the result from "Commerce Order Sub-total" rule action in "Calculate a Value" action. Therefore, I make a little change in Commerce Order Sub-total Module code. 

If you need the module action result as "DECIMAL" instead of NUMBER". Here are some steps you might be interested in.

  1. Create a commerce_order_total.rules.inc file in commerce_order_sub_total folder.
  2. Find the following function code in commerce_order_total.module file
    /**
     * Implements hook_rules_action_info().
     */
    function commerce_order_total_rules_action_info() {
        return [
            'commerce_order_total_calculate' => [
                'label' => 'Commerce Order Sub-Total',
                'group' => t('Commerce Order'),
                'parameter' => [
                    'commerce_order' => [
                        'type' => 'commerce_order',
                        'label' => t('Order'),
                        'description' => t('The order whose product line item quantities should be totalled.'),
                    ],
                    'exclude_types' => [
                        'label' => t('Exclude these Line Item Types from Order Total'),
                        'type' => 'list',
                        'optional' => TRUE,
                        'default value' => '',
                        'options list' => 'commerce_order_total_line_item_types',
                        'restriction' => 'input',
                        'description' => 'The most common line item types include product and shipping. Select any line item type you don\'t want to include in the order total.',
                    ],
                    'exclude_prod_types' => [
                        'label' => t('Exclude these Product Types from Order Total'),
                        'type' => 'list',
                        'optional' => TRUE,
                        'default value' => '',
                        'options list' => 'commerce_order_total_product_types',
                        'restriction' => 'input',
                    ],
                    'exclude_shipping_services' => [
                        'label' => t('Exclude these Shipping Services from Order Total.'),
                        'type' => 'list',
                        'optional' => TRUE,
                        'default value' => '',
                        'options list' => 'commerce_order_total_shipping_services',
                        'restriction' => 'input',
                        'description' => 'Note that shipping services are often not available in the order object unless we are at the end of the checkout process.',
                    ],
                    'skus' => [
                        'type' => 'text',
                        'label' => t('Exclude these SKUs'),
                        'default value' => '',
                        'optional' => TRUE,
                        'description' => t('Separate each sku with a comma.'),
                    ],
                ],
                'provides' => [
                    'commerce_order_subtotal' => [
                        'type' => "number", 
                        'label' => "Commerce Order Sub-Total",
                    ],
                ],
            ],
        ];
    }
    
  3. Modify the pink code in step 2 to the following code. AND delete the function from commerce_order_total_module file. ALSOcopy the new code into commerce_order_total.rules.inc
    
    'base' => 'commerce_order_total_action_data_calc',
    'provides' => [
         'commerce_order_subtotal' => [
               'type' => "decimal",
               'label' => "Commerce Order Sub-Total",
          ],
    ],
    
  4. Find the following function code in commerce_order_total.module file
    /**
     * Action: Calculate a value.
     */
    function commerce_order_total_action_data_calc($order, $exclude_types, $exclude_prod_types, $exclude_shipping_services, $skus) {
        // If we have skus:
        if (!empty($skus) && trim($skus) != NULL) {
            $skus_explode = explode(",", trim($skus));
            array_walk($skus_explode, 'trim');
            $skus = $skus_explode;
        }
        else {
            $skus = [];
        }
    
        // Find allowed line item types.
        $include_types = array();
        $line_item_types = commerce_order_total_line_item_types();
        $exclude_types = array_filter($exclude_types);
        if (is_array($exclude_types) && !empty($exclude_types)) {
            foreach (array_keys($line_item_types) as $line_item_type) {
                if (!in_array($line_item_type, $exclude_types)) {
                    $include_types[] = $line_item_type;
                }
            }
        }
        else {
            $include_types = array_keys($line_item_types);
        }
    
        // Find allowed Product Types.
        $include_prod_types = array();
        $product_types = commerce_order_total_product_types();
        $exclude_prod_types = array_filter($exclude_prod_types);
        if (is_array($exclude_prod_types) && !empty($exclude_prod_types)) {
            foreach (array_keys($product_types) as $product_type) {
                if (!in_array($product_type, $exclude_prod_types)) {
                    $include_prod_types[] = $product_type;
                }
            }
        }
        else {
            $include_prod_types = array_keys($product_types);
        }
    
        // Find allowed Shipping Services.
        $include_shipping_services = array();
        $shipping_services = commerce_order_total_shipping_services();
        $exclude_shipping_services = array_filter($exclude_shipping_services);
        if (is_array($exclude_shipping_services) && !empty($exclude_shipping_services)) {
            foreach (array_keys($shipping_services) as $shipping_service) {
                if (!in_array($shipping_service, $exclude_shipping_services)) {
                    $include_shipping_services[] = $shipping_service;
                }
            }
        }
        else {
            $include_shipping_services = array_keys($shipping_services);
        }
    
        // Calculate the order total.
        if (!empty($order)) {
            $wrapper = entity_metadata_wrapper('commerce_order', $order);
    
            if (!empty($wrapper->commerce_line_items)) {
    
                $line_items = $wrapper->commerce_line_items;
    
                // Sum up the order total of all matching line items.
                $order_total = 0;
    
                foreach ($line_items as $line_item) {
                    if (!$line_item instanceof EntityMetadataWrapper) {
                        $line_item = entity_metadata_wrapper('commerce_line_item', $line_item);
                    }
    
                    // Proceed if line item type is allowed.
                    if (in_array($line_item->getBundle(), $include_types)) {
                        // Enforce Product filters.
                        if (in_array('commerce_product', array_keys($line_item->getPropertyInfo()))) {
                            // Skip product types that are excluded.
                            if (!in_array($line_item->commerce_product->type->value(), $include_prod_types)) {
                                continue;
                            }
    
                            // Skip product line items that have an excluded sku.
                            if (in_array($line_item->commerce_product->value()->sku, $skus)) {
                                continue;
                            }
                        }
    
                        // Skip if we are dealing with an excluded shipping service.
                        if (in_array('commerce_shipping_service', array_keys($line_item->getPropertyInfo()))) {
                            if (!in_array($line_item->commerce_shipping_service->value(), $include_shipping_services)) {
                                continue;
                            }
                        }
    
                        // Only total those line items with a price associated with them.
                        if (in_array('commerce_total', array_keys($line_item->getPropertyInfo()))) {
                            $order_total += $line_item->commerce_total->amount->value();
                        }
                    }
                }
                return $order_total;
            }
        }
        return 0;
    }
  5. Modify the pink code in step 4 to the following code. AND copy the new code into commerce_order_total.rules.inc
    $order_total = (int) $order_total;
    return array('commerce_order_subtotal' => $order_total);
    
    return array('commerce_order_subtotal' => 0);
  6. Done~ DALA

So, here is what commerce_order_total.rules.inc looks like:

/**
 * Action: Calculate a value.
 */
function commerce_order_total_action_data_calc($order, $exclude_types, $exclude_prod_types, $exclude_shipping_services, $skus) {
    // If we have skus:
    if (!empty($skus) && trim($skus) != NULL) {
        $skus_explode = explode(",", trim($skus));
        array_walk($skus_explode, 'trim');
        $skus = $skus_explode;
    }
    else {
        $skus = [];
    }

    // Find allowed line item types.
    $include_types = array();
    $line_item_types = commerce_order_total_line_item_types();
    $exclude_types = array_filter($exclude_types);
    if (is_array($exclude_types) && !empty($exclude_types)) {
        foreach (array_keys($line_item_types) as $line_item_type) {
            if (!in_array($line_item_type, $exclude_types)) {
                $include_types[] = $line_item_type;
            }
        }
    }
    else {
        $include_types = array_keys($line_item_types);
    }

    // Find allowed Product Types.
    $include_prod_types = array();
    $product_types = commerce_order_total_product_types();
    $exclude_prod_types = array_filter($exclude_prod_types);
    if (is_array($exclude_prod_types) && !empty($exclude_prod_types)) {
        foreach (array_keys($product_types) as $product_type) {
            if (!in_array($product_type, $exclude_prod_types)) {
                $include_prod_types[] = $product_type;
            }
        }
    }
    else {
        $include_prod_types = array_keys($product_types);
    }

    // Find allowed Shipping Services.
    $include_shipping_services = array();
    $shipping_services = commerce_order_total_shipping_services();
    $exclude_shipping_services = array_filter($exclude_shipping_services);
    if (is_array($exclude_shipping_services) && !empty($exclude_shipping_services)) {
        foreach (array_keys($shipping_services) as $shipping_service) {
            if (!in_array($shipping_service, $exclude_shipping_services)) {
                $include_shipping_services[] = $shipping_service;
            }
        }
    }
    else {
        $include_shipping_services = array_keys($shipping_services);
    }

    // Calculate the order total.
    if (!empty($order)) {
        $wrapper = entity_metadata_wrapper('commerce_order', $order);

        if (!empty($wrapper->commerce_line_items)) {

            $line_items = $wrapper->commerce_line_items;

            // Sum up the order total of all matching line items.
            $order_total = 0;

            foreach ($line_items as $line_item) {
                if (!$line_item instanceof EntityMetadataWrapper) {
                    $line_item = entity_metadata_wrapper('commerce_line_item', $line_item);
                }

                // Proceed if line item type is allowed.
                if (in_array($line_item->getBundle(), $include_types)) {
                    // Enforce Product filters.
                    if (in_array('commerce_product', array_keys($line_item->getPropertyInfo()))) {
                        // Skip product types that are excluded.
                        if (!in_array($line_item->commerce_product->type->value(), $include_prod_types)) {
                            continue;
                        }

                        // Skip product line items that have an excluded sku.
                        if (in_array($line_item->commerce_product->value()->sku, $skus)) {
                            continue;
                        }
                    }

                    // Skip if we are dealing with an excluded shipping service.
                    if (in_array('commerce_shipping_service', array_keys($line_item->getPropertyInfo()))) {
                        if (!in_array($line_item->commerce_shipping_service->value(), $include_shipping_services)) {
                            continue;
                        }
                    }

                    // Only total those line items with a price associated with them.
                    if (in_array('commerce_total', array_keys($line_item->getPropertyInfo()))) {
                        $order_total += $line_item->commerce_total->amount->value();
                    }
                }
            }
            $order_total = (int) $order_total;
            return array('commerce_order_subtotal' => $order_total);
        }
    }
    return array('commerce_order_subtotal' => 0);
}
/**
 * Implements hook_rules_action_info().
 */
function commerce_order_total_rules_action_info() {
    return [
        'commerce_order_total_calculate' => [
            'label' => 'Commerce Order Sub-Total',
            'group' => t('Commerce Order'),
            'parameter' => [
                'commerce_order' => [
                    'type' => 'commerce_order',
                    'label' => t('Order'),
                    'description' => t('The order whose product line item quantities should be totalled.'),
                ],
                'exclude_types' => [
                    'label' => t('Exclude these Line Item Types from Order Total'),
                    'type' => 'list',
                    'optional' => TRUE,
                    'default value' => '',
                    'options list' => 'commerce_order_total_line_item_types',
                    'restriction' => 'input',
                    'description' => 'The most common line item types include product and shipping. Select any line item type you don\'t want to include in the order total.',
                ],
                'exclude_prod_types' => [
                    'label' => t('Exclude these Product Types from Order Total'),
                    'type' => 'list',
                    'optional' => TRUE,
                    'default value' => '',
                    'options list' => 'commerce_order_total_product_types',
                    'restriction' => 'input',
                ],
                'exclude_shipping_services' => [
                    'label' => t('Exclude these Shipping Services from Order Total.'),
                    'type' => 'list',
                    'optional' => TRUE,
                    'default value' => '',
                    'options list' => 'commerce_order_total_shipping_services',
                    'restriction' => 'input',
                    'description' => 'Note that shipping services are often not available in the order object unless we are at the end of the checkout process.',
                ],
                'skus' => [
                    'type' => 'text',
                    'label' => t('Exclude these SKUs'),
                    'default value' => '',
                    'optional' => TRUE,
                    'description' => t('Separate each sku with a comma.'),
                ],
            ],
            'base' => 'commerce_order_total_action_data_calc',
            'provides' => [
                'commerce_order_subtotal' => [
                    'type' => "decimal",
                    'label' => "Commerce Order Sub-Total",
                ],
            ],
        ],
    ];
}

This works for me. Hope you can get some idea from here.

Add new comment

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.