docs: added storefront guide on prices with taxes (#8326)

This commit is contained in:
Shahed Nasser
2024-07-30 12:52:21 +03:00
committed by GitHub
parent 4bd77174f0
commit 70d48a91b3
7 changed files with 391 additions and 56 deletions
@@ -1,14 +1,52 @@
---
sidebar_position: 2
sidebar_label: "Example: Show Sale Price"
---
export const metadata = {
title: `React Example: Show Product Variant's Sale Price`,
title: `Example: Show Product Variant's Sale Price`,
}
# {metadata.title}
This document provides an example of how to show a product variant's sale price.
In this document, you'll learn how to display a product variant's sale price, with a full React example.
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`.
## Check if a Price is a Sale
In that case, the original price is in the variant's `calculated_price.original_amount` field.
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`:
```ts
const isSale = selectedVariantPrice.calculated_price.calculated_price.price_list_type === "sale"
```
Where `selectedVariantPrice` is either the variant the customer selected or the cheapest variant.
---
## Display Original and Discount Amounts
If the price is a sale price, the original price is in the variant's `calculated_price.original_amount` field:
```ts
const salePrice = formatPrice(selectedVariantPrice.calculated_price.calculated_amount)
const originalPrice = formatPrice(selectedVariantPrice.calculated_price.original_amount)
const discountedAmount = formatPrice(
selectedVariantPrice.calculated_price.original_amount -
selectedVariantPrice.calculated_price.calculated_amount
)
```
You can use the original price either to display it or calculate and display the discounted amount.
<Note title="Tip">
Learn more about the `formatPrice` function in [this guide](../show-price/page.mdx#price-formatting)
</Note>
---
## Full React Example
For example, in a React-based storefront:
@@ -93,7 +131,7 @@ export default function Product({ params: { id } }: Params) {
.format(amount)
}
const variantPrice = useMemo(() => {
const selectedVariantPrice = useMemo(() => {
if (selectedVariant) {
return selectedVariant
}
@@ -107,22 +145,22 @@ export default function Product({ params: { id } }: Params) {
}, [selectedVariant, product])
const price = useMemo(() => {
if (!variantPrice) {
if (!selectedVariantPrice) {
return
}
// @ts-ignore
return formatPrice(variantPrice.calculated_price.calculated_amount)
}, [variantPrice])
return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)
}, [selectedVariantPrice])
const isSale = useMemo(() => {
if (!variantPrice) {
if (!selectedVariantPrice) {
return false
}
// @ts-ignore
return variantPrice.calculated_price.calculated_price.price_list_type === "sale"
}, [variantPrice])
return selectedVariantPrice.calculated_price.calculated_price.price_list_type === "sale"
}, [selectedVariantPrice])
const originalPrice = useMemo(() => {
if (!isSale) {
@@ -130,8 +168,8 @@ export default function Product({ params: { id } }: Params) {
}
// @ts-ignore
return formatPrice(variantPrice.calculated_price.original_amount)
}, [isSale, variantPrice])
return formatPrice(selectedVariantPrice.calculated_price.original_amount)
}, [isSale, selectedVariantPrice])
return (
<div>
@@ -188,9 +226,3 @@ 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.
<Note title="Tip">
Learn more about the `formatPrice` function in [this guide](../show-price/page.mdx#price-formatting)
</Note>
@@ -1,9 +1,48 @@
---
sidebar_position: 1
sidebar_label: "Example: Show Variant's Price"
---
export const metadata = {
title: `React Example: Show Product Variant's Price`,
title: `Example: Show Product Variant's Price`,
}
# {metadata.title}
In this document, you'll learn how to display a product variant's price with a full React example.
## Display Selected Variant Price
Once the customer selects a variant, use its `calculated_price.calculated_amount` property to display its price:
```ts
const price = formatPrice(
selectedVariantPrice.calculated_price.calculated_amount
)
```
You'll learn about the `formatPrice` function in the next section.
---
## 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)
}
```
---
## Full React Example
The following React-based storefront example retrieves the product's price based on the selected variant:
<Note type="check">
@@ -19,7 +58,7 @@ export const priceHighlights = [
["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."],
["66", "selectedVariantPrice", "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."],
@@ -46,7 +85,7 @@ export default function Product({ params: { id } }: Params) {
HttpTypes.StoreProduct | undefined
>()
const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({})
const { region } = useRegion
const { region } = useRegion()
useEffect(() => {
if (!loading) {
@@ -93,7 +132,7 @@ export default function Product({ params: { id } }: Params) {
.format(amount)
}
const variantPrice = useMemo(() => {
const selectedVariantPrice = useMemo(() => {
if (selectedVariant) {
return selectedVariant
}
@@ -107,13 +146,13 @@ export default function Product({ params: { id } }: Params) {
}, [selectedVariant, product])
const price = useMemo(() => {
if (!variantPrice) {
if (!selectedVariantPrice) {
return
}
// @ts-ignore
return formatPrice(variantPrice.calculated_price.calculated_amount)
}, [variantPrice])
return formatPrice(selectedVariantPrice.calculated_price.calculated_amount)
}, [selectedVariantPrice])
return (
<div>
@@ -173,17 +212,3 @@ In the example above, you:
- 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)
}
```
@@ -0,0 +1,253 @@
---
sidebar_position: 3
sidebar_label: "Example: Show Price with Taxes"
---
export const metadata = {
title: `Example: Show Product Variant's Price with Taxes`,
}
# {metadata.title}
In this document, you'll learn how to show a product variant's price with taxes, with a full React example.
## Retrieve Variant's Price with Tax
To retrieve a product variant's price with taxes, you must pass the `region_id` and `country_code` query parameters:
export const fetchHighlights = [
["3", "region_id", "Pass the region ID as a query parameter."],
["4", "country_code", "Pass the ISO 2 country code as a parameter."],
["4", "region.countries[0].iso_2", "You can instead allow the customer to select a specific country."],
]
```ts highlights={fetchHighlights}
const queryParams = new URLSearchParams({
fields: `*variants.calculated_price`,
region_id: region.id,
country_code: region.countries[0].iso_2
})
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 }) => {
// TODO use product
console.log(product)
})
```
You pass the selected region's ID and the code of its first country as query parameters to the [Get Product API route](https://docs.medusajs.com/v2/api/store#products_getproductsid).
<Note title="Tip">
You can instead allow the customer to choose their country.
</Note>
---
## Display Variant's Price with Taxes
After passing the `region_id` and `country_code` as query parameters when retrieving the product, each variant's price object will have a `calculated_amount_with_tax` property to indicate the price taxes applied:
```ts
const price = formatPrice(selectedVariantPrice.calculated_price.calculated_amount_with_tax)
```
Where `selectedVariantPrice` is either the variant the customer selected or the cheapest variant.
<Note title="Tip">
Learn more about the `formatPrice` function in [this guide](../show-price/page.mdx#price-formatting)
</Note>
---
## Tax Price Properties
Aside from the `calculated_amount_with_tax` property, a variant's `calculated_price` object has the following properties related to taxes:
1. `calculated_amount_without_tax`: The calculated amount without taxes.
2. `is_calculated_price_tax_inclusive`: Whether the `calculated_amount` property includes taxes. If enabled, you can display it instead of `calculated_amount_with_tax`.
---
## Full React Example
For example, in a React-based storefront:
<Note type="check">
The example 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).
</Note>
export const taxHighlight = [
["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."],
["28", "region_id", "Pass the region ID as a query parameter."],
["29", "country_code", "Pass the ISO 2 country code as a parameter."],
["29", "region.countries[0].iso_2", "You can instead allow the customer to select a specific country."],
["59", "formatPrice", "A utility function to format an amount with its currency."],
["60", `"en-US"`, "If you use a different locale change it here."],
["67", "selectedVariantPrice", "Assign the variant to compute its price, which is either the selected or cheapest variant."],
["69", "selectedVariant", "Use the selected variant for pricing."],
["72", "", "If there isn't a selected variant, retrieve the variant with the cheapest price."],
["80", "price", "Compute the price of the selected or cheapest variant."],
["87", "calculated_amount_with_tax", "Use the variant price's `calculated_amount_with_tax` property to display the price."],
["126", "", "If there's a computed price but no selected variant, show a `From` prefix to the price."],
["127", "price", "Display the computed price."]
]
```tsx highlights={taxHighlight}
"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<Record<string, string>>({})
const region = useRegion()
useEffect(() => {
if (!loading) {
return
}
const queryParams = new URLSearchParams({
fields: `*variants.calculated_price`,
region_id: region.id,
country_code: region.countries[0].iso_2
})
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 selectedVariantPrice = useMemo(() => {
if (selectedVariant) {
return selectedVariant
}
return product?.variants?.sort((a: any, b: any) => {
return (
a.calculated_price.calculated_amount_with_tax -
b.calculated_price.calculated_amount_with_tax
)
})[0]
}, [selectedVariant, product])
const price = useMemo(() => {
if (!selectedVariantPrice) {
return
}
// @ts-ignore
return formatPrice(
selectedVariantPrice.calculated_price.calculated_amount_with_tax
)
}, [selectedVariantPrice])
return (
<div>
{loading && <span>Loading...</span>}
{product && (
<>
<h1>{product.title}</h1>
{(product.options?.length || 0) > 0 && (
<ul>
{product.options!.map((option) => (
<li key={option.id}>
{option.title}
{option.values?.map((optionValue) => (
<button
key={optionValue.id}
onClick={() => {
setSelectedOptions((prev) => {
return {
...prev,
[option.id!]: optionValue.value!,
}
})
}}
>
{optionValue.value}
</button>
))}
</li>
))}
</ul>
)}
{selectedVariant && (
<span>Selected Variant: {selectedVariant.id}</span>
)}
{price && (
<span>
{!selectedVariant && "From: "}
{price}
</span>
)}
{product.images?.map((image) => (
<img src={image.url} key={image.id} />
))}
</>
)}
</div>
)
}
```
In this example, you:
- Pass the selected region's ID and the code of its first country as query parameters to the [Get Product API route](https://docs.medusajs.com/v2/api/store#products_getproductsid).
- You can instead allow the customer to choose their country.
- Display the selected variant's price by formatting its price's `calculated_amount_with_tax` property.
@@ -10,7 +10,7 @@ In this document, you'll learn how to show a product's variants' prices in the s
## 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 include in the beginning of the `fields` query parameter the value `*variants.calculated_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 include in the beginning of the `fields` query parameter the value `*variants.calculated_price`.
You also must pass at least one of the following query parameters to retrieve the correct product variant price:
@@ -19,6 +19,29 @@ You also must pass at least one of the following query parameters to retrieve th
- `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.
For example:
```ts
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 }) => {
// TODO use product
console.log(product)
})
```
In this example, you pass the selected region's ID as a query parameter with the `fields` query parameter set to `*variants.calculated_price`.
---
## Product Variant's Price Properties
@@ -81,9 +104,8 @@ If you pass the parameters mentioned above, each variant has a `calculated_price
</Table.Body>
</Table>
---
## Examples
- [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).
- [Example: Show Product Variant's Price](./examples/show-price/page.mdx).
- [Example: Show Product Variant's Sale Price](./examples/sale-price/page.mdx).
- [Example: Show Product Variant's Price with Taxes](./examples/tax-price/page.mdx).
@@ -923,6 +923,10 @@ export const filesMap = [
"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/examples/tax-price/page.mdx",
"pathname": "/storefront-development/products/price/examples/tax-price"
},
{
"filePath": "/www/apps/resources/app/storefront-development/products/price/page.mdx",
"pathname": "/storefront-development/products/price"
+9 -1
View File
@@ -6755,12 +6755,13 @@ export const generatedSidebar = [
"isPathHref": true,
"path": "/storefront-development/products/price",
"title": "Retrieve Variant Prices",
"autogenerate_path": "storefront-development/products/price/examples",
"children": [
{
"loaded": true,
"isPathHref": true,
"path": "/storefront-development/products/price/examples/show-price",
"title": "Example: Show Variant Price",
"title": "Example: Show Variant's Price",
"children": []
},
{
@@ -6769,6 +6770,13 @@ export const generatedSidebar = [
"path": "/storefront-development/products/price/examples/sale-price",
"title": "Example: Show Sale Price",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"path": "/storefront-development/products/price/examples/tax-price",
"title": "Example: Show Price with Taxes",
"children": []
}
]
},
+1 -10
View File
@@ -1537,16 +1537,7 @@ 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",
},
],
autogenerate_path: "storefront-development/products/price/examples",
},
{
path: "/storefront-development/products/categories",