fix(medusa): Optimize Cart totals calculation (#2372)
**What** The existing totals calculations are extremely heavy and perform an enormous amount of duplicate work. The changes here remove large parts of the overhead and improves response times for cart endpoints up to 30x.
This commit is contained in:
1 parent
527c587d8f
commit
3d255302b0
45 files changed
+10964
-8076
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve performance of cart total calculations
|
||||||
@@ -32,7 +32,7 @@ describe("/admin/orders", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd })
|
medusaProcess = await setupServer({ cwd, verbose: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -495,7 +495,6 @@ describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /store/carts", () => {
|
|||||||
|
|
||||||
const expectedItemTotals = {
|
const expectedItemTotals = {
|
||||||
subtotal: 200,
|
subtotal: 200,
|
||||||
gift_card_total: 0,
|
|
||||||
discount_total: 30,
|
discount_total: 30,
|
||||||
total: 204,
|
total: 204,
|
||||||
original_total: 240,
|
original_total: 240,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("Automatic Cart Taxes", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd })
|
medusaProcess = await setupServer({ cwd, verbose: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ describe("Cart Totals Calculations", () => {
|
|||||||
expect(res.data.cart.items[0].original_total).toEqual(110)
|
expect(res.data.cart.items[0].original_total).toEqual(110)
|
||||||
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].discount_total).toEqual(0)
|
expect(res.data.cart.items[0].discount_total).toEqual(0)
|
||||||
expect(res.data.cart.items[0].gift_card_total).toEqual(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("sets correct line item totals for a cart with item of price 100; tax rate 10; discount 10", async () => {
|
it("sets correct line item totals for a cart with item of price 100; tax rate 10; discount 10", async () => {
|
||||||
@@ -123,7 +122,6 @@ describe("Cart Totals Calculations", () => {
|
|||||||
expect(res.data.cart.items[0].original_total).toEqual(110)
|
expect(res.data.cart.items[0].original_total).toEqual(110)
|
||||||
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].gift_card_total).toEqual(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("doesn't include taxes in !automatic_taxes regions", async () => {
|
it("doesn't include taxes in !automatic_taxes regions", async () => {
|
||||||
@@ -174,7 +172,6 @@ describe("Cart Totals Calculations", () => {
|
|||||||
expect(res.data.cart.items[0].original_total).toEqual(100)
|
expect(res.data.cart.items[0].original_total).toEqual(100)
|
||||||
expect(res.data.cart.items[0].original_tax_total).toEqual(0)
|
expect(res.data.cart.items[0].original_tax_total).toEqual(0)
|
||||||
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].gift_card_total).toEqual(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("includes taxes in !automatic_taxes regions when forced", async () => {
|
it("includes taxes in !automatic_taxes regions when forced", async () => {
|
||||||
@@ -227,6 +224,5 @@ describe("Cart Totals Calculations", () => {
|
|||||||
expect(res.data.cart.items[0].original_total).toEqual(110)
|
expect(res.data.cart.items[0].original_total).toEqual(110)
|
||||||
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
expect(res.data.cart.items[0].original_tax_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
expect(res.data.cart.items[0].discount_total).toEqual(10)
|
||||||
expect(res.data.cart.items[0].gift_card_total).toEqual(0)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -25,7 +25,7 @@ describe("Manual Cart Taxes", () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
dbConnection = await initDb({ cwd })
|
dbConnection = await initDb({ cwd })
|
||||||
medusaProcess = await setupServer({ cwd })
|
medusaProcess = await setupServer({ cwd, verbose: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -76,7 +76,7 @@ describe("Manual Cart Taxes", () => {
|
|||||||
|
|
||||||
const response = await api.get("/store/carts/test-cart")
|
const response = await api.get("/store/carts/test-cart")
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.cart.tax_total).toEqual(null)
|
expect(response.data.cart.tax_total).toEqual(0)
|
||||||
expect(response.data.cart.total).toEqual(100)
|
expect(response.data.cart.total).toEqual(100)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
npmRegistryServer: "http://localhost:4873"
|
||||||
|
|
||||||
|
unsafeHttpWhitelist:
|
||||||
|
- localhost
|
||||||
@@ -328,7 +328,6 @@ Object {
|
|||||||
"title": "Intelligent Plastic Chips",
|
"title": "Intelligent Plastic Chips",
|
||||||
"totals": Object {
|
"totals": Object {
|
||||||
"discount_total": 0,
|
"discount_total": 0,
|
||||||
"gift_card_total": 0,
|
|
||||||
"original_tax_total": 200,
|
"original_tax_total": 200,
|
||||||
"original_total": 1200,
|
"original_total": 1200,
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
@@ -1022,7 +1021,6 @@ Object {
|
|||||||
"title": "Intelligent Plastic Chips",
|
"title": "Intelligent Plastic Chips",
|
||||||
"totals": Object {
|
"totals": Object {
|
||||||
"discount_total": 0,
|
"discount_total": 0,
|
||||||
"gift_card_total": 0,
|
|
||||||
"original_tax_total": 400,
|
"original_tax_total": 400,
|
||||||
"original_total": 2400,
|
"original_total": 2400,
|
||||||
"quantity": 2,
|
"quantity": 2,
|
||||||
@@ -1477,7 +1475,6 @@ Object {
|
|||||||
"title": "Intelligent Plastic Chips",
|
"title": "Intelligent Plastic Chips",
|
||||||
"totals": Object {
|
"totals": Object {
|
||||||
"discount_total": 0,
|
"discount_total": 0,
|
||||||
"gift_card_total": 0,
|
|
||||||
"original_tax_total": 200,
|
"original_tax_total": 200,
|
||||||
"original_total": 1200,
|
"original_total": 1200,
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
|
|||||||
@@ -8,18 +8,18 @@
|
|||||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/medusa": "1.2.1-dev-1649009241281",
|
"@medusajs/medusa": "1.4.1-dev-1665082901122",
|
||||||
"faker": "^5.5.3",
|
"faker": "^5.5.3",
|
||||||
"medusa-fulfillment-webshipper": "1.2.1-dev-1649009241281",
|
"medusa-fulfillment-webshipper": "1.3.3-dev-1665082901122",
|
||||||
"medusa-interfaces": "1.2.1-dev-1649009241281",
|
"medusa-interfaces": "1.3.3-dev-1665082901122",
|
||||||
"medusa-plugin-sendgrid": "1.2.1-dev-1649009241281",
|
"medusa-plugin-sendgrid": "1.3.3-dev-1665082901122",
|
||||||
"typeorm": "^0.2.31"
|
"typeorm": "^0.2.31"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.12.10",
|
"@babel/cli": "^7.12.10",
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.10",
|
"@babel/node": "^7.12.10",
|
||||||
"babel-preset-medusa-package": "1.1.19-dev-1649009241281",
|
"babel-preset-medusa-package": "1.1.19-dev-1665082901122",
|
||||||
"jest": "^26.6.3"
|
"jest": "^26.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10648
-7781
File diff suppressed because it is too large.
Load diff
@@ -33,7 +33,8 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
@@ -75,7 +76,8 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
@@ -124,7 +126,8 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe("POST /store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ describe("POST /store/carts/:id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(3)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls LineItemService generate", () => {
|
it("calls LineItemService generate", () => {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe("POST /store/carts/:id/payment-sessions", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls Cart service retrieve", () => {
|
it("calls Cart service retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ describe("GET /store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls retrieve from CartService", () => {
|
it("calls retrieve from CartService", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns cart", () => {
|
it("returns cart", () => {
|
||||||
@@ -41,7 +42,7 @@ describe("GET /store/carts", () => {
|
|||||||
it("calls get product from productSerice", () => {
|
it("calls get product from productSerice", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith("none", {
|
expect(CartServiceMock.retrieve).toHaveBeenCalledWith("none", {
|
||||||
relations: ["customer"],
|
select: ["id", "customer_id"],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe("POST /store/carts/:id/payment-session/update", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrive", () => {
|
it("calls CartService retrive", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -61,14 +61,15 @@ describe("POST /store/carts/:id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls get product from productService", () => {
|
it("calls get product from productService", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
||||||
IdMap.getId("emptyCart"),
|
IdMap.getId("emptyCart"),
|
||||||
{
|
{
|
||||||
relations: ["payment_sessions", "shipping_methods"],
|
relations: ["payment_sessions", "shipping_methods"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledWith(
|
||||||
IdMap.getId("emptyCart"),
|
IdMap.getId("emptyCart"),
|
||||||
{
|
{
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ describe("POST /store/carts/:id/line-items/:line_id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrieve", () => {
|
it("calls CartService retrieve", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(3)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
||||||
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe("POST /store/carts/:id/payment-session/update", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls CartService retrive", () => {
|
it("calls CartService retrive", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
|||||||
|
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,13 +85,11 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedCart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(updatedCart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { CartService, IdempotencyKeyService } from "../../../../services"
|
|||||||
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { IdempotencyKey } from "../../../../models/idempotency-key"
|
import { IdempotencyKey } from "../../../../models/idempotency-key"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /carts/{id}/taxes
|
* @oas [post] /carts/{id}/taxes
|
||||||
@@ -49,11 +48,11 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const headerKey = req.get("Idempotency-Key") || ""
|
const headerKey = req.get("Idempotency-Key") || ""
|
||||||
|
|
||||||
let idempotencyKey
|
let idempotencyKey: IdempotencyKey
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await manager.transaction(async (transactionManager) => {
|
idempotencyKey = await manager.transaction(async (transactionManager) => {
|
||||||
idempotencyKey = await idempotencyKeyService
|
return await idempotencyKeyService
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.initializeRequest(headerKey, req.method, req.params, req.path)
|
.initializeRequest(headerKey, req.method, req.params, req.path)
|
||||||
})
|
})
|
||||||
@@ -82,29 +81,11 @@ export default async (req, res) => {
|
|||||||
async (manager: EntityManager) => {
|
async (manager: EntityManager) => {
|
||||||
const cart = await cartService
|
const cart = await cartService
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.retrieve(
|
.retrieveWithTotals(id, {}, { force_taxes: true })
|
||||||
id,
|
|
||||||
{
|
|
||||||
relations: ["items", "items.adjustments"],
|
|
||||||
select: [
|
|
||||||
"total",
|
|
||||||
"subtotal",
|
|
||||||
"tax_total",
|
|
||||||
"discount_total",
|
|
||||||
"shipping_total",
|
|
||||||
"gift_card_total",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ force_taxes: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req, {
|
|
||||||
force_taxes: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response_code: 200,
|
response_code: 200,
|
||||||
response_body: { cart: data },
|
response_body: { cart },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -113,7 +94,7 @@ export default async (req, res) => {
|
|||||||
inProgress = false
|
inProgress = false
|
||||||
err = error
|
err = error
|
||||||
} else {
|
} else {
|
||||||
idempotencyKey = key
|
idempotencyKey = key!
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { Cart } from "../../../../models"
|
|||||||
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators"
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { CartCreateProps } from "../../../../types/cart"
|
import { CartCreateProps } from "../../../../types/cart"
|
||||||
import { isDefined } from "../../../../utils"
|
import { isDefined } from "../../../../utils"
|
||||||
|
|
||||||
@@ -181,14 +180,12 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
cart = await cartService.retrieve(cart!.id, {
|
cart = await cartService.retrieveWithTotals(cart!.id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
res.status(200).json({ cart })
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Item {
|
export class Item {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { EntityManager } from "typeorm"
|
|||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService, LineItemService } from "../../../../services"
|
import { CartService, LineItemService } from "../../../../services"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { FlagRouter } from "../../../../utils/flag-router"
|
import { FlagRouter } from "../../../../utils/flag-router"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,13 +97,11 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import IdempotencyKeyService from "../../../../services/idempotency-key"
|
import IdempotencyKeyService from "../../../../services/idempotency-key"
|
||||||
|
|
||||||
@@ -92,19 +91,14 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const cart = await cartService
|
const cart = await cartService
|
||||||
.withTransaction(stageManager)
|
.withTransaction(stageManager)
|
||||||
.retrieve(id, {
|
.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req, {
|
|
||||||
force_taxes: false,
|
|
||||||
transactionManager: stageManager,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response_code: 200,
|
response_code: 200,
|
||||||
response_body: { cart: data },
|
response_body: { cart },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import { Request } from "express"
|
|
||||||
import { TotalsService } from "../../../../services"
|
|
||||||
import { Cart, LineItem } from "../../../../models"
|
|
||||||
import { EntityManager } from "typeorm"
|
|
||||||
|
|
||||||
export const decorateLineItemsWithTotals = async (
|
|
||||||
cart: Cart,
|
|
||||||
req: Request,
|
|
||||||
options: { force_taxes: boolean; transactionManager?: EntityManager } = {
|
|
||||||
force_taxes: false,
|
|
||||||
}
|
|
||||||
): Promise<Cart> => {
|
|
||||||
const totalsService: TotalsService = req.scope.resolve("totalsService")
|
|
||||||
|
|
||||||
if (cart.items && cart.region) {
|
|
||||||
const getItems = async (manager) => {
|
|
||||||
const totalsServiceTx = totalsService.withTransaction(manager)
|
|
||||||
return await Promise.all(
|
|
||||||
cart.items.map(async (item: LineItem) => {
|
|
||||||
const itemTotals = await totalsServiceTx.getLineItemTotals(
|
|
||||||
item,
|
|
||||||
cart,
|
|
||||||
{
|
|
||||||
include_tax: options.force_taxes || cart.region.automatic_taxes,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Object.assign(item, itemTotals)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let items
|
|
||||||
if (options.transactionManager) {
|
|
||||||
items = await getItems(options.transactionManager)
|
|
||||||
} else {
|
|
||||||
const manager: EntityManager =
|
|
||||||
options.transactionManager ?? req.scope.resolve("manager")
|
|
||||||
items = await manager.transaction(async (transactionManager) => {
|
|
||||||
return await getItems(transactionManager)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(cart, { items })
|
|
||||||
}
|
|
||||||
|
|
||||||
return cart
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [delete] /carts/{id}/discounts/{code}
|
* @oas [delete] /carts/{id}/discounts/{code}
|
||||||
@@ -67,11 +66,10 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [delete] /carts/{id}/line-items/{line_id}
|
* @oas [delete] /carts/{id}/line-items/{line_id}
|
||||||
@@ -67,11 +66,9 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,11 +58,10 @@ export default async (req, res) => {
|
|||||||
.deletePaymentSession(id, provider_id)
|
.deletePaymentSession(id, provider_id)
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [get] /carts/{id}
|
* @oas [get] /carts/{id}
|
||||||
@@ -49,8 +48,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
const cartService: CartService = req.scope.resolve("cartService")
|
const cartService: CartService = req.scope.resolve("cartService")
|
||||||
|
|
||||||
let cart = await cartService.retrieve(id, {
|
const cart = await cartService.retrieve(id, {
|
||||||
relations: ["customer"],
|
select: ["id", "customer_id"],
|
||||||
})
|
})
|
||||||
|
|
||||||
// If there is a logged in user add the user to the cart
|
// If there is a logged in user add the user to the cart
|
||||||
@@ -66,8 +65,6 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cart = await cartService.retrieve(id, req.retrieveConfig)
|
const data = await cartService.retrieveWithTotals(id, req.retrieveConfig)
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
res.json({ cart: data })
|
res.json({ cart: data })
|
||||||
}
|
}
|
||||||
@@ -121,14 +121,7 @@ export default (app, container) => {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultStoreCartFields: (keyof Cart)[] = [
|
export const defaultStoreCartFields: (keyof Cart)[] = []
|
||||||
"subtotal",
|
|
||||||
"tax_total",
|
|
||||||
"shipping_total",
|
|
||||||
"discount_total",
|
|
||||||
"gift_card_total",
|
|
||||||
"total",
|
|
||||||
]
|
|
||||||
|
|
||||||
export const defaultStoreCartRelations = [
|
export const defaultStoreCartRelations = [
|
||||||
"gift_cards",
|
"gift_cards",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,14 +56,7 @@ export default async (req, res) => {
|
|||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.refreshPaymentSession(id, provider_id)
|
.refreshPaymentSession(id, provider_id)
|
||||||
})
|
})
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: [
|
|
||||||
"subtotal",
|
|
||||||
"tax_total",
|
|
||||||
"shipping_total",
|
|
||||||
"discount_total",
|
|
||||||
"total",
|
|
||||||
],
|
|
||||||
relations: [
|
relations: [
|
||||||
"region",
|
"region",
|
||||||
"region.countries",
|
"region.countries",
|
||||||
@@ -75,6 +67,5 @@ export default async (req, res) => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
|||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
import { IsString } from "class-validator"
|
import { IsString } from "class-validator"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,12 +72,11 @@ export default async (req, res) => {
|
|||||||
.setPaymentSession(id, validated.provider_id)
|
.setPaymentSession(id, validated.provider_id)
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
|
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators
|
|||||||
import { IsType } from "../../../../utils/validators/is-type"
|
import { IsType } from "../../../../utils/validators/is-type"
|
||||||
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels"
|
||||||
import { Type } from "class-transformer"
|
import { Type } from "class-transformer"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /carts/{id}
|
* @oas [post] /carts/{id}
|
||||||
@@ -152,12 +151,10 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.json({ cart: data })
|
res.json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { EntityManager } from "typeorm"
|
|||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @oas [post] /carts/{id}/line-items/{line_id}
|
* @oas [post] /carts/{id}/line-items/{line_id}
|
||||||
@@ -107,11 +106,10 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { IsObject } from "class-validator"
|
|||||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||||
import { CartService } from "../../../../services"
|
import { CartService } from "../../../../services"
|
||||||
import { validator } from "../../../../utils/validator"
|
import { validator } from "../../../../utils/validator"
|
||||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
|
||||||
import { EntityManager } from "typeorm"
|
import { EntityManager } from "typeorm"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,11 +77,10 @@ export default async (req, res) => {
|
|||||||
.updatePaymentSession(id, validated.data)
|
.updatePaymentSession(id, validated.data)
|
||||||
})
|
})
|
||||||
|
|
||||||
const cart = await cartService.retrieve(id, {
|
const data = await cartService.retrieveWithTotals(id, {
|
||||||
select: defaultStoreCartFields,
|
select: defaultStoreCartFields,
|
||||||
relations: defaultStoreCartRelations,
|
relations: defaultStoreCartRelations,
|
||||||
})
|
})
|
||||||
const data = await decorateLineItemsWithTotals(cart, req)
|
|
||||||
|
|
||||||
res.status(200).json({ cart: data })
|
res.status(200).json({ cart: data })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,6 +333,8 @@ export class Cart extends SoftDeletableEntity {
|
|||||||
|
|
||||||
shipping_total?: number
|
shipping_total?: number
|
||||||
discount_total?: number
|
discount_total?: number
|
||||||
|
item_tax_total?: number | null
|
||||||
|
shipping_tax_total?: number | null
|
||||||
tax_total?: number | null
|
tax_total?: number | null
|
||||||
refunded_total?: number
|
refunded_total?: number
|
||||||
total?: number
|
total?: number
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ export class ShippingMethod {
|
|||||||
@FeatureFlagColumn(TaxInclusivePricingFeatureFlag.key, { default: false })
|
@FeatureFlagColumn(TaxInclusivePricingFeatureFlag.key, { default: false })
|
||||||
includes_tax: boolean
|
includes_tax: boolean
|
||||||
|
|
||||||
|
subtotal?: number
|
||||||
|
total?: number
|
||||||
|
tax_total?: number
|
||||||
|
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
private beforeInsert(): void {
|
private beforeInsert(): void {
|
||||||
this.id = generateEntityId(this.id, "sm")
|
this.id = generateEntityId(this.id, "sm")
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ export const carts = {
|
|||||||
},
|
},
|
||||||
total: 1000,
|
total: 1000,
|
||||||
region_id: IdMap.getId("testRegion"),
|
region_id: IdMap.getId("testRegion"),
|
||||||
shipping_options: [{
|
shipping_options: [
|
||||||
|
{
|
||||||
id: IdMap.getId("tax-inclusive-option"),
|
id: IdMap.getId("tax-inclusive-option"),
|
||||||
includes_tax: true
|
includes_tax: true,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
testSwapCart: {
|
testSwapCart: {
|
||||||
id: IdMap.getId("test-swap"),
|
id: IdMap.getId("test-swap"),
|
||||||
@@ -223,7 +225,7 @@ export const carts = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CartServiceMock = {
|
export const CartServiceMock = {
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
updatePaymentSession: jest.fn().mockImplementation((data) => {
|
updatePaymentSession: jest.fn().mockImplementation((data) => {
|
||||||
@@ -254,6 +256,36 @@ export const CartServiceMock = {
|
|||||||
}
|
}
|
||||||
return Promise.resolve(carts.regionCart)
|
return Promise.resolve(carts.regionCart)
|
||||||
}),
|
}),
|
||||||
|
retrieveWithTotals: jest.fn().mockImplementation((cartId) => {
|
||||||
|
if (cartId === IdMap.getId("fr-cart")) {
|
||||||
|
return Promise.resolve(carts.frCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("swap-cart")) {
|
||||||
|
return Promise.resolve(carts.testSwapCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("test-cart")) {
|
||||||
|
return Promise.resolve(carts.testCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("cartLineItemMetadata")) {
|
||||||
|
return Promise.resolve(carts.cartWithMetadataLineItem)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("regionCart")) {
|
||||||
|
return Promise.resolve(carts.regionCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("emptyCart")) {
|
||||||
|
return Promise.resolve(carts.emptyCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("cartWithPaySessions")) {
|
||||||
|
return Promise.resolve(carts.cartWithPaySessions)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("test-cart2")) {
|
||||||
|
return Promise.resolve(carts.testCart)
|
||||||
|
}
|
||||||
|
if (cartId === IdMap.getId("tax-inclusive-option")) {
|
||||||
|
return Promise.resolve(carts.testCartTaxInclusive)
|
||||||
|
}
|
||||||
|
throw new MedusaError(MedusaError.Types.NOT_FOUND, "cart not found")
|
||||||
|
}),
|
||||||
retrieve: jest.fn().mockImplementation((cartId) => {
|
retrieve: jest.fn().mockImplementation((cartId) => {
|
||||||
if (cartId === IdMap.getId("fr-cart")) {
|
if (cartId === IdMap.getId("fr-cart")) {
|
||||||
return Promise.resolve(carts.frCart)
|
return Promise.resolve(carts.frCart)
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ describe("CartService", () => {
|
|||||||
withTransaction: function () {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
getShippingMethodTotals: (m) => {
|
||||||
|
return m
|
||||||
|
},
|
||||||
|
getLineItemTotals: (i) => {
|
||||||
|
return i
|
||||||
|
},
|
||||||
|
getCalculationContext: () => {},
|
||||||
getTotal: (o) => {
|
getTotal: (o) => {
|
||||||
return o.total || 0
|
return o.total || 0
|
||||||
},
|
},
|
||||||
@@ -849,10 +856,12 @@ describe("CartService", () => {
|
|||||||
}
|
}
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
id: IdMap.getId("cartWithLine"),
|
id: IdMap.getId("cartWithLine"),
|
||||||
|
total: 100,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
id: IdMap.getId("existing"),
|
id: IdMap.getId("existing"),
|
||||||
variant_id: IdMap.getId("good"),
|
variant_id: IdMap.getId("good"),
|
||||||
|
subtotal: 100,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1370,6 +1379,7 @@ describe("CartService", () => {
|
|||||||
describe("setPaymentSessions", () => {
|
describe("setPaymentSessions", () => {
|
||||||
const cart1 = {
|
const cart1 = {
|
||||||
total: 100,
|
total: 100,
|
||||||
|
items: [{ subtotal: 100 }],
|
||||||
payment_sessions: [],
|
payment_sessions: [],
|
||||||
region: {
|
region: {
|
||||||
payment_providers: [{ id: "provider_1" }, { id: "provider_2" }],
|
payment_providers: [{ id: "provider_1" }, { id: "provider_2" }],
|
||||||
@@ -1386,6 +1396,8 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
const cart3 = {
|
const cart3 = {
|
||||||
total: 100,
|
total: 100,
|
||||||
|
items: [{ subtotal: 100 }],
|
||||||
|
shipping_methods: [{ subtotal: 100 }],
|
||||||
payment_sessions: [
|
payment_sessions: [
|
||||||
{ provider_id: "provider_1" },
|
{ provider_id: "provider_1" },
|
||||||
{ provider_id: "not_in_region" },
|
{ provider_id: "not_in_region" },
|
||||||
@@ -1397,6 +1409,8 @@ describe("CartService", () => {
|
|||||||
|
|
||||||
const cart4 = {
|
const cart4 = {
|
||||||
total: 0,
|
total: 0,
|
||||||
|
items: [{ total: 0 }],
|
||||||
|
shipping_methods: [],
|
||||||
payment_sessions: [
|
payment_sessions: [
|
||||||
{ provider_id: "provider_1" },
|
{ provider_id: "provider_1" },
|
||||||
{ provider_id: "provider_2" },
|
{ provider_id: "provider_2" },
|
||||||
@@ -1577,7 +1591,7 @@ describe("CartService", () => {
|
|||||||
shipping_methods: [{ id: "ship1", profile: "profile1" }],
|
shipping_methods: [{ id: "ship1", profile: "profile1" }],
|
||||||
})
|
})
|
||||||
const cart3 = buildCart("lines", {
|
const cart3 = buildCart("lines", {
|
||||||
items: [{ id: "line", profile: "profile1" }],
|
items: [{ id: "line", profile: "profile1", subtotal: 100 }],
|
||||||
})
|
})
|
||||||
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { LineItemServiceMock } from "../__mocks__/line-item"
|
|||||||
|
|
||||||
describe("OrderService", () => {
|
describe("OrderService", () => {
|
||||||
const totalsService = {
|
const totalsService = {
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
getLineItemRefund: () => {},
|
getLineItemRefund: () => {},
|
||||||
@@ -40,7 +40,7 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
const eventBusService = {
|
const eventBusService = {
|
||||||
emit: jest.fn(),
|
emit: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -56,20 +56,20 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const shippingOptionService = {
|
const shippingOptionService = {
|
||||||
updateShippingMethod: jest.fn(),
|
updateShippingMethod: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const giftCardService = {
|
const giftCardService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
createTransaction: jest.fn(),
|
createTransaction: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ describe("OrderService", () => {
|
|||||||
cancelPayment: jest.fn().mockImplementation((payment) => {
|
cancelPayment: jest.fn().mockImplementation((payment) => {
|
||||||
return Promise.resolve({ ...payment, status: "cancelled" })
|
return Promise.resolve({ ...payment, status: "cancelled" })
|
||||||
}),
|
}),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ describe("OrderService", () => {
|
|||||||
total: 0,
|
total: 0,
|
||||||
}
|
}
|
||||||
const cartService = {
|
const cartService = {
|
||||||
retrieve: jest.fn().mockImplementation((query) => {
|
retrieveWithTotals: jest.fn().mockImplementation((query) => {
|
||||||
if (query === "empty") {
|
if (query === "empty") {
|
||||||
return Promise.resolve(emptyCart)
|
return Promise.resolve(emptyCart)
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ describe("OrderService", () => {
|
|||||||
total: 100,
|
total: 100,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,9 @@ describe("OrderService", () => {
|
|||||||
total: 100,
|
total: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
orderService.cartService_.retrieve = jest.fn(() => Promise.resolve(cart))
|
orderService.cartService_.retrieveWithTotals = jest.fn(() =>
|
||||||
|
Promise.resolve(cart)
|
||||||
|
)
|
||||||
orderService.cartService_.update = jest.fn(() => Promise.resolve())
|
orderService.cartService_.update = jest.fn(() => Promise.resolve())
|
||||||
|
|
||||||
await orderService.createFromCart("cart_id")
|
await orderService.createFromCart("cart_id")
|
||||||
@@ -199,9 +201,8 @@ describe("OrderService", () => {
|
|||||||
metadata: {},
|
metadata: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(cartService.retrieve).toHaveBeenCalledTimes(1)
|
expect(cartService.retrieveWithTotals).toHaveBeenCalledTimes(1)
|
||||||
expect(cartService.retrieve).toHaveBeenCalledWith("cart_id", {
|
expect(cartService.retrieveWithTotals).toHaveBeenCalledWith("cart_id", {
|
||||||
select: ["subtotal", "total"],
|
|
||||||
relations: [
|
relations: [
|
||||||
"region",
|
"region",
|
||||||
"payment",
|
"payment",
|
||||||
@@ -283,7 +284,7 @@ describe("OrderService", () => {
|
|||||||
total: 100,
|
total: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
orderService.cartService_.retrieve = () => {
|
orderService.cartService_.retrieveWithTotals = () => {
|
||||||
return Promise.resolve(cart)
|
return Promise.resolve(cart)
|
||||||
}
|
}
|
||||||
orderService.cartService_.update = () => Promise.resolve()
|
orderService.cartService_.update = () => Promise.resolve()
|
||||||
@@ -374,7 +375,7 @@ describe("OrderService", () => {
|
|||||||
],
|
],
|
||||||
total: 0,
|
total: 0,
|
||||||
}
|
}
|
||||||
orderService.cartService_.retrieve = () => Promise.resolve(cart)
|
orderService.cartService_.retrieveWithTotals = () => Promise.resolve(cart)
|
||||||
await orderService.createFromCart(cart)
|
await orderService.createFromCart(cart)
|
||||||
const order = {
|
const order = {
|
||||||
payment_status: "awaiting",
|
payment_status: "awaiting",
|
||||||
@@ -432,7 +433,7 @@ describe("OrderService", () => {
|
|||||||
],
|
],
|
||||||
total: 100,
|
total: 100,
|
||||||
}
|
}
|
||||||
orderService.cartService_.retrieve = () => Promise.resolve(cart)
|
orderService.cartService_.retrieveWithTotals = () => Promise.resolve(cart)
|
||||||
orderService.cartService_.update = () => Promise.resolve()
|
orderService.cartService_.update = () => Promise.resolve()
|
||||||
const res = orderService.createFromCart(cart)
|
const res = orderService.createFromCart(cart)
|
||||||
await expect(res).rejects.toThrow(
|
await expect(res).rejects.toThrow(
|
||||||
@@ -617,14 +618,14 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
const fulfillmentService = {
|
const fulfillmentService = {
|
||||||
cancelFulfillment: jest.fn(),
|
cancelFulfillment: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const paymentProviderService = {
|
const paymentProviderService = {
|
||||||
cancelPayment: jest.fn(),
|
cancelPayment: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -721,7 +722,7 @@ describe("OrderService", () => {
|
|||||||
? Promise.reject()
|
? Promise.reject()
|
||||||
: Promise.resolve({ ...p, captured_at: "notnull" })
|
: Promise.resolve({ ...p, captured_at: "notnull" })
|
||||||
),
|
),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -826,7 +827,7 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -839,7 +840,7 @@ describe("OrderService", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1006,7 +1007,7 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1075,7 +1076,7 @@ describe("OrderService", () => {
|
|||||||
.mockImplementation((p) =>
|
.mockImplementation((p) =>
|
||||||
p.id === "payment_fail" ? Promise.reject() : Promise.resolve()
|
p.id === "payment_fail" ? Promise.reject() : Promise.resolve()
|
||||||
),
|
),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1216,7 +1217,7 @@ describe("OrderService", () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(() => Promise.resolve({})),
|
.mockImplementation(() => Promise.resolve({})),
|
||||||
|
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1350,7 +1351,7 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
const lineItemService = {
|
const lineItemService = {
|
||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1373,7 +1374,7 @@ describe("OrderService", () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1400,9 +1401,7 @@ describe("OrderService", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(fulfillmentService.createShipment).toHaveBeenCalledTimes(1)
|
expect(fulfillmentService.createShipment).toHaveBeenCalledTimes(1)
|
||||||
expect(
|
expect(fulfillmentService.createShipment).toHaveBeenCalledWith(
|
||||||
fulfillmentService.createShipment
|
|
||||||
).toHaveBeenCalledWith(
|
|
||||||
IdMap.getId("fulfillment"),
|
IdMap.getId("fulfillment"),
|
||||||
[{ tracking_number: "1234" }, { tracking_number: "2345" }],
|
[{ tracking_number: "1234" }, { tracking_number: "2345" }],
|
||||||
{ metadata: undefined, no_notification: true }
|
{ metadata: undefined, no_notification: true }
|
||||||
@@ -1494,7 +1493,7 @@ describe("OrderService", () => {
|
|||||||
refundPayment: jest
|
refundPayment: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementation((p) => Promise.resolve({ id: "ref" })),
|
.mockImplementation((p) => Promise.resolve({ id: "ref" })),
|
||||||
withTransaction: function() {
|
withTransaction: function () {
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
LineItemUpdate,
|
LineItemUpdate,
|
||||||
} from "../types/cart"
|
} from "../types/cart"
|
||||||
import { AddressPayload, FindConfig, TotalField } from "../types/common"
|
import { AddressPayload, FindConfig, TotalField } from "../types/common"
|
||||||
import { buildQuery, isDefined, setMetadata, validateId } from "../utils"
|
import { buildQuery, isDefined, setMetadata } from "../utils"
|
||||||
import { FlagRouter } from "../utils/flag-router"
|
import { FlagRouter } from "../utils/flag-router"
|
||||||
import { validateEmail } from "../utils/is-email"
|
import { validateEmail } from "../utils/is-email"
|
||||||
import CustomShippingOptionService from "./custom-shipping-option"
|
import CustomShippingOptionService from "./custom-shipping-option"
|
||||||
@@ -175,6 +175,24 @@ class CartService extends TransactionBaseService {
|
|||||||
this.storeService_ = storeService
|
this.storeService_ = storeService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTotalsRelations(config: FindConfig<Cart>): string[] {
|
||||||
|
const relationSet = new Set(config.relations)
|
||||||
|
|
||||||
|
relationSet.add("items")
|
||||||
|
relationSet.add("items.tax_lines")
|
||||||
|
relationSet.add("items.adjustments")
|
||||||
|
relationSet.add("gift_cards")
|
||||||
|
relationSet.add("discounts")
|
||||||
|
relationSet.add("discounts.rule")
|
||||||
|
relationSet.add("shipping_methods")
|
||||||
|
relationSet.add("shipping_methods.tax_lines")
|
||||||
|
relationSet.add("shipping_address")
|
||||||
|
relationSet.add("region")
|
||||||
|
relationSet.add("region.tax_rates")
|
||||||
|
|
||||||
|
return Array.from(relationSet.values())
|
||||||
|
}
|
||||||
|
|
||||||
protected transformQueryForTotals_(
|
protected transformQueryForTotals_(
|
||||||
config: FindConfig<Cart>
|
config: FindConfig<Cart>
|
||||||
): FindConfig<Cart> & { totalsToSelect: TotalField[] } {
|
): FindConfig<Cart> & { totalsToSelect: TotalField[] } {
|
||||||
@@ -297,7 +315,6 @@ class CartService extends TransactionBaseService {
|
|||||||
* Gets a cart by id.
|
* Gets a cart by id.
|
||||||
* @param cartId - the id of the cart to get.
|
* @param cartId - the id of the cart to get.
|
||||||
* @param options - the options to get a cart
|
* @param options - the options to get a cart
|
||||||
* @param totalsConfig - configuration for retrieval of totals
|
|
||||||
* @return the cart document.
|
* @return the cart document.
|
||||||
*/
|
*/
|
||||||
async retrieve(
|
async retrieve(
|
||||||
@@ -307,15 +324,11 @@ class CartService extends TransactionBaseService {
|
|||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
const manager = this.manager_
|
const manager = this.manager_
|
||||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||||
const validatedId = validateId(cartId)
|
|
||||||
|
|
||||||
const { select, relations, totalsToSelect } =
|
const { select, relations, totalsToSelect } =
|
||||||
this.transformQueryForTotals_(options)
|
this.transformQueryForTotals_(options)
|
||||||
|
|
||||||
const query = buildQuery(
|
const query = buildQuery({ id: cartId }, { ...options, select, relations })
|
||||||
{ id: validatedId },
|
|
||||||
{ ...options, select, relations }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (relations && relations.length > 0) {
|
if (relations && relations.length > 0) {
|
||||||
query.relations = relations
|
query.relations = relations
|
||||||
@@ -327,6 +340,32 @@ class CartService extends TransactionBaseService {
|
|||||||
query.select = undefined
|
query.select = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryRelations = query.relations
|
||||||
|
query.relations = undefined
|
||||||
|
const raw = await cartRepo.findOneWithRelations(queryRelations, query)
|
||||||
|
if (!raw) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_FOUND,
|
||||||
|
`Cart with ${cartId} was not found`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.decorateTotals_(raw, totalsToSelect, totalsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async retrieveNew(
|
||||||
|
cartId: string,
|
||||||
|
options: FindConfig<Cart> = {}
|
||||||
|
): Promise<Cart> {
|
||||||
|
const manager = this.manager_
|
||||||
|
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||||
|
|
||||||
|
const query = buildQuery({ id: cartId }, options)
|
||||||
|
|
||||||
|
if ((options.select || []).length <= 0) {
|
||||||
|
query.select = undefined
|
||||||
|
}
|
||||||
|
|
||||||
const queryRelations = query.relations
|
const queryRelations = query.relations
|
||||||
query.relations = undefined
|
query.relations = undefined
|
||||||
|
|
||||||
@@ -339,7 +378,22 @@ class CartService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.decorateTotals_(raw, totalsToSelect, totalsConfig)
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
async retrieveWithTotals(
|
||||||
|
cartId: string,
|
||||||
|
options: FindConfig<Cart> = {},
|
||||||
|
totalsConfig: TotalsConfig = {}
|
||||||
|
): Promise<Cart> {
|
||||||
|
const relations = this.getTotalsRelations(options)
|
||||||
|
|
||||||
|
const cart = await this.retrieveNew(cartId, {
|
||||||
|
...options,
|
||||||
|
relations,
|
||||||
|
})
|
||||||
|
|
||||||
|
return await this.decorateTotals(cart, totalsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1427,14 +1481,7 @@ class CartService extends TransactionBaseService {
|
|||||||
this.paymentSessionRepository_
|
this.paymentSessionRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieveWithTotals(cartId, {
|
||||||
select: [
|
|
||||||
"total",
|
|
||||||
"subtotal",
|
|
||||||
"tax_total",
|
|
||||||
"discount_total",
|
|
||||||
"gift_card_total",
|
|
||||||
],
|
|
||||||
relations: ["region", "region.payment_providers", "payment_sessions"],
|
relations: ["region", "region.payment_providers", "payment_sessions"],
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1503,17 +1550,9 @@ class CartService extends TransactionBaseService {
|
|||||||
const cartId =
|
const cartId =
|
||||||
typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id
|
typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id
|
||||||
|
|
||||||
const cart = await this.retrieve(
|
const cart = await this.retrieveWithTotals(
|
||||||
cartId,
|
cartId,
|
||||||
{
|
{
|
||||||
select: [
|
|
||||||
"total",
|
|
||||||
"subtotal",
|
|
||||||
"tax_total",
|
|
||||||
"discount_total",
|
|
||||||
"shipping_total",
|
|
||||||
"gift_card_total",
|
|
||||||
],
|
|
||||||
relations: [
|
relations: [
|
||||||
"items",
|
"items",
|
||||||
"items.adjustments",
|
"items.adjustments",
|
||||||
@@ -2069,7 +2108,6 @@ class CartService extends TransactionBaseService {
|
|||||||
this.cartRepository_
|
this.cartRepository_
|
||||||
)
|
)
|
||||||
|
|
||||||
const validatedId = validateId(cartId)
|
|
||||||
if (typeof key !== "string") {
|
if (typeof key !== "string") {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
@@ -2077,7 +2115,7 @@ class CartService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cart = await cartRepo.findOne(validatedId)
|
const cart = await cartRepo.findOne(cartId)
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
@@ -2151,6 +2189,77 @@ class CartService extends TransactionBaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decorateTotals(cart: Cart, totalsConfig?: TotalsConfig): Promise<Cart> {
|
||||||
|
const totalsService = this.totalsService_
|
||||||
|
|
||||||
|
const calculationContext = await totalsService.getCalculationContext(cart, {
|
||||||
|
exclude_shipping: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
cart.items = await Promise.all(
|
||||||
|
(cart.items || []).map(async (item) => {
|
||||||
|
const itemTotals = await totalsService.getLineItemTotals(item, cart, {
|
||||||
|
include_tax: totalsConfig?.force_taxes || cart.region.automatic_taxes,
|
||||||
|
calculation_context: calculationContext,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.assign(item, itemTotals)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
cart.shipping_methods = await Promise.all(
|
||||||
|
(cart.shipping_methods || []).map(async (shippingMethod) => {
|
||||||
|
const shippingTotals = await totalsService.getShippingMethodTotals(
|
||||||
|
shippingMethod,
|
||||||
|
cart,
|
||||||
|
{
|
||||||
|
include_tax:
|
||||||
|
totalsConfig?.force_taxes || cart.region.automatic_taxes,
|
||||||
|
calculation_context: calculationContext,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return Object.assign(shippingMethod, shippingTotals)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
cart.shipping_total = cart.shipping_methods.reduce((acc, method) => {
|
||||||
|
return acc + (method.subtotal ?? 0)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
cart.subtotal = cart.items.reduce((acc, item) => {
|
||||||
|
return acc + (item.subtotal ?? 0)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
cart.discount_total = cart.items.reduce((acc, item) => {
|
||||||
|
return acc + (item.discount_total ?? 0)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
cart.item_tax_total = cart.items.reduce((acc, item) => {
|
||||||
|
return acc + (item.tax_total ?? 0)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
cart.shipping_tax_total = cart.shipping_methods.reduce((acc, method) => {
|
||||||
|
return acc + (method.tax_total ?? 0)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
const giftCardTotal = await totalsService.getGiftCardTotal(cart, {
|
||||||
|
gift_cardable: cart.subtotal - cart.discount_total,
|
||||||
|
})
|
||||||
|
cart.gift_card_total = giftCardTotal.total || 0
|
||||||
|
cart.gift_card_tax_total = giftCardTotal.tax_total || 0
|
||||||
|
|
||||||
|
cart.tax_total = cart.item_tax_total + cart.shipping_tax_total
|
||||||
|
|
||||||
|
cart.total =
|
||||||
|
cart.subtotal +
|
||||||
|
cart.shipping_total +
|
||||||
|
cart.tax_total -
|
||||||
|
(cart.gift_card_total + cart.discount_total + cart.gift_card_tax_total)
|
||||||
|
|
||||||
|
return cart
|
||||||
|
}
|
||||||
|
|
||||||
protected async refreshAdjustments_(cart: Cart): Promise<void> {
|
protected async refreshAdjustments_(cart: Cart): Promise<void> {
|
||||||
const transactionManager = this.transactionManager_ ?? this.manager_
|
const transactionManager = this.transactionManager_ ?? this.manager_
|
||||||
|
|
||||||
|
|||||||
@@ -492,8 +492,7 @@ class OrderService extends TransactionBaseService {
|
|||||||
const cartServiceTx = this.cartService_.withTransaction(manager)
|
const cartServiceTx = this.cartService_.withTransaction(manager)
|
||||||
const inventoryServiceTx = this.inventoryService_.withTransaction(manager)
|
const inventoryServiceTx = this.inventoryService_.withTransaction(manager)
|
||||||
|
|
||||||
const cart = await cartServiceTx.retrieve(cartId, {
|
const cart = await cartServiceTx.retrieveWithTotals(cartId, {
|
||||||
select: ["subtotal", "total"],
|
|
||||||
relations: [
|
relations: [
|
||||||
"region",
|
"region",
|
||||||
"payment",
|
"payment",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type ShippingMethodTotals = {
|
|||||||
type GetShippingMethodTotalsOptions = {
|
type GetShippingMethodTotalsOptions = {
|
||||||
include_tax?: boolean
|
include_tax?: boolean
|
||||||
use_tax_lines?: boolean
|
use_tax_lines?: boolean
|
||||||
|
calculation_context?: TaxCalculationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
type LineItemTotals = {
|
type LineItemTotals = {
|
||||||
@@ -54,18 +55,17 @@ type LineItemTotals = {
|
|||||||
original_tax_total: number
|
original_tax_total: number
|
||||||
tax_lines: LineItemTaxLine[]
|
tax_lines: LineItemTaxLine[]
|
||||||
discount_total: number
|
discount_total: number
|
||||||
gift_card_total: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LineItemTotalsOptions = {
|
type LineItemTotalsOptions = {
|
||||||
include_tax?: boolean
|
include_tax?: boolean
|
||||||
use_tax_lines?: boolean
|
use_tax_lines?: boolean
|
||||||
exclude_gift_cards?: boolean
|
exclude_gift_cards?: boolean
|
||||||
|
calculation_context?: TaxCalculationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetLineItemTotalOptions = {
|
type GetLineItemTotalOptions = {
|
||||||
include_tax?: boolean
|
include_tax?: boolean
|
||||||
exclude_gift_cards?: boolean
|
|
||||||
exclude_discounts?: boolean
|
exclude_discounts?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,9 +189,11 @@ class TotalsService extends TransactionBaseService {
|
|||||||
cartOrOrder: Cart | Order,
|
cartOrOrder: Cart | Order,
|
||||||
opts: GetShippingMethodTotalsOptions = {}
|
opts: GetShippingMethodTotalsOptions = {}
|
||||||
): Promise<ShippingMethodTotals> {
|
): Promise<ShippingMethodTotals> {
|
||||||
const calculationContext = await this.getCalculationContext(cartOrOrder, {
|
const calculationContext =
|
||||||
|
opts.calculation_context ||
|
||||||
|
(await this.getCalculationContext(cartOrOrder, {
|
||||||
exclude_shipping: true,
|
exclude_shipping: true,
|
||||||
})
|
}))
|
||||||
calculationContext.shipping_methods = [shippingMethod]
|
calculationContext.shipping_methods = [shippingMethod]
|
||||||
|
|
||||||
const totals = {
|
const totals = {
|
||||||
@@ -459,42 +461,6 @@ class TotalsService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.exclude_gift_cards) {
|
|
||||||
let lineGiftCards: LineDiscountAmount[] = []
|
|
||||||
if (orderOrCart.gift_cards && orderOrCart.gift_cards.length) {
|
|
||||||
const subtotal = await this.getSubtotal(orderOrCart)
|
|
||||||
const giftCardTotal = await this.getGiftCardTotal(orderOrCart)
|
|
||||||
|
|
||||||
// If the fixed discount exceeds the subtotal we should
|
|
||||||
// calculate a 100% discount
|
|
||||||
const nominator = Math.min(giftCardTotal.total, subtotal)
|
|
||||||
const percentage = nominator / subtotal
|
|
||||||
|
|
||||||
lineGiftCards = orderOrCart.items.map((l) => {
|
|
||||||
return {
|
|
||||||
item: l,
|
|
||||||
amount: Math.round(l.unit_price * l.quantity * percentage),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const lgc of lineGiftCards) {
|
|
||||||
if (allocationMap[lgc.item.id]) {
|
|
||||||
allocationMap[lgc.item.id].gift_card = {
|
|
||||||
amount: lgc.amount,
|
|
||||||
unit_amount: Math.round(lgc.amount / lgc.item.quantity),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
allocationMap[lgc.item.id] = {
|
|
||||||
gift_card: {
|
|
||||||
amount: lgc.amount,
|
|
||||||
unit_amount: Math.round(lgc.amount / lgc.item.quantity),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocationMap
|
return allocationMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,10 +750,12 @@ class TotalsService extends TransactionBaseService {
|
|||||||
cartOrOrder: Cart | Order,
|
cartOrOrder: Cart | Order,
|
||||||
options: LineItemTotalsOptions = {}
|
options: LineItemTotalsOptions = {}
|
||||||
): Promise<LineItemTotals> {
|
): Promise<LineItemTotals> {
|
||||||
const calculationContext = await this.getCalculationContext(cartOrOrder, {
|
const calculationContext =
|
||||||
|
options.calculation_context ||
|
||||||
|
(await this.getCalculationContext(cartOrOrder, {
|
||||||
exclude_shipping: true,
|
exclude_shipping: true,
|
||||||
exclude_gift_cards: options.exclude_gift_cards,
|
exclude_gift_cards: options.exclude_gift_cards,
|
||||||
})
|
}))
|
||||||
const lineItemAllocation =
|
const lineItemAllocation =
|
||||||
calculationContext.allocation_map[lineItem.id] || {}
|
calculationContext.allocation_map[lineItem.id] || {}
|
||||||
|
|
||||||
@@ -802,7 +770,6 @@ class TotalsService extends TransactionBaseService {
|
|||||||
subtotal = 0 // in that case we need to know the tax rate to compute it later
|
subtotal = 0 // in that case we need to know the tax rate to compute it later
|
||||||
}
|
}
|
||||||
|
|
||||||
const gift_card_total = lineItemAllocation.gift_card?.amount || 0
|
|
||||||
const discount_total =
|
const discount_total =
|
||||||
(lineItemAllocation.discount?.unit_amount || 0) * lineItem.quantity
|
(lineItemAllocation.discount?.unit_amount || 0) * lineItem.quantity
|
||||||
|
|
||||||
@@ -810,7 +777,6 @@ class TotalsService extends TransactionBaseService {
|
|||||||
unit_price: lineItem.unit_price,
|
unit_price: lineItem.unit_price,
|
||||||
quantity: lineItem.quantity,
|
quantity: lineItem.quantity,
|
||||||
subtotal,
|
subtotal,
|
||||||
gift_card_total,
|
|
||||||
discount_total,
|
discount_total,
|
||||||
total: subtotal - discount_total,
|
total: subtotal - discount_total,
|
||||||
original_total: subtotal,
|
original_total: subtotal,
|
||||||
@@ -899,12 +865,16 @@ class TotalsService extends TransactionBaseService {
|
|||||||
lineItemTotals.tax_lines,
|
lineItemTotals.tax_lines,
|
||||||
calculationContext
|
calculationContext
|
||||||
)
|
)
|
||||||
calculationContext.allocation_map = {} // Don't account for discounts
|
const noDiscountContext = {
|
||||||
|
...calculationContext,
|
||||||
|
allocation_map: {}, // Don't account for discounts
|
||||||
|
}
|
||||||
|
|
||||||
lineItemTotals.original_tax_total =
|
lineItemTotals.original_tax_total =
|
||||||
await this.taxCalculationStrategy_.calculate(
|
await this.taxCalculationStrategy_.calculate(
|
||||||
[lineItem],
|
[lineItem],
|
||||||
lineItemTotals.tax_lines,
|
lineItemTotals.tax_lines,
|
||||||
calculationContext
|
noDiscountContext
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -949,10 +919,6 @@ class TotalsService extends TransactionBaseService {
|
|||||||
toReturn += lineItemTotals.discount_total
|
toReturn += lineItemTotals.discount_total
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.exclude_gift_cards) {
|
|
||||||
toReturn += lineItemTotals.gift_card_total
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.include_tax) {
|
if (options.include_tax) {
|
||||||
toReturn += lineItemTotals.tax_total
|
toReturn += lineItemTotals.tax_total
|
||||||
}
|
}
|
||||||
@@ -983,13 +949,21 @@ class TotalsService extends TransactionBaseService {
|
|||||||
* @param cartOrOrder - the cart or order to get gift card amount for
|
* @param cartOrOrder - the cart or order to get gift card amount for
|
||||||
* @return the gift card amount applied to the cart or order
|
* @return the gift card amount applied to the cart or order
|
||||||
*/
|
*/
|
||||||
async getGiftCardTotal(cartOrOrder: Cart | Order): Promise<{
|
async getGiftCardTotal(
|
||||||
|
cartOrOrder: Cart | Order,
|
||||||
|
opts: { gift_cardable?: number } = {}
|
||||||
|
): Promise<{
|
||||||
total: number
|
total: number
|
||||||
tax_total: number
|
tax_total: number
|
||||||
}> {
|
}> {
|
||||||
|
let giftCardable: number
|
||||||
|
if (typeof opts.gift_cardable !== "undefined") {
|
||||||
|
giftCardable = opts.gift_cardable
|
||||||
|
} else {
|
||||||
const subtotal = await this.getSubtotal(cartOrOrder)
|
const subtotal = await this.getSubtotal(cartOrOrder)
|
||||||
const discountTotal = await this.getDiscountTotal(cartOrOrder)
|
const discountTotal = await this.getDiscountTotal(cartOrOrder)
|
||||||
const giftCardable = subtotal - discountTotal
|
giftCardable = subtotal - discountTotal
|
||||||
|
}
|
||||||
|
|
||||||
if ("gift_card_transactions" in cartOrOrder) {
|
if ("gift_card_transactions" in cartOrOrder) {
|
||||||
// gift_card_transactions only exist on orders so we can
|
// gift_card_transactions only exist on orders so we can
|
||||||
@@ -1040,7 +1014,9 @@ class TotalsService extends TransactionBaseService {
|
|||||||
if (cartOrOrder.region?.gift_cards_taxable) {
|
if (cartOrOrder.region?.gift_cards_taxable) {
|
||||||
return {
|
return {
|
||||||
total: orderGiftCardAmount,
|
total: orderGiftCardAmount,
|
||||||
tax_total: (orderGiftCardAmount * cartOrOrder.region.tax_rate) / 100,
|
tax_total: Math.round(
|
||||||
|
(orderGiftCardAmount * cartOrOrder.region.tax_rate) / 100
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ describe("CartCompletionStrategy", () => {
|
|||||||
deleteTaxLines: jest.fn(() => Promise.resolve(cart)),
|
deleteTaxLines: jest.fn(() => Promise.resolve(cart)),
|
||||||
authorizePayment: jest.fn(() => Promise.resolve(cart)),
|
authorizePayment: jest.fn(() => Promise.resolve(cart)),
|
||||||
retrieve: jest.fn(() => Promise.resolve(cart)),
|
retrieve: jest.fn(() => Promise.resolve(cart)),
|
||||||
|
retrieveWithTotals: jest.fn(() => Promise.resolve(cart)),
|
||||||
}
|
}
|
||||||
const orderServiceMock = {
|
const orderServiceMock = {
|
||||||
withTransaction: function () {
|
withTransaction: function () {
|
||||||
|
|||||||
@@ -160,14 +160,8 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
|
|||||||
async (manager: EntityManager) => {
|
async (manager: EntityManager) => {
|
||||||
const cart = await cartService
|
const cart = await cartService
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.retrieve(id, {
|
.retrieveWithTotals(id, {
|
||||||
select: ["total"],
|
relations: ["payment", "payment_sessions"],
|
||||||
relations: [
|
|
||||||
"items",
|
|
||||||
"items.adjustments",
|
|
||||||
"payment",
|
|
||||||
"payment_sessions",
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// If cart is part of swap, we register swap as complete
|
// If cart is part of swap, we register swap as complete
|
||||||
|
|||||||
Reference in new issue
Block a user