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](/references/pricing/calculatePrices) of the Pricing Module's main service.

## calculatePrices Method

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.

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](#original-price-selection-logic) 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](../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)"
    },
    {
      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:

![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, 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>
