From 462c3e8057029fa1772a861a6a245d04d4770af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frane=20Poli=C4=87?= <16856471+fPolic@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:10:08 +0100 Subject: [PATCH] feat(core-flows): cart complete shipping validate (#10984) **What** - validate that there is a shipping method if any of the line items have requires_shipping=true - validate that products shipping profile is supported by a shipping method on the cart - update tests --- CLOSES CMRC-683 --- .../http/__tests__/cart/store/cart.spec.ts | 436 ++++++++++++++---- .../http/__tests__/fixtures/order.ts | 78 ++-- .../http/__tests__/order/admin/order.spec.ts | 14 +- .../cart/store/cart.workflows.spec.ts | 5 +- .../__tests__/cart/store/carts.spec.ts | 1 + .../core/core-flows/src/cart/steps/index.ts | 3 +- .../src/cart/steps/validate-shipping.ts | 69 +++ .../core/core-flows/src/cart/utils/fields.ts | 1 + .../src/cart/workflows/complete-cart.ts | 23 +- 9 files changed, 512 insertions(+), 118 deletions(-) create mode 100644 packages/core/core-flows/src/cart/steps/validate-shipping.ts diff --git a/integration-tests/http/__tests__/cart/store/cart.spec.ts b/integration-tests/http/__tests__/cart/store/cart.spec.ts index 2ad8995c56..7968985cf9 100644 --- a/integration-tests/http/__tests__/cart/store/cart.spec.ts +++ b/integration-tests/http/__tests__/cart/store/cart.spec.ts @@ -96,7 +96,11 @@ medusaIntegrationTestRunner({ ).data.region product = ( - await api.post("/admin/products", { ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id }, adminHeaders) + await api.post( + "/admin/products", + { ...medusaTshirtProduct, shipping_profile_id: shippingProfile.id }, + adminHeaders + ) ).data.product salesChannel = ( @@ -995,87 +999,89 @@ medusaIntegrationTestRunner({ }) describe("POST /store/carts/:id/complete", () => { - beforeEach(async () => { - cart = ( - await api.post( - `/store/carts`, - { - currency_code: "usd", - sales_channel_id: salesChannel.id, - region_id: region.id, - shipping_address: shippingAddressData, - items: [{ variant_id: product.variants[0].id, quantity: 1 }], - promo_codes: [promotion.code], - }, - storeHeadersWithCustomer - ) - ).data.cart - - const paymentCollection = ( - await api.post( - `/store/payment-collections`, - { cart_id: cart.id }, - storeHeaders - ) - ).data.payment_collection - - await api.post( - `/store/payment-collections/${paymentCollection.id}/payment-sessions`, - { provider_id: "pp_system_default" }, - storeHeaders - ) - }) - - it("should successfully complete cart", async () => { - const response = await api.post( - `/store/carts/${cart.id}/complete`, - {}, - storeHeaders - ) - - expect(response.status).toEqual(200) - expect(response.data.order).toEqual( - expect.objectContaining({ - id: expect.any(String), - currency_code: "usd", - items: expect.arrayContaining([ - expect.objectContaining({ - unit_price: 1500, - compare_at_unit_price: null, - quantity: 1, - }), - ]), - }) - ) - }) - - describe("with sale price lists", () => { - let priceList - + describe("should successfully complete cart", () => { beforeEach(async () => { - priceList = ( + const stockLocation = ( await api.post( - `/admin/price-lists`, + `/admin/stock-locations`, + { name: "test location" }, + adminHeaders + ) + ).data.stock_location + + await api.post( + `/admin/stock-locations/${stockLocation.id}/sales-channels`, + { add: [salesChannel.id] }, + adminHeaders + ) + + const fulfillmentSets = ( + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`, { - title: "test price list", - description: "test", - status: PriceListStatus.ACTIVE, - type: PriceListType.SALE, - prices: [ - { - amount: 350, - currency_code: "usd", - variant_id: product.variants[0].id, - }, - ], + name: `Test-${shippingProfile.id}`, + type: "test-type", }, adminHeaders ) - ).data.price_list + ).data.stock_location.fulfillment_sets + + const fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`, + { + name: `Test-${shippingProfile.id}`, + geo_zones: [{ type: "country", country_code: "US" }], + }, + adminHeaders + ) + ).data.fulfillment_set await api.post( - `/store/carts/${cart.id}/line-items`, - { variant_id: product.variants[0].id, quantity: 1 }, + `/admin/stock-locations/${stockLocation.id}/fulfillment-providers`, + { add: ["manual_test-provider"] }, + adminHeaders + ) + + const shippingOption = ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + 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", + }, + prices: [{ currency_code: "usd", amount: 1000 }], + rules: [], + }, + adminHeaders + ) + ).data.shipping_option + + cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + + await api.post( + `/store/carts/${cart.id}/shipping-methods`, + { option_id: shippingOption.id }, storeHeaders ) @@ -1094,27 +1100,297 @@ medusaIntegrationTestRunner({ ) }) - it("should add price from price list and set compare_at_unit_price for order item", async () => { + it("should successfully complete cart", async () => { const response = await api.post( `/store/carts/${cart.id}/complete`, - { variant_id: product.variants[0].id, quantity: 1 }, + {}, storeHeaders ) expect(response.status).toEqual(200) expect(response.data.order).toEqual( expect.objectContaining({ + id: expect.any(String), + currency_code: "usd", items: expect.arrayContaining([ expect.objectContaining({ - unit_price: 350, - compare_at_unit_price: 1500, - is_tax_inclusive: true, - quantity: 2, + unit_price: 1500, + compare_at_unit_price: null, + quantity: 1, }), ]), }) ) }) + + describe("with sale price lists", () => { + let priceList + + beforeEach(async () => { + priceList = ( + await api.post( + `/admin/price-lists`, + { + title: "test price list", + description: "test", + status: PriceListStatus.ACTIVE, + type: PriceListType.SALE, + prices: [ + { + amount: 350, + currency_code: "usd", + variant_id: product.variants[0].id, + }, + ], + }, + adminHeaders + ) + ).data.price_list + + await api.post( + `/store/carts/${cart.id}/line-items`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + await api.post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + }) + + it("should add price from price list and set compare_at_unit_price for order item", async () => { + const response = await api.post( + `/store/carts/${cart.id}/complete`, + { variant_id: product.variants[0].id, quantity: 1 }, + storeHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.order).toEqual( + expect.objectContaining({ + items: expect.arrayContaining([ + expect.objectContaining({ + unit_price: 350, + compare_at_unit_price: 1500, + is_tax_inclusive: true, + quantity: 2, + }), + ]), + }) + ) + }) + }) + }) + + describe("shipping validation", () => { + it("should fail to complete the cart if no shipping method is selected and items require shipping", async () => { + const cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + await api.post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + + const response = await api + .post(`/store/carts/${cart.id}/complete`, {}, storeHeaders) + .catch((e) => e) + + expect(response.response.status).toEqual(400) + expect(response.response.data.message).toEqual( + "No shipping method selected but the cart contains items that require shipping." + ) + }) + + it("should fail to complete the cart if the shipping profile of a product is not supported by the shipping method", async () => { + const stockLocation = ( + await api.post( + `/admin/stock-locations`, + { name: "test location" }, + adminHeaders + ) + ).data.stock_location + + await api.post( + `/admin/stock-locations/${stockLocation.id}/sales-channels`, + { add: [salesChannel.id] }, + adminHeaders + ) + + const shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: `test-${stockLocation.id}`, type: "default" }, + adminHeaders + ) + ).data.shipping_profile + + const fulfillmentSets = ( + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`, + { + name: `Test-${shippingProfile.id}`, + type: "test-type", + }, + adminHeaders + ) + ).data.stock_location.fulfillment_sets + + const fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`, + { + name: `Test-${shippingProfile.id}`, + geo_zones: [{ type: "country", country_code: "US" }], + }, + adminHeaders + ) + ).data.fulfillment_set + + await api.post( + `/admin/stock-locations/${stockLocation.id}/fulfillment-providers`, + { add: ["manual_test-provider"] }, + adminHeaders + ) + + const shippingOption = ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + 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", + }, + prices: [{ currency_code: "usd", amount: 1000 }], + rules: [], + }, + adminHeaders + ) + ).data.shipping_option + + const specialShippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: "special-shipping-profile", type: "special" }, + adminHeaders + ) + ).data.shipping_profile + + const product = ( + await api.post( + `/admin/products`, + { + title: "test product", + description: "test", + options: [ + { + title: "Size", + values: ["S", "M", "L", "XL"], + }, + ], + variants: [ + { + title: "S / Black", + sku: "special-shirt", + options: { + Size: "S", + }, + manage_inventory: false, + prices: [ + { + amount: 1500, + currency_code: "usd", + }, + ], + }, + ], + shipping_profile_id: specialShippingProfile.id, // --> product has a different shipping profile than + }, + adminHeaders + ) + ).data.product + + const cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + sales_channel_id: salesChannel.id, + region_id: region.id, + shipping_address: shippingAddressData, + items: [{ variant_id: product.variants[0].id, quantity: 1 }], + promo_codes: [promotion.code], + }, + storeHeadersWithCustomer + ) + ).data.cart + + await api.post( + `/store/carts/${cart.id}/shipping-methods`, + { option_id: shippingOption.id }, + storeHeaders + ) + + const paymentCollection = ( + await api.post( + `/store/payment-collections`, + { cart_id: cart.id }, + storeHeaders + ) + ).data.payment_collection + + await api.post( + `/store/payment-collections/${paymentCollection.id}/payment-sessions`, + { provider_id: "pp_system_default" }, + storeHeaders + ) + + const response = await api + .post(`/store/carts/${cart.id}/complete`, {}, storeHeaders) + .catch((e) => e) + + expect(response.response.status).toEqual(400) + expect(response.response.data.message).toEqual( + "The cart items require shipping profiles that are not satisfied by the current shipping methods" + ) + }) }) }) diff --git a/integration-tests/http/__tests__/fixtures/order.ts b/integration-tests/http/__tests__/fixtures/order.ts index 7b4c7ca31e..8cf6c2c8a7 100644 --- a/integration-tests/http/__tests__/fixtures/order.ts +++ b/integration-tests/http/__tests__/fixtures/order.ts @@ -29,11 +29,17 @@ export async function createOrderSeeder({ stockChannelOverride?: AdminStockLocation additionalProducts?: { variant_id: string; quantity: number }[] inventoryItemOverride?: AdminInventoryItem - shippingProfileOverride?: AdminShippingProfile + shippingProfileOverride?: AdminShippingProfile | AdminShippingProfile[] withoutShipping?: boolean }) { const publishableKey = await generatePublishableKey(container) + const shippingProfileOverrideArray = !shippingProfileOverride + ? undefined + : Array.isArray(shippingProfileOverride) + ? shippingProfileOverride + : [shippingProfileOverride] + const storeHeaders = storeHeaderOverride ?? generateStoreHeaders({ @@ -92,7 +98,7 @@ export async function createOrderSeeder({ ) const shippingProfile = - shippingProfileOverride ?? + shippingProfileOverrideArray?.[0] ?? ( await api.post( `/admin/shipping-profiles`, @@ -168,29 +174,38 @@ export async function createOrderSeeder({ adminHeaders ) - const shippingOption = ( - await api.post( - `/admin/shipping-options`, - { - name: `Test shipping option ${fulfillmentSet.id}`, - 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", - }, - prices: [ - { currency_code: "usd", amount: 1000 }, - { region_id: region.id, amount: 1100 }, - ], - rules: [], - }, - adminHeaders - ) - ).data.shipping_option + /** + * Create shipping options for each shipping profile provided + */ + const shippingOptions = await Promise.all( + (shippingProfileOverrideArray || [shippingProfile]).map(async (sp) => { + return ( + await api.post( + `/admin/shipping-options`, + { + name: `Test shipping option ${fulfillmentSet.id}`, + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: sp.id, + provider_id: "manual_test-provider", + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [ + { currency_code: "usd", amount: 1000 }, + { region_id: region.id, amount: 1100 }, + ], + rules: [], + }, + adminHeaders + ) + ).data.shipping_option + }) + ) + + const shippingOption = shippingOptions[0] const cart = ( await api.post( @@ -226,10 +241,15 @@ export async function createOrderSeeder({ ).data.cart if (!withoutShipping) { - await api.post( - `/store/carts/${cart.id}/shipping-methods`, - { option_id: shippingOption.id }, - storeHeaders + // Create shipping methods for each shipping option so shipping profiles of products in the cart are supported + await Promise.all( + shippingOptions.map(async (so) => { + await api.post( + `/store/carts/${cart.id}/shipping-methods`, + { option_id: so.id }, + storeHeaders + ) + }) ) } diff --git a/integration-tests/http/__tests__/order/admin/order.spec.ts b/integration-tests/http/__tests__/order/admin/order.spec.ts index 1ffb6e02ca..6e0e5a6280 100644 --- a/integration-tests/http/__tests__/order/admin/order.spec.ts +++ b/integration-tests/http/__tests__/order/admin/order.spec.ts @@ -798,7 +798,7 @@ medusaIntegrationTestRunner({ ], stockChannelOverride, inventoryItemOverride, - shippingProfileOverride: shippingProfile, + shippingProfileOverride: [shippingProfile, shippingProfileOverride], }) order = seeder.order order = (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data @@ -932,6 +932,7 @@ medusaIntegrationTestRunner({ .post( `/admin/orders/${order.id}/fulfillments`, { + shipping_option_id: seeder.shippingOption.id, // shipping option with the "regular" shipping profile location_id: stockChannelOverride.id, items: [{ id: orderItemId, quantity: 1 }], }, @@ -946,6 +947,9 @@ medusaIntegrationTestRunner({ }) it("should only create fulfillments grouped by shipping requirement", async () => { + const item1Id = order.items.find((i) => i.requires_shipping).id + const item2Id = order.items.find((i) => !i.requires_shipping).id + const { response: { data }, } = await api @@ -955,11 +959,11 @@ medusaIntegrationTestRunner({ location_id: seeder.stockLocation.id, items: [ { - id: order.items[0].id, + id: item1Id, quantity: 1, }, { - id: order.items[1].id, + id: item2Id, quantity: 1, }, ], @@ -979,7 +983,7 @@ medusaIntegrationTestRunner({ `/admin/orders/${order.id}/fulfillments?fields=+fulfillments.id,fulfillments.requires_shipping`, { location_id: seeder.stockLocation.id, - items: [{ id: order.items[0].id, quantity: 1 }], + items: [{ id: item1Id, quantity: 1 }], }, adminHeaders ) @@ -992,7 +996,7 @@ medusaIntegrationTestRunner({ `/admin/orders/${order.id}/fulfillments?fields=+fulfillments.id,fulfillments.requires_shipping`, { location_id: seeder.stockLocation.id, - items: [{ id: order.items[1].id, quantity: 1 }], + items: [{ id: item2Id, quantity: 1 }], }, adminHeaders ) 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 1c4c3e0faf..ff3265f297 100644 --- a/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts @@ -266,6 +266,7 @@ medusaIntegrationTestRunner({ const inventoryItem = await inventoryModule.createInventoryItems({ sku: "inv-1234", + requires_shipping: false, }) await inventoryModule.createInventoryLevels([ @@ -743,7 +744,7 @@ medusaIntegrationTestRunner({ title: "Test item", subtitle: "Test subtitle", thumbnail: "some-url", - requires_shipping: true, + requires_shipping: false, is_discountable: false, is_tax_inclusive: false, unit_price: 3000, @@ -825,7 +826,7 @@ medusaIntegrationTestRunner({ precision: 20, value: "3000", }, - requires_shipping: true, + requires_shipping: false, subtitle: "Test subtitle", thumbnail: "some-url", title: "Test item", diff --git a/integration-tests/modules/__tests__/cart/store/carts.spec.ts b/integration-tests/modules/__tests__/cart/store/carts.spec.ts index a09050380c..1a33af58e9 100644 --- a/integration-tests/modules/__tests__/cart/store/carts.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/carts.spec.ts @@ -1161,6 +1161,7 @@ medusaIntegrationTestRunner({ `/admin/inventory-items`, { sku: "12345", + requires_shipping: false, }, adminHeaders ) diff --git a/packages/core/core-flows/src/cart/steps/index.ts b/packages/core/core-flows/src/cart/steps/index.ts index df002d717d..3f988d7ef0 100644 --- a/packages/core/core-flows/src/cart/steps/index.ts +++ b/packages/core/core-flows/src/cart/steps/index.ts @@ -30,4 +30,5 @@ export * from "./validate-variant-prices" export * from "./validate-cart" export * from "./validate-line-item-prices" export * from "./validate-shipping-methods-data" -export * from "./validate-shipping-options-price" \ No newline at end of file +export * from "./validate-shipping-options-price" +export * from "./validate-shipping" diff --git a/packages/core/core-flows/src/cart/steps/validate-shipping.ts b/packages/core/core-flows/src/cart/steps/validate-shipping.ts new file mode 100644 index 0000000000..d9a8f62cb0 --- /dev/null +++ b/packages/core/core-flows/src/cart/steps/validate-shipping.ts @@ -0,0 +1,69 @@ +import { + CartLineItemDTO, + CartWorkflowDTO, + ProductVariantDTO, + ShippingOptionDTO, +} from "@medusajs/types" +import { createStep, StepResponse } from "@medusajs/workflows-sdk" + +import { MedusaError } from "../../../../utils/dist/common" + +export type ValidateShippingInput = { + cart: Omit & { + items: (CartLineItemDTO & { + variant: ProductVariantDTO + })[] + } + shippingOptions: ShippingOptionDTO[] +} + +export const validateShippingStepId = "validate-shipping" +/** + * This step validates shipping data when cart is completed. + * + * It ensures that a shipping method is selected if there is an item in the cart that requires shipping. + * It also ensures that product's shipping profile mathes the selected shipping options. + */ +export const validateShippingStep = createStep( + validateShippingStepId, + async (data: ValidateShippingInput) => { + const { cart, shippingOptions } = data + + const optionProfileMap: Map = new Map( + shippingOptions.map((option) => [option.id, option.shipping_profile_id]) + ) + + const cartItemsWithShipping = + cart.items?.filter((item) => item.requires_shipping) || [] + + const cartShippingMethods = cart.shipping_methods || [] + + if (cartItemsWithShipping.length > 0 && cartShippingMethods.length === 0) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "No shipping method selected but the cart contains items that require shipping." + ) + } + + const requiredShippingPorfiles = cartItemsWithShipping.map( + (item) => (item.variant.product as any)?.shipping_profile?.id + ) + + const availableShippingPorfiles = cartShippingMethods.map((method) => + optionProfileMap.get(method.shipping_option_id!) + ) + + const missingShippingPorfiles = requiredShippingPorfiles.filter( + (profile) => !availableShippingPorfiles.includes(profile) + ) + + if (missingShippingPorfiles.length > 0) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "The cart items require shipping profiles that are not satisfied by the current shipping methods" + ) + } + + return new StepResponse(void 0) + } +) diff --git a/packages/core/core-flows/src/cart/utils/fields.ts b/packages/core/core-flows/src/cart/utils/fields.ts index 27eb63991b..44e89ef6b5 100644 --- a/packages/core/core-flows/src/cart/utils/fields.ts +++ b/packages/core/core-flows/src/cart/utils/fields.ts @@ -99,6 +99,7 @@ export const completeCartFields = [ "payment_collection.payment_sessions.*", "items.variant.id", "items.variant.product.id", + "items.variant.product.shipping_profile.id", "items.variant.manage_inventory", "items.variant.allow_backorder", "items.variant.inventory_items.inventory_item_id", diff --git a/packages/core/core-flows/src/cart/workflows/complete-cart.ts b/packages/core/core-flows/src/cart/workflows/complete-cart.ts index 909e71bd4b..40089b61d8 100644 --- a/packages/core/core-flows/src/cart/workflows/complete-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/complete-cart.ts @@ -25,7 +25,11 @@ import { import { createOrdersStep } from "../../order/steps/create-orders" import { authorizePaymentSessionStep } from "../../payment/steps/authorize-payment-session" import { registerUsageStep } from "../../promotion/steps/register-usage" -import { updateCartsStep, validateCartPaymentsStep } from "../steps" +import { + updateCartsStep, + validateCartPaymentsStep, + validateShippingStep, +} from "../steps" import { reserveInventoryStep } from "../steps/reserve-inventory" import { completeCartFields } from "../utils/fields" import { prepareConfirmInventoryInput } from "../utils/prepare-confirm-inventory-input" @@ -101,6 +105,8 @@ export const completeCartWorkflow = createWorkflow( fields: completeCartFields, variables: { id: input.id }, list: false, + }).config({ + name: "cart-query", }) const validate = createHook("validate", { @@ -112,6 +118,21 @@ export const completeCartWorkflow = createWorkflow( const order = when("create-order", { orderId }, ({ orderId }) => { return !orderId }).then(() => { + const cartOptionIds = transform({ cart }, ({ cart }) => { + return cart.shipping_methods?.map((sm) => sm.shipping_option_id) + }) + + const shippingOptions = useRemoteQueryStep({ + entry_point: "shipping_option", + fields: ["id", "shipping_profile_id"], + variables: { id: cartOptionIds }, + list: true, + }).config({ + name: "shipping-options-query", + }) + + validateShippingStep({ cart, shippingOptions }) + const paymentSessions = validateCartPaymentsStep({ cart }) const payment = authorizePaymentSessionStep({