feat: Add necessary middlewares for tax inclusive pricing (#7827)

We are adding tax inclusive pricing calculation when listing products.

Two things to keep in mind:
- `region_id` will be required if you request calculated prices.
- We won't accept `currency_code` anymore, as that will come from the region info (since ultimately a cart and its currency are tied to a region)

REF CORE-2376
DEPENDS ON #8003
This commit is contained in:
Stevche Radevski
2024-07-09 09:37:13 +00:00
committed by GitHub
parent db6969578f
commit 1c3ef13371
22 changed files with 824 additions and 121 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import { HttpTypes } from "@medusajs/types"
export const getProductFixture = (
overrides: Partial<HttpTypes.AdminProduct>
overrides: Partial<HttpTypes.AdminCreateProduct>
) => ({
title: "Test fixture",
description: "test-product-description",
@@ -892,7 +892,7 @@ medusaIntegrationTestRunner({
expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
message:
"Missing required pricing context to calculate prices - currency_code or region_id",
"Missing required pricing context to calculate prices - region_id",
type: "invalid_data",
})
})
@@ -907,7 +907,7 @@ medusaIntegrationTestRunner({
).data.region
let response = await api.get(
`/store/products?fields=*variants.calculated_price&currency_code=usd`
`/store/products?fields=*variants.calculated_price&region_id=${region.id}`
)
const expectation = expect.arrayContaining([
@@ -948,12 +948,6 @@ medusaIntegrationTestRunner({
expect(response.data.count).toEqual(3)
expect(response.data.products).toEqual(expectation)
// Without calculated_price fields
response = await api.get(`/store/products?currency_code=usd`)
expect(response.status).toEqual(200)
expect(response.data.products).toEqual(expectation)
// with only region_id
response = await api.get(`/store/products?region_id=${region.id}`)
@@ -1205,7 +1199,7 @@ medusaIntegrationTestRunner({
expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
message:
"Missing required pricing context to calculate prices - currency_code or region_id",
"Missing required pricing context to calculate prices - region_id",
type: "invalid_data",
})
})
@@ -1220,7 +1214,7 @@ medusaIntegrationTestRunner({
).data.region
let response = await api.get(
`/store/products/${product.id}?fields=*variants.calculated_price&currency_code=usd`
`/store/products/${product.id}?fields=*variants.calculated_price&region_id=${region.id}`
)
const expectation = expect.objectContaining({
@@ -1258,14 +1252,6 @@ medusaIntegrationTestRunner({
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(expectation)
// Without calculated_price fields
response = await api.get(
`/store/products/${product.id}?currency_code=usd`
)
expect(response.status).toEqual(200)
expect(response.data.product).toEqual(expectation)
// with only region_id
response = await api.get(
`/store/products/${product.id}?region_id=${region.id}`
@@ -1275,5 +1261,317 @@ medusaIntegrationTestRunner({
expect(response.data.product).toEqual(expectation)
})
})
describe("Tax handling", () => {
let usRegion
let euRegion
let dkRegion
let euCart
beforeEach(async () => {
usRegion = (
await api.post(
"/admin/regions",
{
name: "Test Region",
currency_code: "usd",
countries: ["us"],
is_tax_inclusive: false,
automatic_taxes: false,
},
adminHeaders
)
).data.region
euRegion = (
await api.post(
"/admin/regions",
{
name: "Test Region",
currency_code: "eur",
countries: ["it", "de"],
is_tax_inclusive: true,
automatic_taxes: true,
},
adminHeaders
)
).data.region
dkRegion = (
await api.post(
"/admin/regions",
{
name: "Test Region",
currency_code: "dkk",
countries: ["dk"],
is_tax_inclusive: false,
automatic_taxes: true,
},
adminHeaders
)
).data.region
product1 = (
await api.post(
"/admin/products",
getProductFixture({
title: "test1",
status: "published",
variants: [
{
title: "Test taxes",
prices: [
{
amount: 45,
currency_code: "eur",
rules: { region_id: euRegion.id },
},
{
amount: 100,
currency_code: "usd",
rules: { region_id: usRegion.id },
},
{
amount: 30,
currency_code: "dkk",
rules: { region_id: dkRegion.id },
},
],
},
],
}),
adminHeaders
)
).data.product
euCart = (await api.post("/store/carts", { region_id: euRegion.id }))
.data.cart
await api.post(
`/admin/tax-regions`,
{
country_code: "us",
default_tax_rate: {
code: "default",
rate: 5,
name: "default rate",
},
},
adminHeaders
)
await api.post(
`/admin/tax-regions`,
{
country_code: "it",
default_tax_rate: {
code: "default",
rate: 10,
name: "default rate",
},
},
adminHeaders
)
await api.post(
`/admin/tax-regions`,
{
country_code: "dk",
default_tax_rate: {
code: "default",
rate: 20,
name: "default rate",
},
},
adminHeaders
)
})
it("should not return tax pricing if the context is not sufficient when listing products", async () => {
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&region_id=${usRegion.id}`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_with_tax"
)
expect(products[0].variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_without_tax"
)
})
it("should not return tax pricing if automatic taxes are off when listing products", async () => {
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&region_id=${usRegion.id}&country_code=us`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_with_tax"
)
expect(products[0].variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_without_tax"
)
})
it("should return prices with and without tax for a tax inclusive region when listing products", async () => {
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&region_id=${euRegion.id}&country_code=it`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
calculated_price: expect.objectContaining({
currency_code: "eur",
calculated_amount: 45,
calculated_amount_with_tax: 45,
}),
}),
])
)
// TODO: Return an integer instead of a float for the pricing
expect(
products[0].variants[0].calculated_price.calculated_amount_without_tax.toFixed(
1
)
).toEqual("40.9")
})
it("should return prices with and without tax for a tax exclusive region when listing products", async () => {
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&region_id=${dkRegion.id}&country_code=dk`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
calculated_price: expect.objectContaining({
currency_code: "dkk",
calculated_amount: 30,
calculated_amount_with_tax: 36,
calculated_amount_without_tax: 30,
}),
}),
])
)
})
it("should return prices with and without tax when the cart is available and a country is passed when listing products", async () => {
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&cart_id=${euCart.id}&country_code=it`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
calculated_price: expect.objectContaining({
currency_code: "eur",
calculated_amount: 45,
calculated_amount_with_tax: 45,
}),
}),
])
)
// TODO: Return an integer instead of a float for the pricing
expect(
products[0].variants[0].calculated_price.calculated_amount_without_tax.toFixed(
1
)
).toEqual("40.9")
})
it("should return prices with and without tax when the cart context is available when listing products", async () => {
await api.post(`/store/carts/${euCart.id}`, {
shipping_address: {
country_code: "it",
},
})
const products = (
await api.get(
`/store/products?fields=id,*variants.calculated_price&cart_id=${euCart.id}`
)
).data.products
expect(products.length).toBe(1)
expect(products[0].variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
calculated_price: expect.objectContaining({
currency_code: "eur",
calculated_amount: 45,
calculated_amount_with_tax: 45,
}),
}),
])
)
// TODO: Return an integer instead of a float for the pricing
expect(
products[0].variants[0].calculated_price.calculated_amount_without_tax.toFixed(
1
)
).toEqual("40.9")
})
it("should not return tax pricing if the context is not sufficient when fetching a single product", async () => {
const product = (
await api.get(
`/store/products/${product1.id}?fields=id,*variants.calculated_price&region_id=${usRegion.id}`
)
).data.product
expect(product.variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_with_tax"
)
expect(product.variants[0].calculated_price).not.toHaveProperty(
"calculated_amount_without_tax"
)
})
it("should return prices with and without tax for a tax inclusive region when fetching a single product", async () => {
const product = (
await api.get(
`/store/products/${product1.id}?fields=id,*variants.calculated_price&region_id=${euRegion.id}&country_code=it`
)
).data.product
expect(product.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
calculated_price: expect.objectContaining({
currency_code: "eur",
calculated_amount: 45,
calculated_amount_with_tax: 45,
}),
}),
])
)
// TODO: Return an integer instead of a float for the pricing
expect(
product.variants[0].calculated_price.calculated_amount_without_tax.toFixed(
1
)
).toEqual("40.9")
})
})
},
})