diff --git a/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx b/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx index a16ed77950..82a4c1a35e 100644 --- a/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx +++ b/www/apps/resources/app/commerce-modules/pricing/price-calculation/page.mdx @@ -1,7 +1,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper, TypeList } from "docs-ui" export const metadata = { - title: `Prices Calculation Strategy`, + title: `Prices Calculation`, } # {metadata.title} @@ -77,6 +77,16 @@ Both prices are returned in an object along with the following properties: 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`", @@ -224,6 +234,9 @@ const priceSet = await pricingModuleService.createPriceSets({ currency_code: "EUR", + is_calculated_price_tax_inclusive: false, + is_original_price_tax_inclusive: false, + calculated_price: { price_id: "", price_list_id: null, @@ -288,6 +301,9 @@ const priceSet = await pricingModuleService.createPriceSets({ currency_code: "EUR", + is_calculated_price_tax_inclusive: false, + is_original_price_tax_inclusive: false, + calculated_price: { price_id: "", price_list_id: null, @@ -375,6 +391,9 @@ const priceSet = await pricingModuleService.createPriceSets({ currency_code: "EUR", + is_calculated_price_tax_inclusive: false, + is_original_price_tax_inclusive: false, + calculated_price: { price_id: "", price_list_id: null, diff --git a/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx b/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx new file mode 100644 index 0000000000..10b8bb87a9 --- /dev/null +++ b/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx @@ -0,0 +1,74 @@ +export const metadata = { + title: `Tax-Inclusive Pricing`, +} + +# {metadata.title} + +In this document, you’ll learn about tax-inclusive pricing and how it's used during prices calculation. + +## What is Tax-Inclusive Pricing? + +A tax-inclusive price is a price that includes taxes. The tax amount is calculated from the price rather than added to it. + +For example, if a product’s price is $50 and the tax rate is 2%, then the tax-inclusive price is $49, and the applied tax amount is $1. + +--- + +## How is Tax-Inclusive Pricing Set? + +The `PricePreference` data model holds the tax-inclusive setting for a context. It has two properties that indicate the context: + +- `attribute`: The name of the attribute to compare against. For example, `region_id` or `currency_code`. +- `value`: The attribute’s value. For example, `reg_123` or `usd`. + + + +Only `region_id` and `currency_code` are supported as an `attribute` at the moment. + + + +The `is_tax_inclusive` property indicates whether tax-inclusivity is enabled in the specified context. + +For example: + +```json +{ + "attribute": "currency_code", + "value": "USD", + "is_tax_inclusive": true, +} +``` + +In this example, tax-inclusivity is enabled for the `USD` currency code. + +--- + +## Tax-Inclusive Pricing in Price Calculation + +### Tax Context + +As mentioned in the [Price Calculation documentation](../price-calculation/page.mdx#calculation-context), The `calculatePrices` method accepts as a parameter a calculation context. + +To get accurate tax results, pass the `region_id` and / or `currency_code` in the calculation context. + +### Returned Tax Properties + +The `calculatePrices` method returns two properties related to tax-inclusivity: + +- `is_calculated_price_tax_inclusive`: Whether the selected `calculated_price` is tax-inclusive. +- `is_original_price_tax_inclusive` : Whether the selected `original_price` is tax-inclusive. + +A price is considered tax-inclusive if: + +1. It belongs to the region or currency code specified in the calculation context; +2. and the region or currency code has a price preference with `is_tax_inclusive` enabled. + +### Tax Context Precedence + +If: + +- both the `region_id` and `currency_code` are provided in the calculation context; +- the selected price belongs to the region; +- and the region has a price preference + +Then, the region’s price preference’s `is_tax_inclusive`'s value takes higher precedence in determining whether a price is tax-inclusive. diff --git a/www/apps/resources/app/storefront-development/products/price/examples/sale-price/page.mdx b/www/apps/resources/app/storefront-development/products/price/examples/sale-price/page.mdx new file mode 100644 index 0000000000..d4657e798f --- /dev/null +++ b/www/apps/resources/app/storefront-development/products/price/examples/sale-price/page.mdx @@ -0,0 +1,196 @@ +export const metadata = { + title: `React Example: Show Product Variant's Sale Price`, +} + +# {metadata.title} + +This document provides an example of how to show a product variant's sale price. + +To check if a product variant's price is a sale price, check whether the variant's `calculated_price.calculated_price.price_list_type` field is equal to `sale`. + +In that case, the original price is in the variant's `calculated_price.original_amount` field. + +For example, in a React-based storefront: + + + +The example only passes the `region_id` query parameter for pricing. Learn how to store and retrieve the customer's region in the [Regions guides](../../../../regions/context/page.mdx). + + + +export const saleHighlights = [ + ["5", "useRegion", "The `useRegion` hook is implemented in the Region React Context guide."], + ["13", "{ params: { id } }: Params", "This is based on Next.js which passes the path parameters as a prop."], + ["19", "region", "Access the region using the `useRegion` hook."], + ["88", "isSale", "Determine whether the price is a sale price based on the value of the variant's `calculated_price.calculated_price.price_list_type` field."], + ["97", "originalPrice", "Retrieve the original price from the variant's `calculated_price.original_amount` field if the price is a sale price."], + ["143", "", "If the price is a sale price, show that to the customer along with the original price."] +] + +```tsx highlights={saleHighlights} +"use client" // include with Next.js 13+ + +import { useEffect, useMemo, useState } from "react" +import { HttpTypes } from "@medusajs/types" +import { useRegion } from "../providers/region" + +type Params = { + params: { + id: string + } +} + +export default function Product({ params: { id } }: Params) { + const [loading, setLoading] = useState(true) + const [product, setProduct] = useState< + HttpTypes.StoreProduct | undefined + >() + const [selectedOptions, setSelectedOptions] = useState>({}) + const { region } = useRegion() + + useEffect(() => { + if (!loading) { + return + } + + const queryParams = new URLSearchParams({ + fields: `*variants.calculated_price`, + region_id: region.id, + }) + + fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { + credentials: "include", + headers: { + "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", + }, + }) + .then((res) => res.json()) + .then(({ product: dataProduct }) => { + setProduct(dataProduct) + setLoading(false) + }) + }, [loading]) + + const selectedVariant = useMemo(() => { + if ( + !product?.variants || + !product.options || + Object.keys(selectedOptions).length !== product.options?.length + ) { + return + } + + return product.variants.find((variant) => variant.options?.every( + (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!] + )) + }, [selectedOptions, product]) + + const formatPrice = (amount: number): string => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: region.currency_code, + }) + .format(amount) + } + + const variantPrice = useMemo(() => { + if (selectedVariant) { + return selectedVariant + } + + return product?.variants?.sort((a: any, b: any) => { + return ( + a.calculated_price.calculated_amount - + b.calculated_price.calculated_amount + ) + })[0] + }, [selectedVariant, product]) + + const price = useMemo(() => { + if (!variantPrice) { + return + } + + // @ts-ignore + return formatPrice(variantPrice.calculated_price.calculated_amount) + }, [variantPrice]) + + const isSale = useMemo(() => { + if (!variantPrice) { + return false + } + + // @ts-ignore + return variantPrice.calculated_price.calculated_price.price_list_type === "sale" + }, [variantPrice]) + + const originalPrice = useMemo(() => { + if (!isSale) { + return + } + + // @ts-ignore + return formatPrice(variantPrice.calculated_price.original_amount) + }, [isSale, variantPrice]) + + return ( +
+ {loading && Loading...} + {product && ( + <> +

{product.title}

+ {(product.options?.length || 0) > 0 && ( +
    + {product.options!.map((option) => ( +
  • + {option.title} + {option.values?.map((optionValue) => ( + + ))} +
  • + ))} +
+ )} + {selectedVariant && ( + Selected Variant: {selectedVariant.id} + )} + {price && ( + + {!selectedVariant && "From: "} + {price} + {isSale && `SALE - Original Price: ${originalPrice}`} + + )} + {product.images?.map((image) => ( + + ))} + + )} +
+ ) +} +``` + +In this example, you: + +- Define an `isSale` memo variable that determines whether the chosen variant's price is a sale price. You do that by checking if the value of the variant's `calculated_price.calculated_price.price_list_type` field is `sale`. +- Define an `originalPrice` memo variable that, if `isSale` is enabled, has the formatted original price of the chosen variant. The variant's original price is in the `calculated_price.original_amount` field. +- If `isSale` is enabled, show a message to the customer indicating that this product is on sale along with the original price. + + + +Learn more about the `formatPrice` function in [this guide](../show-price/page.mdx#price-formatting) + + \ No newline at end of file diff --git a/www/apps/resources/app/storefront-development/products/price/examples/show-price/page.mdx b/www/apps/resources/app/storefront-development/products/price/examples/show-price/page.mdx new file mode 100644 index 0000000000..97ade25085 --- /dev/null +++ b/www/apps/resources/app/storefront-development/products/price/examples/show-price/page.mdx @@ -0,0 +1,189 @@ +export const metadata = { + title: `React Example: Show Product Variant's Price`, +} + +# {metadata.title} + +The following React-based storefront example retrieves the product's price based on the selected variant: + + + +The example only passes the `region_id` query parameter for pricing. Learn how to store and retrieve the customer's region in the [Regions guides](../../../../regions/context/page.mdx). + + + +export const priceHighlights = [ + ["5", "useRegion", "The `useRegion` hook is implemented in the Region React Context guide."], + ["13", "{ params: { id } }: Params", "This is based on Next.js which passes the path parameters as a prop."], + ["19", "region", "Access the region using the `useRegion` hook."], + ["26", "queryParams", "Build the pricing query parameters."], + ["58", "formatPrice", "A utility function to format an amount with its currency."], + ["59", `"en-US"`, "If you use a different locale change it here."], + ["66", "variantPrice", "Assign the variant to compute its price, which is either the selected or cheapest variant."], + ["68", "selectedVariant", "Use the selected variant for pricing."], + ["71", "", "If there isn't a selected variant, retrieve the variant with the cheapest price."], + ["79", "price", "Compute the price of the selected or cheapest variant."], + ["123", "", "If there's a computed price but no selected variant, show a `From` prefix to the price."], + ["124", "price", "Display the computed price."] +] + +```tsx highlights={priceHighlights} +"use client" // include with Next.js 13+ + +import { useEffect, useMemo, useState } from "react" +import { HttpTypes } from "@medusajs/types" +import { useRegion } from "../providers/region" + +type Params = { + params: { + id: string + } +} + +export default function Product({ params: { id } }: Params) { + const [loading, setLoading] = useState(true) + const [product, setProduct] = useState< + HttpTypes.StoreProduct | undefined + >() + const [selectedOptions, setSelectedOptions] = useState>({}) + const { region } = useRegion + + useEffect(() => { + if (!loading) { + return + } + + const queryParams = new URLSearchParams({ + fields: `*variants.calculated_price`, + region_id: region.id, + }) + + fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { + credentials: "include", + headers: { + "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", + }, + }) + .then((res) => res.json()) + .then(({ product: dataProduct }) => { + setProduct(dataProduct) + setLoading(false) + }) + }, [loading]) + + const selectedVariant = useMemo(() => { + if ( + !product?.variants || + !product.options || + Object.keys(selectedOptions).length !== product.options?.length + ) { + return + } + + return product.variants.find((variant) => variant.options?.every( + (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!] + )) + }, [selectedOptions, product]) + + const formatPrice = (amount: number): string => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: region.currency_code, + }) + .format(amount) + } + + const variantPrice = useMemo(() => { + if (selectedVariant) { + return selectedVariant + } + + return product?.variants?.sort((a: any, b: any) => { + return ( + a.calculated_price.calculated_amount - + b.calculated_price.calculated_amount + ) + })[0] + }, [selectedVariant, product]) + + const price = useMemo(() => { + if (!variantPrice) { + return + } + + // @ts-ignore + return formatPrice(variantPrice.calculated_price.calculated_amount) + }, [variantPrice]) + + return ( +
+ {loading && Loading...} + {product && ( + <> +

{product.title}

+ {(product.options?.length || 0) > 0 && ( +
    + {product.options!.map((option) => ( +
  • + {option.title} + {option.values?.map((optionValue) => ( + + ))} +
  • + ))} +
+ )} + {selectedVariant && ( + Selected Variant: {selectedVariant.id} + )} + {price && ( + + {!selectedVariant && "From: "} + {price} + + )} + {product.images?.map((image) => ( + + ))} + + )} +
+ ) +} +``` + +In the example above, you: + +- Use the `useRegion` hook defined in the previous [Region React Context guide](../../../../regions/context/page.mdx). +- Pass the pricing query parameters to the request retrieving the product. This retrieves for every variant a new `calculated_price` field holding details about the variant's price. +- Choose the variant to show its price: + - If there's a selected variant, choose it. + - If there isn't a selected variant, retrieve and choose the variant with the cheapest price. +- Format the price based on the chosen variant in the previous step. The variant's `calculated_price.calculated_amount` field is used. +- Display the formatted price to the customer. If there isn't a select variant, show a `From` label to indicate that the price shown is the cheapest. + +### Price Formatting + +To format the price, use JavaScript's [NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) utility. You pass it the amount and the currency code (which you retrieve from the selected region): + +```ts +const formatPrice = (amount: number): string => { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: region.currency_code, + }) + .format(amount) +} +``` \ No newline at end of file diff --git a/www/apps/resources/app/storefront-development/products/price/page.mdx b/www/apps/resources/app/storefront-development/products/price/page.mdx index 3ad4e9eb34..a578faf1e5 100644 --- a/www/apps/resources/app/storefront-development/products/price/page.mdx +++ b/www/apps/resources/app/storefront-development/products/price/page.mdx @@ -1,4 +1,4 @@ -import { CodeTabs, CodeTab } from "docs-ui" +import { CodeTabs, CodeTab, Table } from "docs-ui" export const metadata = { title: `Retrieve Product Variant's Prices in Storefront`, @@ -8,382 +8,82 @@ export const metadata = { In this document, you'll learn how to show a product's variants' prices in the storefront, including handling sale prices. -## Pricing Parameters +## Pricing Query Parameters -When you retrieve products either with the [List Products](!api!/store#products_getproducts) or [Retrieve Products](!api!/store#products_getproductsid) API routes, you must pass at least one of the following query parameters to retrieve the correct product variant price: +When you retrieve products either with the [List Products](!api!/store#products_getproducts) or [Retrieve Products](!api!/store#products_getproductsid) API routes, you must pass include in the beginning of the `fields` query parameter the value `*variants.calculated_price`. -- `region_id`: The ID of the customer's region. -- `currency_code`: The currency code the customer is viewing prices in. +You also must pass at least one of the following query parameters to retrieve the correct product variant price: + +- `region_id`: The ID of the customer's region. This parameter must be included if you want to apply taxes on the product's price. +- `country_code`: The customer's country code. This parameter must be included if you want to apply taxes on the product's price. - `customer_id`: The ID of the customer viewing the prices. This is useful when you have a promotion or price list overriding a product's price for specific customer groups. - `customer_group_id`: The ID of the group of the customer viewing the prices. This is useful when you have a promotion or price list overriding a product's price for specific customer groups. -Also, you must include in the beginning of the `fields` query parameter the value `*variants.calculated_price`. +--- - +## Product Variant's Price Properties -The examples in this guide only pass the `region_id` query parameter. Learn how to store and retrieve the customer's region in the [Regions guides](../../regions/page.mdx). +If you pass the parameters mentioned above, each variant has a `calculated_price` object with the following properties: - + + + + Property + Description + Notes + + + + + + `calculated_amount` + + + The product variant's price. + + + Show this price if you didn't supply the `region_id` and `country_code` query parameters to retrieve prices with taxes applied. + + + + + `calculated_amount_with_tax` + + + The `calculated_amount` with taxes applied. + + + This property is only available if you supply both the `region_id` and `country_code` query parameters. + + + + + `calculated_amount_without_tax` + + + The `calculated_amount` without taxes applied. + + + This property is only available if you supply both the `region_id` and `country_code` query parameters. + + + + + `price_list_type` + + + The type of the variant price. + + + If its value is `sale`, it means the `calculated_amount` is a sale price. You can show the amount before the sale using the `original_amount` property. + + + +
--- -## Show Product Variant's Price +## Examples -The following React-based storefront example retrieves the product's price based on the selected variant: - -export const priceHighlights = [ - ["5", "useRegion", "The `useRegion` hook is implemented in the Region React Context guide."], - ["13", "{ params: { id } }: Params", "This is based on Next.js which passes the path parameters as a prop."], - ["19", "region", "Access the region using the `useRegion` hook."], - ["26", "queryParams", "Build the pricing query parameters."], - ["58", "formatPrice", "A utility function to format an amount with its currency."], - ["59", `"en-US"`, "If you use a different locale change it here."], - ["66", "variantPrice", "Assign the variant to compute its price, which is either the selected or cheapest variant."], - ["68", "selectedVariant", "Use the selected variant for pricing."], - ["71", "", "If there isn't a selected variant, retrieve the variant with the cheapest price."], - ["79", "price", "Compute the price of the selected or cheapest variant."], - ["123", "", "If there's a computed price but no selected variant, show a `From` prefix to the price."], - ["124", "price", "Display the computed price."] -] - -```tsx highlights={priceHighlights} -"use client" // include with Next.js 13+ - -import { useEffect, useMemo, useState } from "react" -import { HttpTypes } from "@medusajs/types" -import { useRegion } from "../providers/region" - -type Params = { - params: { - id: string - } -} - -export default function Product({ params: { id } }: Params) { - const [loading, setLoading] = useState(true) - const [product, setProduct] = useState< - HttpTypes.StoreProduct | undefined - >() - const [selectedOptions, setSelectedOptions] = useState>({}) - const { region } = useRegion - - useEffect(() => { - if (!loading) { - return - } - - const queryParams = new URLSearchParams({ - fields: `*variants.calculated_price`, - region_id: region.id, - }) - - fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { - credentials: "include", - headers: { - "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", - }, - }) - .then((res) => res.json()) - .then(({ product: dataProduct }) => { - setProduct(dataProduct) - setLoading(false) - }) - }, [loading]) - - const selectedVariant = useMemo(() => { - if ( - !product?.variants || - !product.options || - Object.keys(selectedOptions).length !== product.options?.length - ) { - return - } - - return product.variants.find((variant) => variant.options?.every( - (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!] - )) - }, [selectedOptions, product]) - - const formatPrice = (amount: number): string => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: region.currency_code, - }) - .format(amount) - } - - const variantPrice = useMemo(() => { - if (selectedVariant) { - return selectedVariant - } - - return product?.variants?.sort((a: any, b: any) => { - return ( - a.calculated_price.calculated_amount - - b.calculated_price.calculated_amount - ) - })[0] - }, [selectedVariant, product]) - - const price = useMemo(() => { - if (!variantPrice) { - return - } - - // @ts-ignore - return formatPrice(variantPrice.calculated_price.calculated_amount) - }, [variantPrice]) - - return ( -
- {loading && Loading...} - {product && ( - <> -

{product.title}

- {(product.options?.length || 0) > 0 && ( -
    - {product.options!.map((option) => ( -
  • - {option.title} - {option.values?.map((optionValue) => ( - - ))} -
  • - ))} -
- )} - {selectedVariant && ( - Selected Variant: {selectedVariant.id} - )} - {price && ( - - {!selectedVariant && "From: "} - {price} - - )} - {product.images?.map((image) => ( - - ))} - - )} -
- ) -} -``` - -In the example above, you: - -- Use the `useRegion` hook defined in the previous [Region React Context guide](../../regions/context/page.mdx). -- Pass the pricing query parameters to the request retrieving the product. This retrieves for every variant a new `calculated_price` field holding details about the variant's price. -- Choose the variant to show its price: - - If there's a selected variant, choose it. - - If there isn't a selected variant, retrieve and choose the variant with the cheapest price. -- Format the price based on the chosen variant in the previous step. The variant's `calculated_price.calculated_amount` field is used. -- Display the formatted price to the customer. If there isn't a select variant, show a `From` label to indicate that the price shown is the cheapest. - -### Price Formatting - -To format the price, use JavaScript's [NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) utility. You pass it the amount and the currency code (which you retrieve from the selected region): - -```ts -const formatPrice = (amount: number): string => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: region.currency_code, - }) - .format(amount) -} -``` - ---- - -## Show Product Variant's Sale Price - -To check if a product variant's price is a sale price, check whether the variant's `calculated_price.calculated_price.price_list_type` field is equal to `sale`. - -In that case, the original price is in the variant's `calculated_price.original_amount` field. - -For example, in a React-based storefront: - -export const saleHighlights = [ - ["5", "useRegion", "The `useRegion` hook is implemented in the Region React Context guide."], - ["13", "{ params: { id } }: Params", "This is based on Next.js which passes the path parameters as a prop."], - ["19", "region", "Access the region using the `useRegion` hook."], - ["88", "isSale", "Determine whether the price is a sale price based on the value of the variant's `calculated_price.calculated_price.price_list_type` field."], - ["97", "originalPrice", "Retrieve the original price from the variant's `calculated_price.original_amount` field if the price is a sale price."], - ["143", "", "If the price is a sale price, show that to the customer along with the original price."] -] - -```tsx highlights={saleHighlights} -"use client" // include with Next.js 13+ - -import { useEffect, useMemo, useState } from "react" -import { HttpTypes } from "@medusajs/types" -import { useRegion } from "../providers/region" - -type Params = { - params: { - id: string - } -} - -export default function Product({ params: { id } }: Params) { - const [loading, setLoading] = useState(true) - const [product, setProduct] = useState< - HttpTypes.StoreProduct | undefined - >() - const [selectedOptions, setSelectedOptions] = useState>({}) - const { region } = useRegion() - - useEffect(() => { - if (!loading) { - return - } - - const queryParams = new URLSearchParams({ - fields: `*variants.calculated_price`, - region_id: region.id, - }) - - fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { - credentials: "include", - headers: { - "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", - }, - }) - .then((res) => res.json()) - .then(({ product: dataProduct }) => { - setProduct(dataProduct) - setLoading(false) - }) - }, [loading]) - - const selectedVariant = useMemo(() => { - if ( - !product?.variants || - !product.options || - Object.keys(selectedOptions).length !== product.options?.length - ) { - return - } - - return product.variants.find((variant) => variant.options?.every( - (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!] - )) - }, [selectedOptions, product]) - - const formatPrice = (amount: number): string => { - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: region.currency_code, - }) - .format(amount) - } - - const variantPrice = useMemo(() => { - if (selectedVariant) { - return selectedVariant - } - - return product?.variants?.sort((a: any, b: any) => { - return ( - a.calculated_price.calculated_amount - - b.calculated_price.calculated_amount - ) - })[0] - }, [selectedVariant, product]) - - const price = useMemo(() => { - if (!variantPrice) { - return - } - - // @ts-ignore - return formatPrice(variantPrice.calculated_price.calculated_amount) - }, [variantPrice]) - - const isSale = useMemo(() => { - if (!variantPrice) { - return false - } - - // @ts-ignore - return variantPrice.calculated_price.calculated_price.price_list_type === "sale" - }, [variantPrice]) - - const originalPrice = useMemo(() => { - if (!isSale) { - return - } - - // @ts-ignore - return formatPrice(variantPrice.calculated_price.original_amount) - }, [isSale, variantPrice]) - - return ( -
- {loading && Loading...} - {product && ( - <> -

{product.title}

- {(product.options?.length || 0) > 0 && ( -
    - {product.options!.map((option) => ( -
  • - {option.title} - {option.values?.map((optionValue) => ( - - ))} -
  • - ))} -
- )} - {selectedVariant && ( - Selected Variant: {selectedVariant.id} - )} - {price && ( - - {!selectedVariant && "From: "} - {price} - {isSale && `SALE - Original Price: ${originalPrice}`} - - )} - {product.images?.map((image) => ( - - ))} - - )} -
- ) -} -``` - -In this example, you: - -- Define an `isSale` memo variable that determines whether the chosen variant's price is a sale price. You do that by checking if the value of the variant's `calculated_price.calculated_price.price_list_type` field is `sale`. -- Define an `originalPrice` memo variable that, if `isSale` is enabled, has the formatted original price of the chosen variant. The variant's original price is in the `calculated_price.original_amount` field. -- If `isSale` is enabled, show a message to the customer indicating that this product is on sale along with the original price. +- [React Example: Show Product Variant's Price](./examples/show-price/page.mdx). +- [React Example: Show Product Variant's Sale Price](./examples/sale-price/page.mdx). diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index 8f31d58119..667fff0b25 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -431,6 +431,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/commerce-modules/pricing/relations-to-other-modules/page.mdx", "pathname": "/commerce-modules/pricing/relations-to-other-modules" }, + { + "filePath": "/www/apps/resources/app/commerce-modules/pricing/tax-inclusive-pricing/page.mdx", + "pathname": "/commerce-modules/pricing/tax-inclusive-pricing" + }, { "filePath": "/www/apps/resources/app/commerce-modules/product/_events/_events-table/page.mdx", "pathname": "/commerce-modules/product/_events/_events-table" @@ -899,6 +903,14 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/storefront-development/products/page.mdx", "pathname": "/storefront-development/products" }, + { + "filePath": "/www/apps/resources/app/storefront-development/products/price/examples/sale-price/page.mdx", + "pathname": "/storefront-development/products/price/examples/sale-price" + }, + { + "filePath": "/www/apps/resources/app/storefront-development/products/price/examples/show-price/page.mdx", + "pathname": "/storefront-development/products/price/examples/show-price" + }, { "filePath": "/www/apps/resources/app/storefront-development/products/price/page.mdx", "pathname": "/storefront-development/products/price" diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs index c2d06148d1..0b3703c730 100644 --- a/www/apps/resources/generated/sidebar.mjs +++ b/www/apps/resources/generated/sidebar.mjs @@ -3777,6 +3777,13 @@ export const generatedSidebar = [ "title": "Prices Calculation", "children": [] }, + { + "loaded": true, + "isPathHref": true, + "path": "/commerce-modules/pricing/tax-inclusive-pricing", + "title": "Tax-Inclusive Pricing", + "children": [] + }, { "loaded": true, "isPathHref": true, @@ -7644,7 +7651,22 @@ export const generatedSidebar = [ "isPathHref": true, "path": "/storefront-development/products/price", "title": "Retrieve Variant Prices", - "children": [] + "children": [ + { + "loaded": true, + "isPathHref": true, + "path": "/storefront-development/products/price/examples/show-price", + "title": "Example: Show Variant Price", + "children": [] + }, + { + "loaded": true, + "isPathHref": true, + "path": "/storefront-development/products/price/examples/sale-price", + "title": "Example: Show Sale Price", + "children": [] + } + ] }, { "loaded": true, diff --git a/www/apps/resources/sidebar.mjs b/www/apps/resources/sidebar.mjs index e67c747aa9..50d9461203 100644 --- a/www/apps/resources/sidebar.mjs +++ b/www/apps/resources/sidebar.mjs @@ -697,6 +697,10 @@ export const sidebar = sidebarAttachHrefCommonOptions([ path: "/commerce-modules/pricing/price-calculation", title: "Prices Calculation", }, + { + path: "/commerce-modules/pricing/tax-inclusive-pricing", + title: "Tax-Inclusive Pricing", + }, { path: "/commerce-modules/pricing/relations-to-other-modules", title: "Relation to Modules", @@ -1835,6 +1839,16 @@ export const sidebar = sidebarAttachHrefCommonOptions([ { path: "/storefront-development/products/price", title: "Retrieve Variant Prices", + children: [ + { + path: "/storefront-development/products/price/examples/show-price", + title: "Example: Show Variant Price", + }, + { + path: "/storefront-development/products/price/examples/sale-price", + title: "Example: Show Sale Price", + }, + ], }, { path: "/storefront-development/products/categories",