How do you add a line item to an order (either with Rules or code) on the checkout page?

I am using Commerce Registration, which makes a product type of Registration (like Event Registration). Based on some field values on the Registration that the user puts in, I would like to evaluate that and add line items on the order (extra charges) with the updated total.

Comments

kevinquillen’s picture

Based on other comments, I found that I can just do this:

global $user;
  $order = commerce_cart_order_load($user->uid);
  $dorms = commerce_product_load(15);
  $line_item = commerce_line_item_new('product', $order->order_id);

  $line_item->quantity = $orderItem['quantity'];
  $line_item->line_item_label = 'Dormitory Charge (tournament name)';

  $line_item = commerce_product_line_item_new($dorms, 1);
  $line_item = commerce_cart_product_add($user->uid, $line_item);
  commerce_order_save($order);

Which does add the appropriate line item and total up correctly, however it gets added too many times, basically because I was testing inside of a form_alter. Is there an appropriate hook on the checkout page that will let me modify the order like this?

rszrama’s picture

Status: Active » Fixed

I'm not sure which checkout page or at what point in its processing you mean. If you're trying to do it when you're redirecting from the registration form to the checkout form, then add a submit handler to the registration form. If your'e trying to do it when one of the pages of the checkout form has been submitted, add a submit handler to the continue button of the checkout form.

kevinquillen’s picture

There is a form prior to the Checkout Pane for Entity Registration- it is here where I want to intercept and add a line item into the order based on certain values in this field, after they click 'Continue' which goes to Checkout.

If I can in fact add a submit handler here, other questions arise. Such as, if they are doing two registrations on one order, and edit one of the registrations, how can I delete the proper line item from the order? They would have 2 additional line items, go back and edit the registration, and should now have 1. Also, when adding the line item to the order, how can I adjust the label/name on the order so I can say what the line item is for? For example, I would like to say 'Room and Board Extra for (Silver Member)' - where silver member is the value selected on the Registration, and 'Room and Board Extra' is the generic label of the product in the backend.

rszrama’s picture

I'd assume you'd store the necessary information to maintain the association between the registration entities and line items, like keeping the ID in the line item's data array or adding a field to your line item type that functions as a registration reference. If you need custom displays of these line items, you might just consider making a custom line item type that functions as a product but has its own title callback that loads the related registration entity and returns the title accordingly. You can create a new product line item type using the same "base" value as the line item type definition in Product Reference, and then you can set a specific title callback in the callbacks array that will override the default product line item title callback.

kevinquillen’s picture

Gotcha.

What I wound up doing yesterday was adding a custom validation/submit handler into the Registration form before commerce_checkout submit handler is called, reviewing the cart contents, looping the registrations, checking the values and adding a new line item based on product id (to the item), and also stored this action in their $_SESSION. I did that because if they go back and remove it, I want to know that they did so I can decrement appropriately. Here is the code, if there is a better way of doing it.

Should/do I need to use $_SESSION?


function satb_registrations_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'commerce_checkout_form_registration') {
    array_unshift($form['buttons']['continue']['#validate'], 'satb_registrations_process_registration_checkout_validate');
    array_unshift($form['buttons']['continue']['#submit'], 'satb_registrations_process_registration_checkout_submit');
  }
}

function satb_registrations_process_registration_checkout_validate($form, &$form_state) {
  // @todo :: add specific data validation checks and check our business rules before checkout
}

function satb_registrations_process_registration_checkout_submit($form, &$form_state) {
  global $user;

  foreach($form_state['values']['registration_information'] as $key => $registrations) {
    if ($key != 'register_entities') {
      foreach ($registrations as $registration_key => $registration) {
        if (isset($registration['field_weekend_dormitories'][LANGUAGE_NONE][0]['value']) && !isset($_SESSION['registration_track']['dorms'][$registration_key])) {
          $order = commerce_cart_order_load($user->uid);
          satb_registrations_add_dormitory_line_item($order, $registration_key);
        } elseif (!isset($registration['field_weekend_dormitories'][LANGUAGE_NONE][0]['value']) && isset($_SESSION['registration_track']['dorms'][$registration_key])) {
          $order = commerce_cart_order_load($user->uid);
          $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
          satb_registrations_remove_dormitory_line_item($order, $order_wrapper, $registration_key);
        }
      }
    }
  }
}

function satb_registrations_add_dormitory_line_item($order, $registration_key) {
  global $user;

  $dorms = commerce_product_load(15);
  $dorms->title = 'Dormitory Charge';

  $line_item = commerce_line_item_new('product', $order->order_id);
  $line_item->quantity = 1;
  $line_item->line_item_label = 'Dormitory Charge';
  $line_item = commerce_product_line_item_new($dorms, 1);
  commerce_cart_product_add($user->uid, $line_item);

  $_SESSION['registration_track']['dorms'][$registration_key] = 'has dorms';
  commerce_order_save($order);
}

function satb_registrations_remove_dormitory_line_item($order, $order_wrapper, $registration_key) {
  foreach ($order_wrapper->commerce_line_items as $line_item_wrapper) {
    $line_item = $line_item_wrapper->value();
    $quantity = (int) $line_item->quantity;

    if ($line_item->line_item_label === 'dorms' && $quantity == 1) {
      commerce_line_item_delete($line_item->line_item_id);
      commerce_order_save($order);
      unset($_SESSION['registration_track']['dorms'][$registration_key]);
      drupal_set_message('Dormitory Charge removed from your order.', 'status', FALSE);
    } elseif ($line_item->line_item_label === 'dorms' && $quantity > 1) {
      $quantity--;
      $line_item->quantity = $quantity;
      commerce_line_item_save($line_item);
      commerce_order_save($order);
      unset($_SESSION['registration_track']['dorms'][$registration_key]);
      drupal_set_message('Dormitory Charge removed from your order.', 'status', FALSE);
    }
  }
}

I guess I could also load all line items each time they leave and comeback to the checkout page and compare specific line item quantities vs registration values, removing the need for $_SESSION.

rszrama’s picture

Storing in the session may do just fine, but typically I'd try to store any data related to the purchase of something in the line item itself. If you put the data there, then it's going to work across sessions and logout / login. Naturally if this works for you, then you can run with it, but I'd avoid the session when possible just because you don't know what could happen during the course of a checkout or administrator order creation.

kevinquillen’s picture

Yeah, I ditched that last night in favor of data in $order and tried to do the same with $line_item.

I was able to store things in $order->data fine, but doing the same for $line_item->data (an array) did not seem to save when passed to commerce_line_item_create(). Did I miss something or am I being overwritten? I did notice that I could not override the line item label with the above either, the cart and checkout showed the product title instead.

rszrama’s picture

It may be getting overwritten when sell price calculation happens, but I thought we were preserving data. Using the order data array should do just as fine, though.

kevinquillen’s picture

I had a second thought this morning. We don't need to track against each registration, the only thing that matters is a correct quantity based on a registration value. In this case, 1 qty = 1 night. The registration will have the night value on it, all we really need is the order to reflect additional amounts owed as a line item.

I scrapped it all, just grabbed the total summed input, wiped the particular line item if it existed, and add it back with the new value. Made it much easier, no tracking and loops etc needed.

Status: Fixed » Closed (fixed)

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