feat(core-flows, medusa): add shipping methods to cart API (#7150)
* feat(core-flows, medusa): add shipping methods to cart API * chore: change id to option_id * chore: use list listShippingOptionsForContext instead of validateShippingOption * chore: remove comment * chore: add refresh shipping methods step * chore: set cart step * chore: update all workflows to refresh shipping methods * chore: add tests + cleanup
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
||||
ISalesChannelModuleService,
|
||||
IStockLocationServiceNext,
|
||||
} from "@medusajs/types"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
import { ContainerRegistrationKeys, RuleOperator } from "@medusajs/utils"
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||
|
||||
@@ -1397,34 +1397,44 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("AddShippingMethodToCartWorkflow", () => {
|
||||
it("should add shipping method to cart", async () => {
|
||||
let cart = await cartModuleService.create({
|
||||
let cart
|
||||
let shippingProfile
|
||||
let fulfillmentSet
|
||||
let priceSet
|
||||
|
||||
beforeEach(async () => {
|
||||
cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
country_code: "us",
|
||||
province: "ny",
|
||||
},
|
||||
})
|
||||
|
||||
const shippingProfile =
|
||||
await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
shippingProfile = await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
|
||||
const fulfillmentSet = await fulfillmentModule.create({
|
||||
fulfillmentSet = await fulfillmentModule.create({
|
||||
name: "Test",
|
||||
type: "test-type",
|
||||
service_zones: [
|
||||
{
|
||||
name: "Test",
|
||||
geo_zones: [
|
||||
{
|
||||
type: "country",
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
geo_zones: [{ type: "country", country_code: "us" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
priceSet = await pricingModule.create({
|
||||
prices: [{ amount: 3000, currency_code: "usd" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should add shipping method to cart", async () => {
|
||||
const shippingOption = await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option",
|
||||
service_zone_id: fulfillmentSet.service_zones[0].id,
|
||||
@@ -1436,41 +1446,26 @@ medusaIntegrationTestRunner({
|
||||
description: "Test description",
|
||||
code: "test-code",
|
||||
},
|
||||
})
|
||||
|
||||
const priceSet = await pricingModule.create({
|
||||
prices: [
|
||||
rules: [
|
||||
{
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "shipping_address.province",
|
||||
value: "ny",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.FULFILLMENT]: {
|
||||
shipping_option_id: shippingOption.id,
|
||||
},
|
||||
[Modules.PRICING]: {
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
[Modules.FULFILLMENT]: { shipping_option_id: shippingOption.id },
|
||||
[Modules.PRICING]: { price_set_id: priceSet.id },
|
||||
},
|
||||
])
|
||||
|
||||
cart = await cartModuleService.retrieve(cart.id, {
|
||||
select: ["id", "region_id", "currency_code"],
|
||||
})
|
||||
|
||||
await addShippingMethodToWorkflow(appContainer).run({
|
||||
input: {
|
||||
options: [
|
||||
{
|
||||
id: shippingOption.id,
|
||||
},
|
||||
],
|
||||
options: [{ id: shippingOption.id }],
|
||||
cart_id: cart.id,
|
||||
currency_code: cart.currency_code,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1491,6 +1486,77 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw error when shipping option is not valid", async () => {
|
||||
const shippingOption = await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "shipping_address.city",
|
||||
value: "sf",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.FULFILLMENT]: { shipping_option_id: shippingOption.id },
|
||||
[Modules.PRICING]: { price_set_id: priceSet.id },
|
||||
},
|
||||
])
|
||||
|
||||
const { errors } = await addShippingMethodToWorkflow(
|
||||
appContainer
|
||||
).run({
|
||||
input: {
|
||||
options: [{ id: shippingOption.id }],
|
||||
cart_id: cart.id,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
// Rules are setup only for Germany, this should throw an error
|
||||
expect(errors).toEqual([
|
||||
expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
message: `Shipping Options are invalid for cart.`,
|
||||
type: "invalid_data",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should throw error when shipping option is not present in the db", async () => {
|
||||
const { errors } = await addShippingMethodToWorkflow(
|
||||
appContainer
|
||||
).run({
|
||||
input: {
|
||||
options: [{ id: "does-not-exist" }],
|
||||
cart_id: cart.id,
|
||||
},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
// Rules are setup only for Berlin, this should throw an error
|
||||
expect(errors).toEqual([
|
||||
expect.objectContaining({
|
||||
error: expect.objectContaining({
|
||||
message: "Shipping Options are invalid for cart.",
|
||||
type: "invalid_data",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listShippingOptionsForCartWorkflow", () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
ICartModuleService,
|
||||
ICustomerModuleService,
|
||||
IFulfillmentModuleService,
|
||||
IPricingModuleService,
|
||||
IProductModuleService,
|
||||
IPromotionModuleService,
|
||||
@@ -14,7 +15,11 @@ import {
|
||||
ISalesChannelModuleService,
|
||||
ITaxModuleService,
|
||||
} from "@medusajs/types"
|
||||
import { PromotionRuleOperator, PromotionType } from "@medusajs/utils"
|
||||
import {
|
||||
PromotionRuleOperator,
|
||||
PromotionType,
|
||||
RuleOperator,
|
||||
} from "@medusajs/utils"
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
|
||||
@@ -38,6 +43,7 @@ medusaIntegrationTestRunner({
|
||||
let remoteLink: RemoteLink
|
||||
let promotionModule: IPromotionModuleService
|
||||
let taxModule: ITaxModuleService
|
||||
let fulfillmentModule: IFulfillmentModuleService
|
||||
|
||||
let defaultRegion
|
||||
|
||||
@@ -52,6 +58,9 @@ medusaIntegrationTestRunner({
|
||||
remoteLink = appContainer.resolve(LinkModuleUtils.REMOTE_LINK)
|
||||
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
|
||||
taxModule = appContainer.resolve(ModuleRegistrationName.TAX)
|
||||
fulfillmentModule = appContainer.resolve(
|
||||
ModuleRegistrationName.FULFILLMENT
|
||||
)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -548,7 +557,7 @@ medusaIntegrationTestRunner({
|
||||
await setupTaxStructure(taxModule)
|
||||
|
||||
const region = await regionModule.create({
|
||||
name: "US",
|
||||
name: "us",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
@@ -562,9 +571,9 @@ medusaIntegrationTestRunner({
|
||||
shipping_address: {
|
||||
address_1: "test address 1",
|
||||
address_2: "test address 2",
|
||||
city: "NY",
|
||||
country_code: "US",
|
||||
province: "NY",
|
||||
city: "ny",
|
||||
country_code: "us",
|
||||
province: "ny",
|
||||
postal_code: "94016",
|
||||
},
|
||||
items: [
|
||||
@@ -578,11 +587,78 @@ medusaIntegrationTestRunner({
|
||||
],
|
||||
})
|
||||
|
||||
const shippingProfile =
|
||||
await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
|
||||
const fulfillmentSet = await fulfillmentModule.create({
|
||||
name: "Test",
|
||||
type: "test-type",
|
||||
service_zones: [
|
||||
{
|
||||
name: "Test",
|
||||
geo_zones: [{ type: "country", country_code: "us" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption = await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "customer.email",
|
||||
value: "tony@stark.com",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption2 = await fulfillmentModule.createShippingOptions(
|
||||
{
|
||||
name: "Test shipping option",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "customer.email",
|
||||
value: "tony@stark.com",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
// Manually inserting shipping methods here since the cart does not
|
||||
// currently support it. Move to API when ready.
|
||||
await cartModule.addShippingMethods(cart.id, [
|
||||
{ amount: 500, name: "express" },
|
||||
{ amount: 500, name: "standard" },
|
||||
{
|
||||
amount: 500,
|
||||
name: "express",
|
||||
shipping_option_id: shippingOption.id,
|
||||
},
|
||||
{
|
||||
amount: 500,
|
||||
name: "standard",
|
||||
shipping_option_id: shippingOption2.id,
|
||||
},
|
||||
])
|
||||
|
||||
let updated = await api.post(`/store/carts/${cart.id}`, {
|
||||
@@ -590,7 +666,10 @@ medusaIntegrationTestRunner({
|
||||
email: "tony@stark.com",
|
||||
sales_channel_id: salesChannel.id,
|
||||
})
|
||||
|
||||
console.log(
|
||||
"updated.data.cart --- ",
|
||||
JSON.stringify(updated.data.cart, null, 4)
|
||||
)
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -606,13 +685,13 @@ medusaIntegrationTestRunner({
|
||||
}),
|
||||
sales_channel_id: salesChannel.id,
|
||||
shipping_address: expect.objectContaining({
|
||||
city: "NY",
|
||||
country_code: "US",
|
||||
province: "NY",
|
||||
city: "ny",
|
||||
country_code: "us",
|
||||
province: "ny",
|
||||
}),
|
||||
shipping_methods: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
shipping_option_id: null,
|
||||
shipping_option_id: shippingOption2.id,
|
||||
amount: 500,
|
||||
tax_lines: [
|
||||
expect.objectContaining({
|
||||
@@ -625,7 +704,7 @@ medusaIntegrationTestRunner({
|
||||
adjustments: [],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
shipping_option_id: null,
|
||||
shipping_option_id: shippingOption.id,
|
||||
amount: 500,
|
||||
tax_lines: [
|
||||
expect.objectContaining({
|
||||
@@ -685,6 +764,140 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should remove invalid shipping methods", async () => {
|
||||
await setupTaxStructure(taxModule)
|
||||
|
||||
const region = await regionModule.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const cart = await cartModule.create({
|
||||
region_id: region.id,
|
||||
currency_code: "eur",
|
||||
email: "tony@stark.com",
|
||||
shipping_address: {
|
||||
address_1: "test address 1",
|
||||
address_2: "test address 2",
|
||||
city: "ny",
|
||||
country_code: "us",
|
||||
province: "ny",
|
||||
postal_code: "94016",
|
||||
},
|
||||
})
|
||||
|
||||
const shippingProfile =
|
||||
await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
|
||||
const fulfillmentSet = await fulfillmentModule.create({
|
||||
name: "Test",
|
||||
type: "test-type",
|
||||
service_zones: [
|
||||
{
|
||||
name: "Test",
|
||||
geo_zones: [{ type: "country", country_code: "us" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOptionOldValid =
|
||||
await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "customer.email",
|
||||
value: "tony@stark.com",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOptionNewValid =
|
||||
await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option new",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "customer.email",
|
||||
value: "jon@stark.com",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await cartModule.addShippingMethods(cart.id, [
|
||||
// should be removed
|
||||
{
|
||||
amount: 500,
|
||||
name: "express",
|
||||
shipping_option_id: shippingOptionOldValid.id,
|
||||
},
|
||||
// should be kept
|
||||
{
|
||||
amount: 500,
|
||||
name: "express-new",
|
||||
shipping_option_id: shippingOptionNewValid.id,
|
||||
},
|
||||
// should be removed
|
||||
{
|
||||
amount: 500,
|
||||
name: "standard",
|
||||
shipping_option_id: "does-not-exist",
|
||||
},
|
||||
])
|
||||
|
||||
let updated = await api.post(`/store/carts/${cart.id}`, {
|
||||
email: "jon@stark.com",
|
||||
})
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
email: "jon@stark.com",
|
||||
shipping_methods: [
|
||||
expect.objectContaining({
|
||||
shipping_option_id: shippingOptionNewValid.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
updated = await api.post(`/store/carts/${cart.id}`, {
|
||||
email: null,
|
||||
sales_channel_id: null,
|
||||
})
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
email: null,
|
||||
shipping_methods: [],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id", () => {
|
||||
@@ -1100,6 +1313,85 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
it("should add a shipping methods to a cart", async () => {
|
||||
const cart = await cartModule.create({
|
||||
currency_code: "usd",
|
||||
shipping_address: { country_code: "us" },
|
||||
items: [],
|
||||
})
|
||||
|
||||
const shippingProfile =
|
||||
await fulfillmentModule.createShippingProfiles({
|
||||
name: "Test",
|
||||
type: "default",
|
||||
})
|
||||
|
||||
const fulfillmentSet = await fulfillmentModule.create({
|
||||
name: "Test",
|
||||
type: "test-type",
|
||||
service_zones: [
|
||||
{
|
||||
name: "Test",
|
||||
geo_zones: [{ type: "country", country_code: "us" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const priceSet = await pricingModule.create({
|
||||
prices: [{ amount: 3000, currency_code: "usd" }],
|
||||
})
|
||||
|
||||
const shippingOption = await fulfillmentModule.createShippingOptions({
|
||||
name: "Test shipping option",
|
||||
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",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
operator: RuleOperator.EQ,
|
||||
attribute: "shipping_address.country_code",
|
||||
value: "us",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await remoteLink.create([
|
||||
{
|
||||
[Modules.FULFILLMENT]: { shipping_option_id: shippingOption.id },
|
||||
[Modules.PRICING]: { price_set_id: priceSet.id },
|
||||
},
|
||||
])
|
||||
|
||||
let response = await api.post(
|
||||
`/store/carts/${cart.id}/shipping-methods`,
|
||||
{ option_id: shippingOption.id }
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option_id: shippingOption.id,
|
||||
amount: 3000,
|
||||
id: expect.any(String),
|
||||
tax_lines: [],
|
||||
adjustments: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user