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:
calculatedFinalPriceWithCustomOptionscalculatedFinalPriceinitialFinalPrice
Price calculation occurs in the reverse order of precedence:
initialFinalPriceis the server-side rendered final price.calculatedFinalPriceis computed when a configurable option is selected or the quantity changes.calculatedFinalPriceWithCustomOptionsis 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-activeupdate-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:
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.