Drupal Commerce Line Items: alter the price?

19,904

Solution 1

For those people who don't want to use rules and hope to alter the price directly. Here is my solution:

// Alter the price in list and single product page.
function my_module_commerce_product_calculate_sell_price_line_item_alter($line_item){

    $price = 100; //1 dollar
    $line_item->commerce_unit_price[LANGUAGE_NONE]['0']['amount'] = $price;

}

// Alter the price in cart & order.
function my_module_commerce_cart_line_item_refresh($line_item, $order_wrapper){

    $price = 100; //1 dollar
    $line_item->commerce_unit_price[LANGUAGE_NONE]['0']['amount'] = $price;
    // Alter the base_price component.
    $line_item->commerce_unit_price[LANGUAGE_NONE]['0']['data']['components']['0']['price']['amount'] = $price;

}

Solution 2

If you're looking to ignore whatever previous values have been saved to a line item and recalculate the total from your new amount the function you're looking for is commerce_line_item_rebase_unit_price.

Set the new amount value and then run your line item through there, save the line item and the order:

$line_item_wrapper->commerce_unit_price->amount = 13;

commerce_line_item_rebase_unit_price($line_item_wrapper->value());

commerce_line_item_save($line_item_wrapper->value());

Solution 3

I struggled through this issue all day today and final figured out the correct path to altering line items prices. The problem is that, even if you successfully change the line item price to a custom value, on the next page refresh the cart will reset the line items to match the original product price. Take a look at the commerce_cart_order_refresh() function for details. This function is executed every time an order/cart is loaded on the page and there is no way around it.

It turns out that the proper way to alter a line item price is to either use Rules or to implement the hook_commerce_cart_line_item_refresh() function. Either way, Drupal Commerce need to be able to apply the alteration logic each time the cart/order is loaded.

I ended up creating a custom field in the Line Item where I stored the custom price value I wanted. I then used a Pricing Rule to copy the custom price value to the product price value whenever the cart is refreshed.

The following blog post was very helpful in figuring this out. It shows you how to add a custom field to a line item type and how to setup a pricing rule to copy the custom amount to the unit price.

http://commerceguys.com/blog/using-custom-line-items-provide-donation-feature-drupal-commerce

Solution 4

Recently I had to implement a donation form in Commerce but the Commerce Express Checkout module doesn't handle custom line items. Since it was a donation and all (who is trying to screw the house?), I felt it appropriate to pass the donation amount as a 3rd parameter in URL the Express Checkout module provides. Here is how I went about hacking the module:

I added a new entry to the router:

$items['commerce-express-checkout/%/%/%'] = array(
      'title' => 'Express Checkout w/ extra argument',
      // 'page callback' => 'commerce_express_checkout_create_order',
      'page callback' => 'commerce_express_checkout_create_order_extra',
      'page arguments' => array(1, 2, 3),
      'access arguments' => array('access checkout'),
      'type' => MENU_CALLBACK,
  );

I duplicated and tweaked the default callback and tacked '_extra' to it. Note that the "data" property seems to be a static variable store for occasions just such as this and persists the life of the line item.

function commerce_express_checkout_create_order_extra($product_id, $token, $amount) {

  if (drupal_hmac_base64($product_id, drupal_get_private_key().drupal_get_hash_salt()) == $token && is_numeric($amount)) {
    global $user;

    $product = commerce_product_load($product_id);

    $product->commerce_price['und'][0]['amount'] = (int)$amount;

    $order = ($user->uid) ? commerce_order_new($user->uid, 'checkout_checkout') : commerce_cart_order_new();

    commerce_order_save($order);

    $price = array('amount' => commerce_round(COMMERCE_ROUND_HALF_UP, $amount), 'currency_code' => commerce_default_currency());

    $line_item = commerce_product_line_item_new($product, 1, $order->order_id);
    $line_item->data = array('und' => array('0' => $price));
    commerce_line_item_save($line_item);

    $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

    $order_wrapper->commerce_line_items[] = $line_item;

    $order->data['type'] = 'commerce_express_checkout_order';

    commerce_order_save($order);

    drupal_goto('checkout/' . $order->order_id);

    return "";
  }

   return "";
}

Here is the part that ended up being most tricky simply due to the learning curve and not knowing what the heck function to use:

/**                                                                             
 * Implements hook_commerce_cart_line_item_refresh().                           
 */                                                                             
function commerce_express_checkout_commerce_cart_line_item_refresh($line_item, $order_wrapper) { 
  if ($line_item->commerce_product['und'][0]['line_item_label'] == 'DONATE' || $line_item->commerce_product['und'][0]['product_id'] == '11') {
    $price = array('amount' => commerce_round(COMMERCE_ROUND_HALF_UP, $line_item->data['und'][0]['amount']), 'currency_code' => commerce_default_currency());
    $line_item->commerce_unit_price = array('und' => array('0' => $price));
    $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
    $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
      $line_item_wrapper->commerce_unit_price->value(), 'base_price', $price, TRUE
    );
  }
}

Every single time the cart is modified, it refreshes and attempts to set the products in the cart to their in-code prototype. It seems pretty inefficient to me too, but I could be missing something.

Share:
19,904
Strae
Author by

Strae

I can accept failure, everyone fails at something - But I can't accept not trying. -- You HAVE to assume your visitor is a maniac serial killer, out to destroy your application. And you have to prevent it. Hire me

Updated on June 05, 2022

Comments

  • Strae
    Strae almost 2 years

    I have to add to my cart some line items with a custom amount. The commerce product is saved with price = 0, and my module compute the price and add the line item to the cart/order, but i dont understand how to set programmatically the price.

    I've read about using Rules, but I need my module to be able to set/alter the price, without invoking rules.

    I've tryed with an entity wrapper, i tryed to alter the line item created with commerce_product_line_item_new(), but nothing, when the line item gets into the cart always has the original product price (in my case, 0).

    How to alter a line item price programmatically?

    My code so far looks like:

    // For debugging, this function is called by hook_menu()
    function mymodule_test($product_id)
    {
        global $user;
        $user = user_load($user->uid);
    
        $order = commerce_cart_order_load($user->uid);
        $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
    
        $product = commerce_product_load($product_id);
    
        $line_item = commerce_product_line_item_new(
                $product,
                1,
                0,
                array(
                ),
                'cover'
        );
    
        $line_item_wrapper = entity_metadata_wrapper("commerce_line_item", $line_item);
    
        $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
                $line_item_wrapper->commerce_unit_price->value(),
                'base_price',
                array(
                                'amount' => 1234,
                                'currency_code' => 'EUR',
                                'data' => array(),
                ),
                TRUE
        );
    
        $insert_line_item = commerce_cart_product_add($user->uid, $line_item_wrapper->value(), FALSE);
    
        return 'done';
    }
    

    The strange thing, is that I tryed to adapt the code of commerce_line_item_unit_price_amount() found in commerce/modules/line_item/commerce_line_item.rules.inc, but this test:

    <?php
        global $user;
        $product = commerce_product_load(4); // my commerce product for test
    
        $line_item = commerce_product_line_item_new(
            $product,
            1,
            0,
            array(
            ),
            'cover' // I do have this line_items type
        );
    
        // manually set amount and component name
        $amount = 1234;
        $component_name = 'base_price'; // tryed with discount, nothing change
    
        $wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
        $unit_price = commerce_price_wrapper_value($wrapper, 'commerce_unit_price', TRUE);
    
        // Calculate the updated amount and create a price array representing the
        // difference between it and the current amount.
        $current_amount = $unit_price['amount'];
        $updated_amount = commerce_round(COMMERCE_ROUND_HALF_UP, $amount);
    
        $difference = array(
            'amount' => $updated_amount - $current_amount, 
            'currency_code' => $unit_price['currency_code'], 
            'data' => array(),
        );
    
        // Set the amount of the unit price and add the difference as a component.
        $wrapper->commerce_unit_price->amount = $updated_amount;
    
        $wrapper->commerce_unit_price->data = commerce_price_component_add(
            $wrapper->commerce_unit_price->value(), 
            $component_name, 
            $difference, 
            TRUE
        );
    
        $insert_line_item = commerce_cart_product_add($user->uid, $line_item, FALSE);
    ?>
    

    still fail, the line_item get into the cart but with the original price of the referenced product.

    Any idea?

    • Clive
      Clive over 11 years
      You're not saving your wrappers...that might be the problem (i.e. $line_item_wrapper->save();)
    • Strae
      Strae over 11 years
      @Clive i think i did tryed that too, but let me give another try just in case
    • Clive
      Clive over 11 years
      Oh, you'll also need to save the $order_wrapper (that one got me when I was doing something similar a few months back)
    • Strae
      Strae over 11 years
      @Clive Tryed, still doesnt works.. I added $line_item_wrapper->save(); $order_wrapper->save(); before (and after too, just in case) commerce_cart_product_add but nothing change... I dont get any error, the line item get into the cart, but with the original product's price!
    • Strae
      Strae over 11 years
      Mmmhh i got a strange behavior btw, if i call save() on the order_wrapper, the line item doesnt get into the cart at all (but still no erros)
    • m4olivei
      m4olivei over 11 years
      Did you ever figure this one out?
    • Strae
      Strae over 11 years
      @m4olivei not yet... i've been busy on another project those days.. but i have to do this for a project this week, i'll report if i get it
    • Strae
      Strae about 11 years
      @m4olivei sorry not yet.. if you solve this, let me know!
  • Strae
    Strae almost 11 years
    Thanks for sharing! Drupal Commerce is powerfull and flexible,but I still dont get why the prices alteration are bounded to Rules in a so strict way.
  • mmmpop
    mmmpop almost 11 years
    @MichaelStevens I've posted an example that implements this function.
  • James
    James almost 10 years
    Hi Tim, The above hook wasn't triggering from my module. Tried Single product page hook. I'm using DC Kickstart.
  • Tim Yao
    Tim Yao almost 10 years
    Do you have any other place to set the price? you can try to kpr($line_item) in the hook to see what it is like.
  • jaskho
    jaskho about 8 years
    To my mind this is the best answer. I'm sure there are situations where Drupal Commerce's complex pricing system is very helpful, but in the use-cases I've come upon I just want to set the dang price, and I don't want to have to implement another hook (thus making my code even harder to understand).