docs: fixes and improvements to price calculation guide (#13419)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-09-08 09:47:54 +03:00
1 parent a0693727a0
commit ac5e23b96c
4 files changed
+213 -93

No files matched your search

@@ -6,17 +6,19 @@ export const metadata = {
# {metadata.title}
In this document, you'll learn how prices are calculated when you use the [calculatePrices method](/references/pricing/calculatePrices) of the Pricing Module's main service.
In this guide, you'll learn how prices are calculated when you use the [calculatePrices method](/references/pricing/calculatePrices) of the Pricing Module's main service.
## calculatePrices Method
The [calculatePrices method](/references/pricing/calculatePrices) accepts as parameters the ID of one or more price sets and a context.
The [calculatePrices method](/references/pricing/calculatePrices) accepts the ID of one or more price sets and a context as parameters.
It returns a price object with the best matching price for each price set.
It returns a price object with the best-matching price for each price set.
The `calculatePrices` method is useful for retrieving the prices of a product variant or a shipping option that matches a specific context, such as a currency code, in your backend customizations.
### Calculation Context
The calculation context is an optional object passed as a second parameter to the `calculatePrices` method. It accepts rules to restrict the selected prices in the price set.
The calculation context is an optional object passed as the second parameter to the `calculatePrices` method. It accepts rules as key-value pairs to restrict the selected prices in the price set.
For example:
@@ -25,7 +27,7 @@ const price = await pricingModuleService.calculatePrices(
{ id: [priceSetId] },
{
context: {
currency_code: currencyCode,
currency_code: "eur",
region_id: "reg_123",
},
}
@@ -40,10 +42,10 @@ For each price set, the `calculatePrices` method selects two prices:
- A calculated price: Either a price that belongs to a price list and best matches the specified context, or the same as the original price.
- An original price, which is either:
- The same price as the calculated price if the price list it belongs to is of type `override`;
- Or a price that doesn't belong to a price list and best matches the specified context.
- The same price as the calculated price if it belongs to a price list of type `override`;
- Otherwise, a price that doesn't belong to a price list and [best matches](#original-price-selection-logic) the specified context.
Both prices are returned in an object that has the following properties:
Both prices are returned in an object with the following properties:
<TypeList
types={[
@@ -70,7 +72,7 @@ Both prices are returned in an object that has the following properties:
{
name: "original_amount",
type: "`number`",
description: "The amount of the original price, or `null` if there isn't an original price. This amount is useful to compare with the `calculated_amount`, such as to check for discounted value."
description: "The amount of the original price, or `null` if there isn't an original price. This amount is useful for comparing with the `calculated_amount`, such as to check for a discounted value."
},
{
name: "currency_code",
@@ -80,12 +82,12 @@ Both prices are returned in an object that has the following properties:
{
name: "is_calculated_price_tax_inclusive",
type: "`boolean`",
description: "Whether the calculated price is tax inclusive. Learn more about tax-inclusivity in [this document](../tax-inclusive-pricing/page.mdx)"
description: "Whether the calculated price is tax inclusive. Learn more about tax inclusivity in [this document](../tax-inclusive-pricing/page.mdx)"
},
{
name: "is_original_price_tax_inclusive",
type: "`boolean`",
description: "Whether the original price is tax inclusive. Learn more about tax-inclusivity in [this document](../tax-inclusive-pricing/page.mdx)"
description: "Whether the original price is tax inclusive. Learn more about tax inclusivity in [this document](../tax-inclusive-pricing/page.mdx)"
},
{
name: "calculated_price",
@@ -110,12 +112,12 @@ Both prices are returned in an object that has the following properties:
{
name: "min_quantity",
type: "`number`",
description: "The price's min quantity condition."
description: "The price's minimum quantity condition."
},
{
name: "max_quantity",
type: "`number`",
description: "The price's max quantity condition."
description: "The price's maximum quantity condition."
}
]
},
@@ -142,12 +144,12 @@ Both prices are returned in an object that has the following properties:
{
name: "min_quantity",
type: "`number`",
description: "The price's min quantity condition."
description: "The price's minimum quantity condition."
},
{
name: "max_quantity",
type: "`number`",
description: "The price's max quantity condition."
description: "The price's maximum quantity condition."
}
]
}
@@ -155,11 +157,24 @@ Both prices are returned in an object that has the following properties:
sectionTitle="Returned Calculated Price"
/>
### Original Price Selection Logic
When the calculated price isn't from a price list of type `override`, the original price is selected based on the following logic:
![Diagram illustrating the original price selection logic](https://res.cloudinary.com/dza7lstvk/image/upload/v1757058523/Medusa%20Resources/original-price-calculation_sxjw3l.jpg)
1. If the context doesn't have any rules, select the default price (the price without any rules).
2. If the context has rules and there's a price that matches all the rules, select that price.
3. If the context has rules and there's no price that matches all the rules:
- Find all the prices whose rules match at least one rule in the context.
- Sort the matched prices by the number of matched rules in descending order.
- Select the first price in the sorted list (the one that matches the most rules).
---
## Examples
Consider the following price set:
Consider the following price set, which has a default price, prices with rules, and tiered pricing:
```ts
const priceSet = await pricingModuleService.createPriceSets({
@@ -167,35 +182,36 @@ const priceSet = await pricingModuleService.createPriceSets({
// default price
{
amount: 5,
currency_code: "EUR",
currency_code: "eur",
rules: {},
},
// prices with rules
{
amount: 4,
currency_code: "EUR",
currency_code: "eur",
rules: {
region_id: "reg_123",
},
},
{
amount: 4.5,
currency_code: "EUR",
currency_code: "eur",
rules: {
city: "krakow",
},
},
{
amount: 5,
currency_code: "EUR",
amount: 3.5,
currency_code: "eur",
rules: {
city: "warsaw",
region_id: "reg_123",
},
},
// tiered price
{
amount: 2,
currency_code: "EUR",
currency_code: "eur",
min_quantity: 100,
},
],
@@ -217,7 +233,7 @@ const priceSet = await pricingModuleService.createPriceSets({
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR"
currency_code: "eur"
}
}
)
@@ -237,7 +253,7 @@ const priceSet = await pricingModuleService.createPriceSets({
is_original_price_price_list: false,
original_amount: 5,
currency_code: "EUR",
currency_code: "eur",
is_calculated_price_tax_inclusive: false,
is_original_price_tax_inclusive: false,
@@ -260,14 +276,14 @@ const priceSet = await pricingModuleService.createPriceSets({
}
```
- Original price selection: since there are no provided rules in the context, the original price is the default price.
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
- Original price selection: Since there are no provided rules in the context, the original price is the default price of the price set.
- Calculated price selection: Since there are no associated price lists, the calculated price is set to the original price.
</TabsContent>
</TabsContentWrapper>
</Tabs>
### Calculate Prices with Rules
### Calculate Prices with Exact Match
<Tabs defaultValue="code">
<TabsList>
@@ -282,7 +298,75 @@ const priceSet = await pricingModuleService.createPriceSets({
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR",
currency_code: "eur",
region_id: "reg_123",
city: "warsaw"
}
}
)
```
</TabsContent>
<TabsContent value="result">
The returned price is:
```ts
const price = {
id: "<PRICE_SET_ID>",
is_calculated_price_price_list: false,
calculated_amount: 3.5,
is_original_price_price_list: false,
original_amount: 3.5,
currency_code: "eur",
is_calculated_price_tax_inclusive: false,
is_original_price_tax_inclusive: false,
calculated_price: {
price_id: "<FOURTH_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
original_price: {
price_id: "<FOURTH_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: null,
max_quantity: null,
},
}
```
- Original price selection: The fourth price in the price set is selected as the best price because it matches both the `region_id` and `city` rules.
- Calculated price selection: Since there are no associated price lists, the calculated price is set to the original price.
</TabsContent>
</TabsContentWrapper>
</Tabs>
### Calculate Prices with Partial Match
<Tabs defaultValue="code">
<TabsList>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="result">Result</TabsTrigger>
</TabsList>
<TabsContentWrapper>
<TabsContent value="code">
```ts
const price = await pricingModuleService.calculatePrices(
{ id: [priceSet.id] },
{
context: {
currency_code: "eur",
region_id: "reg_123",
city: "krakow"
}
@@ -299,18 +383,18 @@ const priceSet = await pricingModuleService.createPriceSets({
const price = {
id: "<PRICE_SET_ID>",
is_calculated_price_price_list: false,
calculated_amount: 5,
calculated_amount: 4,
is_original_price_price_list: false,
original_amount: 5,
original_amount: 4,
currency_code: "EUR",
currency_code: "eur",
is_calculated_price_tax_inclusive: false,
is_original_price_tax_inclusive: false,
calculated_price: {
price_id: "<FOURTH_PRICE_ID>",
price_id: "<SECOND_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: null,
@@ -318,7 +402,7 @@ const priceSet = await pricingModuleService.createPriceSets({
},
original_price: {
price_id: "<FOURTH_PRICE_ID>",
price_id: "<SECOND_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: null,
@@ -327,8 +411,9 @@ const priceSet = await pricingModuleService.createPriceSets({
}
```
- Original price selection: The fourth price in the price set is selected as the best price.
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
- Original price selection: The second price in the price set is selected as the best price because it matches the `region_id` rule.
- Although the third price also matches a rule (`city`), the second price is selected because it appears first in the price set.
- Calculated price selection: Since there are no associated price lists, the calculated price is set to the original price.
</TabsContent>
</TabsContentWrapper>
@@ -353,7 +438,7 @@ const priceSet = await pricingModuleService.createPriceSets({
items: [
{
id: "item_1",
quantity: 2,
quantity: 150,
// assuming the price set belongs to this variant
variant_id: "variant_1",
// ...
@@ -380,13 +465,13 @@ const priceSet = await pricingModuleService.createPriceSets({
is_original_price_price_list: false,
original_amount: 2,
currency_code: "EUR",
currency_code: "eur",
is_calculated_price_tax_inclusive: false,
is_original_price_tax_inclusive: false,
calculated_price: {
price_id: "<DEFAULT_PRICE_ID>",
price_id: "<FIFTH_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: 100,
@@ -394,7 +479,7 @@ const priceSet = await pricingModuleService.createPriceSets({
},
original_price: {
price_id: "<DEFAULT_PRICE_ID>",
price_id: "<FIFTH_PRICE_ID>",
price_list_id: null,
price_list_type: null,
min_quantity: 100,
@@ -403,9 +488,8 @@ const priceSet = await pricingModuleService.createPriceSets({
}
```
- Original price selection: the fifth price in the price set is selected as the best price because the cart item quantity is `200`.
- This is assuming the price set belongs to the cart item's variant.
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
- Original price selection: Since the cart's item quantity is `100` or more, the tiered price is selected as the best price.
- Calculated price selection: Since there are no associated price lists, the calculated price is set to the original price.
</TabsContent>
</TabsContentWrapper>
@@ -428,20 +512,20 @@ const priceSet = await pricingModuleService.createPriceSets({
starts_at: Date.parse("01/10/2023").toString(),
ends_at: Date.parse("31/10/2023").toString(),
rules: {
region_id: ['PL']
region_id: ['region_123', 'region_456'],
},
type: "sale",
prices: [
{
amount: 4,
currency_code: "EUR",
amount: 2,
currency_code: "eur",
price_set_id: priceSet.id,
},
{
amount: 4.5,
currency_code: "EUR",
amount: 1.5,
currency_code: "usd",
price_set_id: priceSet.id,
},
}
],
}]);
@@ -449,8 +533,8 @@ const priceSet = await pricingModuleService.createPriceSets({
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR",
region_id: "PL",
currency_code: "eur",
region_id: "reg_123",
city: "krakow"
}
}
@@ -466,12 +550,12 @@ const priceSet = await pricingModuleService.createPriceSets({
const price = {
id: "<PRICE_SET_ID>",
is_calculated_price_price_list: true,
calculated_amount: 4,
calculated_amount: 2,
is_original_price_price_list: false,
original_amount: 5,
original_amount: 4,
currency_code: "EUR",
currency_code: "eur",
is_calculated_price_tax_inclusive: false,
is_original_price_tax_inclusive: false,
@@ -494,8 +578,9 @@ const priceSet = await pricingModuleService.createPriceSets({
}
```
- Original price selection: The fourth price in the price set is selected as the best price.
- Calculated price selection: The first price of the price list is selected as the best price.
- Original price selection: The second price in the price set is selected as the best price because it matches the `region_id` rule.
- Although the third price also matches a rule (`city`), the second price is selected because it appears first in the price set.
- Calculated price selection: The price from the price list is selected as the calculated price because it matches the `region_id` rule of the price list.
</TabsContent>
</TabsContentWrapper>