From e4acde1aa2eb57f07e6692fe8b61f728948b9a96 Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Thu, 7 Mar 2024 21:47:43 +0530 Subject: [PATCH] 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 --- .changeset/clever-bees-argue.md | 7 + .../__tests__/cart/store/carts.spec.ts | 367 ++++++++++++++---- .../modules/__tests__/fixtures/index.ts | 1 + .../modules/__tests__/fixtures/tax/index.ts | 245 ++++++++++++ .../cart/steps/get-item-tax-lines.ts | 115 ++++++ .../src/definition/cart/steps/index.ts | 3 + .../cart/steps/retrieve-cart-with-links.ts | 32 ++ .../cart/steps/set-tax-lines-for-items.ts | 128 ++++++ .../src/definition/cart/steps/update-carts.ts | 2 +- .../definition/cart/steps/update-tax-lines.ts | 24 ++ .../cart/steps/validate-variants-existence.ts | 8 +- .../cart/utils/prepare-line-item-data.ts | 1 + .../definition/cart/workflows/add-to-cart.ts | 75 ++-- .../definition/cart/workflows/create-carts.ts | 48 ++- .../src/definition/cart/workflows/index.ts | 1 + .../definition/cart/workflows/update-cart.ts | 23 +- .../cart/workflows/update-tax-lines.ts | 91 +++++ .../store/carts/[id]/line-items/route.ts | 23 +- .../src/api-v2/store/carts/[id]/route.ts | 14 +- .../src/api-v2/store/carts/query-config.ts | 31 ++ .../medusa/src/api-v2/store/carts/route.ts | 10 +- .../src/api-v2/store/carts/validators.ts | 8 + packages/tax/src/providers/system.ts | 2 + .../tax/src/services/tax-module-service.ts | 7 +- packages/types/src/cart/workflows.ts | 9 +- packages/types/src/tax/common.ts | 1 + 26 files changed, 1096 insertions(+), 180 deletions(-) create mode 100644 .changeset/clever-bees-argue.md create mode 100644 integration-tests/modules/__tests__/fixtures/index.ts create mode 100644 integration-tests/modules/__tests__/fixtures/tax/index.ts create mode 100644 packages/core-flows/src/definition/cart/steps/get-item-tax-lines.ts create mode 100644 packages/core-flows/src/definition/cart/steps/retrieve-cart-with-links.ts create mode 100644 packages/core-flows/src/definition/cart/steps/set-tax-lines-for-items.ts create mode 100644 packages/core-flows/src/definition/cart/steps/update-tax-lines.ts create mode 100644 packages/core-flows/src/definition/cart/workflows/update-tax-lines.ts diff --git a/.changeset/clever-bees-argue.md b/.changeset/clever-bees-argue.md new file mode 100644 index 0000000000..f2021bcfae --- /dev/null +++ b/.changeset/clever-bees-argue.md @@ -0,0 +1,7 @@ +--- +"@medusajs/core-flows": patch +"@medusajs/medusa": patch +"@medusajs/types": patch +--- + +feat(medusa,core-flows,types): add cart <> tax integration workflows + steps diff --git a/integration-tests/modules/__tests__/cart/store/carts.spec.ts b/integration-tests/modules/__tests__/cart/store/carts.spec.ts index ec4228ee3f..0102f101eb 100644 --- a/integration-tests/modules/__tests__/cart/store/carts.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/carts.spec.ts @@ -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, }) diff --git a/integration-tests/modules/__tests__/fixtures/index.ts b/integration-tests/modules/__tests__/fixtures/index.ts new file mode 100644 index 0000000000..1793574b72 --- /dev/null +++ b/integration-tests/modules/__tests__/fixtures/index.ts @@ -0,0 +1 @@ +export * from "./tax" diff --git a/integration-tests/modules/__tests__/fixtures/tax/index.ts b/integration-tests/modules/__tests__/fixtures/tax/index.ts new file mode 100644 index 0000000000..68d7bbd320 --- /dev/null +++ b/integration-tests/modules/__tests__/fixtures/tax/index.ts @@ -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, + }, + }, + } +} diff --git a/packages/core-flows/src/definition/cart/steps/get-item-tax-lines.ts b/packages/core-flows/src/definition/cart/steps/get-item-tax-lines.ts new file mode 100644 index 0000000000..2c377a64a8 --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/get-item-tax-lines.ts @@ -0,0 +1,115 @@ +import { + CartLineItemDTO, + CartShippingMethodDTO, + CartWorkflowDTO, + ITaxModuleService, + ItemTaxLineDTO, + ShippingTaxLineDTO, + TaxCalculationContext, + TaxableItemDTO, + TaxableShippingDTO, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" +import { ModuleRegistrationName } from "../../../../../modules-sdk/dist" + +interface StepInput { + cart: CartWorkflowDTO + items: CartLineItemDTO[] + shipping_methods: CartShippingMethodDTO[] +} + +function normalizeTaxModuleContext( + cart: CartWorkflowDTO +): TaxCalculationContext | null { + const address = cart.shipping_address + + if (!address || !address.country_code) { + return null + } + + let customer = cart.customer + ? { + id: cart.customer.id, + email: cart.customer.email, + customer_groups: cart.customer.groups?.map((g) => g.id) || [], + } + : undefined + + return { + address: { + country_code: address.country_code, + province_code: address.province, + address_1: address.address_1, + address_2: address.address_2, + city: address.city, + postal_code: address.postal_code, + }, + customer, + // TODO: Should probably come in from order module, defaulting to false + is_return: false, + } +} + +function normalizeLineItemsForTax( + cart: CartWorkflowDTO, + items: CartLineItemDTO[] +): TaxableItemDTO[] { + return items.map((item) => ({ + id: item.id, + product_id: item.product_id!, + product_name: item.variant_title, + product_sku: item.variant_sku, + product_type: item.product_type, + product_type_id: item.product_type, + quantity: item.quantity, + unit_price: item.unit_price, + currency_code: cart.currency_code, + })) +} + +function normalizeLineItemsForShipping( + cart: CartWorkflowDTO, + shippingMethods: CartShippingMethodDTO[] +): TaxableShippingDTO[] { + return shippingMethods.map((shippingMethod) => ({ + id: shippingMethod.id, + shipping_option_id: shippingMethod.shipping_option_id!, + unit_price: shippingMethod.amount, + currency_code: cart.currency_code, + })) +} + +export const getItemTaxLinesStepId = "get-item-tax-lines" +export const getItemTaxLinesStep = createStep( + getItemTaxLinesStepId, + async (data: StepInput, { container }) => { + const { cart, items, shipping_methods: shippingMethods } = data + const taxService = container.resolve( + ModuleRegistrationName.TAX + ) + + const taxContext = normalizeTaxModuleContext(cart) + + if (!taxContext) { + return new StepResponse({ + lineItemTaxLines: [], + shippingMethodsTaxLines: [], + }) + } + + const lineItemTaxLines = (await taxService.getTaxLines( + normalizeLineItemsForTax(cart, items), + taxContext + )) as ItemTaxLineDTO[] + + const shippingMethodsTaxLines = (await taxService.getTaxLines( + normalizeLineItemsForShipping(cart, shippingMethods), + taxContext + )) as ShippingTaxLineDTO[] + + return new StepResponse({ + lineItemTaxLines, + shippingMethodsTaxLines, + }) + } +) diff --git a/packages/core-flows/src/definition/cart/steps/index.ts b/packages/core-flows/src/definition/cart/steps/index.ts index 68df836551..8f046c5d67 100644 --- a/packages/core-flows/src/definition/cart/steps/index.ts +++ b/packages/core-flows/src/definition/cart/steps/index.ts @@ -6,12 +6,15 @@ export * from "./find-one-or-any-region" export * from "./find-or-create-customer" export * from "./find-sales-channel" export * from "./get-actions-to-compute-from-promotions" +export * from "./get-item-tax-lines" export * from "./get-variant-price-sets" export * from "./get-variants" export * from "./prepare-adjustments-from-promotion-actions" export * from "./remove-line-item-adjustments" export * from "./remove-shipping-method-adjustments" export * from "./retrieve-cart" +export * from "./retrieve-cart-with-links" +export * from "./set-tax-lines-for-items" export * from "./update-cart-promotions" export * from "./update-carts" export * from "./validate-variants-existence" diff --git a/packages/core-flows/src/definition/cart/steps/retrieve-cart-with-links.ts b/packages/core-flows/src/definition/cart/steps/retrieve-cart-with-links.ts new file mode 100644 index 0000000000..3a0453f029 --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/retrieve-cart-with-links.ts @@ -0,0 +1,32 @@ +import { LinkModuleUtils, Modules } from "@medusajs/modules-sdk" +import { CartWorkflowDTO } from "@medusajs/types" +import { isObject, remoteQueryObjectFromString } from "@medusajs/utils" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +interface StepInput { + cart_or_cart_id: string | CartWorkflowDTO + fields: string[] +} + +export const retrieveCartWithLinksStepId = "retrieve-cart-with-links" +export const retrieveCartWithLinksStep = createStep( + retrieveCartWithLinksStepId, + async (data: StepInput, { container }) => { + const { cart_or_cart_id: cartOrCartId, fields } = data + + if (isObject(cartOrCartId)) { + return new StepResponse(cartOrCartId) + } + + const id = cartOrCartId + const remoteQuery = container.resolve(LinkModuleUtils.REMOTE_QUERY) + const query = remoteQueryObjectFromString({ + entryPoint: Modules.CART, + fields, + }) + + const [cart] = await remoteQuery(query, { cart: { id } }) + + return new StepResponse(cart) + } +) diff --git a/packages/core-flows/src/definition/cart/steps/set-tax-lines-for-items.ts b/packages/core-flows/src/definition/cart/steps/set-tax-lines-for-items.ts new file mode 100644 index 0000000000..f2797612ae --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/set-tax-lines-for-items.ts @@ -0,0 +1,128 @@ +import { ModuleRegistrationName } from "@medusajs/modules-sdk" +import { + CartWorkflowDTO, + CreateLineItemTaxLineDTO, + CreateShippingMethodTaxLineDTO, + ICartModuleService, + ItemTaxLineDTO, + ShippingTaxLineDTO, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" + +interface StepInput { + cart: CartWorkflowDTO + item_tax_lines: ItemTaxLineDTO[] + shipping_tax_lines: ShippingTaxLineDTO[] +} + +export const setTaxLinesForItemsStepId = "set-tax-lines-for-items" +export const setTaxLinesForItemsStep = createStep( + setTaxLinesForItemsStepId, + async (data: StepInput, { container }) => { + const { cart, item_tax_lines, shipping_tax_lines } = data + const cartService = container.resolve( + ModuleRegistrationName.CART + ) + + const getShippingTaxLinesPromise = + await cartService.listShippingMethodTaxLines({ + shipping_method_id: shipping_tax_lines.map((t) => t.shipping_line_id), + }) + + const getItemTaxLinesPromise = await cartService.listLineItemTaxLines({ + item_id: item_tax_lines.map((t) => t.line_item_id), + }) + + const itemsTaxLinesData = normalizeItemTaxLinesForCart(item_tax_lines) + const setItemTaxLinesPromise = itemsTaxLinesData.length + ? cartService.setLineItemTaxLines(cart.id, itemsTaxLinesData) + : 0 + + const shippingTaxLinesData = + normalizeShippingTaxLinesForCart(shipping_tax_lines) + const setShippingTaxLinesPromise = shippingTaxLinesData.length + ? await cartService.setShippingMethodTaxLines( + cart.id, + shippingTaxLinesData + ) + : 0 + + const [existingShippingMethodTaxLines, existingLineItemTaxLines] = + await Promise.all([ + getShippingTaxLinesPromise, + getItemTaxLinesPromise, + setItemTaxLinesPromise, + setShippingTaxLinesPromise, + ]) + + return new StepResponse(null, { + cart, + existingLineItemTaxLines, + existingShippingMethodTaxLines, + }) + }, + async (revertData, { container }) => { + if (!revertData) { + return + } + + const { cart, existingLineItemTaxLines, existingShippingMethodTaxLines } = + revertData + + const cartService = container.resolve( + ModuleRegistrationName.CART + ) + + if (existingLineItemTaxLines) { + await cartService.setLineItemTaxLines( + cart.id, + existingLineItemTaxLines.map((taxLine) => ({ + description: taxLine.description, + tax_rate_id: taxLine.tax_rate_id, + code: taxLine.code, + rate: taxLine.rate, + provider_id: taxLine.provider_id, + item_id: taxLine.item_id, + })) + ) + } + + await cartService.setShippingMethodTaxLines( + cart.id, + existingShippingMethodTaxLines.map((taxLine) => ({ + description: taxLine.description, + tax_rate_id: taxLine.tax_rate_id, + code: taxLine.code, + rate: taxLine.rate, + provider_id: taxLine.provider_id, + shipping_method_id: taxLine.shipping_method_id, + })) + ) + } +) + +function normalizeItemTaxLinesForCart( + taxLines: ItemTaxLineDTO[] +): CreateLineItemTaxLineDTO[] { + return taxLines.map((taxLine) => ({ + description: taxLine.name, + tax_rate_id: taxLine.rate_id, + code: taxLine.code!, + rate: taxLine.rate!, + provider_id: taxLine.provider_id, + item_id: taxLine.line_item_id, + })) +} + +function normalizeShippingTaxLinesForCart( + taxLines: ShippingTaxLineDTO[] +): CreateShippingMethodTaxLineDTO[] { + return taxLines.map((taxLine) => ({ + description: taxLine.name, + tax_rate_id: taxLine.rate_id, + code: taxLine.code!, + rate: taxLine.rate!, + provider_id: taxLine.provider_id, + shipping_method_id: taxLine.shipping_line_id, + })) +} diff --git a/packages/core-flows/src/definition/cart/steps/update-carts.ts b/packages/core-flows/src/definition/cart/steps/update-carts.ts index f152e62da1..7b9438bce6 100644 --- a/packages/core-flows/src/definition/cart/steps/update-carts.ts +++ b/packages/core-flows/src/definition/cart/steps/update-carts.ts @@ -48,6 +48,6 @@ export const updateCartsStep = createStep( }) } - await cartModule.update(dataToUpdate) + return await cartModule.update(dataToUpdate) } ) diff --git a/packages/core-flows/src/definition/cart/steps/update-tax-lines.ts b/packages/core-flows/src/definition/cart/steps/update-tax-lines.ts new file mode 100644 index 0000000000..2eaf7189e9 --- /dev/null +++ b/packages/core-flows/src/definition/cart/steps/update-tax-lines.ts @@ -0,0 +1,24 @@ +import { + CartLineItemDTO, + CartShippingMethodDTO, + CartWorkflowDTO, +} from "@medusajs/types" +import { StepResponse, createStep } from "@medusajs/workflows-sdk" +import { updateTaxLinesWorkflow } from "../workflows" + +interface StepInput { + cart_or_cart_id: CartWorkflowDTO | string + items?: CartLineItemDTO[] + shipping_methods?: CartShippingMethodDTO[] +} + +export const updateTaxLinesStepId = "update-tax-lines-step" +export const updateTaxLinesStep = createStep( + updateTaxLinesStepId, + async (input: StepInput, { container }) => { + // TODO: manually trigger rollback on workflow when step fails + await updateTaxLinesWorkflow(container).run({ input }) + + return new StepResponse(null) + } +) diff --git a/packages/core-flows/src/definition/cart/steps/validate-variants-existence.ts b/packages/core-flows/src/definition/cart/steps/validate-variants-existence.ts index df07d230d8..dfcc0da1cf 100644 --- a/packages/core-flows/src/definition/cart/steps/validate-variants-existence.ts +++ b/packages/core-flows/src/definition/cart/steps/validate-variants-existence.ts @@ -16,12 +16,8 @@ export const validateVariantsExistStep = createStep( ) const variants = await productModuleService.listVariants( - { - id: data.variantIds, - }, - { - select: ["id"], - } + { id: data.variantIds }, + { select: ["id"] } ) const variantIdToData = new Set(variants.map((v) => v.id)) diff --git a/packages/core-flows/src/definition/cart/utils/prepare-line-item-data.ts b/packages/core-flows/src/definition/cart/utils/prepare-line-item-data.ts index 61c61bd80d..33313f0992 100644 --- a/packages/core-flows/src/definition/cart/utils/prepare-line-item-data.ts +++ b/packages/core-flows/src/definition/cart/utils/prepare-line-item-data.ts @@ -10,6 +10,7 @@ interface Input { export function prepareLineItemData(data: Input) { const { variant, unitPrice, quantity, metadata, cartId } = data + const lineItem: any = { quantity, title: variant.title, diff --git a/packages/core-flows/src/definition/cart/workflows/add-to-cart.ts b/packages/core-flows/src/definition/cart/workflows/add-to-cart.ts index fd9e379149..330a734cb9 100644 --- a/packages/core-flows/src/definition/cart/workflows/add-to-cart.ts +++ b/packages/core-flows/src/definition/cart/workflows/add-to-cart.ts @@ -14,6 +14,7 @@ import { validateVariantsExistStep, } from "../steps" import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions" +import { updateTaxLinesStep } from "../steps/update-tax-lines" import { prepareLineItemData } from "../utils/prepare-line-item-data" // TODO: The AddToCartWorkflow are missing the following steps: @@ -25,12 +26,12 @@ export const addToCartWorkflowId = "add-to-cart" export const addToCartWorkflow = createWorkflow( addToCartWorkflowId, (input: WorkflowData) => { - const variantIds = transform({ input }, (data) => { - return (data.input.items ?? []).map((i) => i.variant_id) + const variantIds = validateVariantsExistStep({ + variantIds: transform({ input }, (data) => { + return (data.input.items ?? []).map((i) => i.variant_id) + }), }) - validateVariantsExistStep({ variantIds }) - // TODO: This is on par with the context used in v1.*, but we can be more flexible. const pricingContext = transform({ cart: input.cart }, (data) => { return { @@ -45,31 +46,55 @@ export const addToCartWorkflow = createWorkflow( context: pricingContext, }) - const variants = getVariantsStep({ - filter: { id: variantIds }, - }) - - const lineItems = transform( - { priceSets, input, variants, cart: input.cart }, - (data) => { - const items = (data.input.items ?? []).map((item) => { - const variant = data.variants.find((v) => v.id === item.variant_id)! - - return prepareLineItemData({ - variant: variant, - unitPrice: data.priceSets[item.variant_id].calculated_amount, - quantity: item.quantity, - metadata: item?.metadata ?? {}, - cartId: data.cart.id, - }) as CreateLineItemForCartDTO - }) - - return items - } + const variants = getVariantsStep( + transform({ variantIds }, (data) => { + return { + filter: { id: data.variantIds }, + config: { + select: [ + "id", + "title", + "sku", + "barcode", + "product.id", + "product.title", + "product.description", + "product.subtitle", + "product.thumbnail", + "product.type", + "product.collection", + "product.handle", + ], + relations: ["product"], + }, + } + }) ) + const lineItems = transform({ priceSets, input, variants }, (data) => { + const items = (data.input.items ?? []).map((item) => { + const variant = data.variants.find((v) => v.id === item.variant_id)! + + return prepareLineItemData({ + variant: variant, + unitPrice: data.priceSets[item.variant_id].calculated_amount, + quantity: item.quantity, + metadata: item?.metadata ?? {}, + cartId: data.input.cart.id, + }) as CreateLineItemForCartDTO + }) + + return items + }) + const items = addToCartStep({ items: lineItems }) + updateTaxLinesStep({ + cart_or_cart_id: input.cart, + items, + // TODO: add shipping methods here when its ready + }) + refreshCartPromotionsStep({ id: input.cart.id }) return items diff --git a/packages/core-flows/src/definition/cart/workflows/create-carts.ts b/packages/core-flows/src/definition/cart/workflows/create-carts.ts index af47c474f8..86351ce5a6 100644 --- a/packages/core-flows/src/definition/cart/workflows/create-carts.ts +++ b/packages/core-flows/src/definition/cart/workflows/create-carts.ts @@ -14,6 +14,7 @@ import { getVariantsStep, validateVariantsExistStep, } from "../steps" +import { updateTaxLinesStep } from "../steps/update-tax-lines" import { prepareLineItemData } from "../utils/prepare-line-item-data" // TODO: The UpdateLineItemsWorkflow are missing the following steps: @@ -83,26 +84,30 @@ export const createCartWorkflow = createWorkflow( } ) - const variants = getVariantsStep({ - filter: { id: variantIds }, - config: { - select: [ - "id", - "title", - "sku", - "barcode", - "product.id", - "product.title", - "product.description", - "product.subtitle", - "product.thumbnail", - "product.type", - "product.collection", - "product.handle", - ], - relations: ["product"], - }, - }) + const variants = getVariantsStep( + transform({ variantIds }, (data) => { + return { + filter: { id: data.variantIds }, + config: { + select: [ + "id", + "title", + "sku", + "barcode", + "product.id", + "product.title", + "product.description", + "product.subtitle", + "product.thumbnail", + "product.type", + "product.collection", + "product.handle", + ], + relations: ["product"], + }, + } + }) + ) const lineItems = transform({ priceSets, input, variants }, (data) => { const items = (data.input.items ?? []).map((item) => { @@ -127,9 +132,10 @@ export const createCartWorkflow = createWorkflow( }) const carts = createCartsStep([cartToCreate]) - const cart = transform({ carts }, (data) => data.carts?.[0]) + updateTaxLinesStep({ cart_or_cart_id: cart.id }) + return cart } ) diff --git a/packages/core-flows/src/definition/cart/workflows/index.ts b/packages/core-flows/src/definition/cart/workflows/index.ts index fbf89e5539..9753920928 100644 --- a/packages/core-flows/src/definition/cart/workflows/index.ts +++ b/packages/core-flows/src/definition/cart/workflows/index.ts @@ -5,3 +5,4 @@ export * from "./refresh-payment-collection" export * from "./update-cart" export * from "./update-cart-promotions" export * from "./update-line-item-in-cart" +export * from "./update-tax-lines" diff --git a/packages/core-flows/src/definition/cart/workflows/update-cart.ts b/packages/core-flows/src/definition/cart/workflows/update-cart.ts index 01bea38343..51649d8eea 100644 --- a/packages/core-flows/src/definition/cart/workflows/update-cart.ts +++ b/packages/core-flows/src/definition/cart/workflows/update-cart.ts @@ -1,4 +1,4 @@ -import { CartDTO, UpdateCartWorkflowInputDTO } from "@medusajs/types" +import { UpdateCartWorkflowInputDTO } from "@medusajs/types" import { PromotionActions, isPresent } from "@medusajs/utils" import { WorkflowData, @@ -10,16 +10,16 @@ import { findOneOrAnyRegionStep, findOrCreateCustomerStep, findSalesChannelStep, - retrieveCartStep, updateCartsStep, } from "../steps" import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions" +import { updateTaxLinesStep } from "../steps/update-tax-lines" import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection" export const updateCartWorkflowId = "update-cart" export const updateCartWorkflow = createWorkflow( updateCartWorkflowId, - (input: WorkflowData): WorkflowData => { + (input: WorkflowData): WorkflowData => { const [salesChannel, region, customerData] = parallelize( findSalesChannelStep({ salesChannelId: input.sales_channel_id, @@ -61,8 +61,9 @@ export const updateCartWorkflow = createWorkflow( } ) - updateCartsStep([cartInput]) + const carts = updateCartsStep([cartInput]) + updateTaxLinesStep({ cart_or_cart_id: carts[0].id }) refreshCartPromotionsStep({ id: input.id, promo_codes: input.promo_codes, @@ -72,19 +73,5 @@ export const updateCartWorkflow = createWorkflow( refreshPaymentCollectionForCartStep({ cart_id: input.id, }) - - const retrieveCartInput = { - id: input.id, - config: { - relations: [ - "items", - "items.adjustments", - "shipping_methods", - "shipping_methods.adjustments", - ], - }, - } - - return retrieveCartStep(retrieveCartInput) } ) diff --git a/packages/core-flows/src/definition/cart/workflows/update-tax-lines.ts b/packages/core-flows/src/definition/cart/workflows/update-tax-lines.ts new file mode 100644 index 0000000000..5a81c1caf8 --- /dev/null +++ b/packages/core-flows/src/definition/cart/workflows/update-tax-lines.ts @@ -0,0 +1,91 @@ +import { + CartLineItemDTO, + CartShippingMethodDTO, + CartWorkflowDTO, +} from "@medusajs/types" +import { + WorkflowData, + createWorkflow, + transform, +} from "@medusajs/workflows-sdk" +import { + getItemTaxLinesStep, + retrieveCartWithLinksStep, + setTaxLinesForItemsStep, +} from "../steps" + +const cartFields = [ + "id", + "currency_code", + "email", + "items.id", + "items.variant_id", + "items.product_id", + "items.product_title", + "items.product_description", + "items.product_subtitle", + "items.product_type", + "items.product_collection", + "items.product_handle", + "items.variant_sku", + "items.variant_barcode", + "items.variant_title", + "items.title", + "items.quantity", + "items.unit_price", + "items.tax_lines.id", + "items.tax_lines.description", + "items.tax_lines.code", + "items.tax_lines.rate", + "items.tax_lines.provider_id", + "shipping_methods.tax_lines.id", + "shipping_methods.tax_lines.description", + "shipping_methods.tax_lines.code", + "shipping_methods.tax_lines.rate", + "shipping_methods.tax_lines.provider_id", + "shipping_methods.shipping_option_id", + "shipping_methods.amount", + "customer.id", + "customer.email", + "customer.groups.id", + "shipping_address.id", + "shipping_address.address_1", + "shipping_address.address_2", + "shipping_address.city", + "shipping_address.postal_code", + "shipping_address.country_code", + "shipping_address.region_code", + "shipping_address.province", +] + +type WorkflowInput = { + cart_or_cart_id: string | CartWorkflowDTO + items?: CartLineItemDTO[] + shipping_methods?: CartShippingMethodDTO[] +} + +export const updateTaxLinesWorkflowId = "update-tax-lines" +export const updateTaxLinesWorkflow = createWorkflow( + updateTaxLinesWorkflowId, + (input: WorkflowData): WorkflowData => { + const cart = retrieveCartWithLinksStep({ + cart_or_cart_id: input.cart_or_cart_id, + fields: cartFields, + }) + + const taxLineItems = getItemTaxLinesStep( + transform({ input, cart }, (data) => ({ + cart: data.cart, + items: data.input.items || data.cart.items, + shipping_methods: + data.input.shipping_methods || data.cart.shipping_methods, + })) + ) + + setTaxLinesForItemsStep({ + cart, + item_tax_lines: taxLineItems.lineItemTaxLines, + shipping_tax_lines: taxLineItems.shippingMethodsTaxLines, + }) + } +) diff --git a/packages/medusa/src/api-v2/store/carts/[id]/line-items/route.ts b/packages/medusa/src/api-v2/store/carts/[id]/line-items/route.ts index 4be30ea6f4..9b4cc9664d 100644 --- a/packages/medusa/src/api-v2/store/carts/[id]/line-items/route.ts +++ b/packages/medusa/src/api-v2/store/carts/[id]/line-items/route.ts @@ -1,18 +1,20 @@ import { addToCartWorkflow } from "@medusajs/core-flows" -import { ModuleRegistrationName } from "@medusajs/modules-sdk" -import { ICartModuleService } from "@medusajs/types" +import { LinkModuleUtils, Modules } from "@medusajs/modules-sdk" import { remoteQueryObjectFromString } from "@medusajs/utils" import { MedusaRequest, MedusaResponse } from "../../../../../types/routing" import { defaultStoreCartFields } from "../../query-config" import { StorePostCartsCartLineItemsReq } from "./validators" export const POST = async (req: MedusaRequest, res: MedusaResponse) => { - const cartModuleService = req.scope.resolve( - ModuleRegistrationName.CART - ) + const remoteQuery = req.scope.resolve(LinkModuleUtils.REMOTE_QUERY) - const cart = await cartModuleService.retrieve(req.params.id, { - select: ["id", "region_id", "currency_code"], + const query = remoteQueryObjectFromString({ + entryPoint: Modules.CART, + fields: defaultStoreCartFields, + }) + + const [cart] = await remoteQuery(query, { + cart: { id: req.params.id }, }) const workflowInput = { @@ -29,13 +31,6 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => { throw errors[0].error } - const remoteQuery = req.scope.resolve("remoteQuery") - - const query = remoteQueryObjectFromString({ - entryPoint: "cart", - fields: defaultStoreCartFields, - }) - const [updatedCart] = await remoteQuery(query, { cart: { id: req.params.id }, }) diff --git a/packages/medusa/src/api-v2/store/carts/[id]/route.ts b/packages/medusa/src/api-v2/store/carts/[id]/route.ts index dda5cf38af..1db6e194b4 100644 --- a/packages/medusa/src/api-v2/store/carts/[id]/route.ts +++ b/packages/medusa/src/api-v2/store/carts/[id]/route.ts @@ -1,4 +1,5 @@ import { updateCartWorkflow } from "@medusajs/core-flows" +import { LinkModuleUtils, Modules } from "@medusajs/modules-sdk" import { UpdateCartDataDTO } from "@medusajs/types" import { remoteQueryObjectFromString } from "@medusajs/utils" @@ -6,12 +7,11 @@ import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { defaultStoreCartFields } from "../query-config" export const GET = async (req: MedusaRequest, res: MedusaResponse) => { - const remoteQuery = req.scope.resolve("remoteQuery") - + const remoteQuery = req.scope.resolve(LinkModuleUtils.REMOTE_QUERY) const variables = { id: req.params.id } const query = remoteQueryObjectFromString({ - entryPoint: "cart", + entryPoint: Modules.CART, fields: defaultStoreCartFields, }) @@ -38,16 +38,16 @@ export const POST = async ( throw errors[0].error } - const remoteQuery = req.scope.resolve("remoteQuery") + const remoteQuery = req.scope.resolve(LinkModuleUtils.REMOTE_QUERY) const query = remoteQueryObjectFromString({ - entryPoint: "cart", + entryPoint: Modules.CART, fields: defaultStoreCartFields, }) - const [updatedCart] = await remoteQuery(query, { + const [cart] = await remoteQuery(query, { cart: { id: req.params.id }, }) - res.status(200).json({ cart: updatedCart }) + res.status(200).json({ cart }) } diff --git a/packages/medusa/src/api-v2/store/carts/query-config.ts b/packages/medusa/src/api-v2/store/carts/query-config.ts index 2220f8a497..9a518d19aa 100644 --- a/packages/medusa/src/api-v2/store/carts/query-config.ts +++ b/packages/medusa/src/api-v2/store/carts/query-config.ts @@ -5,16 +5,40 @@ export const defaultStoreCartFields = [ "created_at", "updated_at", "items.id", + "items.variant_id", + "items.product_id", + "items.product_title", + "items.product_description", + "items.product_subtitle", + "items.product_type", + "items.product_collection", + "items.product_handle", + "items.variant_sku", + "items.variant_barcode", + "items.variant_title", "items.created_at", "items.updated_at", "items.title", "items.quantity", "items.unit_price", + "items.tax_lines.id", + "items.tax_lines.description", + "items.tax_lines.code", + "items.tax_lines.rate", + "items.tax_lines.provider_id", "items.adjustments.id", "items.adjustments.code", "items.adjustments.amount", "customer.id", "customer.email", + "customer.groups.id", + "shipping_methods.tax_lines.id", + "shipping_methods.tax_lines.description", + "shipping_methods.tax_lines.code", + "shipping_methods.tax_lines.rate", + "shipping_methods.tax_lines.provider_id", + "shipping_methods.shipping_option_id", + "shipping_methods.amount", "shipping_methods.adjustments.id", "shipping_methods.adjustments.code", "shipping_methods.adjustments.amount", @@ -27,6 +51,7 @@ export const defaultStoreCartFields = [ "shipping_address.postal_code", "shipping_address.country_code", "shipping_address.region_code", + "shipping_address.province", "shipping_address.phone", "billing_address.id", "billing_address.first_name", @@ -51,23 +76,29 @@ export const defaultStoreCartFields = [ export const defaultStoreCartRelations = [ "items", + "items.tax_lines", "items.adjustments", "region", "customer", + "customer.groups", "shipping_address", "billing_address", "shipping_methods", + "shipping_methods.tax_lines", "shipping_methods.adjustments", ] export const allowedRelations = [ "items", + "items.tax_lines", "items.adjustments", "region", "customer", + "customer.groups", "shipping_address", "billing_address", "shipping_methods", + "shipping_methods.tax_lines", "shipping_methods.adjustments", "sales_channel", ] diff --git a/packages/medusa/src/api-v2/store/carts/route.ts b/packages/medusa/src/api-v2/store/carts/route.ts index 516c1908f4..faeb52a5d6 100644 --- a/packages/medusa/src/api-v2/store/carts/route.ts +++ b/packages/medusa/src/api-v2/store/carts/route.ts @@ -1,4 +1,5 @@ import { createCartWorkflow } from "@medusajs/core-flows" +import { LinkModuleUtils, Modules } from "@medusajs/modules-sdk" import { CreateCartWorkflowInputDTO } from "@medusajs/types" import { remoteQueryObjectFromString } from "@medusajs/utils" import { @@ -25,16 +26,13 @@ export const POST = async ( throw errors[0].error } - const remoteQuery = req.scope.resolve("remoteQuery") - - const variables = { id: result.id } - + const remoteQuery = req.scope.resolve(LinkModuleUtils.REMOTE_QUERY) const query = remoteQueryObjectFromString({ - entryPoint: "cart", + entryPoint: Modules.CART, fields: defaultStoreCartFields, }) - const [cart] = await remoteQuery(query, { cart: variables }) + const [cart] = await remoteQuery(query, { cart: { id: result.id } }) res.status(200).json({ cart }) } diff --git a/packages/medusa/src/api-v2/store/carts/validators.ts b/packages/medusa/src/api-v2/store/carts/validators.ts index 041ed2f5e4..796d225e79 100644 --- a/packages/medusa/src/api-v2/store/carts/validators.ts +++ b/packages/medusa/src/api-v2/store/carts/validators.ts @@ -29,6 +29,14 @@ export class StorePostCartReq { @IsString() region_id?: string + @IsOptional() + @IsType([AddressPayload, String]) + shipping_address?: AddressPayload | string + + @IsOptional() + @IsType([AddressPayload, String]) + billing_address?: AddressPayload | string + @IsOptional() @IsString() email?: string diff --git a/packages/tax/src/providers/system.ts b/packages/tax/src/providers/system.ts index 0b67832ebb..626a48d7a3 100644 --- a/packages/tax/src/providers/system.ts +++ b/packages/tax/src/providers/system.ts @@ -20,6 +20,7 @@ export default class SystemTaxService implements ITaxProvider { name: r.name, code: r.code, line_item_id: l.line_item.id, + provider_id: this.getIdentifier(), })) }) @@ -31,6 +32,7 @@ export default class SystemTaxService implements ITaxProvider { name: r.name, code: r.code, shipping_line_id: l.shipping_line.id, + provider_id: this.getIdentifier(), })) }) ) diff --git a/packages/tax/src/services/tax-module-service.ts b/packages/tax/src/services/tax-module-service.ts index ac7e24b4d2..aad7eb11c6 100644 --- a/packages/tax/src/services/tax-module-service.ts +++ b/packages/tax/src/services/tax-module-service.ts @@ -6,6 +6,7 @@ import { InternalModuleDeclaration, ModuleJoinerConfig, ModulesSdkTypes, + TaxRegionDTO, TaxTypes, } from "@medusajs/types" import { @@ -14,16 +15,14 @@ import { MedusaContext, MedusaError, ModulesSdkUtils, - arrayDifference, isDefined, isString, promiseAll, } from "@medusajs/utils" -import { TaxProvider, TaxRate, TaxRegion, TaxRateRule } from "@models" +import { TaxProvider, TaxRate, TaxRateRule, TaxRegion } from "@models" import { entityNameToLinkableKeysMap, joinerConfig } from "../joiner-config" -import { TaxRegionDTO } from "@medusajs/types" -import { uniqueRateReferenceIndexName } from "../models/tax-rate-rule" import { singleDefaultRegionIndexName } from "../models/tax-rate" +import { uniqueRateReferenceIndexName } from "../models/tax-rate-rule" import { countryCodeProvinceIndexName } from "../models/tax-region" type InjectedDependencies = { diff --git a/packages/types/src/cart/workflows.ts b/packages/types/src/cart/workflows.ts index 226fedd020..d81f860fbf 100644 --- a/packages/types/src/cart/workflows.ts +++ b/packages/types/src/cart/workflows.ts @@ -1,3 +1,5 @@ +import { CustomerDTO } from "../customer" +import { ProductDTO } from "../product" import { CartDTO, CartLineItemDTO } from "./common" import { UpdateLineItemDTO } from "./mutations" @@ -70,7 +72,7 @@ export interface CreateCartWorkflowInputDTO { export interface AddToCartWorkflowInputDTO { items: CreateCartCreateLineItemDTO[] - cart: CartDTO + cart: CartWorkflowDTO } export interface UpdateCartWorkflowInputDTO { @@ -91,3 +93,8 @@ export interface CreatePaymentCollectionForCartWorkflowInputDTO { amount: number metadata?: Record } + +export interface CartWorkflowDTO extends CartDTO { + customer?: CustomerDTO + product?: ProductDTO +} diff --git a/packages/types/src/tax/common.ts b/packages/types/src/tax/common.ts index b9c2c72567..70a4d24fd8 100644 --- a/packages/types/src/tax/common.ts +++ b/packages/types/src/tax/common.ts @@ -165,6 +165,7 @@ interface TaxLineDTO { rate: number | null code: string | null name: string + provider_id: string } export interface ItemTaxLineDTO extends TaxLineDTO {