diff --git a/.changeset/twenty-oranges-agree.md b/.changeset/twenty-oranges-agree.md new file mode 100644 index 0000000000..31478572de --- /dev/null +++ b/.changeset/twenty-oranges-agree.md @@ -0,0 +1,5 @@ +--- +"@medusajs/core-flows": patch +--- + +chore(core-flows): avoid overfetching to refresh cart diff --git a/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts b/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts index ff4a908eaf..7d53b67c86 100644 --- a/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts @@ -2068,7 +2068,7 @@ medusaIntegrationTestRunner({ unit_price: 4000, is_custom_price: true, quantity: 2, - title: "Test variant", + title: "Test item", }) ) }) diff --git a/packages/core/core-flows/src/cart/utils/fields.ts b/packages/core/core-flows/src/cart/utils/fields.ts index f7bf8def30..8dd6f25357 100644 --- a/packages/core/core-flows/src/cart/utils/fields.ts +++ b/packages/core/core-flows/src/cart/utils/fields.ts @@ -35,6 +35,11 @@ export const cartFieldsForRefreshSteps = [ "customer.*", "customer.groups.*", "promotions.code", + "payment_collection.id", + "payment_collection.raw_amount", + "payment_collection.amount", + "payment_collection.currency_code", + "payment_collection.payment_sessions.id", ] export const completeCartFields = [ diff --git a/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts index 1a8ad28842..807dea450d 100644 --- a/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts +++ b/packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts @@ -4,7 +4,6 @@ import { PromotionActions, } from "@medusajs/framework/utils" import { - createHook, createWorkflow, transform, when, @@ -41,19 +40,23 @@ export type RefreshCartItemsWorkflowInput = { * These promotion codes will replace previously applied codes. */ promo_codes?: string[] + /** + * Force refresh the cart items + */ + force_refresh?: boolean } export const refreshCartItemsWorkflowId = "refresh-cart-items" /** * This workflow refreshes a cart to ensure its prices, promotion codes, taxes, and other details are applied correctly. It's useful * after making a chnge to a cart, such as after adding an item to the cart or adding a promotion code. - * + * * This workflow is used by other cart-related workflows, such as the {@link addToCartWorkflow} after an item * is added to the cart. - * + * * You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart after making updates to it in your * custom flows. - * + * * @example * const { result } = await refreshCartItemsWorkflow(container) * .run({ @@ -61,37 +64,34 @@ export const refreshCartItemsWorkflowId = "refresh-cart-items" * cart_id: "cart_123", * } * }) - * + * * @summary - * + * * Refresh a cart's details after an update. - * - * @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution. + * */ export const refreshCartItemsWorkflow = createWorkflow( refreshCartItemsWorkflowId, - ( - input: WorkflowData - ) => { - const cart = useRemoteQueryStep({ - entry_point: "cart", - fields: cartFieldsForRefreshSteps, - variables: { id: input.cart_id }, - list: false, - }) - - const variantIds = transform({ cart }, (data) => { - return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean) - }) - - const cartPricingContext = transform({ cart }, ({ cart }) => { - return filterObjectByKeys(cart, cartFieldsForPricingContext) - }) - - const variants = when({ variantIds }, ({ variantIds }) => { - return !!variantIds.length + (input: WorkflowData) => { + when({ input }, ({ input }) => { + return !!input.force_refresh }).then(() => { - return useRemoteQueryStep({ + const cart = useRemoteQueryStep({ + entry_point: "cart", + fields: cartFieldsForRefreshSteps, + variables: { id: input.cart_id }, + list: false, + }) + + const variantIds = transform({ cart }, (data) => { + return (data.cart.items ?? []).map((i) => i.variant_id).filter(Boolean) + }) + + const cartPricingContext = transform({ cart }, ({ cart }) => { + return filterObjectByKeys(cart, cartFieldsForPricingContext) + }) + + const variants = useRemoteQueryStep({ entry_point: "variants", fields: productVariantsFields, variables: { @@ -101,62 +101,59 @@ export const refreshCartItemsWorkflow = createWorkflow( }, }, }).config({ name: "fetch-variants" }) - }) - validateVariantPricesStep({ variants }) + validateVariantPricesStep({ variants }) - const validate = createHook("validate", { - input, - cart, - }) + const lineItems = transform({ cart, variants }, ({ cart, variants }) => { + const items = cart.items.map((item) => { + const variant = (variants ?? []).find( + (v) => v.id === item.variant_id + )! - const lineItems = transform({ cart, variants }, ({ cart, variants }) => { - const items = cart.items.map((item) => { - const variant = (variants ?? []).find((v) => v.id === item.variant_id)! + const input: PrepareLineItemDataInput = { + item, + variant: variant, + cartId: cart.id, + unitPrice: item.unit_price, + isTaxInclusive: item.is_tax_inclusive, + } - const input: PrepareLineItemDataInput = { - item, - variant: variant, - cartId: cart.id, - unitPrice: item.unit_price, - isTaxInclusive: item.is_tax_inclusive, - } + if (variant && !item.is_custom_price) { + input.unitPrice = variant.calculated_price?.calculated_amount + input.isTaxInclusive = + variant.calculated_price?.is_calculated_price_tax_inclusive + } - if (variant && !item.is_custom_price) { - input.unitPrice = variant.calculated_price?.calculated_amount - input.isTaxInclusive = - variant.calculated_price?.is_calculated_price_tax_inclusive - } + const preparedItem = prepareLineItemData(input) - const preparedItem = prepareLineItemData(input) + return { + selector: { id: item.id }, + data: preparedItem, + } + }) - return { - selector: { id: item.id }, - data: preparedItem, - } + return items }) - return items - }) - - updateLineItemsStep({ - id: cart.id, - items: lineItems, + updateLineItemsStep({ + id: cart.id, + items: lineItems, + }) }) const refetchedCart = useRemoteQueryStep({ entry_point: "cart", fields: cartFieldsForRefreshSteps, - variables: { id: cart.id }, + variables: { id: input.cart_id }, list: false, }).config({ name: "refetch–cart" }) refreshCartShippingMethodsWorkflow.runAsStep({ - input: { cart_id: cart.id }, + input: { cart_id: input.cart_id }, }) updateTaxLinesWorkflow.runAsStep({ - input: { cart_id: cart.id }, + input: { cart_id: input.cart_id }, }) const cartPromoCodes = transform( @@ -172,18 +169,16 @@ export const refreshCartItemsWorkflow = createWorkflow( updateCartPromotionsWorkflow.runAsStep({ input: { - cart_id: cart.id, + cart_id: input.cart_id, promo_codes: cartPromoCodes, action: PromotionActions.REPLACE, }, }) refreshPaymentCollectionForCartWorkflow.runAsStep({ - input: { cart_id: cart.id }, + input: { cart: refetchedCart }, }) - return new WorkflowResponse(refetchedCart, { - hooks: [validate], - }) + return new WorkflowResponse(refetchedCart) } ) diff --git a/packages/core/core-flows/src/cart/workflows/refresh-payment-collection.ts b/packages/core/core-flows/src/cart/workflows/refresh-payment-collection.ts index e873df8202..6eb15c0295 100644 --- a/packages/core/core-flows/src/cart/workflows/refresh-payment-collection.ts +++ b/packages/core/core-flows/src/cart/workflows/refresh-payment-collection.ts @@ -19,22 +19,26 @@ export type RefreshPaymentCollectionForCartWorklowInput = { /** * The cart's ID. */ - cart_id: string + cart_id?: string + /** + * The Cart reference. + */ + cart?: any } export const refreshPaymentCollectionForCartWorkflowId = "refresh-payment-collection-for-cart" /** * This workflow refreshes a cart's payment collection, which is useful once the cart is created or when its details - * are updated. If the cart's total changes to the amount in its payment collection, the payment collection's payment sessions are + * are updated. If the cart's total changes to the amount in its payment collection, the payment collection's payment sessions are * deleted. It also syncs the payment collection's amount, currency code, and other details with the details in the cart. - * + * * This workflow is used by other cart-related workflows, such as the {@link refreshCartItemsWorkflow} to refresh the cart's * payment collection after an update. - * + * * You can use this workflow within your own customizations or custom workflows, allowing you to refresh the cart's payment collection after making updates to it in your * custom flows. - * + * * @example * const { result } = await refreshPaymentCollectionForCartWorkflow(container) * .run({ @@ -42,33 +46,41 @@ export const refreshPaymentCollectionForCartWorkflowId = * cart_id: "cart_123", * } * }) - * + * * @summary - * + * * Refresh a cart's payment collection details. - * + * * @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution. */ export const refreshPaymentCollectionForCartWorkflow = createWorkflow( refreshPaymentCollectionForCartWorkflowId, (input: WorkflowData) => { - const cart = useRemoteQueryStep({ - entry_point: "cart", - fields: [ - "id", - "region_id", - "currency_code", - "total", - "raw_total", - "payment_collection.id", - "payment_collection.raw_amount", - "payment_collection.amount", - "payment_collection.currency_code", - "payment_collection.payment_sessions.id", - ], - variables: { id: input.cart_id }, - throw_if_key_not_found: true, - list: false, + const fetchCart = when({ input }, ({ input }) => { + return !input.cart + }).then(() => { + return useRemoteQueryStep({ + entry_point: "cart", + fields: [ + "id", + "region_id", + "currency_code", + "total", + "raw_total", + "payment_collection.id", + "payment_collection.raw_amount", + "payment_collection.amount", + "payment_collection.currency_code", + "payment_collection.payment_sessions.id", + ], + variables: { id: input.cart_id }, + throw_if_key_not_found: true, + list: false, + }) + }) + + const cart = transform({ fetchCart, input }, ({ fetchCart, input }) => { + return input.cart ?? fetchCart }) const validate = createHook("validate", { diff --git a/packages/core/core-flows/src/cart/workflows/transfer-cart-customer.ts b/packages/core/core-flows/src/cart/workflows/transfer-cart-customer.ts index afe1e24220..1b6765a2fc 100644 --- a/packages/core/core-flows/src/cart/workflows/transfer-cart-customer.ts +++ b/packages/core/core-flows/src/cart/workflows/transfer-cart-customer.ts @@ -13,11 +13,11 @@ import { refreshCartItemsWorkflow } from "./refresh-cart-items" /** * The cart ownership transfer details. */ -export type TransferCartCustomerWorkflowInput = { +export type TransferCartCustomerWorkflowInput = { /** * The cart's ID. */ - id: string; + id: string /** * The ID of the customer to transfer the cart to. */ @@ -29,9 +29,9 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer" * This workflow transfers a cart's customer ownership to another customer. It's useful if a customer logs in after * adding the items to their cart, allowing you to transfer the cart's ownership to the logged-in customer. This workflow is used * by the [Set Cart's Customer Store API Route](https://docs.medusajs.com/api/store#carts_postcartsidcustomer). - * + * * You can use this workflow within your own customizations or custom workflows, allowing you to set the cart's customer within your custom flows. - * + * * @example * const { result } = await transferCartCustomerWorkflow(container) * .run({ @@ -40,11 +40,11 @@ export const transferCartCustomerWorkflowId = "transfer-cart-customer" * customer_id: "cus_123" * } * }) - * + * * @summary - * + * * Refresh a cart's payment collection details. - * + * * @property hooks.validate - This hook is executed before all operations. You can consume this hook to perform any custom validation. If validation fails, you can throw an error to stop the workflow execution. */ export const transferCartCustomerWorkflow = createWorkflow( @@ -108,7 +108,7 @@ export const transferCartCustomerWorkflow = createWorkflow( updateCartsStep(cartInput) refreshCartItemsWorkflow.runAsStep({ - input: { cart_id: input.id }, + input: { cart_id: input.id, force_refresh: true }, }) } ) diff --git a/packages/core/core-flows/src/cart/workflows/update-cart.ts b/packages/core/core-flows/src/cart/workflows/update-cart.ts index dfe2a64e27..487603d028 100644 --- a/packages/core/core-flows/src/cart/workflows/update-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/update-cart.ts @@ -27,8 +27,8 @@ import { findSalesChannelStep, updateCartsStep, } from "../steps" -import { refreshCartItemsWorkflow } from "./refresh-cart-items" import { validateSalesChannelStep } from "../steps/validate-sales-channel" +import { refreshCartItemsWorkflow } from "./refresh-cart-items" /** * The data to update the cart, along with custom data that's passed to the workflow's hooks. @@ -278,7 +278,11 @@ export const updateCartWorkflow = createWorkflow( }) const cart = refreshCartItemsWorkflow.runAsStep({ - input: { cart_id: cartInput.id, promo_codes: input.promo_codes }, + input: { + cart_id: cartInput.id, + promo_codes: input.promo_codes, + force_refresh: !!newRegion, + }, }) const cartUpdated = createHook("cartUpdated", {