feat(core-flows,framework,medusa): list shipping options pass in cart as pricing context (#10374)
* feat(core-flows,framework,medusa): list shipping options pass in cart as pricing context * chore: add test for shipping options returning free shipping
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/core-flows": patch
|
||||||
|
"@medusajs/framework": patch
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(core-flows,framework,medusa): list shipping options pass in cart as pricing context
|
||||||
@@ -152,12 +152,12 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 500,
|
amount: 500,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gte",
|
operator: "gte",
|
||||||
value: 100,
|
value: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "lte",
|
operator: "lte",
|
||||||
value: 200,
|
value: 200,
|
||||||
},
|
},
|
||||||
@@ -218,12 +218,12 @@ medusaIntegrationTestRunner({
|
|||||||
rules_count: 2,
|
rules_count: 2,
|
||||||
price_rules: expect.arrayContaining([
|
price_rules: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gte",
|
operator: "gte",
|
||||||
value: "100",
|
value: "100",
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "lte",
|
operator: "lte",
|
||||||
value: "200",
|
value: "200",
|
||||||
}),
|
}),
|
||||||
@@ -327,7 +327,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 500,
|
amount: 500,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gt",
|
operator: "gt",
|
||||||
value: 200,
|
value: 200,
|
||||||
},
|
},
|
||||||
@@ -378,7 +378,7 @@ medusaIntegrationTestRunner({
|
|||||||
rules_count: 2,
|
rules_count: 2,
|
||||||
price_rules: expect.arrayContaining([
|
price_rules: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gt",
|
operator: "gt",
|
||||||
value: "200",
|
value: "200",
|
||||||
}),
|
}),
|
||||||
@@ -458,7 +458,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 500,
|
amount: 500,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "not_whitelisted",
|
operator: "not_whitelisted",
|
||||||
value: 100,
|
value: 100,
|
||||||
},
|
},
|
||||||
@@ -496,7 +496,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 500,
|
amount: 500,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gt",
|
operator: "gt",
|
||||||
value: "string",
|
value: "string",
|
||||||
},
|
},
|
||||||
@@ -626,7 +626,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 5,
|
amount: 5,
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gt",
|
operator: "gt",
|
||||||
value: 200,
|
value: 200,
|
||||||
},
|
},
|
||||||
@@ -702,7 +702,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 5,
|
amount: 5,
|
||||||
price_rules: [
|
price_rules: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
attribute: "cart_total",
|
attribute: "total",
|
||||||
operator: "gt",
|
operator: "gt",
|
||||||
value: "200",
|
value: "200",
|
||||||
}),
|
}),
|
||||||
|
|||||||
+88
@@ -190,6 +190,17 @@ medusaIntegrationTestRunner({
|
|||||||
region_id: region.id,
|
region_id: region.id,
|
||||||
amount: 1100,
|
amount: 1100,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
amount: 0,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
operator: "gt",
|
||||||
|
attribute: "total",
|
||||||
|
value: 2000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
region_id: regionTwo.id,
|
region_id: regionTwo.id,
|
||||||
amount: 500,
|
amount: 500,
|
||||||
@@ -266,6 +277,83 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should return prices based on cart total", async () => {
|
||||||
|
cart = (
|
||||||
|
await api.post(
|
||||||
|
`/store/carts`,
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "test@admin.com",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
// Adding a quantity of 100 to emulate total being greater than 2000
|
||||||
|
quantity: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
const resp = await api.get(
|
||||||
|
`/store/shipping-options?cart_id=${cart.id}`,
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
|
||||||
|
const shippingOptions = resp.data.shipping_options
|
||||||
|
|
||||||
|
expect(shippingOptions).toHaveLength(1)
|
||||||
|
expect(shippingOptions[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: shippingOption.id,
|
||||||
|
name: "Test shipping option",
|
||||||
|
// Free shipping due to cart total being greater than 2000
|
||||||
|
amount: 0,
|
||||||
|
price_type: "flat",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw when required fields of a cart are not present", async () => {
|
||||||
|
cart = (
|
||||||
|
await api.post(
|
||||||
|
`/store/carts`,
|
||||||
|
{
|
||||||
|
region_id: region.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
sales_channel_id: null,
|
||||||
|
email: "test@admin.com",
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
storeHeaders
|
||||||
|
)
|
||||||
|
).data.cart
|
||||||
|
|
||||||
|
const { response } = await api
|
||||||
|
.get(`/store/shipping-options?cart_id=${cart.id}`, storeHeaders)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message:
|
||||||
|
"Field(s) are required to have value to continue - sales_channel_id",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw error when cart_id is not passed as a parameter", async () => {
|
||||||
|
const { response } = await api
|
||||||
|
.get(`/store/shipping-options`, storeHeaders)
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
type: "invalid_data",
|
||||||
|
message: "Invalid request: Field 'cart_id' is required",
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
updatePaymentCollectionStepId,
|
updatePaymentCollectionStepId,
|
||||||
updateTaxLinesWorkflow,
|
updateTaxLinesWorkflow,
|
||||||
} from "@medusajs/core-flows"
|
} from "@medusajs/core-flows"
|
||||||
|
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
ICustomerModuleService,
|
ICustomerModuleService,
|
||||||
@@ -30,7 +31,6 @@ import {
|
|||||||
Modules,
|
Modules,
|
||||||
RuleOperator,
|
RuleOperator,
|
||||||
} from "@medusajs/utils"
|
} from "@medusajs/utils"
|
||||||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|
||||||
import {
|
import {
|
||||||
adminHeaders,
|
adminHeaders,
|
||||||
createAdminUser,
|
createAdminUser,
|
||||||
@@ -1835,6 +1835,15 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("listShippingOptionsForCartWorkflow", () => {
|
describe("listShippingOptionsForCartWorkflow", () => {
|
||||||
|
let region
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
region = await regionModuleService.createRegions({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("should list shipping options for cart", async () => {
|
it("should list shipping options for cart", async () => {
|
||||||
const salesChannel = await scModuleService.createSalesChannels({
|
const salesChannel = await scModuleService.createSalesChannels({
|
||||||
name: "Webshop",
|
name: "Webshop",
|
||||||
@@ -1846,6 +1855,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
let cart = await cartModuleService.createCarts({
|
let cart = await cartModuleService.createCarts({
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
|
region_id: region.id,
|
||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
city: "CPH",
|
city: "CPH",
|
||||||
@@ -1935,13 +1945,6 @@ medusaIntegrationTestRunner({
|
|||||||
).run({
|
).run({
|
||||||
input: {
|
input: {
|
||||||
cart_id: cart.id,
|
cart_id: cart.id,
|
||||||
sales_channel_id: salesChannel.id,
|
|
||||||
currency_code: "usd",
|
|
||||||
shipping_address: {
|
|
||||||
city: cart.shipping_address?.city,
|
|
||||||
province: cart.shipping_address?.province,
|
|
||||||
country_code: cart.shipping_address?.country_code,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1965,6 +1968,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
let cart = await cartModuleService.createCarts({
|
let cart = await cartModuleService.createCarts({
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
|
region_id: region.id,
|
||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
city: "CPH",
|
city: "CPH",
|
||||||
@@ -2044,16 +2048,7 @@ medusaIntegrationTestRunner({
|
|||||||
const { result } = await listShippingOptionsForCartWorkflow(
|
const { result } = await listShippingOptionsForCartWorkflow(
|
||||||
appContainer
|
appContainer
|
||||||
).run({
|
).run({
|
||||||
input: {
|
input: { cart_id: cart.id },
|
||||||
cart_id: cart.id,
|
|
||||||
sales_channel_id: salesChannel.id,
|
|
||||||
currency_code: "usd",
|
|
||||||
shipping_address: {
|
|
||||||
city: cart.shipping_address?.city,
|
|
||||||
province: cart.shipping_address?.province,
|
|
||||||
country_code: cart.shipping_address?.country_code,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toEqual([])
|
expect(result).toEqual([])
|
||||||
@@ -2070,6 +2065,7 @@ medusaIntegrationTestRunner({
|
|||||||
|
|
||||||
let cart = await cartModuleService.createCarts({
|
let cart = await cartModuleService.createCarts({
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
|
region_id: region.id,
|
||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
shipping_address: {
|
shipping_address: {
|
||||||
city: "CPH",
|
city: "CPH",
|
||||||
@@ -2140,16 +2136,7 @@ medusaIntegrationTestRunner({
|
|||||||
const { errors } = await listShippingOptionsForCartWorkflow(
|
const { errors } = await listShippingOptionsForCartWorkflow(
|
||||||
appContainer
|
appContainer
|
||||||
).run({
|
).run({
|
||||||
input: {
|
input: { cart_id: cart.id },
|
||||||
cart_id: cart.id,
|
|
||||||
sales_channel_id: salesChannel.id,
|
|
||||||
currency_code: "usd",
|
|
||||||
shipping_address: {
|
|
||||||
city: cart.shipping_address?.city,
|
|
||||||
province: cart.shipping_address?.province,
|
|
||||||
country_code: cart.shipping_address?.country_code,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { ListShippingOptionsForCartWorkflowInputDTO } from "@medusajs/framework/types"
|
|
||||||
import { deepFlatMap, isPresent, MedusaError } from "@medusajs/framework/utils"
|
import { deepFlatMap, isPresent, MedusaError } from "@medusajs/framework/utils"
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
|
when,
|
||||||
WorkflowData,
|
WorkflowData,
|
||||||
WorkflowResponse,
|
WorkflowResponse,
|
||||||
} from "@medusajs/framework/workflows-sdk"
|
} from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { useQueryGraphStep, validatePresenceOfStep } from "../../common"
|
||||||
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
|
import { useRemoteQueryStep } from "../../common/steps/use-remote-query"
|
||||||
|
|
||||||
export const listShippingOptionsForCartWorkflowId =
|
export const listShippingOptionsForCartWorkflowId =
|
||||||
@@ -15,26 +16,52 @@ export const listShippingOptionsForCartWorkflowId =
|
|||||||
*/
|
*/
|
||||||
export const listShippingOptionsForCartWorkflow = createWorkflow(
|
export const listShippingOptionsForCartWorkflow = createWorkflow(
|
||||||
listShippingOptionsForCartWorkflowId,
|
listShippingOptionsForCartWorkflowId,
|
||||||
(input: WorkflowData<ListShippingOptionsForCartWorkflowInputDTO>) => {
|
(input: WorkflowData<{ cart_id: string; is_return?: boolean }>) => {
|
||||||
const scLocationFulfillmentSets = useRemoteQueryStep({
|
const cartQuery = useQueryGraphStep({
|
||||||
entry_point: "sales_channels",
|
entity: "cart",
|
||||||
|
filters: { id: input.cart_id },
|
||||||
|
fields: [
|
||||||
|
"id",
|
||||||
|
"sales_channel_id",
|
||||||
|
"currency_code",
|
||||||
|
"region_id",
|
||||||
|
"shipping_address.city",
|
||||||
|
"shipping_address.country_code",
|
||||||
|
"shipping_address.province",
|
||||||
|
"total",
|
||||||
|
],
|
||||||
|
options: { throwIfKeyNotFound: true },
|
||||||
|
}).config({ name: "get-cart" })
|
||||||
|
|
||||||
|
const cart = transform({ cartQuery }, ({ cartQuery }) => cartQuery.data[0])
|
||||||
|
|
||||||
|
validatePresenceOfStep({
|
||||||
|
entity: cart,
|
||||||
|
fields: ["sales_channel_id", "region_id", "currency_code"],
|
||||||
|
})
|
||||||
|
|
||||||
|
const scFulfillmentSetQuery = useQueryGraphStep({
|
||||||
|
entity: "sales_channels",
|
||||||
|
filters: { id: cart.sales_channel_id },
|
||||||
fields: ["stock_locations.fulfillment_sets.id"],
|
fields: ["stock_locations.fulfillment_sets.id"],
|
||||||
variables: {
|
|
||||||
id: input.sales_channel_id,
|
|
||||||
},
|
|
||||||
}).config({ name: "sales_channels-fulfillment-query" })
|
}).config({ name: "sales_channels-fulfillment-query" })
|
||||||
|
|
||||||
|
const scFulfillmentSets = transform(
|
||||||
|
{ scFulfillmentSetQuery },
|
||||||
|
({ scFulfillmentSetQuery }) => scFulfillmentSetQuery.data[0]
|
||||||
|
)
|
||||||
|
|
||||||
const fulfillmentSetIds = transform(
|
const fulfillmentSetIds = transform(
|
||||||
{ options: scLocationFulfillmentSets },
|
{ options: scFulfillmentSets },
|
||||||
(data) => {
|
(data) => {
|
||||||
const fulfillmentSetIds = new Set<string>()
|
const fulfillmentSetIds = new Set<string>()
|
||||||
|
|
||||||
deepFlatMap(
|
deepFlatMap(
|
||||||
data.options,
|
data.options,
|
||||||
"stock_locations.fulfillment_sets",
|
"stock_locations.fulfillment_sets",
|
||||||
({ fulfillment_sets }) => {
|
({ fulfillment_sets: fulfillmentSet }) => {
|
||||||
if (fulfillment_sets?.id) {
|
if (fulfillmentSet?.id) {
|
||||||
fulfillmentSetIds.add(fulfillment_sets.id)
|
fulfillmentSetIds.add(fulfillmentSet.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -43,6 +70,36 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const customerGroupIds = when({ cart }, ({ cart }) => {
|
||||||
|
return !!cart.id
|
||||||
|
}).then(() => {
|
||||||
|
const customerQuery = useQueryGraphStep({
|
||||||
|
entity: "customer",
|
||||||
|
filters: { id: cart.customer_id },
|
||||||
|
fields: ["groups.id"],
|
||||||
|
}).config({ name: "get-customer" })
|
||||||
|
|
||||||
|
return transform({ customerQuery }, ({ customerQuery }) => {
|
||||||
|
const customer = customerQuery.data[0]
|
||||||
|
|
||||||
|
if (!isPresent(customer)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const { groups = [] } = customer
|
||||||
|
|
||||||
|
return groups.map((group) => group.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const pricingContext = transform(
|
||||||
|
{ cart, customerGroupIds },
|
||||||
|
({ cart, customerGroupIds }) => ({
|
||||||
|
...cart,
|
||||||
|
customer_group_id: customerGroupIds,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const shippingOptions = useRemoteQueryStep({
|
const shippingOptions = useRemoteQueryStep({
|
||||||
entry_point: "shipping_options",
|
entry_point: "shipping_options",
|
||||||
fields: [
|
fields: [
|
||||||
@@ -71,61 +128,53 @@ export const listShippingOptionsForCartWorkflow = createWorkflow(
|
|||||||
],
|
],
|
||||||
variables: {
|
variables: {
|
||||||
context: {
|
context: {
|
||||||
is_return: input.is_return,
|
is_return: !!input.is_return,
|
||||||
enabled_in_store: "true",
|
enabled_in_store: "true",
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
fulfillment_set_id: fulfillmentSetIds,
|
fulfillment_set_id: fulfillmentSetIds,
|
||||||
address: {
|
address: {
|
||||||
city: input.shipping_address?.city,
|
city: cart.shipping_address?.city,
|
||||||
country_code: input.shipping_address?.country_code,
|
country_code: cart.shipping_address?.country_code,
|
||||||
province_code: input.shipping_address?.province,
|
province_code: cart.shipping_address?.province,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
calculated_price: {
|
calculated_price: {
|
||||||
context: {
|
context: pricingContext,
|
||||||
currency_code: input.currency_code,
|
|
||||||
region_id: input.region_id,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).config({ name: "shipping-options-query" })
|
}).config({ name: "shipping-options-query" })
|
||||||
|
|
||||||
const shippingOptionsWithPrice = transform(
|
const shippingOptionsWithPrice = transform({ shippingOptions }, (data) => {
|
||||||
{
|
const optionsMissingPrices: string[] = []
|
||||||
shippingOptions,
|
|
||||||
},
|
|
||||||
(data) => {
|
|
||||||
const optionsMissingPrices: string[] = []
|
|
||||||
|
|
||||||
const options = data.shippingOptions.map((shippingOption) => {
|
const options = data.shippingOptions.map((shippingOption) => {
|
||||||
const { calculated_price, ...options } = shippingOption ?? {}
|
const { calculated_price, ...options } = shippingOption ?? {}
|
||||||
|
|
||||||
if (options?.id && !isPresent(calculated_price?.calculated_amount)) {
|
if (options?.id && !isPresent(calculated_price?.calculated_amount)) {
|
||||||
optionsMissingPrices.push(options.id)
|
optionsMissingPrices.push(options.id)
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...options,
|
|
||||||
amount: calculated_price?.calculated_amount,
|
|
||||||
is_tax_inclusive:
|
|
||||||
!!calculated_price?.is_calculated_price_tax_inclusive,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (optionsMissingPrices.length) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_DATA,
|
|
||||||
`Shipping options with IDs ${optionsMissingPrices.join(
|
|
||||||
", "
|
|
||||||
)} do not have a price`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return options
|
return {
|
||||||
|
...options,
|
||||||
|
amount: calculated_price?.calculated_amount,
|
||||||
|
is_tax_inclusive:
|
||||||
|
!!calculated_price?.is_calculated_price_tax_inclusive,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (optionsMissingPrices.length) {
|
||||||
|
const ids = optionsMissingPrices.join(", ")
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Shipping options with IDs ${ids} do not have a price`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
return options
|
||||||
|
})
|
||||||
|
|
||||||
return new WorkflowResponse(shippingOptionsWithPrice)
|
return new WorkflowResponse(shippingOptionsWithPrice)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
export * from "./steps/create-remote-links"
|
export * from "./steps/create-remote-links"
|
||||||
export * from "./steps/dismiss-remote-links"
|
export * from "./steps/dismiss-remote-links"
|
||||||
export * from "./steps/remove-remote-links"
|
|
||||||
export * from "./steps/emit-event"
|
export * from "./steps/emit-event"
|
||||||
|
export * from "./steps/remove-remote-links"
|
||||||
export * from "./steps/update-remote-links"
|
export * from "./steps/update-remote-links"
|
||||||
export * from "./steps/use-query-graph"
|
export * from "./steps/use-query-graph"
|
||||||
export * from "./steps/use-remote-query"
|
export * from "./steps/use-remote-query"
|
||||||
|
export * from "./steps/validate-presence-of"
|
||||||
export * from "./workflows/batch-links"
|
export * from "./workflows/batch-links"
|
||||||
export * from "./workflows/create-links"
|
export * from "./workflows/create-links"
|
||||||
export * from "./workflows/dismiss-links"
|
export * from "./workflows/dismiss-links"
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { isPresent, MedusaError } from "@medusajs/framework/utils"
|
||||||
|
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This step validates the presence of attributes on an object
|
||||||
|
*/
|
||||||
|
export const validatePresenceOfStep = createStep(
|
||||||
|
"validate-presence-of",
|
||||||
|
async function ({
|
||||||
|
entity,
|
||||||
|
fields,
|
||||||
|
}: {
|
||||||
|
entity: Record<any, unknown>
|
||||||
|
fields: string[]
|
||||||
|
}) {
|
||||||
|
const invalid: string[] = []
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
if (!isPresent(entity[field])) {
|
||||||
|
invalid.push(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalid.length) {
|
||||||
|
const invalidFields = invalid.join(", ")
|
||||||
|
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
`Field(s) are required to have value to continue - ${invalidFields}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -90,16 +90,12 @@ export type GlobalMiddlewareDescriptor = {
|
|||||||
config?: MiddlewaresConfig
|
config?: MiddlewaresConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MedusaRequest<Body = unknown>
|
export interface MedusaRequest<
|
||||||
extends Request<
|
Body = unknown,
|
||||||
{
|
QueryFields = Record<string, unknown>
|
||||||
[key: string]: string
|
> extends Request<{ [key: string]: string }, any, Body> {
|
||||||
},
|
|
||||||
any,
|
|
||||||
Body
|
|
||||||
> {
|
|
||||||
validatedBody: Body
|
validatedBody: Body
|
||||||
validatedQuery: RequestQueryFields & Record<string, unknown>
|
validatedQuery: RequestQueryFields & QueryFields
|
||||||
/**
|
/**
|
||||||
* TODO: shouldn't this correspond to returnable fields instead of allowed fields? also it is used by the cleanResponseData util
|
* TODO: shouldn't this correspond to returnable fields instead of allowed fields? also it is used by the cleanResponseData util
|
||||||
*/
|
*/
|
||||||
@@ -122,7 +118,7 @@ export interface MedusaRequest<Body = unknown>
|
|||||||
/**
|
/**
|
||||||
* An object containing the fields that are filterable e.g `{ id: Any<String> }`
|
* An object containing the fields that are filterable e.g `{ id: Any<String> }`
|
||||||
*/
|
*/
|
||||||
filterableFields: Record<string, unknown>
|
filterableFields: QueryFields
|
||||||
includes?: Record<string, boolean>
|
includes?: Record<string, boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const AdminCreateShippingOptionTypeObject = z
|
|||||||
|
|
||||||
const AdminPriceRules = z.array(
|
const AdminPriceRules = z.array(
|
||||||
z.object({
|
z.object({
|
||||||
attribute: z.literal("cart_total"),
|
attribute: z.literal("total"),
|
||||||
operator: z.nativeEnum(PricingRuleOperator),
|
operator: z.nativeEnum(PricingRuleOperator),
|
||||||
value: z.number(),
|
value: z.number(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,52 +1,17 @@
|
|||||||
import { listShippingOptionsForCartWorkflow } from "@medusajs/core-flows"
|
import { listShippingOptionsForCartWorkflow } from "@medusajs/core-flows"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { HttpTypes, ICartModuleService } from "@medusajs/framework/types"
|
import { HttpTypes } from "@medusajs/framework/types"
|
||||||
import { MedusaError, Modules } from "@medusajs/framework/utils"
|
|
||||||
import { StoreGetShippingOptionsType } from "./validators"
|
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: MedusaRequest<HttpTypes.StoreGetShippingOptionList>,
|
req: MedusaRequest<{}, HttpTypes.StoreGetShippingOptionList>,
|
||||||
res: MedusaResponse<HttpTypes.StoreShippingOptionListResponse>
|
res: MedusaResponse<HttpTypes.StoreShippingOptionListResponse>
|
||||||
) => {
|
) => {
|
||||||
const { cart_id, is_return } =
|
const { cart_id, is_return } = req.filterableFields
|
||||||
req.filterableFields as StoreGetShippingOptionsType
|
|
||||||
|
|
||||||
if (!cart_id) {
|
const workflow = listShippingOptionsForCartWorkflow(req.scope)
|
||||||
throw new MedusaError(
|
const { result: shipping_options } = await workflow.run({
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
input: { cart_id, is_return: !!is_return },
|
||||||
"You must provide the cart_id to list shipping options"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cartService = req.scope.resolve<ICartModuleService>(Modules.CART)
|
|
||||||
|
|
||||||
const cart = await cartService.retrieveCart(cart_id, {
|
|
||||||
select: [
|
|
||||||
"id",
|
|
||||||
"sales_channel_id",
|
|
||||||
"currency_code",
|
|
||||||
"region_id",
|
|
||||||
"shipping_address.city",
|
|
||||||
"shipping_address.country_code",
|
|
||||||
"shipping_address.province",
|
|
||||||
],
|
|
||||||
relations: ["shipping_address"],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const { result } = await listShippingOptionsForCartWorkflow(req.scope).run({
|
res.json({ shipping_options })
|
||||||
input: {
|
|
||||||
cart_id: cart.id,
|
|
||||||
sales_channel_id: cart.sales_channel_id,
|
|
||||||
currency_code: cart.currency_code,
|
|
||||||
region_id: cart.region_id,
|
|
||||||
is_return: !!is_return,
|
|
||||||
shipping_address: {
|
|
||||||
city: cart.shipping_address?.city,
|
|
||||||
country_code: cart.shipping_address?.country_code,
|
|
||||||
province: cart.shipping_address?.province,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
res.json({ shipping_options: result })
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { createFindParams } from "../../utils/validators"
|
|
||||||
import { applyAndAndOrOperators } from "../../utils/common-validators"
|
import { applyAndAndOrOperators } from "../../utils/common-validators"
|
||||||
|
import { createFindParams } from "../../utils/validators"
|
||||||
|
|
||||||
export const StoreGetShippingOptionsFields = z.object({
|
export const StoreGetShippingOptionsFields = z
|
||||||
cart_id: z.string(),
|
.object({
|
||||||
is_return: z.boolean().optional(),
|
cart_id: z.string(),
|
||||||
})
|
is_return: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
export type StoreGetShippingOptionsType = z.infer<
|
export type StoreGetShippingOptionsType = z.infer<
|
||||||
typeof StoreGetShippingOptions
|
typeof StoreGetShippingOptions
|
||||||
|
|||||||
+11
-11
@@ -1870,7 +1870,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
rules: {
|
rules: {
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: withOperator("between", 300, 400),
|
total: withOperator("between", 300, 400),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1878,7 +1878,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
rules: {
|
rules: {
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: withOperator("betweenEquals", 400, 500),
|
total: withOperator("betweenEquals", 400, 500),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1886,7 +1886,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
rules: {
|
rules: {
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: withOperator("excludingMin", 500, 600),
|
total: withOperator("excludingMin", 500, 600),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1894,7 +1894,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
rules: {
|
rules: {
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: withOperator("excludingMax", 600, 700),
|
total: withOperator("excludingMax", 600, 700),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1906,7 +1906,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 350,
|
total: 350,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1951,7 +1951,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 300,
|
total: 300,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1964,7 +1964,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 400,
|
total: 400,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1979,7 +1979,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 500,
|
total: 500,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1994,7 +1994,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 501,
|
total: 501,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -2009,7 +2009,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 601,
|
total: 601,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -2024,7 +2024,7 @@ moduleIntegrationTestRunner<IPricingModuleService>({
|
|||||||
context: {
|
context: {
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: "de",
|
region_id: "de",
|
||||||
cart_total: 900,
|
total: 900,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user