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:
@@ -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¤cy_code=usd`
|
||||
`/store/products?fields=*variants.calculated_price®ion_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¤cy_code=usd`
|
||||
`/store/products/${product.id}?fields=*variants.calculated_price®ion_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®ion_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®ion_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®ion_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®ion_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®ion_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®ion_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")
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user