12

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?

Strae
  • 18,807
  • 29
  • 92
  • 131
  • You're not saving your wrappers...that might be the problem (i.e. `$line_item_wrapper->save();`) – Clive Nov 21 '12 at 14:17
  • @Clive i think i did tryed that too, but let me give another try just in case – Strae Nov 21 '12 at 15:14
  • Oh, you'll also need to save the `$order_wrapper` (that one got me when I was doing something similar a few months back) – Clive Nov 21 '12 at 15:23
  • @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 Nov 21 '12 at 15:27
  • 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) – Strae Nov 21 '12 at 15:33
  • Did you ever figure this one out? – m4olivei Dec 11 '12 at 19:31
  • @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 Dec 12 '12 at 08:25
  • @m4olivei sorry not yet.. if you solve this, let me know! – Strae Mar 19 '13 at 13:20

5 Answers5

19

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;

}
Tim Yao
  • 1,077
  • 1
  • 13
  • 17
  • Hi Tim, The above `hook` wasn't triggering from my module. Tried Single product page `hook`. I'm using DC Kickstart. – James Jun 24 '14 at 12:26
  • 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. – Tim Yao Jun 25 '14 at 00:19
6

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());
Amarnath Balasubramanian
  • 9,300
  • 8
  • 34
  • 62
Jeremy Isett
  • 61
  • 1
  • 3
  • 2
    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). – jaskho Feb 12 '16 at 21:52
5

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

Nathan Rambeck
  • 146
  • 1
  • 5
  • 1
    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. – Strae May 03 '13 at 07:54
  • @MichaelStevens I've posted an example that implements this function. – mmmpop Jul 23 '13 at 22:10
1

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.

mmmpop
  • 109
  • 1
  • 8
0

This post pointed me in the right direction for programmatically altering a drupal commerce line item by using hook_commerce_cart_line_item_refersh(). However, some of the answers here are either outright wrong, or very inefficient and sloppy.

This would be a correct working solution for altering the line item type in Drupal Commerce:

/*  
 * implements hook_commerce_cart_line_item_refresh()
 *  
 */

function MYMODULE_commerce_cart_line_item_refresh($line_item, $order_wrapper){

    $line_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);

    $new_price = 100; //I use a function to calculate the value of $new_price

    if(!empty($new_price)){
        $line_wrapper->commerce_unit_price->amount->set($new_price);
        $line_wrapper->save();
    }
}
skrrgwasme
  • 9,358
  • 11
  • 54
  • 84