7

I am working on a one page checkout and I'm trying to get the cart review pane updated with a shipping price while user selects a shipping option.

I have attached #ajax information to the shipping options, to update the cart review form when a shipping option is saved.

Modifying the commerce shipping ajax callback on checkout

if($form_id == 'commerce_checkout_form_checkout') {
  $form['commerce_shipping']['shipping_service']['#ajax']['callback'] = 'mshop_shipping_pane_service_details_refresh';
}

Ajax callback

function mshop_shipping_pane_service_details_refresh($form, $form_state) {
  // Update shipping form
  $commands[] = ajax_command_replace('#' . $form['commerce_shipping']['service_details']['#id'], render($form['commerce_shipping']['service_details']));

  // Update checkout cart review
  list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
  $commands[] = ajax_command_replace('.view-mshop-cart-shopping-cart-summary', commerce_embed_view($view_id, $display_id, array(arg(1))));

  return array('#type' => 'ajax', '#commands' => $commands);
}

The issue : The AJAX is working, the updated cart review view is updated, but the shipping option is not applied.

My guess is that, as the form is only updated and not submitted, the order is not saved. So as the cart review's view is based on the order, and the shipping option is not saved yet in the order, it does not display the right shipping information in the cart review.

Any workaround?

EDIT:

Thanks to milkovsky comment I have been able to save the order with the shipping rate applied to the order. Then the cart review view displays correctly with the updated cart rate applied.

/**
 * Ajax callback: Returns the shipping details form elements that match the
 * currently selected shipping service.
 */
function mshop_shipping_pane_service_details_refresh($form, &$form_state, $checkout_pane, $order) {

  // Get order from form
  list($order, $checkout_pane) = $form_state['build_info']['args'];

  // Load up to date order
  $order = commerce_order_load($order->order_id);

  // Get selected 
  $service_name = $form['commerce_shipping']['shipping_service']['#default_value'];

  commerce_shipping_service_rate_order($service_name, $order);

  // Delete any existing shipping line items from the order.
  commerce_shipping_delete_shipping_line_items($order, TRUE);

  // Extract the unit price from the calculated rate.
  $rate_line_item = $order->shipping_rates[$service_name];
  $rate_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $rate_line_item);
  $unit_price = $rate_line_item_wrapper->commerce_unit_price->value();

  // Create a new shipping line item with the calculated rate from the form.
  $line_item = commerce_shipping_line_item_new($service_name, $unit_price, $order->order_id, $rate_line_item->data, $rate_line_item->type);

  // Save and add the line item to the order.
  $new_line_item = commerce_shipping_add_shipping_line_item($line_item, $order, TRUE);
  commerce_order_save($order);


  // Update shipping form
  $commands[] = ajax_command_replace('#' . $form['commerce_shipping']['service_details']['#id'], render($form['commerce_shipping']['service_details']));


  // Update checkout cart review
  list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
  $commands[] = ajax_command_replace('.view-mshop-cart-shopping-cart-summary', commerce_embed_view($view_id, $display_id, array($order->order_id)));

  $commands[] = ajax_command_after('.main form', theme('status_messages'));

  // $commands[] = ajax_command_after('.'.$form['#attributes']['class'][1].':eq(0)',theme('status_messages'));
  return array('#type' => 'ajax', '#commands' => $commands);
}
3
  • Check if you don't override existing callback using $form['commerce_shipping']['shipping_service']['#ajax']['callback'] = ...
    – milkovsky
    Jan 23, 2015 at 11:46
  • I can't get it to work - is the code if($form_id == 'commerce_checkout_form_checkout') { $form['commerce_shipping']['shipping_service']['#ajax']['callback'] = 'mshop_shipping_pane_service_details_refresh'; } implementing hook_form_alter or some special commerce hook?
    – Dalmaz
    Feb 9, 2015 at 20:01
  • Yes i'm implementing a basic hook_form_alter(). On my side it works on the latest version of Drupal Commerce. Is your callback executed? Feb 13, 2015 at 6:11

2 Answers 2

4
+50

You are right. Shipping rates are applied only after you submit the form (Click "Next").

On submit all your shipping rules(or flat rates) applied and order is saved with new line items (Shipping is implemented via custom line item type).

The calculation is executed in the commerce_shipping_pane_checkout_form_submit()

So you can try to execute this function (or some parts of it) in the ajax callback before the cart is updated.

Here is an example how to apply shipping rates if you are using using Flat Rate Module: Adding shipping to order programmatically via module.

3
  • 1
    Thanks, your second solution works like a charm. I preferred your first solution that did not require to load and save the order, in the middle of the checkout process, but did not succeed to implement correctly commerce_shipping_pane_checkout_form_submit() as it requires 2 variables I can't access in this function. I'll update my post with the solution. Jan 26, 2015 at 16:46
  • 1
    Epic answer and edit! Thx
    – Michiel
    Jul 9, 2015 at 11:27
  • So, is there a way to force this through the UI?
    – nizz0k
    Mar 2, 2017 at 6:26
2

Firstly, thanks for posting this solution!

I was getting this 500 error from the AJAX response: "EntityMetadataWrapperException: Missing data values. in EntityMetadataWrapper->value().

I wanted to offer free shipping on orders over $100 so enabled the commerce_order_total module (https://www.drupal.org/sandbox/joshmiller/2718047) which highlighted the error when selecting a shipping method.

After some debugging, I found it was only occurring when the commerce_order_save() line was included.

Since I have set my checkout Panes to all appear on the same page and my Review Pane is disabled, I wrapped this line in an IF statement:

//get checkout panes
$panes = commerce_checkout_panes();

//check if the Review pane is enabled
if ($panes['checkout_review']['enabled']){
  //review is enabled
  commerce_order_save($order);
}

Comments/improvements are encouraged!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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