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
@@ -5,21 +5,24 @@ interface StepInput {
entry_point: string
fields: string[]
variables?: Record<string, any>
list?: boolean
}
export const useRemoteQueryStepId = "use-remote-query"
export const useRemoteQueryStep = createStep(
useRemoteQueryStepId,
async (data: StepInput, { container }) => {
const { list = true, fields, variables, entry_point: entryPoint } = data
const query = container.resolve("remoteQuery")
const queryObject = remoteQueryObjectFromString({
entryPoint: data.entry_point,
fields: data.fields,
variables: data.variables,
entryPoint,
fields,
variables,
})
const result = await query(queryObject)
const entities = await query(queryObject)
const result = list ? entities : entities[0]
return new StepResponse(result)
}
@@ -13,6 +13,7 @@ export * from "./get-shipping-option-price-sets"
export * from "./get-variant-price-sets"
export * from "./get-variants"
export * from "./prepare-adjustments-from-promotion-actions"
export * from "./refresh-cart-shipping-methods"
export * from "./remove-line-item-adjustments"
export * from "./remove-shipping-method-adjustments"
export * from "./retrieve-cart"
@@ -20,5 +21,5 @@ export * from "./retrieve-cart-with-links"
export * from "./set-tax-lines-for-items"
export * from "./update-cart-promotions"
export * from "./update-carts"
export * from "./validate-cart-shipping-options"
export * from "./validate-variants-existence"
@@ -0,0 +1,72 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO, ICartModuleService } from "@medusajs/types"
import { arrayDifference } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { IFulfillmentModuleService } from "../../../../../types/dist/fulfillment/service"
interface StepInput {
cart: CartDTO
}
export const refreshCartShippingMethodsStepId = "refresh-cart-shipping-methods"
export const refreshCartShippingMethodsStep = createStep(
refreshCartShippingMethodsStepId,
async (data: StepInput, { container }) => {
const { cart } = data
const { shipping_methods: shippingMethods = [] } = cart
if (!shippingMethods?.length) {
return new StepResponse(void 0, [])
}
const fulfillmentModule = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
const shippingOptionIds: string[] = shippingMethods.map(
(sm) => sm.shipping_option_id!
)
const validShippingOptions =
await fulfillmentModule.listShippingOptionsForContext(
{
id: shippingOptionIds,
context: { ...cart },
address: {
country_code: cart.shipping_address?.country_code,
province_code: cart.shipping_address?.province,
city: cart.shipping_address?.city,
postal_expression: cart.shipping_address?.postal_code,
},
},
{ relations: ["rules"] }
)
const validShippingOptionIds = validShippingOptions.map((o) => o.id)
const invalidShippingOptionIds = arrayDifference(
shippingOptionIds,
validShippingOptionIds
)
const shippingMethodsToDelete = shippingMethods
.filter((sm) => invalidShippingOptionIds.includes(sm.shipping_option_id!))
.map((sm) => sm.id)
await cartModule.softDeleteShippingMethods(shippingMethodsToDelete)
return new StepResponse(void 0, shippingMethodsToDelete)
},
async (shippingMethodsToRestore, { container }) => {
if (shippingMethodsToRestore?.length) {
const cartModule = container.resolve<ICartModuleService>(
ModuleRegistrationName.CART
)
await cartModule.restoreShippingMethods(shippingMethodsToRestore)
}
}
)
@@ -0,0 +1,54 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CartDTO } from "@medusajs/types"
import { arrayDifference, MedusaError } from "@medusajs/utils"
import { createStep, StepResponse } from "@medusajs/workflows-sdk"
import { IFulfillmentModuleService } from "../../../../../types/dist/fulfillment/service"
interface StepInput {
cart: CartDTO
option_ids: string[]
}
export const validateCartShippingOptionsStepId =
"validate-cart-shipping-options"
export const validateCartShippingOptionsStep = createStep(
validateCartShippingOptionsStepId,
async (data: StepInput, { container }) => {
const { option_ids: optionIds = [], cart } = data
if (!optionIds.length) {
return new StepResponse(void 0)
}
const fulfillmentModule = container.resolve<IFulfillmentModuleService>(
ModuleRegistrationName.FULFILLMENT
)
const validShippingOptions =
await fulfillmentModule.listShippingOptionsForContext(
{
id: optionIds,
context: { ...cart },
address: {
country_code: cart.shipping_address?.country_code,
province_code: cart.shipping_address?.province,
city: cart.shipping_address?.city,
postal_expression: cart.shipping_address?.postal_code,
},
},
{ relations: ["rules"] }
)
const validShippingOptionIds = validShippingOptions.map((o) => o.id)
const invalidOptionIds = arrayDifference(optionIds, validShippingOptionIds)
if (invalidOptionIds.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Shipping Options are invalid for cart.`
)
}
return new StepResponse(void 0)
}
)
@@ -0,0 +1,12 @@
export const cartFieldsForRefreshSteps = [
"region_id",
"currency_code",
"region.*",
"items.*",
"items.tax_lines.*",
"shipping_address.*",
"shipping_methods.*",
"shipping_methods.tax_lines*",
"customer.*",
"customer.groups.*",
]
@@ -4,13 +4,16 @@ import {
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { addShippingMethodToCartStep } from "../steps"
import {
addShippingMethodToCartStep,
validateCartShippingOptionsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
interface AddShippingMethodToCartWorkflowInput {
cart_id: string
currency_code: string
options: {
id: string
data?: Record<string, unknown>
@@ -23,22 +26,32 @@ export const addShippingMethodToWorkflow = createWorkflow(
(
input: WorkflowData<AddShippingMethodToCartWorkflowInput>
): WorkflowData<void> => {
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart_id },
list: false,
})
const optionIds = transform({ input }, (data) => {
return (data.input.options ?? []).map((i) => i.id)
})
validateCartShippingOptionsStep({
option_ids: optionIds,
cart,
})
const shippingOptions = useRemoteQueryStep({
entry_point: "shipping_option",
fields: ["id", "name", "calculated_price.calculated_amount"],
variables: {
id: optionIds,
calculated_price: {
context: {
currency_code: input.currency_code,
},
context: { currency_code: cart.currency_code },
},
},
})
}).config({ name: "fetch-shipping-option" })
const shippingMethodInput = transform(
{ input, shippingOptions },
@@ -14,10 +14,12 @@ import {
confirmInventoryStep,
getVariantPriceSetsStep,
getVariantsStep,
refreshCartShippingMethodsStep,
validateVariantsExistStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { prepareLineItemData } from "../utils/prepare-line-item-data"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
@@ -128,15 +130,19 @@ export const addToCartWorkflow = createWorkflow(
const items = addToCartStep({ items: lineItems })
updateTaxLinesStep({
cart_or_cart_id: input.cart,
items,
})
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart.id },
list: false,
}).config({ name: "refetchcart" })
refreshCartShippingMethodsStep({ cart })
// TODO: since refreshCartShippingMethodsStep potentially removes cart shipping methods, we need the updated cart here
// for the following 2 steps as they act upon final cart shape
updateTaxLinesStep({ cart_or_cart_id: cart, items })
refreshCartPromotionsStep({ id: input.cart.id })
refreshPaymentCollectionForCartStep({
cart_id: input.cart.id,
})
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
return items
}
@@ -6,14 +6,17 @@ import {
parallelize,
transform,
} from "@medusajs/workflows-sdk"
import { useRemoteQueryStep } from "../../../common"
import {
findOneOrAnyRegionStep,
findOrCreateCustomerStep,
findSalesChannelStep,
refreshCartShippingMethodsStep,
updateCartsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { updateTaxLinesStep } from "../steps/update-tax-lines"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
export const updateCartWorkflowId = "update-cart"
@@ -63,6 +66,14 @@ export const updateCartWorkflow = createWorkflow(
const carts = updateCartsStep([cartInput])
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: cartInput.id },
list: false,
}).config({ name: "refetchcart" })
refreshCartShippingMethodsStep({ cart })
updateTaxLinesStep({ cart_or_cart_id: carts[0].id })
refreshCartPromotionsStep({
id: input.id,
@@ -5,14 +5,16 @@ import {
transform,
} from "@medusajs/workflows-sdk"
import { MedusaError } from "medusa-core-utils"
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { updateLineItemsStep } from "../../line-item/steps"
import {
confirmInventoryStep,
getVariantPriceSetsStep,
getVariantsStep,
} from ".."
import { useRemoteQueryStep } from "../../../common/steps/use-remote-query"
import { updateLineItemsStep } from "../../line-item/steps"
refreshCartShippingMethodsStep,
} from "../steps"
import { refreshCartPromotionsStep } from "../steps/refresh-cart-promotions"
import { cartFieldsForRefreshSteps } from "../utils/fields"
import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input"
import { refreshPaymentCollectionForCartStep } from "./refresh-payment-collection"
@@ -106,10 +108,16 @@ export const updateLineItemInCartWorkflow = createWorkflow(
selector: lineItemUpdate.selector,
})
const cart = useRemoteQueryStep({
entry_point: "cart",
fields: cartFieldsForRefreshSteps,
variables: { id: input.cart.id },
list: false,
}).config({ name: "refetchcart" })
refreshCartShippingMethodsStep({ cart })
refreshCartPromotionsStep({ id: input.cart.id })
refreshPaymentCollectionForCartStep({
cart_id: input.cart.id,
})
refreshPaymentCollectionForCartStep({ cart_id: input.cart.id })
const updatedItem = transform({ result }, (data) => data.result?.[0])