import Tabs from "@theme/Tabs" import TabItem from "@theme/TabItem" # 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: ```ts 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: ```ts 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.](https://res.cloudinary.com/dza7lstvk/image/upload/v1700574799/Medusa%20Docs/Diagrams/calculated-price_vjnx3j.jpg) - 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.](https://res.cloudinary.com/dza7lstvk/image/upload/v1700574800/Medusa%20Docs/Diagrams/original-price_i47fso.jpg) - 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 `rules_count`, 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 ```ts 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: ```ts 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 ```ts const price = await pricingService.calculatePrices( { id: [priceSet.id] }, { context: { currency_code: "EUR" } } ) ``` The returned price is: ```ts const price = { id: "", is_calculated_price_price_list: false, calculated_amount: 500, is_original_price_price_list: false, original_amount: 500, currency_code: "EUR", calculated_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, original_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, } ``` - Original price selection: since there are no provided rules in the context, the original price is the default money amount. - Calculated price selection: since there are no associated price lists, the calculated price is set to the original price. ### Exact Match Rule in Context ```ts const price = await pricingService.calculatePrices( { id: [priceSet.id] }, { context: { currency_code: "EUR", region_id: "PL" } } ) ``` The returned price is: ```ts const price = { id: "", is_calculated_price_price_list: false, calculated_amount: 400, is_original_price_price_list: false, original_amount: 400, currency_code: "EUR", calculated_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, original_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, } ``` - Original price selection: Since the second money amount of the price set has the same context as the provided one, it's selected as the original price. - Calculated price selection: since there are no associated price lists, the calculated price is set to the original price. ### Context without Exact Matching Rules ```ts const price = await pricingService.calculatePrices( { id: [priceSet.id] }, { context: { currency_code: "EUR", region_id: "PL", city: "krakow" } } ) ``` The returned price is: ```ts const price = { id: "", is_calculated_price_price_list: false, calculated_amount: 500, is_original_price_price_list: false, original_amount: 500, currency_code: "EUR", calculated_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, original_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, } ``` - Original price selection: The fourth money amount in the price list is selected based on the following process: - There are no money amounts having the same rules as the context. - Retrieve all money amounts matching some combination of the rules in the context. - Sort the money amounts in descending order by their number of rules, each rule type's default priority, and the priority of the rule values. - All rule types, in this case, don't have a priority. So, the sorting depends on the number of rules each money amount has. - The fourth money amount has two rules, and the second and third have one rule. So, the fourth money amount is selected as the original price. - Calculated price selection: since there are no associated price lists, the calculated price is set to the original price. ### Price Selection with Price List ```ts 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" } } ) ``` The returned price is: ```ts const price = { id: "", is_calculated_price_price_list: true, calculated_amount: 400, is_original_price_price_list: false, original_amount: 500, currency_code: "EUR", calculated_price: { money_amount_id: "", price_list_id: null, price_list_type: null, min_quantity: null, max_quantity: null, }, original_price: { money_amount_id: "", price_list_id: "", price_list_type: "sale", min_quantity: null, max_quantity: null, }, } ``` - Original price selection: The process is explained in the Context without Exact Matching Rules section. - Calculated price selection: The first money amount of the price list is selected based on the following process: - The price set has a price list associated with it. So, retrieve its money amounts and sort them in ascending order by their amount. - Since the first money amount of the price list has the lowest amount, it's selected as the calculated price.