Like this:

Color
   Red

Size
   Medium

Price
   $ 49,00

Add to cart

I tried:

  1. changing the field order in the product type and product variation type settings
  2. overriding commerce-product.html.twig
  3. adding some jQuery / Javascript code
  4. positioning the price div using CSS
  5. using hook_form_BASE_FORM_ID_alter() to alter the form

Solution #1 did not work out because the variation fields (color and size in this example) and the add-to-cart-button appear to be packed together as "Variations" in the product type settings.

Solution #2 has the same issue. The variation fields and add-to-cart-button are packed together in "product.variations" as one placeholder. I am not able to decompose it into seperate fields.

For solution #3 i tried moving the price div to the right location using jQuery, like this:

$("article .field--type-commerce-price").insertBefore(".commerce-order-item-add-to-cart-form .form-actions");

At first glance this works. However when the user starts changing variation fields the containing form gets removed along with the price. Commerce regenerates this form in order to update all the variation fields.

So far #4 gave the best results. I posted it on https://drupal.stackexchange.com/questions/248004/commerce-2-display-pri... but it is hacky. Using a top margin i created some space above the add-to-cart-button. Then i position the price div into that space. The CSS-code looks like this:

.commerce-order-item-add-to-cart-form .form-actions {
   margin-top: 4em;
}
article .field--type-commerce-price {
   position: absolute;
   bottom: 2.5em;
}

For solution #5 i was able to insert custom text before the add-to-cart-button which looks promising:

function HOOK_form_commerce_order_item_add_to_cart_form_alter(&$form, FormStateInterface $form_state, $form_id) {
   $form['test'] = array(
      '#markup' => 'Here is where the price should go.',
      '#weight' => 99
   );
}

But then i cannot figure out how to obtain the actual price from there. Then i found a D7 solution at:
https://drupal.stackexchange.com/questions/105946/drupal-commerce-add-to...
It looks like this:

function mytheme_form_commerce_cart_add_to_cart_form_alter(&$form, &$form_state, $form_id) {
  $product = $form_state['default_product'];
  $view_mode = $form_state['context']['view_mode'];
  $form['price'] = field_view_field('commerce_product', $product, 'commerce_price', $view_mode);
}

But i am not able to produce a D8 equivalent for this, in particular the last line.

function mytheme_form_commerce_order_item_add_to_cart_form_alter(&$form, FormStateInterface $form_state, $form_id) {
   $product = $form_state->get('product'));
   $view_mode = $form_state->get('form_display')->getMode();
   // form['price'] = $product->get(???)
}

This is silly. I'm struggling for weeks now to get this working but I am getting nowhere. Does anyone have a better idea?

Comments

rjb created an issue. See original summary.

ishani.addweb’s picture

I hope below code helps you to find product price in D8:

/*
 * Implements hook_form_alter().
 * Alter the product form.
 */
function mytheme_form_alter(&$form, $form_state, $form_id) {
  $arraydata = [];
  if(strpos($form_id, "commerce_order_item_add_to_cart_form") !== false) {
    $arraydata = explode('commerce_order_item_add_to_cart_form_commerce_product_', $form_id);
    $product_id = $arraydata[1];
    $product = \Drupal\commerce_product\Entity\Product::load($product_id);
    $variations = $product->getVariations();
    $amount = '';
    $final_price = '';
    foreach ($variations as $key => $value) {
      $amount = $value->getPrice();
    }
    $final_price = $amount->getNumber();
  }
}
Per’s picture

Hi,
Any new ideas for a twig or any other easy solution for this problem, spend the last few days try to find something on google

Thanks

Per’s picture

Priority: Normal » Major
flamesquirrel’s picture

In the commerce-product.html.twig template you can do something like this:

<article{{ attributes }}>
  {{ product.title }}
  {{ product.body }}
  {{ product.variation_price }}
  {{ product.variations }} //show add to cart button
</article>
hockey2112’s picture

Why should we have to use a twig, when there is a perfectly fine Drupal-based "Manage Display" tab? I just don't understand why we cannot position the price with the Drupal-based tools at our disposal without having to resort to a twig file. Any ideas on how we can do so? Blows my mind that I can't simply and easily control where my price is shown through the website's interface.

I don't mean to sound entitled, but I am just sort of shocked that this is a brick wall I am running into.

lamp5’s picture

You can create pseudo field to make it working.

/**
 * Implements hook_entity_extra_field_info().
 */
function custommodule_entity_extra_field_info(){
  $extra = [];

  $extra['commerce_product']['product']['display']['variation_price'] = [
    'label' => t('Variation price'),
    'visible' => FALSE,
    'weight' => 0,
  ];

  return $extra;
}

and clear cache

bisonbleu’s picture

Version: 8.x-2.0 » 8.x-2.13
Component: Developer experience » Product
Category: Feature request » Bug report

I think this is a genuine bug, the feature request here makes no sense to me. Here's how to reproduce this bug. I'm using the Bartik theme in a Drupal 8.7.4 + Commerce 2.13.

  • In the Manage Display tab of a product variation type (PvT), when Image is above Price (conventional layout), the Add to cart button appears above the price on the product page (unexpected UX).
  • But when Image is below Price in the PvT Manage display tab (unconventional layout), the Add to cart button suddenly appears below the price (expected UX).

Seems like the Add to cart formatter of the Product type is oddly flipping the order of the price and button without reason. As this is totally unexpected and cannot be fixed in the UI, I think it is fair to change this issue to a bug.

Perhaps Major is overkill so feel free to set back to Normal.

ec-adam’s picture

This was a tricky one for me without the simple Twig product template solution in #5—which I could not use because I'm using layout builder for my product type displays.

Ended up using a preprocess solution, on a field in my case, but could be done with anything as long as you can access the product you need the price of.

/**
 * Implements hook_preprocess_field.
 */
function my_theme_preprocess_field(&$variables, $hook) {
    if ($variables['field_name'] == 'product_id') {
  	$product = $variables['element']['#object'];
  	if ($variables['element']['view_mode'] == 'full') {
  	    $variables['calcPrice'] = $product->variations->entity->getPrice()->getNumber();
        }
    }
}

I'm then taking that calcPrice variable and using it a custom template for the product_id field that I have set below my product title in layout builder:

<div class="field-price">
    $<span class="product-price"{{- (calcPrice)|number_format(2, '.', ',') -}}</span>
</div>

NOTE: This will print the price of the loaded variation of the product, but will not update when variant options are changed. To do that, use some jQuery to find the calculated price in the old place on the product (which I'm hiding with css) and update the price in the product_id field. Some simple jQuery math can also find the quantity input and multiple the calc-price times the quantity to show a user what they will actually be charged (excluding shipping, taxes, promotions, etc).

$(document).ajaxComplete(function(){
    var calcPrice = $('.block-system-main-block .commerce-price').text();
    $('.calc-price').text(calcPrice);
});

And yes, could have skipped the preprocess and Twig and just used jQuery to copy the calculated commerce-price and shove it where I want, but I felt this solution was more thorough and bullet proof in case of JS errors or something.

CaptainPinkey’s picture

The commerce docs state to extend the original AddToCartForm for advanced modifications.

That has the advance of getting direct access to the product variation in there.


<?php


namespace Drupal\mymodule\Form;


use Drupal\commerce\Context;
use Drupal\Core\Form\FormStateInterface;

class AddToCartForm extends \Drupal\commerce_cart\Form\AddToCartForm {

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form = parent::buildForm($form, $form_state);

    $entity = $this->buildEntity($form, $form_state);

    $form['price'] = [
      '#type' => 'inline_template',
      '#template' => '{{ price|commerce_price_format }}',
      '#context' => [
        'price' => $entity->getUnitPrice(),
      ],
    ];

    return $form;
  }

}

This places the price field directly over the add to cart button.

Edit:

It is necessary to call he buildEntity by hand method and use the returned object instead the instance once, because the instance once references to the default product variation instead of the currently selected one!

lalop’s picture

I get this working by doing

function mytheme_form_commerce_order_item_add_to_cart_form_alter(array &$form, FormStateInterface $form_state) {
  $form_data = $form_state->getStorage();
  $selectedVariation = ProductVariation::load($form_data['selected_variation']);
  $form['footer']['price'] = $selectedVariation->price->view();
}
b72077’s picture

I solved my similar issue with the price or other variation fields appearing below the add to cart button.

I changed the file: commerce-product.html.twig (override in your theme) from the following:

<article{{ attributes }}>
  {{- product|without('variation_attributes') -}}
</article>

to the following:

<article{{ attributes }}>
  {{- product|without('variations','variation_attributes') -}}
  {{ product.variations }}
</article>
apaderno’s picture

Version: 8.x-2.13 » 8.x-2.x-dev
Issue tags: -Product display, -product variation, -price field
SajithAthukorala’s picture

None of these solutions are worked in my case . Anyone found better solution ? We can easily move the Price on top of the form , but not before the Add to cart button .

Kojo Unsui’s picture

Priority: Major » Normal

I also believe the default UX (with the price above variations, or below add to cart), is a mess.

#11 does not work if you don't have at least 2 product variations, then $form_data['selected_variation'] returns undefined.

Here's a heavier solution, based on #11 and this answer on StackExchange. So far it seems to work in my config. Maybe it's too much for such a little trick but I don't have much more time to dig more into that, or look for a clean patch...
Comments welcome to improve / slim this :

/**
 * Implements hook_form_FORM_ID_alter.
 */
function my_theme_form_commerce_order_item_add_to_cart_form_alter(
  array &$form,
  FormStateInterface $form_state
) {
  $form_data = $form_state->getStorage();

  if ( !empty($form_data['selected_variation']) ) {
    $selectedVariation = ProductVariation::load($form_data['selected_variation']);
    $price = $selectedVariation->price->view();
  }
  else {
    $parameter = \Drupal::routeMatch()->getParameter('commerce_product');
    $product = \Drupal\commerce_product\Entity\Product::load( (int)$parameter->id() );

    $entity_manager = \Drupal::entityTypeManager();
    $product_variation = $entity_manager->getStorage(
      'commerce_product_variation'
    )->load((int) $product->getVariationIds()[0]);

    $price = $product_variation->get('price')->view();
  }
  $form['footer']['price'] = $price;
}
ñull’s picture

Apparently the thread responders agree that this is a bug. I agree with them because imho the price should be always close (below) the fields that can change it, the attribute fields. The idea is that you have cause and result in the right order. Presently the price is under the title. When scrolled down people won't even see the price at all when there are many attribute fields.

Since this is a bug of Commerce, none of the above solutions work for me, because they either require custom module or a custom theme. None of these fix the issue, they work around it. The change should be in Commerce itself. What is the error exactly? How do we fix it with a Commerce patch? And why is this issue 3 years old? I would say this is a mayor issue because it causes all shop interfaces to fail. It is even critical because it can cause sales to go down.

lunk rat’s picture

Title: Move price before/above add-to-cart button » No control over position of price on rendered product
Priority: Normal » Major

The outrage and frustration expressed here is totally justified in my opinion. For example, #6 by @hockey2112

Blows my mind that I can't simply and easily control where my price is shown through the website's interface.
I don't mean to sound entitled, but I am just sort of shocked that this is a brick wall I am running into.

Commerce has such a powerful model for customizing everything through the Drupal UI. Yet in this case we can't consistently control where the price appears on the product page. The price! Probably the most important piece of data on a product page in an online store. Think about that.

I agree with @ñull in #16:

I would say this is a [major] issue because it causes all shop interfaces to fail. It is even critical because it can cause sales to go down.

Changing priority to Major. Commerce module is an amazing gift to us all; let's not allow stuff like this to hinder its adoption.

nattyweb’s picture

This has been causing me a major headache. Now solved with #5 by flamesquirrel. Thank you so much - exactly what I needed.

tonytheferg’s picture

Commerce has such a powerful model for customizing everything through the Drupal UI. Yet in this case we can't consistently control where the price appears on the product page. The price! Probably the most important piece of data on a product page in an online store. Think about that.

I agree. The price field, edit quantity widget and the add to cart button should be separate fields on one display so that they can be adjustable against one another. As it is right now you have to chase around 3 displays/display forms to try and figure out a workable solution.

Also I can confirm #8. At /admin/commerce/config/product-variation-types/MY_VARIATION _TYPE/edit/display, if no fields are above price, the price prints above the entire add to cart form. if any field is above price, the price prints below the entire add to cart form,.

Chris Matthews’s picture

Yikes, I spent a long time trying to figure out what in the world I was doing wrong in the UI before I found this issue. All I wanted to do was move the product variation price field being injected into the rendered product directly above the Add to cart form for a better customer UX. By chance, has anyone pinged any of the Centarro folks on Slack re: this issue to see if it can be looked into further?

zenimagine’s picture

https://drupal.stackexchange.com/questions/303452/how-to-organize-the-di...

It's amazing that there is no solution in the Drupal interface, without using custom code.

This problem is more than 3 years old :-(

It's a shame to have a powerful Commerce module that has this basic problem.

Is there a cotrib module that corrects this problem?

tonytheferg’s picture

@Chris Matthews, I just pinged Ryan on this regarding desired improvements for 3.x

shaunole’s picture

Following up on @Kojo Unsui, I switched out the static calls to use service injection and to pull from a specific view mode (in my example, I have created a custom view mode: Add to Cart Form - machine_id: add_to_cart_form). I've added support for the 'Price' and 'List Price' fields, but theoretically, you could add any fields that exist in the product variations by retrieving their render array via ->view() on the field and saving that value to the $form array. It would be ideal if the visible fields in the variation configuration could easily be queried and inserted into the form, respecting their weights, label, format, etc. settings.

If you want to use the default view mode, you'll need to remove the string 'add_to_cart_form' from the ->view() declarations.

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * Adds a price field to the add to cart form.
 *
 * This function adds a Price field to the Add to Cart form as suggested in the
 * Drupal Issue linked below.
 *
 * @link https://www.drupal.org/project/commerce/issues/2928139#comment-13555651 Drupal Issue 2928139, Comment 15
 *
 * @param array $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *
 * @return void
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function my_module_form_commerce_order_item_add_to_cart_form_alter(
  array &$form,
  FormStateInterface $form_state
) {
  $form_data = $form_state->getStorage();

  /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
  $entity_type_manager = Drupal::service('entity_type.manager');

  /** @var \Drupal\commerce_product\ProductVariationStorageInterface $product_variation_storage */
  $product_variation_storage
    = $entity_type_manager
      ->getStorage('commerce_product_variation');

  if ( !empty($form_data['selected_variation']) ) {
    /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $selected_variation */
    $selected_variation
      = $product_variation_storage
        ->load($form_data['selected_variation']);

    $list_price = $selected_variation->get('list_price')->view('add_to_cart_form');
    $price = $selected_variation->get('price')->view('add_to_cart_form');
  }
  else {
    /** @var \Drupal\Core\Routing\RouteMatchInterface $route_match */
    $route_match = Drupal::service('current_route_match');
    $parameter = $route_match->getParameter('commerce_product');

    $product_storage
      = $entity_type_manager
        ->getStorage('commerce_product');

    /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
    $product = $product_storage->load( (int) $parameter->id() );

    /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $product_variation */
    $product_variation
      = $product_variation_storage->load($product->getVariationIds()[0]);

    $list_price = $product_variation->get('list_price')->view('add_to_cart_form');
    $price = $product_variation->get('price')->view('add_to_cart_form');
  }

  $form['list_price'] = $list_price;
  $form['price'] = $price;
}

The above should function as a work-around until this issue is addressed officially.

leeksoup’s picture

I have been trying to figure out the solution to this same problem and found this post via Drupal Stack Exchange. Thanks for OP for documenting.

Surprised this is still open after 4 years. I see several solutions ... is one of these the recommended solution more than others?

leeksoup’s picture

The easiest fix for me was to add an image field to the product and upload a tiny 5x5 pixel transparent png. Place it just above "variations" field. Thank you to all.

slawomir’s picture

Ufff, spent the last three hours figuring out what's going on... and yeah, experiencing the same issue. My only field in the Product Variation Type seems to always float below the item that precedes it.

I intend to override the displays with Entity View Attachment and a custom View. Keeping an eye out for a core fix, just in case some day I decide not to use EVA.

hockey2112’s picture

@slawomir if you are going to use Views to display the price, make sure that the Views-produced price updates when you select attributes that would change the product price. That was an issue I ran into when using a Viewfield to display the price.

slawomir’s picture

@hockey2112 Thanks! Curious if you solved the updating problem - what the issue was... did you resolve, or find a way around it?

niki v’s picture

It works as designed: the intermingling comes from having product-type display fields numbered 0,1,2,3... and also having product-variation or attribute fields numbered 0,1,2,3.... Number each field in each display as you want them to appear in the product page display. As the variation fields containing price, stock, weight, etc are injected into the product-type display, you use "show row weights" and number them in the product-variation display as you want them in the product-type display.

I put a simple example here:

Product display
image - 0
body - 1

Product variation
price - 2
stock level - 3

Product display
add to cart form - 4
categories - 5
tags - 6