1

In a checkout process, I need to collect additional data depending on the quantity in the cart.

What is it about ?

  • Customers book travels
  • For the sake of argument, let’s say 2 couples of lovers book a week in Venice : they will add 4 "Venice trip" to the cart
  • During checkout, I collect usual customer data, plus additional information (name, passport, birthday…) for each other participant in a dynamic custom pane.

My current work :

  1. Set up a custom pane with Commerce Fieldgroup Panes, disabled by default
  2. Add a rule with Commerce Rules Extra, which enables the pane if qty > 1 for specific product type
  3. Then with MYMODULE_form_FORM_ID_alter, I tried 2 different ways to add the relevant fields :

First one: duplicate the whole custom pane into the checkout page :

function vts_commerce_form_commerce_checkout_form_checkout_alter(&$form, &$form_state, $form_id) {

  $order = $form_state['build_info']['args'][0];
  //load line item
  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);
  //get quantity
  $qty = round($line_item->quantity);

  //duplicate the pane for each participant (minus the customer user) 
  for($n=1; $n<$qty; $n++) {
    $form['commerce_fieldgroup_pane__group_pax_sup_'.$n] = array_merge(array(), $form['commerce_fieldgroup_pane__group_pax_sup']);
  }
}

The pane is duplicated fine, but the fields of duplicated panes are not saved, nor displayed at next step. Maybe because I have to tell the system these ARE checkout panes, + subsequent callbacks (as incheckout_pane_example module) ?

If so, I don't find how to declare the added panes are checkout ones inside that loop.


Second one : duplicate each field of the custom pane inside the checkout pane itself :

function vts_commerce_form_commerce_checkout_form_checkout_alter(&$form, &$form_state, $form_id) {

  $order = $form_state['build_info']['args'][0];
  //load line item
  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);
  //get quantity
  $qty = round($line_item->quantity);

  //duplicate the pane for each participant (minus the customer user) 
  for($n = 1; $n<$qty; $n++) {

    foreach ($form['commerce_fieldgroup_pane__group_pax_sup'] as $key => $value) {

      //searching fields into the fieldgroup array
      if(substr($key, 0, 5) == 'field') {

        //we duplicate the field
        $form['commerce_fieldgroup_pane__group_pax_sup'][$key.'_'.$n] = array_merge(array(), $form['commerce_fieldgroup_pane__group_pax_sup'][$key]);
        //rename field to have it unique
        $form['commerce_fieldgroup_pane__group_pax_sup'][$key.'_'.$n]['und'][0]['#field_name'] = $form['commerce_fieldgroup_pane__group_pax_sup'][$key.'_'.$n]['und'][0]['#field_name'].'_'.$n;
        //add it to the fieldgroup : NOT SURE WHICH IS THE RIGHT ONE OR IF I MUST LEAVE ALL
        $group_pax_sup = $form['commerce_fieldgroup_pane__group_pax_sup']['#groups']['group_pax_sup'];
        $group_pax_sup->children[] = $key.'_'.$n;  
        $form['commerce_fieldgroup_pane__group_pax_sup']['#group_children'][$key.'_'.$n] = 'group_pax_sup' ;
      }
    }
  }

This is adding the fields correctly, but again nothing is saved or displayed in next steps. Here it seems that I don't manage to add the fields to the fieldgroup correctly?

How could I achieve this ? Thank you so much!

  • IIUC the module you're using creates panes based on field groups attached to the order entity. That data model doesn't really work for you because different fields will be needed for different orders. I think custom line item types would suit you better: see Commerce customizable products and hook_commerce_line_item_type_info. – Andy Aug 1 '14 at 16:11
  • Andy, tks. I already use custom line item for that project for other purpose. But I really need to collect this data during checkout, not in add to cart, because it's a lot & it would discourage people ;) . then, if it works perfect if I fix the qty of fields, why couldn't I alter the form on the fly as anywhere else ? tks again. – Kojo Aug 1 '14 at 16:20
  • I've not used Commerce in a while so I'm not ultra-confident (: But I think you should focus on the data model first, and the UI second. So you could use custom line items, but hide the forms on add-to-cart, and add them back at checkout with a custom pane. I don't know if there's already any contrib that could (help) do it. – Andy Aug 1 '14 at 16:23
  • Or at least if you do want to attach the data to the order, I don't think Commerce Fieldgroup Panes is the way to go because it uses the Field API. checkout_pane_example has an example of adding a pane and saving data attached to the order. – Andy Aug 1 '14 at 16:56
  • Well, sorry Andy after fighting a while with this, I believe c.l.items were made to collect info in the add to cart form, while order fields, grouped in fieldgroups & wrapped in a checkout pane, were made to collect info during checkout? And I would have to write about the same gymnastic :) So even thinking data model, I don't follow your analysis very well ? Of course I might be wrong and would be very happy to see a working answer or a serious pist to go further ;-)) – Kojo Aug 1 '14 at 16:58
1
+50

It occurred to me there may be a nice way to do this with contrib so I'll put it down, but it does require different pieces playing together nicely, which doesn't always happen (:

I see twothree clean approaches.

Using the Field API and contrib

If you want to use the Field API to create/update/delete fields, then I still think you should use custom line item products. I'm a little surprised it doesn't have the option of displaying the form at checkout built-in. Maybe a module will be built to add that, it should be relatively simple. But in the meantime this might get you there with existing contrib:

  1. Install editablefields and Commerce Views Pane.
  2. Create a custom line item type for the customisable product. Add the necessary fields but don't configure them to show up on the add-to-cart form. Also ensure the line items can't be combined.
  3. Create a line item view that accepts an order ID as an argument, and add the fields you'd like to edit (and give them the editable formatter). Add a filter to ensure it only displays line items of the correct bundle. You can test the view in preview by creating a new order with a few customisable products, find the order ID (eg. from the checkout page), and pass that order ID to the Views preview. It should show you a row for each customisable line item. I wouldn't try to get the Views pane working until it works in preview.
  4. Add that view as a checkout pane.
  5. Make a circle of salt around the server and whisper a prayer.

Using the Field API and combined line items (involves code)

If you'd like to have a single line item have multiple customisations, then you need to set up the fields to allow that.

  1. Create a field collection with the fields you'd like.
  2. Add a multi-value field collection of that type to the appropriate line item type (ensure it won't show up on the add-to-cart form).
  3. Create your own checkout pane that displays the field edit form. You'd read the quantity from the line item and display just that many fields (as it's a multi-value field it'll save ok however many there are).

Creating a custom pane and storing data serialised with the order (involves code)

This has the disadvantages that you can't use the Field API to set up, modify, or read the fields (so no Views). checkout_pane_example.module shows how to do this, adding a nickname and second email field and storing the data with the order.

Ugly method

I think you could probably do it with Commerce Fieldgroup Panes if you create a field collection with the fields you want, and then add it as a multi-value field to the order entity. You'd need some code to ensure only the correct number of field collections are shown (to match the number of line items that need customising). This method feels dirty and has two concrete drawbacks I can think of:

  1. As you add more customisable products the order entity will get more and more fields.
  2. You can't associate the customisations with a particular line item (what if I want a want one message on my red sweater, and a different one on my blue sweater - which message goes with which line item?).
|improve this answer|||||
  • Really appreciate your efforts to help! Tried FAPI method. Could you explain how I will tell the view to display 2 fields 'name' & 2 '1rst name' if 2 participants(pax), 3 fields of each if 3 pax ? Because THIS is the main pb, whatever the solution I implement. same remark with checkoutpane example module, this was in the question from the beginning : I don't know how to make a loop there with the quantity... THanks Andy ! – Kojo Aug 4 '14 at 17:44
  • @Kojo Assuming you've added the fields to the line item and not the order entity it should work automatically. The View is passed the order ID and lists all the associated line items. (Oh yeah, so you should probably also put a filter in the view to ensure it's only the right kind of line items - I'll update the answer.) Before trying to get it working as a checkout pane, test it in Views preview. – Andy Aug 4 '14 at 18:04
  • Andy, filter line items types is not the pb, thanks. What you described works fine for any field added to line items. the main pb I'm facing: if the customer orders 4 products A or/and 3 products B, I'll need to display following editable fields: for product A, name2/firstname2/name3/firstname3/name4/firstname4 ; for product B, name2/firstname2/name3/firstname3. I wish I don't have to add in the UI 15 fields name + 15 f. fname, and so on, for each line item ! And if so, I don't see how I would filter in the view to display only the necessary ones??? – Kojo Aug 5 '14 at 8:51
  • So I need to define these fields only once (note that in case of line item fields, I'll have to add them once for each line item type...) and then make a loop to add x fields of each, depending on the qty ordered. Maybe in your FAPI solution, with a hook on the view to alter the view output and add editable fields on the fly ??? I'm desperate to explain further more, this is was I exposed the study case, I thought this would be clear enough? – Kojo Aug 5 '14 at 8:51
  • gonna try hook_views_default_views_alter(&$views) as per that answer – Kojo Aug 5 '14 at 8:58
0

I ended using Andy's 2nd solution, using the Field API and combined line items.

Create a field collection with the fields you'd like. Add a multi-value field collection of that type to the appropriate line item type (ensure it won't show up on the add-to-cart form). Create your own checkout pane that displays the field edit form.

As I fighted a while with some aspects of the API, especially with Field collection, here's my full checkout pane .module, maybe it will help somebody.

/**
 * Implements hook_commerce_checkout_pane_info().
 */
function vts_commerce_checkout_pane_info() {

  $panes['paxsup_pane'] = array(
    'title' => t('Other Participants'),
    'base' => 'vts_pane',
    'name' => t('Other Participants'),
    'page' => 'checkout',
    'weight' => -5,
  );

  return $panes;
}

/**
 * Implements hook_pane_checkout_form().
 */
function vts_pane_checkout_form($form, &$form_state, $checkout_pane, $order) { 

  //load the current line item.
  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);

  $pane_form = array();

  if ($checkout_pane['pane_id'] == 'paxsup_pane') {
    field_attach_form('commerce_line_item', $line_item, $pane_form, $form_state, NULL, array('field_name' => 'field_paxsup'));
  }
  return $pane_form;
}

/*
 * Change general GUI edit field cardinality range from 1->1 to 1->20
 */
function vts_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  // Increase the max cardinality to 20
  $range = drupal_map_assoc(range(1, 20));
  $new_options =  array(FIELD_CARDINALITY_UNLIMITED => t('Unlimited')) + $range;

  // Change the options
  $form['field']['cardinality']['#options'] = $new_options;
}

/*
 * Implements hook_form_FORM_ID_alter().
 */
function vts_form_commerce_checkout_form_checkout_alter(&$form, &$form_state, $form_id) {

  //Load line item to get pax number
  $line_item = commerce_line_item_load($form_state['build_info']['args'][0]->commerce_line_items['und'][0]['line_item_id']);
  $paxsup_nb = $line_item->quantity -1;
  if($paxsup_nb > 0){

    //Unset unnecessary items of paxsup field
    for($n=19; $n > $paxsup_nb-1; $n--){
      unset($form['paxsup_pane']['field_paxsup']['und'][$n]);
    }

    //Fill in paxsup with session values
    for($n=0; $n<$paxsup_nb; $n++){ 

      $form['paxsup_pane']['field_paxsup']['und'][$n]['field_paxsup_foo']['und']['#default_value'][0]            = isset($_SESSION['paxsup_foo_'.$n])? $_SESSION['paxsup_foo_'.$n]:'';
      $form['paxsup_pane']['field_paxsup']['und'][$n]['field_paxsup_bar']['und'][0]['value']['#default_value']        = isset($_SESSION['paxsup_bar_'.$n])? $_SESSION['paxsup_bar_'.$n]:'';
      $form['paxsup_pane']['field_paxsup']['und'][$n]['field_paxsup_date_of_birth']['und'][0]['#default_value']['value']  = isset($_SESSION['paxsup_date_of_birth_'.$n])? $_SESSION['paxsup_date_of_birth_'.$n]:'';

    }

  }
  else {
    unset($form['paxsup_pane']);
  }
}

/**
 * Implements hook_pane_checkout_form_validate().
 */
function vts_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {

  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);
  $paxsup_nb = $line_item->quantity -1;
  if($paxsup_nb > 0){

    for($n=0; $n<$paxsup_nb;$n++) {
      if(empty($form_state['values']['field_paxsup']['und'][$n]['field_paxsup_foo']['und'][0]['value'])
        || empty($form_state['values']['field_paxsup']['und'][$n]['field_paxsup_bar']['und'][0]['value']) 
        || empty($form_state['values']['field_paxsup']['und'][$n]['field_paxsup_date_of_birth']['und'][0]['value']) ) {

        return FALSE;
      }
    }
  }

  return TRUE;
}

/**
 * Implements hook_pane_checkout_form_submit().
 */
function vts_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {

  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);
  $paxsup_nb = $line_item->quantity -1;
  if($paxsup_nb > 0){

    //Set paxsup SESSION values
    for($n=0; $n<$paxsup_nb;$n++) { //
      $_SESSION['paxsup_foo_'.$n] = $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_foo']['und'][0]['value'];
      $_SESSION['paxsup_bar_'.$n] = $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_bar']['und'][0]['value']; 
      $_SESSION['paxsup_date_of_birth_'.$n] = $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_date_of_birth']['und'][0]['value']; 
    }

    // Add the paxsup values to the line_item fields 
    if (!empty($form_state['values']['field_paxsup'])) {

      if(0 == count($line_item->field_paxsup)){
        //CREATE A NEW PAXSUP SET
        $entity = entity_load_single('commerce_line_item', $line_item->line_item_id);
        $wrapper = entity_metadata_wrapper('commerce_line_item', $entity);
        $collections = array();        

        for($n=0; $n<$paxsup_nb;$n++) { //
          $fc = entity_create('field_collection_item',  array('field_name' => 'field_paxsup'));
          $fc->setHostEntity('commerce_line_item', $line_item, LANGUAGE_NONE, FALSE);

          $fc->field_paxsup_foo['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_foo']['und'][0]['value']); 
          $fc->field_paxsup_bar['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_bar']['und'][0]['value']); 
          $fc->field_paxsup_date_of_birth['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_date_of_birth']['und'][0]['value']); 

          $fc->save(TRUE);
          $collections[] = $fc;
        }
        //Set the field collection items via the wrapper.
        $wrapper->field_paxsup->set($collections);
        $wrapper->save();

      }
      else {
        //UPDATE FIELD COLLECTION
        for($n=0; $n<$paxsup_nb;$n++) { //

          $fc = entity_load_single('field_collection_item', $line_item->field_paxsup['und'][$n]['value'] );
          $fc->field_paxsup_foo['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_foo']['und'][0]['value']); 
          $fc->field_paxsup_bar['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_bar']['und'][0]['value']); 
          $fc->field_paxsup_date_of_birth['und'][0] = array('value'=> $form_state['values']['field_paxsup']['und'][$n]['field_paxsup_date_of_birth']['und'][0]['value']); 
          $fc->save(TRUE);

        }
      }
    }
  }
}

/**
 * Implements hook_pane_review().
 * 
 */
function vts_pane_review($form, $form_state, $checkout_pane, $order) {

  $line_item = commerce_line_item_load($order->commerce_line_items['und'][0]['line_item_id']);
  $paxsup_nb = $line_item->quantity -1;
  if (!empty($line_item->field_paxsup['und'][0])) {

    $view = '';
    for($n=0; $n<$paxsup_nb;$n++) {

      $paxsup = entity_load_single('field_collection_item', $line_item->field_paxsup['und'][$n]['value'] );

      $display = array('label' => 'hidden');
      $foo = field_view_field('field_collection_item', $paxsup, 'field_paxsup_foo', $display);
      $bar = field_view_field('field_collection_item', $paxsup, 'field_paxsup_bar', $display);
      $date = field_view_field('field_collection_item', $paxsup, 'field_paxsup_date_of_birth', $display);

      $view .= '<p>'.$foo[0]['#markup'] .' '. $bar[0]['#markup'] .' '. $prenom[0]['#markup'] .t(', né(e) le '). $date[0]['#markup'] .'</p>';
    }

    return $view;
  } 
}
|improve this answer|||||
  • @Andy, this is it. Surely it's possible to have a cleaner approach in some lines... but it works fine :) – Kojo Aug 28 '14 at 14:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.