Skip to main content
Skip to main content

Prices Calculation

In this document, you'll learn how prices are calculated under the hood when you use the calculatePrices method of the Pricing module interface.

Overview

The calculatePrices method accepts the ID of one or more price sets and a context. For each price set, it selects two types of prices that best match the context; one belongs to a price list, and one doesn't.

Then, it returns an array of price objects, each price for every supplied price set ID.


Calculation Context

The context is an object passed to the method. It must contain at least the currency_code. Only money amounts in the price sets with the same currency code are considered for the price selection.

For example:

import { 
initialize as initializePricingModule,
} from "@medusajs/pricing"
async function calculatePrice(
priceSetId: string,
currencyCode: string
) {
const pricingService = await initializePricingModule()

const price = await pricingService.calculatePrices(
{ id: [priceSetId] },
{
context: {
currency_code: currencyCode,
},
}
)
}

The context object can also contain any custom rules, with the key being the rule_attribute of a rule type and its value being the rule's value.

For example:

import { 
initialize as initializePricingModule,
} from "@medusajs/pricing"
async function calculatePrice(
priceSetId: string,
currencyCode: string
) {
const pricingService = await initializePricingModule()

const price = await pricingService.calculatePrices(
{ id: [priceSetId] },
{
context: {
currency_code: currencyCode,
region_id: "US",
},
}
)
}

Prices Selection

For each price set, the method selects two money amounts:

  • The calculated price: a money amount that belongs to a price list. If there are no money amounts associated with a price list, it’ll be the same as the original price.
  • The original price: a money amount that doesn't belong to a price list.

Calculated Price Selection Process

A diagram showcasing the calculated price selection process.

  • Find the price set’s associated valid price lists. A price list is considered valid if:
    • The current date is between its start and end dates.
    • The price list's rules satisfy the context's rules.
  • If valid price lists are found, the money amounts within them are sorted by their amount in ascending order. The one having the lowest amount is selected as the calculated price.
  • If no valid price list is found, the selected calculated price will be the same as the original price.

Original Price Selection Process

A diagram showcasing the original price selection process.

  • If the price list associated with the calculated price is of type override, the selected original price is set to the calculated price.
  • If no rules are provided in the context other than the currency_code, the default money amount is selected as the original price. The default money amount is a money amount having no rules applied to it.
  • Otherwise, if a money amount exists in any price set with the same rules provided in the context, it's selected as the original price.
  • If no money amount exists with the same rules as the context, all money amounts satisfying any combination of the provided rules are retrieved.
    • The money amounts are sorted in descending order by the associated PriceSetMoneyAmount's number_rules, the default_priority of the rule types, and the priority of the associated PriceRule. The priority attribute has a higher precedence than the default_priority.
    • The highest money amount sorted is selected as the original price since it's considered the best price.

Returned Calculated Price

After the original and calculated prices are selected, the method will use them to create the following price object for each pr

const price = {
id: priceSetId,
is_calculated_price_price_list:
!!calculatedPrice?.price_list_id,
calculated_amount: parseInt(calculatedPrice?.amount || "") ||
null,

is_original_price_price_list: !!originalPrice?.price_list_id,
original_amount: parseInt(originalPrice?.amount || "") ||
null,

currency_code: calculatedPrice?.currency_code ||
null,

calculated_price: {
money_amount_id: calculatedPrice?.id || null,
price_list_id: calculatedPrice?.price_list_id || null,
price_list_type: calculatedPrice?.price_list_type || null,
min_quantity: parseInt(
calculatedPrice?.min_quantity || ""
) || null,
max_quantity: parseInt(
calculatedPrice?.max_quantity || ""
) || null,
},

original_price: {
money_amount_id: originalPrice?.id || null,
price_list_id: originalPrice?.price_list_id || null,
price_list_type: originalPrice?.price_list_type || null,
min_quantity: parseInt(originalPrice?.min_quantity || "") ||
null,
max_quantity: parseInt(originalPrice?.max_quantity || "") ||
null,
},
}

Where:

  • id: The ID of the price set from which the money amount was selected.
  • is_calculated_price_price_list: whether the calculated price belongs to a price list. As mentioned earlier, if no valid price list is found, the calculated price is set to the original price, which doesn't belong to a price list.
  • calculated_amount: The amount of the calculated price, or null if there isn't a calculated price.
  • is_original_price_price_list: whether the original price belongs to a price list. As mentioned earlier, if the price list of the calculated price is of type override, the original price will be the same as the calculated price.
  • original_amount: The amount of the original price, or null if there isn't an original price.
  • currency_code: The currency code of the calculated price, or null if there isn't a calculated price.
  • calculated_price: An object containing the calculated price's money amount details and potentially its associated price list.
  • original_price: An object containing the original price's money amount details and potentially its associated price list.

The method returns an array of these price objects.


Example

Consider the following rule types and price sets:

const ruleTypes = await pricingService.createRuleTypes([
{
name: "Region",
rule_attribute: "region_id",
},
{
name: "City",
rule_attribute: "city",
},
])

const priceSet = await service.create({
rules: [
{ rule_attribute: "region_id" },
{ rule_attribute: "city" },
],
prices: [
//default
{
amount: 500,
currency_code: "EUR",
rules: {},
},
// prices with rules
{
amount: 400,
currency_code: "EUR",
rules: {
region_id: "PL",
},
},
{
amount: 450,
currency_code: "EUR",
rules: {
city: "krakow",
},
},
{
amount: 500,
currency_code: "EUR",
rules: {
city: "warsaw",
region_id: "PL",
},
},
],
})

Default Price Selection

const price = await pricingService.calculatePrices(
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR"
}
}
)

Exact Match Rule in Context

const price = await pricingService.calculatePrices(
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR",
region_id: "PL"
}
}
)

Context without Exact Matching Rules

const price = await pricingService.calculatePrices(
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR",
region_id: "PL",
city: "krakow"
}
}
)

Price Selection with Price List

const priceList = pricingModuleService.createPriceList({
name: "Test Price List",
starts_at: Date.parse("01/10/2023"),
ends_at: Date.parse("31/10/2023"),
rules: {
region_id: ['PL']
},
type: "sale"
prices: [
{
amount: 400,
currency_code: "EUR",
price_set_id: priceSet.id,
},
{
amount: 450,
currency_code: "EUR",
price_set_id: priceSet.id,
},
],
});

const price = await pricingService.calculatePrices(
{ id: [priceSet.id] },
{
context: {
currency_code: "EUR",
region_id: "PL",
city: "krakow"
}
}
)
Was this section helpful?