feat(core-flows): cart complete shipping validate (#10984)

**What**
- validate that there is a shipping method if any of the line items have requires_shipping=true
- validate that products shipping profile is supported by a shipping method on the cart
- update tests

---

CLOSES CMRC-683
This commit is contained in:
Frane Polić authored and GitHub committed 2025-02-04 10:10:08 +00:00
1 parent 3cf4307296
commit 462c3e8057
9 files changed
+512 -118

No files matched your search

@@ -96,7 +96,11 @@ medusaIntegrationTestRunner({
).data.region
product = (
await api.post("/admin/products", { ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id }, adminHeaders)
await api.post(
"/admin/products",
{ ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id },
adminHeaders
)
).data.product
salesChannel = (
@@ -995,87 +999,89 @@ medusaIntegrationTestRunner({
})
describe("POST /store/carts/:id/complete", () => {
beforeEach(async () => {
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
promo_codes: [promotion.code],
},
storeHeadersWithCustomer
)
).data.cart
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
})
it("should successfully complete cart", async () => {
const response = await api.post(
`/store/carts/${cart.id}/complete`,
{},
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.order).toEqual(
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 1500,
compare_at_unit_price: null,
quantity: 1,
}),
]),
})
)
})
describe("with sale price lists", () => {
let priceList
describe("should successfully complete cart", () => {
beforeEach(async () => {
priceList = (
const stockLocation = (
await api.post(
`/admin/price-lists`,
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
title: "test price list",
description: "test",
status: PriceListStatus.ACTIVE,
type: PriceListType.SALE,
prices: [
{
amount: 350,
currency_code: "usd",
variant_id: product.variants[0].id,
},
],
name: `Test-${shippingProfile.id}`,
type: "test-type",
},
adminHeaders
)
).data.price_list
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: `Test-${shippingProfile.id}`,
geo_zones: [{ type: "country", country_code: "US" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/store/carts/${cart.id}/line-items`,
{ variant_id: product.variants[0].id, quantity: 1 },
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
const shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: `Test shipping option ${fulfillmentSet.id}`,
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
promo_codes: [promotion.code],
},
storeHeadersWithCustomer
)
).data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
@@ -1094,27 +1100,297 @@ medusaIntegrationTestRunner({
)
})
it("should add price from price list and set compare_at_unit_price for order item", async () => {
it("should successfully complete cart", async () => {
const response = await api.post(
`/store/carts/${cart.id}/complete`,
{ variant_id: product.variants[0].id, quantity: 1 },
{},
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.order).toEqual(
expect.objectContaining({
id: expect.any(String),
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 350,
compare_at_unit_price: 1500,
is_tax_inclusive: true,
quantity: 2,
unit_price: 1500,
compare_at_unit_price: null,
quantity: 1,
}),
]),
})
)
})
describe("with sale price lists", () => {
let priceList
beforeEach(async () => {
priceList = (
await api.post(
`/admin/price-lists`,
{
title: "test price list",
description: "test",
status: PriceListStatus.ACTIVE,
type: PriceListType.SALE,
prices: [
{
amount: 350,
currency_code: "usd",
variant_id: product.variants[0].id,
},
],
},
adminHeaders
)
).data.price_list
await api.post(
`/store/carts/${cart.id}/line-items`,
{ variant_id: product.variants[0].id, quantity: 1 },
storeHeaders
)
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
})
it("should add price from price list and set compare_at_unit_price for order item", async () => {
const response = await api.post(
`/store/carts/${cart.id}/complete`,
{ variant_id: product.variants[0].id, quantity: 1 },
storeHeaders
)
expect(response.status).toEqual(200)
expect(response.data.order).toEqual(
expect.objectContaining({
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 350,
compare_at_unit_price: 1500,
is_tax_inclusive: true,
quantity: 2,
}),
]),
})
)
})
})
})
describe("shipping validation", () => {
it("should fail to complete the cart if no shipping method is selected and items require shipping", async () => {
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
promo_codes: [promotion.code],
},
storeHeadersWithCustomer
)
).data.cart
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
const response = await api
.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
.catch((e) => e)
expect(response.response.status).toEqual(400)
expect(response.response.data.message).toEqual(
"No shipping method selected but the cart contains items that require shipping."
)
})
it("should fail to complete the cart if the shipping profile of a product is not supported by the shipping method", async () => {
const stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
const shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: `test-${stockLocation.id}`, type: "default" },
adminHeaders
)
).data.shipping_profile
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{
name: `Test-${shippingProfile.id}`,
type: "test-type",
},
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: `Test-${shippingProfile.id}`,
geo_zones: [{ type: "country", country_code: "US" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
const shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: `Test shipping option ${fulfillmentSet.id}`,
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
const specialShippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "special-shipping-profile", type: "special" },
adminHeaders
)
).data.shipping_profile
const product = (
await api.post(
`/admin/products`,
{
title: "test product",
description: "test",
options: [
{
title: "Size",
values: ["S", "M", "L", "XL"],
},
],
variants: [
{
title: "S / Black",
sku: "special-shirt",
options: {
Size: "S",
},
manage_inventory: false,
prices: [
{
amount: 1500,
currency_code: "usd",
},
],
},
],
shipping_profile_id: specialShippingProfile.id, // --> product has a different shipping profile than
},
adminHeaders
)
).data.product
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
sales_channel_id: salesChannel.id,
region_id: region.id,
shipping_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
promo_codes: [promotion.code],
},
storeHeadersWithCustomer
)
).data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
const response = await api
.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
.catch((e) => e)
expect(response.response.status).toEqual(400)
expect(response.response.data.message).toEqual(
"The cart items require shipping profiles that are not satisfied by the current shipping methods"
)
})
})
})