fix(medusa): Remove shipping on updates to cart.items (#4715)
* rm shipping on line item updates * Add tests * remove verbose flag * Create real-items-rhyme.md
This commit is contained in:
5
.changeset/real-items-rhyme.md
Normal file
5
.changeset/real-items-rhyme.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): Remove shipping on updates to `cart.items`
|
||||||
@@ -1623,7 +1623,8 @@ describe("/store/carts", () => {
|
|||||||
|
|
||||||
const cart = response.data.cart
|
const cart = response.data.cart
|
||||||
|
|
||||||
const shippingAmount = 1000
|
// shipping is 0 because of line item update
|
||||||
|
const shippingAmount = 0
|
||||||
const expectedTotal =
|
const expectedTotal =
|
||||||
quantity * variant1Price +
|
quantity * variant1Price +
|
||||||
quantity * variant2Price +
|
quantity * variant2Price +
|
||||||
@@ -1635,7 +1636,7 @@ describe("/store/carts", () => {
|
|||||||
expect(cart.total).toBe(expectedTotal)
|
expect(cart.total).toBe(expectedTotal)
|
||||||
expect(cart.subtotal).toBe(expectedSubtotal)
|
expect(cart.subtotal).toBe(expectedSubtotal)
|
||||||
expect(cart.discount_total).toBe(discountAmount)
|
expect(cart.discount_total).toBe(discountAmount)
|
||||||
expect(cart.shipping_total).toBe(1000)
|
expect(cart.shipping_total).toBe(shippingAmount)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates cart customer id", async () => {
|
it("updates cart customer id", async () => {
|
||||||
@@ -1648,6 +1649,87 @@ describe("/store/carts", () => {
|
|||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should remove shipping on line item remove", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const cartId = "test-cart-2"
|
||||||
|
const lineId = "test-item"
|
||||||
|
const optionId = "test-option"
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
option_id: optionId,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = await api.get(`/store/carts/${cartId}`)
|
||||||
|
|
||||||
|
expect(cart.data.cart.shipping_total).toEqual(1000)
|
||||||
|
|
||||||
|
const response = await api.delete(
|
||||||
|
`/store/carts/${cartId}/line-items/${lineId}`
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.cart.shipping_total).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should remove shipping on line item update", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const cartId = "test-cart-2"
|
||||||
|
const lineId = "test-item"
|
||||||
|
const optionId = "test-option"
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
option_id: optionId,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = await api.get(`/store/carts/${cartId}`)
|
||||||
|
|
||||||
|
expect(cart.data.cart.shipping_total).toEqual(1000)
|
||||||
|
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts/${cartId}/line-items/${lineId}`,
|
||||||
|
{
|
||||||
|
quantity: 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.data.cart.shipping_total).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should remove shipping on line item add", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const cartId = "test-cart-2"
|
||||||
|
const optionId = "test-option"
|
||||||
|
|
||||||
|
await api.post(
|
||||||
|
`/store/carts/${cartId}/shipping-methods`,
|
||||||
|
{
|
||||||
|
option_id: optionId,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const cart = await api.get(`/store/carts/${cartId}`)
|
||||||
|
|
||||||
|
expect(cart.data.cart.shipping_total).toEqual(1000)
|
||||||
|
|
||||||
|
const response = await api.post(`/store/carts/${cartId}/line-items`, {
|
||||||
|
variant_id: "test-variant-sale-customer",
|
||||||
|
quantity: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.data.cart.shipping_total).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
it("updates prices when cart customer id is updated", async () => {
|
it("updates prices when cart customer id is updated", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
@@ -1958,8 +2040,9 @@ describe("/store/carts", () => {
|
|||||||
expect(getRes.status).toEqual(200)
|
expect(getRes.status).toEqual(200)
|
||||||
expect(getRes.data.type).toEqual("order")
|
expect(getRes.data.type).toEqual("order")
|
||||||
|
|
||||||
|
// inventory pre-purchase was 10
|
||||||
const variantRes = await api.get("/store/variants/test-variant")
|
const variantRes = await api.get("/store/variants/test-variant")
|
||||||
expect(variantRes.data.variant.inventory_quantity).toEqual(0)
|
expect(variantRes.data.variant.inventory_quantity).toEqual(9)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calculates correct payment totals on cart completion taking into account line item adjustments", async () => {
|
it("calculates correct payment totals on cart completion taking into account line item adjustments", async () => {
|
||||||
@@ -2332,7 +2415,8 @@ describe("/store/carts", () => {
|
|||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
expect(cartWithGiftcard.data.cart.total).toBe(2900) // 1000 (giftcard) + 900 (standard item with 10% discount) + 1000 Shipping
|
expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount) - 1000 Shipping (because of line item update)
|
||||||
|
expect(cartWithGiftcard.data.cart.shipping_total).toBe(0) // 0 because of line item update
|
||||||
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
|
expect(cartWithGiftcard.data.cart.discount_total).toBe(100)
|
||||||
expect(cartWithGiftcard.status).toEqual(200)
|
expect(cartWithGiftcard.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ module.exports = async (dataSource, data = {}) => {
|
|||||||
id: "test-variant",
|
id: "test-variant",
|
||||||
title: "test variant",
|
title: "test variant",
|
||||||
product_id: "test-product",
|
product_id: "test-product",
|
||||||
inventory_quantity: 1,
|
inventory_quantity: 10,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
option_id: "test-option",
|
option_id: "test-option",
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ export default async (req, res) => {
|
|||||||
if (validated.quantity === 0) {
|
if (validated.quantity === 0) {
|
||||||
await cartService.withTransaction(m).removeLineItem(id, line_id)
|
await cartService.withTransaction(m).removeLineItem(id, line_id)
|
||||||
} else {
|
} else {
|
||||||
const cart = await cartService
|
const cart = await cartService.withTransaction(m).retrieve(id, {
|
||||||
.withTransaction(m)
|
relations: ["items", "items.variant", "shipping_methods"],
|
||||||
.retrieve(id, { relations: ["items", "items.variant"] })
|
})
|
||||||
|
|
||||||
const existing = cart.items.find((i) => i.id === line_id)
|
const existing = cart.items.find((i) => i.id === line_id)
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
|
|||||||
@@ -3,40 +3,40 @@ import { isEmpty, isEqual } from "lodash"
|
|||||||
import { MedusaError, isDefined } from "medusa-core-utils"
|
import { MedusaError, isDefined } from "medusa-core-utils"
|
||||||
import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm"
|
import { DeepPartial, EntityManager, In, IsNull, Not } from "typeorm"
|
||||||
import {
|
import {
|
||||||
CustomShippingOptionService,
|
CustomShippingOptionService,
|
||||||
CustomerService,
|
CustomerService,
|
||||||
DiscountService,
|
DiscountService,
|
||||||
EventBusService,
|
EventBusService,
|
||||||
GiftCardService,
|
GiftCardService,
|
||||||
LineItemAdjustmentService,
|
LineItemAdjustmentService,
|
||||||
LineItemService,
|
LineItemService,
|
||||||
NewTotalsService,
|
NewTotalsService,
|
||||||
PaymentProviderService,
|
PaymentProviderService,
|
||||||
ProductService,
|
ProductService,
|
||||||
ProductVariantInventoryService,
|
ProductVariantInventoryService,
|
||||||
ProductVariantService,
|
ProductVariantService,
|
||||||
RegionService,
|
RegionService,
|
||||||
SalesChannelService,
|
SalesChannelService,
|
||||||
ShippingOptionService,
|
ShippingOptionService,
|
||||||
StoreService,
|
StoreService,
|
||||||
TaxProviderService,
|
TaxProviderService,
|
||||||
TotalsService,
|
TotalsService,
|
||||||
} from "."
|
} from "."
|
||||||
import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces"
|
import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces"
|
||||||
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels"
|
||||||
import {
|
import {
|
||||||
Address,
|
Address,
|
||||||
Cart,
|
Cart,
|
||||||
CustomShippingOption,
|
CustomShippingOption,
|
||||||
Customer,
|
Customer,
|
||||||
Discount,
|
Discount,
|
||||||
DiscountRule,
|
DiscountRule,
|
||||||
DiscountRuleType,
|
DiscountRuleType,
|
||||||
LineItem,
|
LineItem,
|
||||||
PaymentSession,
|
PaymentSession,
|
||||||
PaymentSessionStatus,
|
PaymentSessionStatus,
|
||||||
SalesChannel,
|
SalesChannel,
|
||||||
ShippingMethod,
|
ShippingMethod,
|
||||||
} from "../models"
|
} from "../models"
|
||||||
import { AddressRepository } from "../repositories/address"
|
import { AddressRepository } from "../repositories/address"
|
||||||
import { CartRepository } from "../repositories/cart"
|
import { CartRepository } from "../repositories/cart"
|
||||||
@@ -44,18 +44,18 @@ import { LineItemRepository } from "../repositories/line-item"
|
|||||||
import { PaymentSessionRepository } from "../repositories/payment-session"
|
import { PaymentSessionRepository } from "../repositories/payment-session"
|
||||||
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||||
import {
|
import {
|
||||||
CartCreateProps,
|
CartCreateProps,
|
||||||
CartUpdateProps,
|
CartUpdateProps,
|
||||||
FilterableCartProps,
|
FilterableCartProps,
|
||||||
LineItemUpdate,
|
LineItemUpdate,
|
||||||
LineItemValidateData,
|
LineItemValidateData,
|
||||||
isCart,
|
isCart,
|
||||||
} from "../types/cart"
|
} from "../types/cart"
|
||||||
import {
|
import {
|
||||||
AddressPayload,
|
AddressPayload,
|
||||||
FindConfig,
|
FindConfig,
|
||||||
TotalField,
|
TotalField,
|
||||||
WithRequiredProperty,
|
WithRequiredProperty,
|
||||||
} from "../types/common"
|
} from "../types/common"
|
||||||
import { PaymentSessionInput } from "../types/payment"
|
import { PaymentSessionInput } from "../types/payment"
|
||||||
import { buildQuery, isString, setMetadata } from "../utils"
|
import { buildQuery, isString, setMetadata } from "../utils"
|
||||||
@@ -480,7 +480,11 @@ class CartService extends TransactionBaseService {
|
|||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
relations: ["items.variant.product.profiles", "payment_sessions"],
|
relations: [
|
||||||
|
"items.variant.product.profiles",
|
||||||
|
"payment_sessions",
|
||||||
|
"shipping_methods",
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const lineItem = cart.items.find((item) => item.id === lineItemId)
|
const lineItem = cart.items.find((item) => item.id === lineItemId)
|
||||||
@@ -488,7 +492,6 @@ class CartService extends TransactionBaseService {
|
|||||||
return cart
|
return cart
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove shipping methods if they are not needed
|
|
||||||
if (cart.shipping_methods?.length) {
|
if (cart.shipping_methods?.length) {
|
||||||
await this.shippingOptionService_
|
await this.shippingOptionService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
@@ -620,7 +623,10 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
let cart = await this.retrieve(cartId, { select })
|
let cart = await this.retrieve(cartId, {
|
||||||
|
select,
|
||||||
|
relations: ["shipping_methods"],
|
||||||
|
})
|
||||||
|
|
||||||
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
if (this.featureFlagRouter_.isFeatureEnabled("sales_channels")) {
|
||||||
if (config.validateSalesChannels) {
|
if (config.validateSalesChannels) {
|
||||||
@@ -710,6 +716,12 @@ class CartService extends TransactionBaseService {
|
|||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (cart.shipping_methods?.length) {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.deleteShippingMethods(cart.shipping_methods)
|
||||||
|
}
|
||||||
|
|
||||||
cart = await this.retrieve(cart.id, {
|
cart = await this.retrieve(cart.id, {
|
||||||
relations: [
|
relations: [
|
||||||
"items.variant.product.profiles",
|
"items.variant.product.profiles",
|
||||||
@@ -920,6 +932,18 @@ class CartService extends TransactionBaseService {
|
|||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
|
const select: (keyof Cart)[] = ["id"]
|
||||||
|
if (
|
||||||
|
this.featureFlagRouter_.isFeatureEnabled(SalesChannelFeatureFlag.key)
|
||||||
|
) {
|
||||||
|
select.push("sales_channel_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
const cart = await this.retrieve(cartId, {
|
||||||
|
select: select,
|
||||||
|
relations: ["shipping_methods"],
|
||||||
|
})
|
||||||
|
|
||||||
const lineItem = await this.lineItemService_.retrieve(lineItemId, {
|
const lineItem = await this.lineItemService_.retrieve(lineItemId, {
|
||||||
select: ["id", "quantity", "variant_id", "cart_id"],
|
select: ["id", "quantity", "variant_id", "cart_id"],
|
||||||
})
|
})
|
||||||
@@ -934,17 +958,6 @@ class CartService extends TransactionBaseService {
|
|||||||
|
|
||||||
if (lineItemUpdate.quantity) {
|
if (lineItemUpdate.quantity) {
|
||||||
if (lineItem.variant_id) {
|
if (lineItem.variant_id) {
|
||||||
const select: (keyof Cart)[] = ["id"]
|
|
||||||
if (
|
|
||||||
this.featureFlagRouter_.isFeatureEnabled(
|
|
||||||
SalesChannelFeatureFlag.key
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
select.push("sales_channel_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
const cart = await this.retrieve(cartId, { select: select })
|
|
||||||
|
|
||||||
const hasInventory =
|
const hasInventory =
|
||||||
await this.productVariantInventoryService_.confirmInventory(
|
await this.productVariantInventoryService_.confirmInventory(
|
||||||
lineItem.variant_id,
|
lineItem.variant_id,
|
||||||
@@ -961,6 +974,12 @@ class CartService extends TransactionBaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cart.shipping_methods?.length) {
|
||||||
|
await this.shippingOptionService_
|
||||||
|
.withTransaction(transactionManager)
|
||||||
|
.deleteShippingMethods(cart.shipping_methods)
|
||||||
|
}
|
||||||
|
|
||||||
await this.lineItemService_
|
await this.lineItemService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.update(lineItemId, lineItemUpdate)
|
.update(lineItemId, lineItemUpdate)
|
||||||
|
|||||||
@@ -485,10 +485,6 @@ class LineItemService extends TransactionBaseService {
|
|||||||
async deleteWithTaxLines(id: string): Promise<LineItem | undefined | null> {
|
async deleteWithTaxLines(id: string): Promise<LineItem | undefined | null> {
|
||||||
return await this.atomicPhase_(
|
return await this.atomicPhase_(
|
||||||
async (transactionManager: EntityManager) => {
|
async (transactionManager: EntityManager) => {
|
||||||
const lineItemRepository = transactionManager.withRepository(
|
|
||||||
this.lineItemRepository_
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.taxProviderService_
|
await this.taxProviderService_
|
||||||
.withTransaction(transactionManager)
|
.withTransaction(transactionManager)
|
||||||
.clearLineItemsTaxLines([id])
|
.clearLineItemsTaxLines([id])
|
||||||
|
|||||||
Reference in New Issue
Block a user