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:
Riqwan Thamir
2024-04-29 09:25:23 +02:00
committed by GitHub
parent 4b57c5d286
commit d2393f004e
19 changed files with 688 additions and 80 deletions
@@ -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: [],
},
],
})
)
})
})
})
},
})