Magento – Magento 2 – Get Custom Options Price in Transactional Email

configurable-productcustom-optionsemailmagento2

I am trying to display on the new order transactional email the custom options for every product(in this case a configurable product), along with the price.

Custom Options are displayed by default, but the prices are not.

This is the method responsible for the custom options:

File:
/app/code/vendor/Email/Block/Order/Email/Items/DefaultItems.php

**
     * @return array
     */
    public function getItemOptions()
    {
        $result = [];
        if ($options = $this->getItem()->getOrderItem()->getProductOptions()) {
            if (isset($options['options'])) {
                $result = array_merge($result, $options['options']);
            }
            if (isset($options['additional_options'])) {
                $result = array_merge($result, $options['additional_options']);
            }
            if (isset($options['attributes_info'])) {
                $result = array_merge($result, $options['attributes_info']);
            }
        }

        return $result;
    }

And this is an example of the result:

'options' => 
    array (size=4)
      0 => 
        array (size=7)
          'label' => string 'Metal' (length=5)
          'value' => string '14ct Yellow Gold' (length=16)
          'print_value' => string '14ct Yellow Gold' (length=16)
          'option_id' => string '3027' (length=4)
          'option_type' => string 'radio' (length=5)
          'option_value' => string '18447' (length=5)
          'custom_view' => boolean false

As you can see there is no key for the price of the actual option;

I've tried to load the product model via productFactory injecting Magento\Catalog\Model\ProductFactory into DefaultItems.php with the following method:

public  function getProductModel($id)
    {
        $product = $this->productFactory->create()->load($id);
        return $product;
    }

and then calling the method on the template file

app/code/vendor/Email/view/frontend/templates/email/items/order/default.phtml

like this:

$_productCustomOption = $block->getProductModel($_item->getProductId())->getProductOptionsCollection()->getItemById('3027');

Where 3027 is the id of the custom options as seen before but, because this custom option has several values with different prices, the price always return null like this:

protected '_data' => 
    array (size=19)
      'option_id' => string '3027' (length=4)
      'product_id' => string '268' (length=3)
      'type' => string 'radio' (length=5)
      'is_require' => string '1' (length=1)
      'sku' => null
      'max_characters' => string '0' (length=1)
      'file_extension' => null
      'image_size_x' => string '0' (length=1)
      'image_size_y' => string '0' (length=1)
      'sort_order' => string '1' (length=1)
      'default_title' => string 'Metal' (length=5)
      'store_title' => null
      'title' => string 'Metal' (length=5)
      'default_price' => null
      'default_price_type' => null
      'store_price' => null
      'store_price_type' => null
      'price' => null
      'price_type' => null

While if I call a custom option with only one value the price it defined.

So how do I get the price for a custom option with several values?

Best Answer

After days I came up with a solution, but it's worth noting that this solution won't work if, for whatever reason, you change the price of the custom option and resend the new order email; this is because the I am generating the product model through the productFactory that is NOT the same model as the one in:

Magento\Sales\Model\Order\Item

I am pretty sure there is a better way to do it , but at the moment I am not able to find it, so this is what I came up with:

In

/app/code/vendor/Email/Block/Order/Email/Items/Order/DefaultOrder.php

Injected PriceCurrencyInterface to format the price once we retrieve it:

public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        ProductFactory $productFactory,
        Magento\Framework\Pricing\PriceCurrencyInterface $amount,
        array $data = []
    )

and then added this function:

 function getCustomOptionPrice($_product, $option_id, $option_value)
    {
        $options = $_product->getData('options');
        $price = '';
        foreach ($options as $option)
        {
            if ($option->getValues() !== null)
            {
                $values = $option->getValues();
                foreach ($values as $key => $value)
                {
                    if ($key === intval($option_value))
                    {
                        $optionValues = $values[$option_value]->getData();
                        if ($optionValues['price'] !== null && $optionValues['price'] != "0.0000")
                        {
                            $price = ' (+ ' . $this->amount->convertAndFormat(floatval($optionValues['price']), false) . ')';
                        }
                    }
                }
            }
            elseif ($option->getValues() === null)
            {
                $optionData = $option->getData();
                if ($optionData['option_id'] == $option_id && ($optionData['price'] != "0.0000" && $optionData['price'] !== null))
                {
                    $price = ' (+ ' . $this->amount->convertAndFormat(floatval($optionData['price']), false) . ')';
                }
            }
        }
        return $price;
    }

This function checks if the price is either not null or 0.0000 and, in this case, we decide not to display it; if either those options are false then the price is returned and is wrapped in a convertAndFormat function which will return a nicely formatted price with your currency of choice.

In the template is then passed in this way:

File: /app/code/vendor/Email/view/frontend/templates/email/items/order/default.phtml

<?php foreach ($block->getItemOptions() as $option): ?>
...    
<?php if (isset($option['option_value']) && (isset($option['option_id'])) ): ?><span><?php echo $block->getCustomOptionPrice($_product, $option['option_id'], $option['option_value'] )  ?></span>
<?php endif;  ?>
...
<?php endforeach; ?>

There are lots of ifs and checking because sometimes the option value it's not a number or it doesn't exists at all. The product object model it's quite complex and deep as structure and that required lots of iteration and getters.

There are hundreds of way to do this better so if you feel like to improve this answer please do.