I'm trying to create a gift wrapping module for Drupal commerce. I have created the checkout pane that has a select box for the user to choose if they want their order gift wrapped (and a field to select the giftwrap price on the configuration form). I've also created a giftwrap line item type. In the pane's base_checkout_form_submit() function I would like to create a giftwrap line item that is added to the order alongside the products. This is what I've got so far:

/**
 * Implements base_checkout_form_submit()
 */
function commerce_giftwrap_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {

  $default_currency_code = commerce_default_currency();
  if ($balance = commerce_payment_order_balance($order)) {
    $default_currency_code = $balance['currency_code'];
  }

  // Create the new line item.
  $line_item = commerce_line_item_new('giftwrap', $order->order_id);

  $line_item->line_item_label = 'Gift Wrapping';
  $line_item->quantity = 1;
  $line_item->commerce_unit_price['amount'] = variable_get('commerce_giftwrap_price', '2.00');
  $line_item->commerce_unit_price['currency_code'] = $default_currency_code;
  
  commerce_line_item_save($line_item);  
}

I haven't wrapped it in an if statement yet, I wanted to get it working first. This code is creating a line item in the database however it isn't adding the line item to shopping cart contents view on the checkout review page. I've altered the shopping cart view to include product line items and my newly created giftwrap line items.

Any help on this would be greatly appreciated.

Comments

rszrama’s picture

Status: Active » Fixed

Creating the line item itself isn't enough to attach it to an Order; you also need to update the Order's line item reference field. See the Cart module for an example.

Snippet from commerce_cart_product_add():

  // Wrap the order to simplify manipulating its field data.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);

  // Other code to populate the $line_item...

  // Save the incoming line item now so we get its ID.
  commerce_line_item_save($line_item);

  // Add it to the order's line item reference value.
  $order_wrapper->commerce_line_items[] = $line_item;

  commerce_order_save($order);
msmithcti’s picture

Thanks for your quick reply Ryan,

I've made the changes you suggested and when I do a dpm() of $order at the review stage the ID of the order is now shown along with the shipping and any products. My problem now is that it isn't showing up on the page either in the view or by affecting the order totals. Here's the code as it currently stands:

function commerce_giftwrap_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {

  $default_currency_code = commerce_default_currency();
  if ($balance = commerce_payment_order_balance($order)) {
    $default_currency_code = $balance['currency_code'];
  }

  // Create the new line item.
  $line_item = commerce_line_item_new('giftwrap', $order->order_id);
  
  // Wrap the order to simplify manipulating its field data.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  
  // Other code to populate the $line_item...
  $line_item->line_item_label = 'Gift Wrapping';
  $line_item->quantity = 1;
  $line_item->commerce_unit_price['amount'] = variable_get('commerce_giftwrap_price', '2.00');
  $line_item->commerce_unit_price['currency_code'] = $default_currency_code;
  
  // Save the incoming line item now so we get its ID.
  commerce_line_item_save($line_item); 
  
  // Add it to the order's line item reference value.
  $order_wrapper->commerce_line_items[] = $line_item;
  
  commerce_order_save($order);
  
}

Thanks in advance.

rszrama’s picture

It's not in the View because that by default filters itself to just show product line items. To complete the integration here, you also need to set a price component to $line_item->commerce_unit_price['data']. Check out how Shipping 2.x does this with a custom price component type.

See more in commerce_shipping_service_rate_calculate():

  $shipping_service = commerce_shipping_service_load($service);

  // Create the new line item for the service rate.
  $line_item = commerce_shipping_line_item_new($service, $price, $order_id);

  // Set the price component of the unit 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(),
    $shipping_service['price_component'],
    $line_item_wrapper->commerce_unit_price->value(),
    TRUE,
    FALSE
  );
msmithcti’s picture

Thanks for baring with me on this, I don't have much module development experience (yet!) so I'm having to take things in baby steps.

So I've used hook_commerce_price_component_type_info() in my .module file to add a new component type:

/**
 * Implements hook_commerce_price_component_type_info().
 */
function commerce_giftwrap_commerce_price_component_type_info() {
  return array(
    'giftwrap' => array(
      'title' => t('Gift Wrapping'),
      'weight' => -40,
    ),
  );
}

Couldn't see a function to put in hook_enable() but would I have to do that before my component type was created?

I've then tried to use the example you gave me to set the unit price price component:

  // Set the price component of the unit price.
  $line_item->commerce_unit_price['data'] = commerce_price_component_add(
    $line_item->commerce_unit_price,
    'giftwrap',
    $line_item->commerce_unit_price,
    TRUE,
    FALSE
  );

I think where I'm going wrong is altering the $price and $price_component arguments. In the shipping module they seem to be using all objects whereas in my code it's an array within an object.

At the review stage I've used commerce_line_item_load() to load the qiftwrap line item and dpm()-ed it. From what I can see the code above seems to be leaving the commerce_unit_price array completely empty.

Thanks again for your help.

frixos12’s picture

Nice job..

I think a gift wrapping module will be very userful especially when you want to have several gift packs.

Can this be transferred to the contibuted modules in the future?

msmithcti’s picture

It's something I've thought about, currently I'm just trying to get it to work for the current project. In the future I'm more than happy to contribute it, however it will need some work to make it more configurable. I think it would also need someone with more knowledge to have a very good look over my code and suggest improvements!

rszrama’s picture

Yep, notice that you didn't copy the code exactly - you're supposed to be using the $line_item_wrapper variable defined on the line above the price component addition.

msmithcti’s picture

I think I'm almost there now, 'Gift Wrapping' is now being shown with the order totals however the price is set to £0.00. From looking at the line item the commerce_unit_price array is still being left empty which I think would explain the total price being set to £0.00. Here's the code at the moment:

/**
 * Implements base_checkout_form_submit()
 */
function commerce_giftwrap_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {

  $default_currency_code = commerce_default_currency();
  if ($balance = commerce_payment_order_balance($order)) {
    $default_currency_code = $balance['currency_code'];
  }

  // Create the new line item.
  $line_item = commerce_line_item_new('giftwrap', $order->order_id);
  
  // Wrap the order to simplify manipulating its field data.
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
  
  // Other code to populate the $line_item...
  $line_item->line_item_label = 'Gift Wrapping';
  $line_item->quantity = 1;
  $line_item->commerce_unit_price['amount'] = variable_get('commerce_giftwrap_price', '2.00');
  $line_item->commerce_unit_price['currency_code'] = $default_currency_code;
  
  // Set the price component of the unit 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(),
    'giftwrap',
    $line_item_wrapper->commerce_unit_price->value(),
    TRUE,
    FALSE
  );
  
  // Save the incoming line item now so we get its ID.
  commerce_line_item_save($line_item); 
  
  // Add it to the order's line item reference value.
  $order_wrapper->commerce_line_items[] = $line_item;
  
  commerce_order_save($order);
  
}

Thanks again for taking time out to help me with this!

rszrama’s picture

Same deal as above. You cannot manipulate field data directly like you are, so move the code to populate the line item below the wrapper and use $line_item_wrapper instead. Also, price amounts must be in cents, so use 200 instead of 2.00.

msmithcti’s picture

Ah, I think the problem is I haven't understood about this wrapper business. I've got it working now so hopefully it should be easy enough from here!

Thanks a lot for your help.

msmithcti’s picture

So I think I may have spoken too soon! I've hit another problem that I've spent the weekend trying to figure out: If the user adds gift wrapping, but then goes back and selects 'No' the line item needs to be deleted and removed from the order. This should be fairly simple looking at how Commerce Shipping does it with the function commerce_shipping_line_item_delete(). I modified the function like so:

// Remove the line item from the line item reference field.
$order = commerce_entity_reference_delete('commerce_order', $order, 'commerce_line_items', $giftwrap_line_item_id);
// Delete the actual line item.
commerce_line_item_delete($giftwrap_line_item_id);
//save the order
commerce_order_save($order);

When testing this I'm getting the error:
EntityMalformedException: Missing bundle property on entity of type commerce_order. in entity_extract_ids() (line 7389 of \includes\common.inc).

rszrama’s picture

Where does your $order variable come from?

msmithcti’s picture

My bad - should have added the surrounding code for context:

function commerce_giftwrap_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
  
  //make sure gift wrap is only added once
  //set default
  $giftwrap_line_item_exists = FALSE;
  
  foreach($order->commerce_line_items['und'] as $item) {
    $line_item_id = $item['line_item_id'];
    $line_item = commerce_line_item_load($line_item_id);
    if($line_item->type == 'giftwrap'){
      $giftwrap_line_item_exists = TRUE;
      $giftwrap_line_item_id = $line_item_id;
    }
  }
  
  $giftwrap_decision = $form_state['values']['commerce_giftwrap']['commerce_giftwrap_decision'];
  
  if(!$giftwrap_line_item_exists && $giftwrap_decision){
    
    //code for creating the line item
    
  } elseif (!$giftwrap_decision) { //giftwrap option selected is 'No'
    // Remove the line item from the line item reference field.
    $order = commerce_entity_reference_delete('commerce_order', $order, 'commerce_line_items', $giftwrap_line_item_id);
    // Delete the actual line item.
    commerce_line_item_delete($giftwrap_line_item_id);
    //save the order
    commerce_order_save($order);
  }
}

Hopefully that makes more sense!

rszrama’s picture

If you dump out your order, does it have $order->type == 'commerce_order'?

msmithcti’s picture

So I've dumped $order both at the _form and _form_submit stages (I assume there's no way of seeing dpm at _form_submit?) and the order seems as it should be including the type being set correctly.

I've also gone through the process using xdebug to keep an eye on $order. Everything seems fine until it get to commerce_entity_reference_delete() where $order (unsurprisingly) disappears.

Not sure if this offers a clue but if I do search for 'commerce_shipping_line_item_delete' (the function I nicked the code from) in the whole of the shipping module it doesn't seem to be called anywhere. It seems strange that it would just be redundant?

rszrama’s picture

It's called from a Rule if I remember right. Also, you shouldn't be passing the first parameter 'commerce_order' to the function commerce_entity_reference_delete(). You should check that functions comments again to send the right parameters.

msmithcti’s picture

There seems to be a two different ways this is documented, in commerce.module the parameters are described as follows:

/**
 * Deletes a reference to another entity from an entity with a reference field.
 *
 * @param $entity
 *   The entity that contains the reference field.
 * @param $field_name
 *   The name of the entity reference field.
 * @param $col_name
 *   The name of the column in the field's schema containing the referenced
 *   entity's ID.
 * @param $ref_entity_id
 *   The ID of the entity to delete from the reference field.
 */

On DrupalContrib it seems to be documented in the same way the shipping module uses:

//Shipping module way
$order = commerce_entity_reference_delete('commerce_order', $order, 'commerce_line_items', $line_item_id);

Anyway, I've tried it the way documented in commerce.module:

$order = commerce_entity_reference_delete($order, 'commerce_line_items', 'commerce_line_items_line_item_id', $giftwrap_line_item_id);

This is resulting in the same error message. I must be missing something glaringly obvious, after all, how hard can it be to pass four arguments into a function!

rszrama’s picture

DrupalContrib is obviously out of date when compared w/ the current code that you've linked above. I also don't see what code you're looking at in Commerce Shipping, so I'm going to assume you're looking at 1.x which may have just been implementing it incorrectly.

In fact, I think I followed your red herring from earlier. There's no way commerce_entity_reference_delete() can be the problem because it doesn't even use an entity metadata wrapper any more. I'd look elsewhere for your bug. And look to Shipping 2.x to see how it's deleting line items, not 1.x.

msmithcti’s picture

I'm still not sure where the bug was in the end but I looked at how 2.x removed line items and got it working right away by adapting that code.

I've made a sandbox project here that I'm going to eventually try and develop into a full project. For now at least it's out there and may be some help to someone.

Once again, thanks for all your help, it's really appreciated.

rszrama’s picture

Excellent! Gift wrapping comes up a lot, so I'm sure once you put the code out there you'll get some good feedback and contributions. : )

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.