PDP Pricing logic
Pricing logic mainly happens in:
[NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/price.phtml
Product page logic happens in steps. We check for an available prices in this order:
calculatedFinalPriceWithCustomOptions
calculatedFinalPrice
initialFinalPrice
Calculation of the price happens in opposite order
initialFinalPrice
is the server-side rendered final price.calculatedFinalPrice
is calculated when a configurable option is selected or qty changescalculatedFinalPriceWithCustomOptions
is calculated when custom-option prices are available and selected
The definitive displayed final price is then output using:
getFormattedFinalPrice() {
return this.formatPrice(
this.calculatedFinalPriceWithCustomOptions ||
this.calculatedFinalPrice ||
this.initialFinalPrice
)
}
Events:
The price box 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
Receives an object with prices for selected configurable product:
activeProductsPriceData: {
oldPrice: {
amount: 75
},
basePrice: {
amount: 50
},
finalPrice: {
amount: 50
},
tierPrices: [{
qty: 4
price: 40
percentage: 20
},
msrpPrice: {
amount: null
}
}
Then it triggers calculateFinalPrice()
which calculates the new calculatedFinalPrice
by looking at the lowest available price.
Then finally calculateFinalPriceWithCustomOptions()
is called which calculates the new calculatedFinalPriceWithCustomOptions
update-qty-[product-id]
event
Updates the selected quantity value as qty
, then recalculates calculateFinalPrice()
followed by calculateFinalPriceWithCustomOptions()
update-custom-option-active
event
Updates an array that tracks active custom-options by option-id by calling updateCustomOptionActive()
.
In case the custom-option has multiple fields (select, radio, checkbox) it tracks the ids as [parent_child]
for example:
update-custom-option-prices
event
Updates an array of option prices for custom options by calling updateCustomOptionPrices()
.
It tracks the prices of custom-options by option-id. Here too, child-options are 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 lives in [NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/options/options.phtml
It listens to the event update-product-final-price
which is fired in [NAMESPACE]/[THEMENAME]/Magento_Catalog/templates/product/view/price.html
and calls calculateOptionPrices()
On initialization calculateOptionPrices()
is also called (using $nextTick
so that we’re sure all Alpine components and their event-listeners are loaded).
The method calculateOptionPrices()
loops through the object optionConfig
that is a server-side rendered object containing the initial custom-options configuration:
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",
},
}
Prices from optionConfig
are returned if there is no productFinalPrice
that came from the price
component. In case productFinalPrice
has been set by the update-product-final-price
event, prices need to be recalculated which happens by looping through the custom-options inside the component and retrieving their prices through data-set entries.
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 then happens as:
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;
}
When all prices have been updated, we trigger update-custom-option-prices
which sends the prices to the main price
component.