Back to Medusa

{metadata.title}

www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx

2.14.215.7 KB
Original Source

import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper, TypeList } from "docs-ui"

export const metadata = { title: Prices Calculation, }

{metadata.title}

In this guide, you'll learn how prices are calculated when you use the calculatePrices method of the Pricing Module's main service.

calculatePrices Method

The calculatePrices method 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.

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 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:

ts
const price = await pricingModuleService.calculatePrices(
  { id: [priceSetId] },
  {
    context: {
      currency_code: "eur",
      region_id: "reg_123",
    },
  }
)

In this example, you retrieve the prices in a price set for the specified currency code and region ID.

Returned Price Object

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 it belongs to a price list of type override;
    • Otherwise, a price that doesn't belong to a price list and best matches the specified context.

Both prices are returned in an object with the following properties:

<TypeList types={[ { name: "id", type: "string", description: "The ID of the price set from which the price was selected." }, { name: "is_calculated_price_price_list", type: "boolean", description: "Whether the calculated price belongs to a price list." }, { name: "calculated_amount", type: "number", description: "The amount of the calculated price, or null if there isn't a calculated price. This is the amount shown to the customer." }, { name: "is_original_price_price_list", type: "boolean", description: "Whether the original price belongs to a price list." }, { 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 for comparing with the calculated_amount, such as to check for a discounted value." }, { name: "currency_code", type: "string", description: "The currency code of the calculated price, or null if there isn't a calculated price." }, { name: "is_calculated_price_tax_inclusive", type: "boolean", description: "Whether the calculated price is tax inclusive. Learn more about tax inclusivity in this document" }, { name: "is_original_price_tax_inclusive", type: "boolean", description: "Whether the original price is tax inclusive. Learn more about tax inclusivity in this document" }, { name: "calculated_price", type: "object", description: "The calculated price's price details.", children: [ { name: "id", type: "string", description: "The ID of the price." }, { name: "price_list_id", type: "string", description: "The ID of the associated price list." }, { name: "price_list_type", type: "string", description: "The price list's type. For example, sale." }, { name: "min_quantity", type: "number", description: "The price's minimum quantity condition." }, { name: "max_quantity", type: "number", description: "The price's maximum quantity condition." } ] }, { name: "original_price", type: "object", description: "The original price's price details.", children: [ { name: "id", type: "string", description: "The ID of the price." }, { name: "price_list_id", type: "string", description: "The ID of the associated price list." }, { name: "price_list_type", type: "string", description: "The price list's type. For example, sale." }, { name: "min_quantity", type: "number", description: "The price's minimum quantity condition." }, { name: "max_quantity", type: "number", description: "The price's maximum quantity condition." } ] } ]} 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:

  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, which has a default price, prices with rules, and tiered pricing:

ts
const priceSet = await pricingModuleService.createPriceSets({
  prices: [
    // default price
    {
      amount: 5,
      currency_code: "eur",
      rules: {},
    },
    // prices with rules
    {
      amount: 4,
      currency_code: "eur",
      rules: {
        region_id: "reg_123",
      },
    },
    {
      amount: 4.5,
      currency_code: "eur",
      rules: {
        city: "krakow",
      },
    },
    {
      amount: 3.5,
      currency_code: "eur",
      rules: {
        city: "warsaw",
        region_id: "reg_123",
      },
    },
    // tiered price
    {
      amount: 2,
      currency_code: "eur",
      min_quantity: 100,
    },
  ],
})

Default Price Selection

<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"
    }
  }
)
```

</TabsContent>
<TabsContent value="result">

The returned price is:

```ts
const price = {
  id: "<PRICE_SET_ID>",
  is_calculated_price_price_list: false,
  calculated_amount: 5,

  is_original_price_price_list: false,
  original_amount: 5,

  currency_code: "eur",

  is_calculated_price_tax_inclusive: false,
  is_original_price_tax_inclusive: false,

  calculated_price: {
    price_id: "<DEFAULT_PRICE_ID>",
    price_list_id: null,
    price_list_type: null,
    min_quantity: null,
    max_quantity: null,
  },

  original_price: {
    price_id: "<DEFAULT_PRICE_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 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 Exact 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: "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"
    }
  }
)
```

</TabsContent>
<TabsContent value="result">

The returned price is:

```ts
const price = {
  id: "<PRICE_SET_ID>",
  is_calculated_price_price_list: false,
  calculated_amount: 4,

  is_original_price_price_list: false,
  original_amount: 4,

  currency_code: "eur",

  is_calculated_price_tax_inclusive: false,
  is_original_price_tax_inclusive: false,

  calculated_price: {
    price_id: "<SECOND_PRICE_ID>",
    price_list_id: null,
    price_list_type: null,
    min_quantity: null,
    max_quantity: null,
  },

  original_price: {
    price_id: "<SECOND_PRICE_ID>",
    price_list_id: null,
    price_list_type: null,
    min_quantity: null,
    max_quantity: null,
  },
}
```

- 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> </Tabs>

Tiered Pricing Selection

<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: {
      cart: {
        items: [
          {
            id: "item_1",
            quantity: 150,
            // assuming the price set belongs to this variant
            variant_id: "variant_1",
            // ...
          }
        ],
        // ...
      }
    }
  }
)
```

</TabsContent>
<TabsContent value="result">

The returned price is:

```ts
const price = {
  id: "<PRICE_SET_ID>",
  is_calculated_price_price_list: false,
  calculated_amount: 2,

  is_original_price_price_list: false,
  original_amount: 2,

  currency_code: "eur",

  is_calculated_price_tax_inclusive: false,
  is_original_price_tax_inclusive: false,

  calculated_price: {
    price_id: "<FIFTH_PRICE_ID>",
    price_list_id: null,
    price_list_type: null,
    min_quantity: 100,
    max_quantity: null,
  },

  original_price: {
    price_id: "<FIFTH_PRICE_ID>",
    price_list_id: null,
    price_list_type: null,
    min_quantity: 100,
    max_quantity: null,
  },
}
```

- 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> </Tabs>

Price Selection with Price List

<Tabs defaultValue="code"> <TabsList> <TabsTrigger value="code">Code</TabsTrigger> <TabsTrigger value="result">Result</TabsTrigger> </TabsList> <TabsContentWrapper> <TabsContent value="code">
```ts
const priceList = pricingModuleService.createPriceLists([{
  title: "Summer Price List",
  description: "Price list for summer sale",
  starts_at: Date.parse("01/10/2023").toString(),
  ends_at: Date.parse("31/10/2023").toString(),
  rules: {
    region_id: ['region_123', 'region_456'],
  },
  type: "sale",
  prices: [
    {
      amount: 2,
      currency_code: "eur",
      price_set_id: priceSet.id,
    },
    {
      amount: 1.5,
      currency_code: "usd",
      price_set_id: priceSet.id,
    }
  ],
}]);

const price = await pricingModuleService.calculatePrices(
  { id: [priceSet.id] },
  {
    context: {
      currency_code: "eur",
      region_id: "reg_123",
      city: "krakow"
    }
  }
)
```

</TabsContent>
<TabsContent value="result">

The returned price is:

```ts
const price = {
  id: "<PRICE_SET_ID>",
  is_calculated_price_price_list: true,
  calculated_amount: 2,

  is_original_price_price_list: false,
  original_amount: 4,

  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: "<PL_PRICE_ID_1>",
    price_list_id: "<PRICE_LIST_ID>",
    price_list_type: "sale",
    min_quantity: null,
    max_quantity: null,
  },
}
```

- 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> </Tabs>