Table of Contents & Menu
Navigation

PDP Pricing Logic

Pricing logic primarily resides in:

[NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/price.phtml

The Product Detail Page (PDP) pricing logic follows these steps. Prices are checked in this order of precedence:

  • calculatedFinalPriceWithCustomOptions
  • calculatedFinalPrice
  • initialFinalPrice

Price calculation occurs in the reverse order of precedence:

  • initialFinalPrice is the server-side rendered final price.
  • calculatedFinalPrice is computed when a configurable option is selected or the quantity changes.
  • calculatedFinalPriceWithCustomOptions is computed when custom option prices are available and selected.

The final displayed price is determined by:

getFormattedFinalPrice() {
    return this.formatPrice(
        this.calculatedFinalPriceWithCustomOptions ||
        this.calculatedFinalPrice ||
        this.initialFinalPrice
    )
}

Events

The price component listens for the following events:

  • update-prices-[product-id]
  • update-qty-[product-id]
  • update-custom-option-active
  • update-custom-option-prices

update-prices-[product-id] event

This event receives an object containing price data for the selected configurable product:

activeProductsPriceData: { 
    oldPrice: {
        amount: 75
    },
    basePrice: {
        amount: 50
    },
    finalPrice: {
        amount: 50
    },
    tierPrices: [{
        qty: 4
        price: 40
        percentage: 20
    },
    msrpPrice: {
        amount: null
    }
}

It then triggers calculateFinalPrice() to determine the new calculatedFinalPrice based on the lowest available price. Finally, calculateFinalPriceWithCustomOptions() is called to update calculatedFinalPriceWithCustomOptions.

update-qty-[product-id] event

This event updates the selected quantity value (`qty`), then recalculates both calculateFinalPrice() and calculateFinalPriceWithCustomOptions().

update-custom-option-active event

This event calls updateCustomOptionActive() to update an array that tracks active custom options by their IDs.

For custom options with multiple fields (e.g., select, radio, checkbox), IDs are tracked as [parent_child]. For example:

activeCustomOptions: ['1', '3_1', '3_2']

update-custom-option-prices event

This event calls updateCustomOptionPrices() to update an array of custom option prices.

Prices are tracked by option ID. Child options are also tracked as [parent_child]. For example:

customOptionPrices: {
    "1": "10.000000",
    "2": "5.000000",
    "3_1": "10.000000",
    "3_2": "15.000000",
    "3_3": "20.000000"
}

Custom Options

Custom option price logic is handled in [NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/options/options.phtml.

It listens for the update-product-final-price event (fired from [NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/price.html), which triggers calculateOptionPrices().

During initialization, calculateOptionPrices() is also called (using $nextTick to ensure all Alpine components and event listeners are loaded).

The calculateOptionPrices() method iterates through the optionConfig object, which is a server-side rendered configuration of initial custom options:

  optionConfig: {
    2: {
      prices: {
        oldPrice: { amount: -25, adjustments: [] },
        basePrice: { amount: -25 },
        finalPrice: { amount: -25 },
      },
      type: "fixed",
      name: "Custom print",
    },
    3: {
      1: {
        prices: {
          oldPrice: { amount: 5, adjustments: [] },
          basePrice: { amount: 5 },
          finalPrice: { amount: 5 },
        },
        type: "fixed",
        name: "wrap as gift",
      },
      2: {
        prices: {
          oldPrice: { amount: -5, adjustments: [] },
          basePrice: { amount: -5 },
          finalPrice: { amount: -5 },
        },
        type: "fixed",
        name: "don't wrap",
      },
    },
    9: {
      prices: {
        oldPrice: { amount: 5, adjustments: [] },
        basePrice: { amount: 5 },
        finalPrice: { amount: 5 },
      },
      type: "fixed",
      name: "file",
    },
  }

If productFinalPrice (from the price component) is not set, prices from optionConfig are used. If productFinalPrice *is* set by the update-product-final-price event, prices are recalculated by iterating through custom options within the component and retrieving their prices from data-set attributes.

Example custom option element:

<input type="text" 
       id="options_2_text"
       ...
       data-price-amount="-25.000000"
       data-price-type="fixed"
       x-ref="option-2"
       x-on:input="updateCustomOptionValue($dispatch, '2', $event.target)"
>

Price calculation logic:

calculateOptionPrice: function calculateOptionPrice(
  customOption,
  customOptionId,
  childCustomOptionId
) {
  const customOptionCode =
    customOptionId + (childCustomOptionId ? "-" + childCustomOptionId : "");

  const optionElement = this.refs && this.refs["option-" + customOptionCode];

  let price = customOption.prices.finalPrice.amount;

  if (
    this.productFinalPrice &&
    optionElement &&
    optionElement.dataset.priceAmount &&
    optionElement.dataset.priceType
  ) {
    price =
      optionElement.dataset.priceType !== "percent"
        ? optionElement.dataset.priceAmount
        : this.productFinalPrice * (optionElement.dataset.priceAmount / 100);
  }

  return price;
}

After all prices are updated, the update-custom-option-prices event is triggered, sending the updated prices to the main price component.