feat(medusa,core-flows,types): add cart <> tax integration workflows + steps (#6580)

what:

- adds tax lines to cart when item operations take place

RESOLVES CORE-1821
RESOLVES CORE-1822
RESOLVES CORE-1823
RESOLVES CORE-1824
This commit is contained in:
Riqwan Thamir
2024-03-07 16:17:43 +00:00
committed by GitHub
parent 8c57e61cb8
commit e4acde1aa2
26 changed files with 1096 additions and 180 deletions
@@ -12,11 +12,13 @@ import {
IPromotionModuleService,
IRegionModuleService,
ISalesChannelModuleService,
ITaxModuleService,
} from "@medusajs/types"
import { PromotionRuleOperator, PromotionType } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { setupTaxStructure } from "../../fixtures"
jest.setTimeout(50000)
@@ -27,38 +29,36 @@ medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Store Carts API", () => {
let appContainer
let cartModuleService: ICartModuleService
let regionModuleService: IRegionModuleService
let scModuleService: ISalesChannelModuleService
let cartModule: ICartModuleService
let regionModule: IRegionModuleService
let scModule: ISalesChannelModuleService
let customerModule: ICustomerModuleService
let productModule: IProductModuleService
let pricingModule: IPricingModuleService
let remoteLink: RemoteLink
let promotionModule: IPromotionModuleService
let taxModule: ITaxModuleService
let defaultRegion
beforeAll(async () => {
appContainer = getContainer()
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
regionModuleService = appContainer.resolve(
ModuleRegistrationName.REGION
)
scModuleService = appContainer.resolve(
ModuleRegistrationName.SALES_CHANNEL
)
cartModule = appContainer.resolve(ModuleRegistrationName.CART)
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
scModule = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
remoteLink = appContainer.resolve(LinkModuleUtils.REMOTE_LINK)
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
taxModule = appContainer.resolve(ModuleRegistrationName.TAX)
})
beforeEach(async () => {
await adminSeeder(dbConnection)
// Here, so we don't have to create a region for each test
defaultRegion = await regionModuleService.create({
defaultRegion = await regionModule.create({
name: "Default Region",
currency_code: "dkk",
})
@@ -66,12 +66,12 @@ medusaIntegrationTestRunner({
describe("POST /store/carts", () => {
it("should create a cart", async () => {
const region = await regionModuleService.create({
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
const salesChannel = await scModule.create({
name: "Webshop",
})
@@ -172,10 +172,44 @@ medusaIntegrationTestRunner({
)
})
it("should create cart with customer from email", async () => {
it("should create cart with customer from email and tax lines", async () => {
await setupTaxStructure(taxModule)
const [product] = await productModule.create([
{
title: "Test product default tax",
variants: [{ title: "Test variant default tax" }],
},
])
const [priceSet] = await pricingModule.create([
{ prices: [{ amount: 3000, currency_code: "usd" }] },
])
await remoteLink.create([
{
productService: { variant_id: product.variants[0].id },
pricingService: { price_set_id: priceSet.id },
},
])
const created = await api.post(`/store/carts`, {
currency_code: "usd",
email: "tony@stark-industries.com",
shipping_address: {
address_1: "test address 1",
address_2: "test address 2",
city: "NY",
country_code: "US",
province: "NY",
postal_code: "94016",
},
items: [
{
quantity: 1,
variant_id: product.variants[0].id,
},
],
})
expect(created.status).toEqual(200)
@@ -188,12 +222,25 @@ medusaIntegrationTestRunner({
id: expect.any(String),
email: "tony@stark-industries.com",
}),
items: [
expect.objectContaining({
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [],
}),
],
})
)
})
it("should create cart with any region", async () => {
await regionModuleService.create({
await regionModule.create({
name: "US",
currency_code: "usd",
})
@@ -217,7 +264,7 @@ medusaIntegrationTestRunner({
})
it("should create cart with region currency code", async () => {
const region = await regionModuleService.create({
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
@@ -278,6 +325,8 @@ medusaIntegrationTestRunner({
describe("POST /store/carts/:id", () => {
it("should update a cart with promo codes with a replace action", async () => {
await setupTaxStructure(taxModule)
const targetRules = [
{
attribute: "product_id",
@@ -293,7 +342,7 @@ medusaIntegrationTestRunner({
type: "fixed",
target_type: "items",
allocation: "each",
value: "300",
value: 300,
apply_to_quantity: 1,
max_quantity: 1,
target_rules: targetRules,
@@ -307,15 +356,23 @@ medusaIntegrationTestRunner({
type: "fixed",
target_type: "items",
allocation: "across",
value: "1000",
value: 1000,
apply_to_quantity: 1,
target_rules: targetRules,
},
})
const cart = await cartModuleService.create({
const cart = await cartModule.create({
currency_code: "usd",
email: "tony@stark.com",
shipping_address: {
address_1: "test address 1",
address_2: "test address 2",
city: "NY",
country_code: "US",
province: "NY",
postal_code: "94016",
},
items: [
{
id: "item-1",
@@ -327,7 +384,7 @@ medusaIntegrationTestRunner({
],
})
const [adjustment] = await cartModuleService.addLineItemAdjustments([
const [adjustment] = await cartModule.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
@@ -353,6 +410,14 @@ medusaIntegrationTestRunner({
items: [
expect.objectContaining({
id: "item-1",
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [
expect.objectContaining({
id: expect.not.stringContaining(adjustment.id),
@@ -363,7 +428,6 @@ medusaIntegrationTestRunner({
],
})
)
// Should remove all adjustments from other promo codes
updated = await api.post(`/store/carts/${cart.id}`, {
promo_codes: [],
@@ -377,26 +441,61 @@ medusaIntegrationTestRunner({
expect.objectContaining({
id: "item-1",
adjustments: [],
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
}),
],
})
)
})
it("should update a cart's region, sales channel and customer data", async () => {
const region = await regionModuleService.create({
it("should update a cart's region, sales channel, customer data and tax lines", async () => {
await setupTaxStructure(taxModule)
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
const salesChannel = await scModule.create({
name: "Webshop",
})
const cart = await cartModuleService.create({
const cart = await cartModule.create({
currency_code: "eur",
email: "tony@stark.com",
shipping_address: {
address_1: "test address 1",
address_2: "test address 2",
city: "NY",
country_code: "US",
province: "NY",
postal_code: "94016",
},
items: [
{
id: "item-1",
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_tshirt",
} as any,
],
})
// Manually inserting shipping methods here since the cart does not
// currently support it. Move to API when ready.
await cartModule.addShippingMethods(cart.id, [
{ amount: 500, name: "express" },
{ amount: 500, name: "standard" },
])
let updated = await api.post(`/store/carts/${cart.id}`, {
region_id: region.id,
email: "tony@stark.com",
@@ -417,6 +516,53 @@ medusaIntegrationTestRunner({
email: "tony@stark.com",
}),
sales_channel_id: salesChannel.id,
shipping_address: expect.objectContaining({
city: "NY",
country_code: "US",
province: "NY",
}),
shipping_methods: expect.arrayContaining([
expect.objectContaining({
shipping_option_id: null,
amount: 500,
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [],
}),
expect.objectContaining({
shipping_option_id: null,
amount: 500,
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [],
}),
]),
items: [
expect.objectContaining({
id: "item-1",
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [],
}),
],
})
)
@@ -432,11 +578,21 @@ medusaIntegrationTestRunner({
currency_code: "usd",
email: null,
customer_id: null,
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
sales_channel_id: null,
items: [
expect.objectContaining({
id: "item-1",
tax_lines: [
expect.objectContaining({
description: "NY Default Rate",
code: "NYDEFAULT",
rate: 6,
provider_id: "system",
}),
],
adjustments: [],
}),
],
})
)
})
@@ -444,12 +600,12 @@ medusaIntegrationTestRunner({
describe("GET /store/carts/:id", () => {
it("should create and update a cart", async () => {
const region = await regionModuleService.create({
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
const salesChannel = await scModule.create({
name: "Webshop",
})
@@ -494,16 +650,16 @@ medusaIntegrationTestRunner({
describe("GET /store/carts", () => {
it("should get cart", async () => {
const region = await regionModuleService.create({
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
const salesChannel = await scModule.create({
name: "Webshop",
})
const cart = await cartModuleService.create({
const cart = await cartModule.create({
currency_code: "usd",
items: [
{
@@ -542,19 +698,40 @@ medusaIntegrationTestRunner({
describe("POST /store/carts/:id/line-items", () => {
it("should add item to cart", async () => {
const [product] = await productModule.create([
await setupTaxStructure(taxModule)
const customer = await customerModule.create({
email: "tony@stark-industries.com",
})
const [productWithSpecialTax] = await productModule.create([
{
// This product ID is setup in the tax structure fixture (setupTaxStructure)
id: "product_id_1",
title: "Test product",
variants: [
{
title: "Test variant",
},
],
variants: [{ title: "Test variant" }],
} as any,
])
const [productWithDefaultTax] = await productModule.create([
{
title: "Test product default tax",
variants: [{ title: "Test variant default tax" }],
},
])
const cart = await cartModuleService.create({
const cart = await cartModule.create({
currency_code: "usd",
customer_id: customer.id,
shipping_address: {
customer_id: customer.id,
address_1: "test address 1",
address_2: "test address 2",
city: "SF",
country_code: "US",
province: "CA",
postal_code: "94016",
},
items: [
{
id: "item-1",
@@ -573,61 +750,60 @@ medusaIntegrationTestRunner({
type: "fixed",
target_type: "items",
allocation: "across",
value: "300",
value: 300,
apply_to_quantity: 2,
target_rules: [
{
attribute: "product_id",
operator: "in",
values: ["prod_mat", product.id],
values: ["prod_mat", productWithSpecialTax.id],
},
],
},
})
const [lineItemAdjustment] =
await cartModuleService.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const [lineItemAdjustment] = await cartModule.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
const [priceSet, priceSetDefaultTax] = await pricingModule.create([
{
prices: [{ amount: 3000, currency_code: "usd" }],
},
{
prices: [{ amount: 2000, currency_code: "usd" }],
},
])
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
variant_id: productWithSpecialTax.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
pricingService: { price_set_id: priceSet.id },
},
{
productService: {
variant_id: productWithDefaultTax.variants[0].id,
},
pricingService: { price_set_id: priceSetDefaultTax.id },
},
{
[Modules.CART]: { cart_id: cart.id },
[Modules.PROMOTION]: { promotion_id: appliedPromotion.id },
},
])
await remoteLink.create({
[Modules.CART]: { cart_id: cart.id },
[Modules.PROMOTION]: { promotion_id: appliedPromotion.id },
let response = await api.post(`/store/carts/${cart.id}/line-items`, {
variant_id: productWithSpecialTax.variants[0].id,
quantity: 1,
})
const response = await api.post(
`/store/carts/${cart.id}/line-items`,
{
variant_id: product.variants[0].id,
quantity: 1,
}
)
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
@@ -638,6 +814,14 @@ medusaIntegrationTestRunner({
unit_price: 3000,
quantity: 1,
title: "Test variant",
tax_lines: [
expect.objectContaining({
description: "CA Reduced Rate for Products",
code: "CAREDUCE_PROD",
rate: 3,
provider_id: "system",
}),
],
adjustments: [
expect.objectContaining({
code: "PROMOTION_APPLIED",
@@ -649,6 +833,7 @@ medusaIntegrationTestRunner({
unit_price: 2000,
quantity: 1,
title: "Test item",
tax_lines: [],
adjustments: [
expect.objectContaining({
id: expect.not.stringContaining(lineItemAdjustment.id),
@@ -660,17 +845,45 @@ medusaIntegrationTestRunner({
]),
})
)
response = await api.post(`/store/carts/${cart.id}/line-items`, {
variant_id: productWithDefaultTax.variants[0].id,
quantity: 1,
})
expect(response.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 2000,
quantity: 1,
title: "Test variant default tax",
tax_lines: [
// Uses the california default rate
expect.objectContaining({
description: "CA Default Rate",
code: "CADEFAULT",
rate: 5,
provider_id: "system",
}),
],
}),
]),
})
)
})
})
describe("POST /store/carts/:id/payment-collections", () => {
it("should create a payment collection for the cart", async () => {
const region = await regionModuleService.create({
const region = await regionModule.create({
name: "US",
currency_code: "usd",
})
const cart = await cartModuleService.create({
const cart = await cartModule.create({
currency_code: "usd",
region_id: region.id,
})
@@ -0,0 +1 @@
export * from "./tax"
@@ -0,0 +1,245 @@
import { ITaxModuleService } from "@medusajs/types"
export const setupTaxStructure = async (service: ITaxModuleService) => {
// Setup for this specific test
//
// Using the following structure to setup tests.
// US - default 2%
// - Region: CA - default 5%
// - Override: Reduced rate (for 3 product ids): 3%
// - Override: Reduced rate (for product type): 1%
// - Region: NY - default: 6%
// - Region: FL - default: 4%
//
// Denmark - default 25%
//
// Germany - default 19%
// - Override: Reduced Rate (for product type) - 7%
//
// Canada - default 5%
// - Override: Reduced rate (for product id) - 3%
// - Override: Reduced rate (for product type) - 3.5%
// - Region: QC - default 2%
// - Override: Reduced rate (for same product type as country reduced rate): 1%
// - Region: BC - default 2%
//
const [us, dk, de, ca] = await service.createTaxRegions([
{
country_code: "US",
default_tax_rate: { name: "US Default Rate", rate: 2, code: "US_DEF" },
},
{
country_code: "DK",
default_tax_rate: {
name: "Denmark Default Rate",
rate: 25,
code: "DK_DEF",
},
},
{
country_code: "DE",
default_tax_rate: {
code: "DE19",
name: "Germany Default Rate",
rate: 19,
},
},
{
country_code: "CA",
default_tax_rate: { name: "Canada Default Rate", rate: 5 },
},
])
// Create province regions within the US
const [cal, ny, fl, qc, bc] = await service.createTaxRegions([
{
country_code: "US",
province_code: "CA",
parent_id: us.id,
default_tax_rate: {
rate: 5,
name: "CA Default Rate",
code: "CADEFAULT",
},
},
{
country_code: "US",
province_code: "NY",
parent_id: us.id,
default_tax_rate: {
rate: 6,
name: "NY Default Rate",
code: "NYDEFAULT",
},
},
{
country_code: "US",
province_code: "FL",
parent_id: us.id,
default_tax_rate: {
rate: 4,
name: "FL Default Rate",
code: "FLDEFAULT",
},
},
{
country_code: "CA",
province_code: "QC",
parent_id: ca.id,
default_tax_rate: {
rate: 2,
name: "QC Default Rate",
code: "QCDEFAULT",
},
},
{
country_code: "CA",
province_code: "BC",
parent_id: ca.id,
default_tax_rate: {
rate: 2,
name: "BC Default Rate",
code: "BCDEFAULT",
},
},
])
const [calProd, calType, deType, canProd, canType, qcType] =
await service.create([
{
tax_region_id: cal.id,
name: "CA Reduced Rate for Products",
rate: 3,
code: "CAREDUCE_PROD",
},
{
tax_region_id: cal.id,
name: "CA Reduced Rate for Product Type",
rate: 1,
code: "CAREDUCE_TYPE",
},
{
tax_region_id: de.id,
name: "Germany Reduced Rate for Product Type",
rate: 7,
code: "DEREDUCE_TYPE",
},
{
tax_region_id: ca.id,
name: "Canada Reduced Rate for Product",
rate: 3,
code: "CAREDUCE_PROD_CA",
},
{
tax_region_id: ca.id,
name: "Canada Reduced Rate for Product Type",
rate: 3.5,
code: "CAREDUCE_TYPE_CA",
},
{
tax_region_id: qc.id,
name: "QC Reduced Rate for Product Type",
rate: 1,
code: "QCREDUCE_TYPE",
},
])
// Create tax rate rules for specific products and product types
await service.createTaxRateRules([
{
reference: "product",
reference_id: "product_id_1",
tax_rate_id: calProd.id,
},
{
reference: "product",
reference_id: "product_id_2",
tax_rate_id: calProd.id,
},
{
reference: "product",
reference_id: "product_id_3",
tax_rate_id: calProd.id,
},
{
reference: "product_type",
reference_id: "product_type_id_1",
tax_rate_id: calType.id,
},
{
reference: "product_type",
reference_id: "product_type_id_2",
tax_rate_id: deType.id,
},
{
reference: "product",
reference_id: "product_id_4",
tax_rate_id: canProd.id,
},
{
reference: "product_type",
reference_id: "product_type_id_3",
tax_rate_id: canType.id,
},
{
reference: "product_type",
reference_id: "product_type_id_3",
tax_rate_id: qcType.id,
},
])
return {
us: {
country: us,
children: {
cal: {
province: cal,
overrides: {
calProd,
calType,
},
},
ny: {
province: ny,
overrides: {},
},
fl: {
province: fl,
overrides: {},
},
},
overrides: {},
},
dk: {
country: dk,
children: {},
overrides: {},
},
de: {
country: de,
children: {},
overrides: {
deType,
},
},
ca: {
country: ca,
children: {
qc: {
province: qc,
overrides: {
qcType,
},
},
bc: {
province: bc,
overrides: {},
},
},
overrides: {
canProd,
canType,
},
},
}
}