feat(medusa): Tax-inclusive pricing (#2131)

* add feature flag for tax inclusive pricing

* update db model for TIP

* add migration

* set featureflag column decorators

* remove unused prop

* update tests to reflect feature_flags as any array

* fix types

* reference key from featureFlag file

* use feature flag key in models

* fix copy paste mistake

* unify spelling

* Create gorgeous-experts-guess.md

* feat(medusa): create/update endpoints of currency/region/price-lists/shipping-options should allow to pass includes_tax

* test(integration): continue to add some integration test

* test(integration): continue to add some integration test

* test(unit): Fix region service tests

* fix(medusa): API unit tests flags management

* feat(medusa): Minor cleanup

* style(medusa): Fix typo

* fix(medusa): rebase

* feat(medusa): Replace old tag with the new one

* feat(medusa): revert flag

* feat(medusa): Cleanup

* feat(medusa): feedback

* feat(medusa): Rename currency retrieve method

* test(medudsa): fix unit tests

* chore(medusa): fix oas

* feat(medusa): ShippingMethod should include tax setting from parent option (#2021)

* feat(medusa): Shipping method should includes tax from parent options

* feat(medusa): Condition the includes tax flag to the availability of the feature and add some other tests

* test(integration): Move cart/order ff test in separate files

* fix: snapshots folder

* fix(integration): snapshots

* Create calm-baboons-sit.md

* test(integration): file naming

Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>

* Feat/tax inclusive pricing extend price selection strategy (#2087)

* initial changes to price selection strategy including unit tests

* typing for tax calculation

* update types and remove region and currency from prices results

* fix casing

* include tax calculation in priceselectionstrategy

* integration tests for tax inclusive pricing price calculations

* fix build

* include tax inclusive considerations when calculating tax fields for variants

* include only "includes_tax" fields from currency and region joins

* test to see errors in pipelines

* conditionally join featureflagged fields

* add "includes_tax" to price list factory

* add tests for tax inclusive price list prices and currency prices

* fix unit tests

* refactor pricing array checks to expect arraycontaining

* undo error handler

* Feat/tax inclusive pricing flag on generated lineitems (#2108)

* include tax inclusive pricing flag on generated lineitems

* initial addition of tax inclusivity for lineitem service

* add generate test to ensure that includes_tax is set when returned from price selection strategy

* add integration test for generating lineitem including tax

* add test for negative tax inclusion

* add tests for mixed pricing

* add negative test for setting tax exclusivity

* restructure the setting of includes_tax on lineitems

* fix: update cwd to be correct in cart test

* feat(medusa): Line item totals calculations (#2123)

* feat(medusa): Update totals and tax calculation way to calculate the totals

* feat(medusa): remove region feetching from decorate total

* feat(medusa): cleanup

* test(medusa): fix tax calculation tests

* comment

* test(integration): cleanup

* test(integration): cleanup

* fix(medusa): return service missing await

* feat(medusa): cleanup

* feat(medusa): cleanup

* test(integration): fix data

* feat(medusa): improve tax calculation readability

* test(medusa): improve tax calculation structure case

Co-authored-by: Sebastian Rindom <skrindom@gmail.com>

* Feat(medusa): tax inclusive pricing in shipping method tax (#2125)

* initial implementation and test

* include tax inclusive calculations for getting shipping options

* remove inaccurate comment

* remove console log

* refactor how prices and taxes are set for shipping methods

* fix integration tests

* remove verbose flag

* fix integration tests

* remove console log

* format util

* use util in price service and tax strategy

* fix faulty integration test

* undo tax calculation strategy changes in favor or Carlos' pr

* undo changes to tax calculation strategy tests

* round tax amount

* feat(medusa): cleanup calculate tax amount utils and its usage (#2136)

* feat(medusa): Refund line totals calculation (#2139)

Rely on the update of the following pr https://github.com/medusajs/medusa/pull/2136

**WIP Missing integration tests**

**What**

Update the totals calculation on the refund line to include the notion of tax inclusive

**Test**

- Update and add new tests around the refund


Fixes CORE-482

* feat(medusa): Tax inclusive discounts calculations (#2137)

**What**

- Calculate line adjustments correctly taking into account the tax inclusivity
- fix totals getLineItemTotals by adjusting the sub total with the original tax amount instead of the tax amount when the unit price includes the taxes

**Tests**
- The tests create a cart with a percentage discount of 15%, the cart includes 2 items mixing the tax inclusive and validate the items on the result cart as well as the totals on each item. I ve based my calculation validation based on what we have done + some articles around discount apply on price without taxes to validate the output.,
FIXES CORE-477

* Chore: shipping methods tax inclusive total (#2130)

* chore: calculate tax inclusive shipping methods

* chore: additional tests and check undefined tax_rate (#2157)

* chore: additional tests and check undefined tax_rate

* fix: naming + correct price type check

* fix: remove price_includes_tax from type

* fix: remove price_includes_tax from type

Co-authored-by: Philip Korsholm <philip.korsholm@hotmail.com>
Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
Co-authored-by: Philip Korsholm <88927411+pKorsholm@users.noreply.github.com>
Co-authored-by: Sebastian Rindom <skrindom@gmail.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Oliver Windall Juhl
2022-09-07 10:21:29 +02:00
committed by GitHub
co-authored by Philip Korsholm adrien2p Carlos R. L. Rodrigues Philip Korsholm Sebastian Rindom Carlos R. L. Rodrigues
parent af80e0fd2e
commit cbb7dd9787
105 changed files with 6788 additions and 1040 deletions
@@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`/admin/currencies GET /admin/currencies should retrieve the currencies 1`] = `
Object {
"count": 120,
"currencies": Array [
Object {
"code": "aed",
"includes_tax": false,
"name": "United Arab Emirates Dirham",
"symbol": "AED",
"symbol_native": "د.إ.",
},
Object {
"code": "afn",
"includes_tax": false,
"name": "Afghan Afghani",
"symbol": "Af",
"symbol_native": "؋",
},
Object {
"code": "all",
"includes_tax": false,
"name": "Albanian Lek",
"symbol": "ALL",
"symbol_native": "Lek",
},
Object {
"code": "amd",
"includes_tax": false,
"name": "Armenian Dram",
"symbol": "AMD",
"symbol_native": "դր.",
},
Object {
"code": "ars",
"includes_tax": false,
"name": "Argentine Peso",
"symbol": "AR$",
"symbol_native": "$",
},
Object {
"code": "aud",
"includes_tax": false,
"name": "Australian Dollar",
"symbol": "AU$",
"symbol_native": "$",
},
Object {
"code": "azn",
"includes_tax": false,
"name": "Azerbaijani Manat",
"symbol": "man.",
"symbol_native": "ман.",
},
Object {
"code": "bam",
"includes_tax": false,
"name": "Bosnia-Herzegovina Convertible Mark",
"symbol": "KM",
"symbol_native": "KM",
},
Object {
"code": "bdt",
"includes_tax": false,
"name": "Bangladeshi Taka",
"symbol": "Tk",
"symbol_native": "৳",
},
Object {
"code": "bgn",
"includes_tax": false,
"name": "Bulgarian Lev",
"symbol": "BGN",
"symbol_native": "лв.",
},
Object {
"code": "bhd",
"includes_tax": false,
"name": "Bahraini Dinar",
"symbol": "BD",
"symbol_native": "د.ب.",
},
Object {
"code": "bif",
"includes_tax": false,
"name": "Burundian Franc",
"symbol": "FBu",
"symbol_native": "FBu",
},
Object {
"code": "bnd",
"includes_tax": false,
"name": "Brunei Dollar",
"symbol": "BN$",
"symbol_native": "$",
},
Object {
"code": "bob",
"includes_tax": false,
"name": "Bolivian Boliviano",
"symbol": "Bs",
"symbol_native": "Bs",
},
Object {
"code": "brl",
"includes_tax": false,
"name": "Brazilian Real",
"symbol": "R$",
"symbol_native": "R$",
},
Object {
"code": "bwp",
"includes_tax": false,
"name": "Botswanan Pula",
"symbol": "BWP",
"symbol_native": "P",
},
Object {
"code": "byn",
"includes_tax": false,
"name": "Belarusian Ruble",
"symbol": "Br",
"symbol_native": "руб.",
},
Object {
"code": "bzd",
"includes_tax": false,
"name": "Belize Dollar",
"symbol": "BZ$",
"symbol_native": "$",
},
Object {
"code": "cad",
"includes_tax": false,
"name": "Canadian Dollar",
"symbol": "CA$",
"symbol_native": "$",
},
Object {
"code": "cdf",
"includes_tax": false,
"name": "Congolese Franc",
"symbol": "CDF",
"symbol_native": "FrCD",
},
],
"limit": 20,
"offset": 0,
}
`;
exports[`/admin/currencies POST /admin/currencies/:code should update currency includes_tax 1`] = `
Object {
"currency": Object {
"code": "aed",
"includes_tax": true,
"name": "United Arab Emirates Dirham",
"symbol": "AED",
"symbol_native": "د.إ.",
},
}
`;
@@ -121,12 +121,7 @@ Object {
"symbol_native": "$",
},
"default_currency_code": "usd",
"feature_flags": Array [
Object {
"key": "sales_channels",
"value": false,
},
],
"feature_flags": Any<Array>,
"fulfillment_providers": Array [
Object {
"id": "test-ful",
@@ -0,0 +1,89 @@
const path = require("path")
const startServerWithEnvironment =
require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder");
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
jest.setTimeout(30000)
describe("/admin/currencies", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("GET /admin/currencies", function () {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
} catch (e) {
console.error(e)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should retrieve the currencies", async () => {
const api = useApi()
const response = await api.get(
`/admin/currencies?order=code`,
adminReqConfig
)
expect(response.data).toMatchSnapshot()
})
});
describe("POST /admin/currencies/:code", function () {
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
} catch (e) {
console.error(e)
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should update currency includes_tax", async () => {
const api = useApi()
const response = await api.post(
`/admin/currencies/aed`,
{
includes_tax: true
},
adminReqConfig
)
expect(response.data).toMatchSnapshot()
})
});
})
@@ -0,0 +1,101 @@
const path = require("path")
const startServerWithEnvironment =
require("../../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../../helpers/use-api")
const { useDb } = require("../../../../helpers/use-db")
const adminSeeder = require("../../../helpers/admin-seeder")
const {
simpleRegionFactory,
simpleShippingOptionFactory,
simpleOrderFactory
} = require("../../../factories");
jest.setTimeout(30000)
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/orders", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /admin/orders/:id/shipping-methods", () => {
let includesTaxShippingOption
let order
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
const shippingAddress = {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
}
const region = await simpleRegionFactory(dbConnection, {
id: "test-region"
})
order = await simpleOrderFactory(dbConnection, {
id: "test-order",
region: region.id,
shipping_address: shippingAddress,
currency_code: "usd",
})
includesTaxShippingOption = await simpleShippingOptionFactory(dbConnection, {
includes_tax: true,
region_id: region.id
})
} catch (err) {
console.log(err)
}
})
afterEach(async() => {
const db = useDb()
return await db.teardown()
})
it("should add a normal shipping method to the order", async () => {
const api = useApi()
const orderWithShippingMethodRes = await api.post(
`/admin/orders/${order.id}/shipping-methods`,
{
option_id: includesTaxShippingOption.id,
price: 10,
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
expect(orderWithShippingMethodRes.status).toEqual(200)
expect(orderWithShippingMethodRes.data.order.shipping_methods)
.toEqual(expect.arrayContaining([
expect.objectContaining({
shipping_option_id: includesTaxShippingOption.id,
includes_tax: true,
})
]))
})
})
})
@@ -7,21 +7,21 @@ const {
ShippingMethod,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const setupServer = require("../../../../helpers/setup-server")
const { useApi } = require("../../../../helpers/use-api")
const { initDb, useDb } = require("../../../../helpers/use-db")
const orderSeeder = require("../../helpers/order-seeder")
const swapSeeder = require("../../helpers/swap-seeder")
const adminSeeder = require("../../helpers/admin-seeder")
const claimSeeder = require("../../helpers/claim-seeder")
const orderSeeder = require("../../../helpers/order-seeder")
const swapSeeder = require("../../../helpers/swap-seeder")
const adminSeeder = require("../../../helpers/admin-seeder")
const claimSeeder = require("../../../helpers/claim-seeder")
const {
expectPostCallToReturn,
expectAllPostCallsToReturn,
callGet,
partial,
} = require("../../helpers/call-helpers")
} = require("../../../helpers/call-helpers")
jest.setTimeout(30000)
@@ -30,7 +30,7 @@ describe("/admin/orders", () => {
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
@@ -1,7 +1,7 @@
const { PriceList, CustomerGroup } = require("@medusajs/medusa")
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const startServerWithEnvironment = require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { useDb, initDb } = require("../../../helpers/use-db")
@@ -9,14 +9,17 @@ const {
simpleProductFactory,
simplePriceListFactory,
} = require("../../factories")
const {
simpleCustomerGroupFactory,
} = require("../../factories/simple-customer-group-factory")
const adminSeeder = require("../../helpers/admin-seeder")
const customerSeeder = require("../../helpers/customer-seeder")
const priceListSeeder = require("../../helpers/price-list-seeder")
const productSeeder = require("../../helpers/product-seeder")
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
jest.setTimeout(30000)
describe("/admin/price-lists", () => {
@@ -1141,54 +1144,52 @@ describe("/admin/price-lists", () => {
expect(response.status).toEqual(200)
expect(response.data.count).toEqual(2)
expect(response.data.products).toHaveLength(2)
expect(response.data.products).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "test-prod-1",
variants: [
expect.objectContaining({
id: "test-variant-1",
prices: [
expect.objectContaining({ currency_code: "usd", amount: 100 }),
expect.objectContaining({
currency_code: "usd",
amount: 150,
price_list_id: "test-list",
}),
],
}),
expect.objectContaining({
id: "test-variant-2",
prices: [
expect.objectContaining({ currency_code: "usd", amount: 100 }),
],
}),
],
}),
expect.objectContaining({
id: "test-prod-2",
variants: [
expect.objectContaining({
id: "test-variant-3",
prices: [
expect.objectContaining({ currency_code: "usd", amount: 100 }),
],
}),
expect.objectContaining({
id: "test-variant-4",
prices: [
expect.objectContaining({ currency_code: "usd", amount: 100 }),
expect.objectContaining({
currency_code: "usd",
amount: 150,
price_list_id: "test-list",
}),
],
}),
],
}),
])
)
expect(response.data.products).toEqual([
expect.objectContaining({
id: "test-prod-1",
variants: expect.arrayContaining([
expect.objectContaining({
id: "test-variant-1",
prices: expect.arrayContaining([
expect.objectContaining({ currency_code: "usd", amount: 100 }),
expect.objectContaining({
currency_code: "usd",
amount: 150,
price_list_id: "test-list",
}),
],)
}),
expect.objectContaining({
id: "test-variant-2",
prices: expect.arrayContaining([
expect.objectContaining({ currency_code: "usd", amount: 100 }),
]),
}),
]),
}),
expect.objectContaining({
id: "test-prod-2",
variants: expect.arrayContaining([
expect.objectContaining({
id: "test-variant-3",
prices: expect.arrayContaining([
expect.objectContaining({ currency_code: "usd", amount: 100 }),
]),
}),
expect.objectContaining({
id: "test-variant-4",
prices: expect.arrayContaining([
expect.objectContaining({ currency_code: "usd", amount: 100 }),
expect.objectContaining({
currency_code: "usd",
amount: 150,
price_list_id: "test-list",
}),
]),
}),
]),
}),
])
})
it("lists only product 2", async () => {
@@ -1387,3 +1388,112 @@ describe("/admin/price-lists", () => {
})
})
})
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/price-lists", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /admin/price-list", () => {
const priceListIncludesTaxId = "price-list-1-includes-tax"
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await customerSeeder(dbConnection)
await productSeeder(dbConnection)
await simplePriceListFactory(dbConnection, {
id: priceListIncludesTaxId,
})
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should creates a price list that includes tax", async () => {
const api = useApi()
const payload = {
name: "VIP Summer sale",
description: "Summer sale for VIP customers. 25% off selected items.",
type: "sale",
status: "active",
starts_at: "2022-07-01T00:00:00.000Z",
ends_at: "2022-07-31T00:00:00.000Z",
customer_groups: [
{
id: "customer-group-1",
},
],
prices: [
{
amount: 85,
currency_code: "usd",
variant_id: "test-variant",
},
],
includes_tax: true,
}
const response = await api
.post("/admin/price-lists", payload, adminReqConfig)
.catch((err) => {
console.warn(err.response.data)
})
expect(response.status).toEqual(200)
expect(response.data.price_list).toEqual(
expect.objectContaining({
id: expect.any(String),
includes_tax: true,
})
)
})
it("should update a price list that include_tax", async () => {
const api = useApi()
let response = await api
.get(`/admin/price-lists/${priceListIncludesTaxId}`, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.data.price_list.includes_tax).toBe(false)
response = await api
.post(
`/admin/price-lists/${priceListIncludesTaxId}`,
{ includes_tax: true, },
adminReqConfig
).catch((err) => {
console.log(err)
})
expect(response.data.price_list.includes_tax).toBe(true)
})
})
})
@@ -2,9 +2,17 @@ const path = require("path")
const { Region } = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const startServerWithEnvironment = require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const { simpleRegionFactory } = require("../../factories");
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
jest.setTimeout(30000)
@@ -286,3 +294,99 @@ describe("/admin/regions", () => {
})
})
})
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/regions", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /admin/regions/:id", () => {
const region1TaxInclusiveId = "region-1-tax-inclusive"
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
await simpleRegionFactory(dbConnection, {
id: region1TaxInclusiveId,
countries: ["fr"],
})
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should allow to create a region that includes tax", async function () {
const api = useApi()
const payload = {
name: "region-including-taxes",
currency_code: "usd",
tax_rate: 0,
payment_providers: ["test-pay"],
fulfillment_providers: ["test-ful"],
countries: ["us"],
includes_tax: true,
}
let response = await api
.post(`/admin/regions`, payload, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.data.region).toEqual(
expect.objectContaining({
id: expect.any(String),
includes_tax: true,
name: "region-including-taxes",
})
)
});
it("should allow to update a region that includes tax", async function () {
const api = useApi()
let response = await api
.get(`/admin/regions/${region1TaxInclusiveId}`, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.data.region.includes_tax).toBe(false)
response = await api.post(
`/admin/regions/${region1TaxInclusiveId}`,
{
includes_tax: true,
},
adminReqConfig,
).catch((err) => {
console.log(err)
})
expect(response.data.region.includes_tax).toBe(true)
});
})
})
@@ -1,16 +1,21 @@
const path = require("path")
const {
Region,
ShippingProfile,
ShippingOption,
ShippingOptionRequirement,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const startServerWithEnvironment = require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const adminSeeder = require("../../helpers/admin-seeder")
const shippingOptionSeeder = require("../../helpers/shipping-option-seeder")
const { simpleShippingOptionFactory, simpleRegionFactory } = require("../../factories")
const adminReqConfig = {
headers: {
Authorization: "Bearer test_token",
},
}
jest.setTimeout(30000)
@@ -460,3 +465,120 @@ describe("/admin/shipping-options", () => {
})
})
})
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/shipping-options", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /admin/shipping-options", () => {
const shippingOptionIncludesTaxId = "shipping-option-1-includes-tax"
let region
beforeEach(async () => {
try {
await adminSeeder(dbConnection)
region = await simpleRegionFactory(dbConnection, {
id: "region",
countries: ["fr"],
})
await simpleShippingOptionFactory(dbConnection, {
id: shippingOptionIncludesTaxId,
region_id: region.id,
})
} catch (err) {
console.log(err)
throw err
}
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
it("should creates a shipping option that includes tax", async () => {
const api = useApi()
const defaultProfile = await dbConnection.manager.findOne(ShippingProfile, {
type: "default",
})
const payload = {
name: "Test option",
amount: 100,
price_type: "flat_rate",
region_id: region.id,
provider_id: "test-ful",
data: {},
profile_id: defaultProfile.id,
includes_tax: true,
}
const response = await api
.post("/admin/shipping-options", payload, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.status).toEqual(200)
expect(response.data.shipping_option).toEqual(
expect.objectContaining({
id: expect.any(String),
includes_tax: true,
})
)
})
it("should update a shipping option that include_tax", async () => {
const api = useApi()
let response = await api
.get(`/admin/shipping-options/${shippingOptionIncludesTaxId}`, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.data.shipping_option.includes_tax).toBe(false)
const payload = {
requirements: [
{
type: "min_subtotal",
amount: 1,
},
{
type: "max_subtotal",
amount: 2,
},
],
includes_tax: true,
}
response = await api
.post(`/admin/shipping-options/${shippingOptionIncludesTaxId}`, payload, adminReqConfig)
.catch((err) => {
console.log(err)
})
expect(response.data.shipping_option.includes_tax).toBe(true)
})
})
})
+12 -15
View File
@@ -52,12 +52,7 @@ describe("/admin/store", () => {
code: "usd",
},
],
feature_flags: [
{
key: "sales_channels",
value: false,
},
],
feature_flags: expect.any(Array),
default_currency_code: "usd",
created_at: expect.any(String),
updated_at: expect.any(String),
@@ -133,15 +128,17 @@ describe("/admin/store", () => {
it("successfully updates default currency code", async () => {
const api = useApi()
const response = await api.post(
"/admin/store",
{
default_currency_code: "dkk",
},
{
headers: { Authorization: "Bearer test_token " },
}
)
const response = await api
.post(
"/admin/store",
{
default_currency_code: "dkk",
},
{
headers: { Authorization: "Bearer test_token " },
}
)
.catch((err) => console.log(err))
expect(response.status).toEqual(200)
expect(response.data.store).toMatchSnapshot({
@@ -250,12 +250,10 @@ describe("/admin/swaps", () => {
data: {},
})
await api.post("/store/carts/cart-test/payment-sessions")
const TEST = await api.post("/store/carts/cart-test/payment-session", {
await api.post("/store/carts/cart-test/payment-session", {
provider_id: "test-pay",
})
console.log("Testing, ", TEST.data.cart.items[0])
// ********* COMPLETE CART *********
const completedOrder = await api.post("/store/carts/cart-test/complete")
@@ -33,9 +33,9 @@ describe("Line Item Adjustments", () => {
})
describe("Tests database constraints", () => {
let cart,
discount,
lineItemId = "line-test"
let cart
let discount
const lineItemId = "line-test"
beforeEach(async () => {
await cartSeeder(dbConnection)
discount = await simpleDiscountFactory(dbConnection, {
@@ -113,7 +113,7 @@ describe("Line Item Adjustments", () => {
})
}
expect(createLineItemWithAdjustment()).resolves.toEqual(
await expect(createLineItemWithAdjustment()).resolves.toEqual(
expect.anything()
)
})
@@ -131,7 +131,7 @@ describe("Line Item Adjustments", () => {
})
}
expect(createAdjustmentNullDiscount()).resolves.toEqual(
await expect(createAdjustmentNullDiscount()).resolves.toEqual(
expect.anything()
)
})
@@ -155,7 +155,8 @@ describe("Line Item Adjustments", () => {
discount_id: null,
})
}
expect(createAdjustmentsNullDiscount()).resolves.toEqual(
await expect(createAdjustmentsNullDiscount()).resolves.toEqual(
expect.anything()
)
})
@@ -184,7 +185,7 @@ describe("Line Item Adjustments", () => {
})
}
expect(createAdjustment()).resolves.toEqual(expect.anything())
await expect(createAdjustment()).resolves.toEqual(expect.anything())
})
})
@@ -199,7 +200,7 @@ describe("Line Item Adjustments", () => {
discount_id: discount.id,
})
expect(createDuplicateAdjustment()).rejects.toEqual(
await expect(createDuplicateAdjustment()).rejects.toEqual(
expect.objectContaining({ code: "23505" })
)
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,276 @@
const path = require("path")
const startServerWithEnvironment =
require("../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../helpers/use-api")
const { useDb } = require("../../../helpers/use-db")
const { simpleProductFactory, simpleOrderFactory } = require("../../factories")
const adminSeeder = require("../../helpers/admin-seeder")
const createReturnableOrder = async (dbConnection, options) => {
await simpleProductFactory(
dbConnection,
{
id: "test-product",
variants: [{ id: "test-variant" }],
},
100
)
let discounts = []
if (options?.discount) {
discounts = [
{
code: "TESTCODE",
},
]
}
let unitPrice = options.includes_tax ? 1200 : 1000
if (options.oldTaxes) {
unitPrice = options.includes_tax ? 1125 : 1000
}
return await simpleOrderFactory(dbConnection, {
email: "test@testson.com",
tax_rate: options?.oldTaxes ? undefined : null,
region: {
id: "test-region",
name: "Test region",
tax_rate: 12.5,
},
discounts,
line_items: [
{
id: "test-item",
variant_id: "test-variant",
quantity: 2,
fulfilled_quantity: options?.shipped ? 2 : undefined,
shipped_quantity: options?.shipped ? 2 : undefined,
unit_price: unitPrice,
includes_tax: options?.includes_tax,
tax_lines: [
{
name: "default",
code: "default",
rate: 20,
},
],
},
],
})
}
jest.setTimeout(30000)
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("creates a return with the old tax system and tax inclusive line", async () => {
await adminSeeder(dbConnection)
const order = await createReturnableOrder(dbConnection, {
oldTaxes: true,
includes_tax: true,
})
const api = useApi()
const response = await api.post(
`/admin/orders/${order.id}/return`,
{
items: [
{
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
)
expect(response.status).toEqual(200)
/*
* Region has default tax rate 12.5 therefore refund amount should be
* 1000 * 1.125 = 1125
*/
expect(response.data.order.returns[0].refund_amount).toEqual(1125)
expect(response.data.order.returns[0].items).toHaveLength(1)
expect(response.data.order.returns[0].items).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
}),
])
)
})
it("creates a return with the old tax system and tax exclusive line", async () => {
await adminSeeder(dbConnection)
const order = await createReturnableOrder(dbConnection, {
oldTaxes: true,
includes_tax: false,
})
const api = useApi()
const response = await api.post(
`/admin/orders/${order.id}/return`,
{
items: [
{
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
)
expect(response.status).toEqual(200)
/*
* Region has default tax rate 12.5 therefore refund amount should be
* 1000 * 1.125 = 1125
*/
expect(response.data.order.returns[0].refund_amount).toEqual(1125)
expect(response.data.order.returns[0].items).toHaveLength(1)
expect(response.data.order.returns[0].items).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
}),
])
)
})
it("creates a return with tax inclusive line", async () => {
await adminSeeder(dbConnection)
const order = await createReturnableOrder(dbConnection, {
includes_tax: true,
})
const api = useApi()
const response = await api.post(
`/admin/orders/${order.id}/return`,
{
items: [
{
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
)
expect(response.status).toEqual(200)
/*
* Region has a tax rate of 20% therefore refund amount should be
* 1000 * 1.2 = 1200
*/
expect(response.data.order.returns[0].refund_amount).toEqual(1200)
expect(response.data.order.returns[0].items).toHaveLength(1)
expect(response.data.order.returns[0].items).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
}),
])
)
})
it("creates a return with tax exclusive line", async () => {
await adminSeeder(dbConnection)
const order = await createReturnableOrder(dbConnection, {
includes_tax: false,
})
const api = useApi()
const response = await api.post(
`/admin/orders/${order.id}/return`,
{
items: [
{
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
)
expect(response.status).toEqual(200)
/*
* Region has a tax rate of 20% therefore refund amount should be
* 1000 * 1.2 = 1200
*/
expect(response.data.order.returns[0].refund_amount).toEqual(1200)
expect(response.data.order.returns[0].items).toHaveLength(1)
expect(response.data.order.returns[0].items).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: "test-item",
quantity: 1,
note: "TOO SMALL",
}),
])
)
})
})
@@ -9,29 +9,29 @@ const {
MoneyAmount,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const setupServer = require("../../../../helpers/setup-server")
const { useApi } = require("../../../../helpers/use-api")
const { initDb, useDb } = require("../../../../helpers/use-db")
const cartSeeder = require("../../helpers/cart-seeder")
const productSeeder = require("../../helpers/product-seeder")
const swapSeeder = require("../../helpers/swap-seeder")
const cartSeeder = require("../../../helpers/cart-seeder")
const productSeeder = require("../../../helpers/product-seeder")
const swapSeeder = require("../../../helpers/swap-seeder")
const {
simpleCartFactory,
simpleRegionFactory,
simpleProductFactory,
simpleShippingOptionFactory,
simpleLineItemFactory,
} = require("../../factories")
} = require("../../../factories")
const {
simpleDiscountFactory,
} = require("../../factories/simple-discount-factory")
} = require("../../../factories/simple-discount-factory")
const {
simpleCustomerFactory,
} = require("../../factories/simple-customer-factory")
} = require("../../../factories/simple-customer-factory")
const {
simpleCustomerGroupFactory,
} = require("../../factories/simple-customer-group-factory")
} = require("../../../factories/simple-customer-group-factory")
jest.setTimeout(30000)
@@ -45,7 +45,7 @@ describe("/store/carts", () => {
}
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd, verbose: false })
})
@@ -1813,7 +1813,7 @@ describe("/store/carts", () => {
type: "swap",
})
const cartWithCustomSo = await manager.save(_cart)
await manager.save(_cart)
await manager.insert(CustomShippingOption, {
id: "another-cso-test",
@@ -0,0 +1,538 @@
const path = require("path")
const startServerWithEnvironment =
require("../../../../helpers/start-server-with-environment").default
const { useApi } = require("../../../../helpers/use-api")
const { useDb } = require("../../../../helpers/use-db")
const {
simpleCartFactory,
simpleRegionFactory,
simpleShippingOptionFactory,
simpleCustomShippingOptionFactory,
simpleProductFactory,
simplePriceListFactory,
simpleDiscountFactory,
} = require("../../../factories")
const { IdMap } = require("medusa-test-utils")
jest.setTimeout(30000)
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
describe("POST /store/carts/:id/shipping-methods", () => {
let includesTaxShippingOption
let cart
let customSoCart
beforeEach(async () => {
try {
const shippingAddress = {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
}
const region = await simpleRegionFactory(dbConnection, {
id: "test-region",
})
cart = await simpleCartFactory(dbConnection, {
id: "test-cart",
email: "some-customer1@email.com",
region: region.id,
shipping_address: shippingAddress,
currency_code: "usd",
})
customSoCart = await simpleCartFactory(dbConnection, {
id: "test-cart-with-cso",
email: "some-customer2@email.com",
region: region.id,
shipping_address: shippingAddress,
currency_code: "usd",
})
includesTaxShippingOption = await simpleShippingOptionFactory(
dbConnection,
{
includes_tax: true,
region_id: region.id,
}
)
await simpleCustomShippingOptionFactory(dbConnection, {
id: "another-cso-test",
cart_id: customSoCart.id,
shipping_option_id: includesTaxShippingOption.id,
price: 5,
})
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("should add a normal shipping method to the cart", async () => {
const api = useApi()
const cartWithShippingMethodRes = await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{
option_id: includesTaxShippingOption.id,
},
{ withCredentials: true }
)
expect(cartWithShippingMethodRes.status).toEqual(200)
expect(cartWithShippingMethodRes.data.cart.shipping_methods).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: includesTaxShippingOption.id,
includes_tax: true,
}),
])
)
})
it("should add a custom shipping method to the cart", async () => {
const api = useApi()
const cartWithCustomShippingMethodRes = await api
.post(
`/store/carts/${customSoCart.id}/shipping-methods`,
{
option_id: includesTaxShippingOption.id,
},
{ withCredentials: true }
)
.catch((err) => err.response)
expect(cartWithCustomShippingMethodRes.status).toEqual(200)
expect(
cartWithCustomShippingMethodRes.data.cart.shipping_methods
).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: includesTaxShippingOption.id,
includes_tax: true,
price: 5,
}),
])
)
})
})
describe("POST /store/carts/:id", () => {
const variantId1 = IdMap.getId("test-variant-1")
const variantId2 = IdMap.getId("test-variant-2")
const productId1 = IdMap.getId("test-product-1")
const productId2 = IdMap.getId("test-product-2")
const regionId = IdMap.getId("test-region")
const regionData = {
id: regionId,
includes_tax: false,
currency_code: "usd",
countries: ["us"],
tax_rate: 20,
name: "region test",
}
const buildProductData = (productId, variantId) => {
return {
id: productId,
variants: [
{
id: variantId,
prices: [],
},
],
}
}
const buildPriceListData = (variantId, price, includesTax) => {
return {
status: "active",
type: "sale",
prices: [
{
variant_id: variantId,
amount: price,
currency_code: "usd",
region_id: regionId,
},
],
includes_tax: includesTax,
}
}
const customnerPayload = {
email: "adrien@test.dk",
password: "adrientest",
first_name: "adrien",
last_name: "adrien",
}
const createCartPayload = {
region_id: regionId,
items: [
{
variant_id: variantId1,
quantity: 1,
},
{
variant_id: variantId2,
quantity: 1,
},
],
}
describe("with a cart with full tax exclusive variant pricing", () => {
beforeEach(async () => {
await simpleRegionFactory(dbConnection, regionData)
await simpleProductFactory(
dbConnection,
buildProductData(productId1, variantId1)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId1, 100, false)
)
await simpleProductFactory(
dbConnection,
buildProductData(productId2, variantId2)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId2, 100, false)
)
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("should calculates correct payment totals on cart completion", async () => {
const api = useApi()
const customerRes = await api.post(
"/store/customers",
customnerPayload,
{ withCredentials: true }
)
const createCartRes = await api.post("/store/carts", createCartPayload)
const cart = createCartRes.data.cart
await api.post(`/store/carts/${cart.id}`, {
customer_id: customerRes.data.customer.id,
})
await api.post(`/store/carts/${cart.id}/payment-sessions`)
const createdOrder = await api.post(
`/store/carts/${cart.id}/complete-cart`
)
expect(createdOrder.data.type).toEqual("order")
expect(createdOrder.data.data.discount_total).toEqual(0)
expect(createdOrder.data.data.subtotal).toEqual(200)
expect(createdOrder.data.data.total).toEqual(240)
expect(createdOrder.status).toEqual(200)
})
})
describe("with a cart with full tax inclusive variant pricing", () => {
beforeEach(async () => {
await simpleRegionFactory(dbConnection, regionData)
await simpleProductFactory(
dbConnection,
buildProductData(productId1, variantId1)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId1, 120, true)
)
await simpleProductFactory(
dbConnection,
buildProductData(productId2, variantId2)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId2, 120, true)
)
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("should calculates correct payment totals on cart completion", async () => {
const api = useApi()
const customerRes = await api.post(
"/store/customers",
customnerPayload,
{ withCredentials: true }
)
const createCartRes = await api.post("/store/carts", createCartPayload)
const cart = createCartRes.data.cart
await api.post(`/store/carts/${cart.id}`, {
customer_id: customerRes.data.customer.id,
})
await api.post(`/store/carts/${cart.id}/payment-sessions`)
const createdOrder = await api.post(
`/store/carts/${cart.id}/complete-cart`
)
expect(createdOrder.data.type).toEqual("order")
expect(createdOrder.data.data.discount_total).toEqual(0)
expect(createdOrder.data.data.subtotal).toEqual(200)
expect(createdOrder.data.data.total).toEqual(240)
expect(createdOrder.status).toEqual(200)
})
})
describe("with a cart mixing tax inclusive and exclusive variant pricing", () => {
beforeEach(async () => {
await simpleRegionFactory(dbConnection, regionData)
await simpleProductFactory(
dbConnection,
buildProductData(productId1, variantId1)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId1, 120, true)
)
await simpleProductFactory(
dbConnection,
buildProductData(productId2, variantId2)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId2, 100, false)
)
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("should calculates correct payment totals on cart completion", async () => {
const api = useApi()
const customerRes = await api.post(
"/store/customers",
customnerPayload,
{ withCredentials: true }
)
const createCartRes = await api.post("/store/carts", createCartPayload)
const cart = createCartRes.data.cart
await api.post(`/store/carts/${cart.id}`, {
customer_id: customerRes.data.customer.id,
})
await api.post(`/store/carts/${cart.id}/payment-sessions`)
const createdOrder = await api.post(
`/store/carts/${cart.id}/complete-cart`
)
expect(createdOrder.data.type).toEqual("order")
expect(createdOrder.data.data.discount_total).toEqual(0)
expect(createdOrder.data.data.subtotal).toEqual(200)
expect(createdOrder.data.data.total).toEqual(240)
expect(createdOrder.status).toEqual(200)
})
})
})
describe("POST /store/carts/:id/line-items", () => {
const cartIdWithItemPercentageDiscount =
"test-cart-w-item-percentage-discount"
const percentage15discountId = IdMap.getId("percentage15discountId")
const variantId1 = IdMap.getId("test-variant-1")
const variantId2 = IdMap.getId("test-variant-2")
const productId1 = IdMap.getId("test-product-1")
const productId2 = IdMap.getId("test-product-2")
const regionId = IdMap.getId("test-region")
const regionData = {
id: regionId,
includes_tax: false,
currency_code: "usd",
countries: ["us"],
tax_rate: 20,
name: "region test",
}
const buildProductData = (productId, variantId) => {
return {
id: productId,
variants: [
{
id: variantId,
prices: [],
},
],
}
}
const buildPriceListData = (variantId, price, includesTax) => {
return {
status: "active",
type: "sale",
prices: [
{
variant_id: variantId,
amount: price,
currency_code: "usd",
region_id: regionId,
},
],
includes_tax: includesTax,
}
}
describe("with a cart mixing tax inclusive and exclusive variant pricing", () => {
beforeEach(async () => {
const region = await simpleRegionFactory(dbConnection, regionData)
await simpleCartFactory(dbConnection, {
id: cartIdWithItemPercentageDiscount,
region,
})
await simpleProductFactory(
dbConnection,
buildProductData(productId1, variantId1)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId1, 120, true)
)
await simpleProductFactory(
dbConnection,
buildProductData(productId2, variantId2)
)
await simplePriceListFactory(
dbConnection,
buildPriceListData(variantId2, 100, false)
)
const tenDaysAgo = ((today) =>
new Date(today.setDate(today.getDate() - 10)))(new Date())
const tenDaysFromToday = ((today) =>
new Date(today.setDate(today.getDate() + 10)))(new Date())
await simpleDiscountFactory(dbConnection, {
id: percentage15discountId,
code: percentage15discountId,
regions: [regionId],
rule: {
type: "percentage",
value: "15",
allocation: "item",
},
starts_at: tenDaysAgo,
ends_at: tenDaysFromToday,
})
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
it("calculates correct item totals for percentage discount with mix of tax inclusive/exclusive items", async () => {
const api = useApi()
await api.post(`/store/carts/${cartIdWithItemPercentageDiscount}`, {
region_id: regionId,
discounts: [{ code: percentage15discountId }],
})
await api.post(
`/store/carts/${cartIdWithItemPercentageDiscount}/line-items`,
{
variant_id: variantId1,
quantity: 2,
},
{ withCredentials: true }
)
const response = await api.post(
`/store/carts/${cartIdWithItemPercentageDiscount}/line-items`,
{
variant_id: variantId2,
quantity: 2,
},
{ withCredentials: true }
)
const expectedItemTotals = {
subtotal: 200,
gift_card_total: 0,
discount_total: 30,
total: 204,
original_total: 240,
original_tax_total: 40,
tax_total: 34,
}
const expectedAdjustment = {
amount: 30,
discount_id: percentage15discountId,
description: "discount",
}
expect(response.data.cart.items).toHaveLength(2)
expect(response.data.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
includes_tax: true,
cart_id: cartIdWithItemPercentageDiscount,
unit_price: 120,
variant_id: variantId1,
quantity: 2,
adjustments: [expect.objectContaining(expectedAdjustment)],
...expectedItemTotals,
}),
expect.objectContaining({
includes_tax: false,
cart_id: cartIdWithItemPercentageDiscount,
unit_price: 100,
variant_id: variantId2,
quantity: 2,
adjustments: [expect.objectContaining(expectedAdjustment)],
...expectedItemTotals,
}),
])
)
})
})
})
})
@@ -0,0 +1,106 @@
const path = require("path")
const { useApi } = require("../../../../helpers/use-api")
const { useDb } = require("../../../../helpers/use-db")
const startServerWithEnvironment =
require("../../../../helpers/start-server-with-environment").default
const {
simpleOrderFactory,
simpleProductFactory,
} = require("../../../factories")
jest.setTimeout(30000)
describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING]: Order Taxes", () => {
let medusaProcess
let dbConnection
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
const [process, connection] = await startServerWithEnvironment({
cwd,
env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true },
verbose: false,
})
dbConnection = connection
medusaProcess = process
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
medusaProcess.kill()
})
afterEach(async () => {
const db = useDb()
return await db.teardown()
})
test("calculates taxes w. tax inclusive shipping method price", async () => {
await simpleProductFactory(
dbConnection,
{
id: "test-product",
variants: [
{
id: "test-variant",
},
],
},
100
)
const order = await simpleOrderFactory(
dbConnection,
{
email: "test@testson.com",
tax_rate: null,
region: {
id: "test-region",
name: "Test region",
tax_rate: null,
},
shipping_methods: [
{
price: 110,
includes_tax: true,
shipping_option: {
region_id: "test-region",
},
tax_lines: [
{
rate: 10,
name: "default",
code: "default",
},
],
},
],
line_items: [
{
variant_id: "test-variant",
unit_price: 1000,
tax_lines: [
{
rate: 20,
name: "default",
code: "default",
},
],
},
],
},
100
)
const api = useApi()
const response = await api.get(`/store/orders/${order.id}`)
expect(response.status).toEqual(200)
expect(response.data.order.tax_total).toEqual(210)
expect(response.data.order.total).toEqual(1310)
})
})
@@ -1,8 +1,8 @@
const path = require("path")
const setupServer = require("../../../helpers/setup-server")
const { useApi } = require("../../../helpers/use-api")
const { initDb, useDb } = require("../../../helpers/use-db")
const setupServer = require("../../../../helpers/setup-server")
const { useApi } = require("../../../../helpers/use-api")
const { initDb, useDb } = require("../../../../helpers/use-db")
const {
simpleOrderFactory,
@@ -10,7 +10,7 @@ const {
simpleCartFactory,
simpleProductFactory,
simpleProductTaxRateFactory,
} = require("../../factories")
} = require("../../../factories")
jest.setTimeout(30000)
@@ -24,7 +24,7 @@ describe("Order Taxes", () => {
}
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."))
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd })
})
+1
View File
@@ -17,3 +17,4 @@ export * from "./simple-product-type-tax-rate-factory"
export * from "./simple-price-list-factory"
export * from "./simple-batch-job-factory"
export * from "./simple-sales-channel-factory"
export * from "./simple-custom-shipping-option-factory"
@@ -0,0 +1,36 @@
import { Connection } from "typeorm"
import faker from "faker"
import {
CustomShippingOption,
} from "@medusajs/medusa"
export type CustomShippingOptionFactoryData = {
id?: string
cart_id: string
shipping_option_id: string
price?: number
metadata?: Record<string, unknown>
}
export const simpleCustomShippingOptionFactory = async (
connection: Connection,
data: CustomShippingOptionFactoryData,
seed?: number
): Promise<CustomShippingOption> => {
if (typeof seed !== "undefined") {
faker.seed(seed)
}
const manager = connection.manager
const customShippingOptionData = {
id: data.id ?? `custon-simple-so-${Math.random() * 1000}`,
price: typeof data.price !== "undefined" ? data.price : 500,
cart_id: data.cart_id,
shipping_option_id: data.shipping_option_id,
metadata: data.metadata ?? {}
}
const created = manager.create(CustomShippingOption, customShippingOptionData)
return await manager.save(created)
}
@@ -12,7 +12,7 @@ import { DiscountConditionJoinTableForeignKey } from "@medusajs/medusa/dist/repo
import faker from "faker"
import { Connection } from "typeorm"
export type DiscuntConditionFactoryData = {
export type DiscountConditionFactoryData = {
id?: string
rule_id: string
type: DiscountConditionType
@@ -67,7 +67,7 @@ const getJoinTableResourceIdentifiers = (type: string) => {
export const simpleDiscountConditionFactory = async (
connection: Connection,
data: DiscuntConditionFactoryData,
data: DiscountConditionFactoryData,
seed?: number
): Promise<void> => {
if (typeof seed !== "undefined") {
@@ -7,7 +7,7 @@ import {
import faker from "faker"
import { Connection } from "typeorm"
import {
DiscuntConditionFactoryData,
DiscountConditionFactoryData,
simpleDiscountConditionFactory,
} from "./simple-discount-condition-factory"
@@ -15,7 +15,7 @@ export type DiscountRuleFactoryData = {
type?: DiscountRuleType
value?: number
allocation?: AllocationType
conditions: DiscuntConditionFactoryData[]
conditions: DiscountConditionFactoryData[]
}
export type DiscountFactoryData = {
@@ -24,6 +24,8 @@ export type DiscountFactoryData = {
is_dynamic?: boolean
rule?: DiscountRuleFactoryData
regions?: string[]
starts_at?: Date
ends_at?: Date
}
export const simpleDiscountFactory = async (
@@ -37,7 +39,7 @@ export const simpleDiscountFactory = async (
const manager = connection.manager
const ruleData = data.rule ?? {}
const ruleData = data.rule ?? ({} as DiscountRuleFactoryData)
const ruleToSave = manager.create(DiscountRule, {
type: ruleData.type ?? DiscountRuleType.PERCENTAGE,
value: ruleData.value ?? 10,
@@ -63,8 +65,9 @@ export const simpleDiscountFactory = async (
rule_id: dRule.id,
code: data.code ?? "TESTCODE",
regions: data.regions?.map((r) => ({ id: r })) || [],
starts_at: data.starts_at,
ends_at: data.ends_at,
})
const discount = await manager.save(toSave)
return discount
return await manager.save(toSave)
}
@@ -30,6 +30,7 @@ export type LineItemFactoryData = {
returned_quantity?: boolean
tax_lines?: TaxLineFactoryData[]
adjustments: LineItemAdjustmentFactoryData[]
includes_tax?: boolean
}
export const simpleLineItemFactory = async (
@@ -70,6 +71,7 @@ export const simpleLineItemFactory = async (
shipped_quantity: data.shipped_quantity || null,
returned_quantity: data.returned_quantity || null,
adjustments: data.adjustments,
includes_tax: data.includes_tax,
})
const line = await manager.save(toSave)
@@ -26,6 +26,7 @@ export type PriceListFactoryData = {
ends_at?: Date
customer_groups?: string[]
prices?: ProductListPrice[]
includes_tax?: boolean
}
export const simplePriceListFactory = async (
@@ -59,6 +60,7 @@ export const simplePriceListFactory = async (
starts_at: data.starts_at || null,
ends_at: data.ends_at || null,
customer_groups: customerGroups,
includes_tax: data.includes_tax,
}
const toSave = manager.create(PriceList, toCreate)
@@ -10,6 +10,8 @@ export type RegionFactoryData = {
countries?: string[]
automatic_taxes?: boolean
gift_cards_taxable?: boolean
fulfillment_providers?: { id: string }[]
includes_tax?: boolean
}
export const simpleRegionFactory = async (
@@ -30,7 +32,9 @@ export const simpleRegionFactory = async (
currency_code: data.currency_code || "usd",
tax_rate: data.tax_rate || 0,
payment_providers: [{ id: "test-pay" }],
fulfillment_providers: data.fulfillment_providers ?? [{ id: "test-ful" }],
gift_cards_taxable: data.gift_cards_taxable ?? true,
includes_tax: data.includes_tax,
automatic_taxes:
typeof data.automatic_taxes !== "undefined" ? data.automatic_taxes : true,
})
@@ -15,6 +15,7 @@ export type ShippingMethodFactoryData = {
price?: number
shipping_option: string | ShippingOptionFactoryData
tax_lines?: ShippingMethodTaxLine[]
includes_tax?: boolean
}
export const simpleShippingMethodFactory = async (
@@ -47,6 +48,7 @@ export const simpleShippingMethodFactory = async (
shipping_option_id: shippingOptionId,
data: data.data || {},
price: typeof data.price !== "undefined" ? data.price : 500,
includes_tax: data.includes_tax,
})
const shippingMethod = await manager.save(toSave)
@@ -8,12 +8,14 @@ import faker from "faker"
import { Connection } from "typeorm"
export type ShippingOptionFactoryData = {
id?: string
name?: string
region_id: string
is_return?: boolean
is_giftcard?: boolean
price?: number
price_type?: ShippingOptionPriceType
includes_tax?: boolean
data?: object
}
@@ -35,8 +37,8 @@ export const simpleShippingOptionFactory = async (
type: ShippingProfileType.GIFT_CARD,
})
const created = manager.create(ShippingOption, {
id: `simple-so-${Math.random() * 1000}`,
const shippingOptionData = {
id: data.id ?? `simple-so-${Math.random() * 1000}`,
name: data.name || "Test Method",
is_return: data.is_return ?? false,
region_id: data.region_id,
@@ -45,7 +47,15 @@ export const simpleShippingOptionFactory = async (
price_type: data.price_type ?? ShippingOptionPriceType.FLAT_RATE,
data: data.data ?? {},
amount: typeof data.price !== "undefined" ? data.price : 500,
})
const option = await manager.save(created)
return option
}
// This is purposefully managed out of the original object for the purpose of separating the data linked to a feature flag
// MEDUSA_FF_TAX_INCLUSIVE_PRICING
const { includes_tax } = data
if (typeof includes_tax !== "undefined") {
shippingOptionData["includes_tax"] = includes_tax
}
const created = manager.create(ShippingOption, shippingOptionData)
return await manager.save(created)
}