fix(medusa): sales_channel_id middleware manipulation (#12234)
* fix(medusa): sales_channel_id middleware manipulation leading to lost of the sc * fix(medusa): sales_channel_id middleware manipulation leading to lost of the sc * add unit tests * add unit tests * improve * integration tests
This commit is contained in:
committed by
GitHub
parent
a8a7af46a6
commit
49c526399e
@@ -1478,7 +1478,7 @@ medusaIntegrationTestRunner({
|
||||
describe("with inventory items", () => {
|
||||
let location1
|
||||
let location2
|
||||
let salesChannel1
|
||||
let salesChannel1, salesChannel2
|
||||
let publishableKey1
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -1503,6 +1503,11 @@ medusaIntegrationTestRunner({
|
||||
[product.id, product2.id]
|
||||
)
|
||||
|
||||
salesChannel2 = await createSalesChannel(
|
||||
{ name: "sales channel test 2" },
|
||||
[product.id, product2.id]
|
||||
)
|
||||
|
||||
const api1Res = await api.post(
|
||||
`/admin/api-keys`,
|
||||
{ title: "Test publishable KEY", type: ApiKeyType.PUBLISHABLE },
|
||||
@@ -1721,6 +1726,63 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
|
||||
it("should list all inventory items for a variant in a given sales channel passed as a query param AND when there are multiple sales channels associated with the publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${publishableKey1.id}/sales-channels`,
|
||||
{ add: [salesChannel2.id] },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
let response = await api.get(
|
||||
`/store/products?sales_channel_id[]=${salesChannel1.id}&fields=variants.inventory_items.inventory.location_levels.*`,
|
||||
{ headers: { "x-publishable-api-key": publishableKey1.token } }
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(2)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product.id,
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_item_id: inventoryItem1.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
inventory_item_id: inventoryItem2.id,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw when multiple sales channels are passed as a query param AND there are multiple sales channels associated with the publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${publishableKey1.id}/sales-channels`,
|
||||
{ add: [salesChannel2.id] },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
let error = await api
|
||||
.get(
|
||||
`/store/products?sales_channel_id[]=${salesChannel1.id}&sales_channel_id[]=${salesChannel2.id}&fields=variants.inventory_quantity`,
|
||||
{ headers: { "x-publishable-api-key": publishableKey1.token } }
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.response.status).toEqual(400)
|
||||
expect(error.response.data).toEqual({
|
||||
message:
|
||||
"Inventory availability cannot be calculated in the given context. Either provide a single sales channel id or configure a single sales channel in the publishable key",
|
||||
type: "invalid_data",
|
||||
})
|
||||
})
|
||||
|
||||
it("should return inventory quantity when variant's manage_inventory is true", async () => {
|
||||
await api.post(
|
||||
`/admin/products/${product.id}/variants/${variant.id}/inventory-items`,
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import { MedusaStoreRequest } from "@medusajs/framework/http"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
import { NextFunction } from "express"
|
||||
import {
|
||||
transformAndValidateSalesChannelIds,
|
||||
filterByValidSalesChannels,
|
||||
} from "../filter-by-valid-sales-channels"
|
||||
|
||||
describe("filter-by-valid-sales-channels", () => {
|
||||
describe("transformAndValidateSalesChannelIds", () => {
|
||||
let req: Partial<MedusaStoreRequest>
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
publishable_key_context: {
|
||||
key: "test-key",
|
||||
sales_channel_ids: ["sc-1", "sc-2"],
|
||||
},
|
||||
validatedQuery: {},
|
||||
}
|
||||
})
|
||||
|
||||
it("should return sales channel ids from request when they exist and are in publishable key", () => {
|
||||
req.validatedQuery = { sales_channel_id: ["sc-1"] }
|
||||
|
||||
const result = transformAndValidateSalesChannelIds(
|
||||
req as MedusaStoreRequest
|
||||
)
|
||||
|
||||
expect(result).toEqual(["sc-1"])
|
||||
})
|
||||
|
||||
it("should handle sales_channel_id as string and transform to array", () => {
|
||||
req.validatedQuery = { sales_channel_id: "sc-2" }
|
||||
|
||||
const result = transformAndValidateSalesChannelIds(
|
||||
req as MedusaStoreRequest
|
||||
)
|
||||
|
||||
expect(result).toEqual(["sc-2"])
|
||||
})
|
||||
|
||||
it("should throw error when requested sales channel is not in publishable key", () => {
|
||||
req.validatedQuery = { sales_channel_id: ["sc-3"] }
|
||||
|
||||
expect(() => {
|
||||
transformAndValidateSalesChannelIds(req as MedusaStoreRequest)
|
||||
}).toThrow(MedusaError)
|
||||
})
|
||||
|
||||
it("should return sales channel ids from publishable key when no ids in request", () => {
|
||||
req.validatedQuery = {}
|
||||
|
||||
const result = transformAndValidateSalesChannelIds(
|
||||
req as MedusaStoreRequest
|
||||
)
|
||||
|
||||
expect(result).toEqual(["sc-1", "sc-2"])
|
||||
})
|
||||
|
||||
it("should return empty array when no sales channel ids in publishable key or request", () => {
|
||||
req.publishable_key_context = {
|
||||
key: "test-key",
|
||||
sales_channel_ids: [],
|
||||
}
|
||||
req.validatedQuery = {}
|
||||
|
||||
const result = transformAndValidateSalesChannelIds(
|
||||
req as MedusaStoreRequest
|
||||
)
|
||||
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("filterByValidSalesChannels", () => {
|
||||
let req: Partial<MedusaStoreRequest>
|
||||
let res: any
|
||||
let next: NextFunction
|
||||
let middleware: ReturnType<typeof filterByValidSalesChannels>
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
publishable_key_context: {
|
||||
key: "test-key",
|
||||
sales_channel_ids: ["sc-1", "sc-2"],
|
||||
},
|
||||
validatedQuery: {},
|
||||
filterableFields: {},
|
||||
}
|
||||
|
||||
res = {}
|
||||
next = jest.fn()
|
||||
middleware = filterByValidSalesChannels()
|
||||
})
|
||||
|
||||
it("should set filterableFields.sales_channel_id and call next", async () => {
|
||||
await middleware(req as MedusaStoreRequest, res, next)
|
||||
|
||||
expect(req.filterableFields!.sales_channel_id).toEqual(["sc-1", "sc-2"])
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should throw error when no sales channels available", async () => {
|
||||
req.publishable_key_context = {
|
||||
key: "test-key",
|
||||
sales_channel_ids: [],
|
||||
}
|
||||
|
||||
await expect(
|
||||
middleware(req as MedusaStoreRequest, res, next)
|
||||
).rejects.toThrow(
|
||||
"Publishable key needs to have a sales channel configured"
|
||||
)
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should use only sales channels from request that are in publishable key", async () => {
|
||||
req.validatedQuery = { sales_channel_id: ["sc-1"] }
|
||||
|
||||
await middleware(req as MedusaStoreRequest, res, next)
|
||||
|
||||
expect(req.filterableFields!.sales_channel_id).toEqual(["sc-1"])
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle sales_channel_id as string in request", async () => {
|
||||
req.validatedQuery = { sales_channel_id: "sc-2" }
|
||||
|
||||
await middleware(req as MedusaStoreRequest, res, next)
|
||||
|
||||
expect(req.filterableFields!.sales_channel_id).toEqual(["sc-2"])
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
deepCopy,
|
||||
getTotalVariantAvailability,
|
||||
getVariantAvailability,
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { MedusaRequest, MedusaStoreRequest } from "@medusajs/framework/http"
|
||||
import {
|
||||
wrapVariantsWithTotalInventoryQuantity,
|
||||
wrapVariantsWithInventoryQuantityForSalesChannel,
|
||||
} from "../variant-inventory-quantity"
|
||||
|
||||
jest.mock("@medusajs/framework/utils", () => {
|
||||
const originalModule = jest.requireActual("@medusajs/framework/utils")
|
||||
return {
|
||||
...originalModule,
|
||||
getTotalVariantAvailability: jest.fn(),
|
||||
getVariantAvailability: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe("variant-inventory-quantity", () => {
|
||||
let req
|
||||
let mockQuery
|
||||
let variants
|
||||
|
||||
beforeEach(() => {
|
||||
mockQuery = jest.fn()
|
||||
variants = [
|
||||
{ id: "variant-1", manage_inventory: true },
|
||||
{ id: "variant-2", manage_inventory: true },
|
||||
{ id: "variant-3", manage_inventory: false },
|
||||
]
|
||||
|
||||
req = {
|
||||
scope: {
|
||||
resolve: jest.fn().mockReturnValue(mockQuery),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("wrapVariantsWithTotalInventoryQuantity", () => {
|
||||
it("should not call getTotalVariantAvailability when variants array is empty", async () => {
|
||||
await wrapVariantsWithTotalInventoryQuantity(req as MedusaRequest, [])
|
||||
|
||||
expect(getTotalVariantAvailability).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should call getTotalVariantAvailability with correct parameters", async () => {
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 10 },
|
||||
"variant-2": { availability: 5 },
|
||||
}
|
||||
|
||||
;(getTotalVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithTotalInventoryQuantity(
|
||||
req as MedusaRequest,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(req.scope.resolve).toHaveBeenCalledWith(
|
||||
ContainerRegistrationKeys.QUERY
|
||||
)
|
||||
expect(getTotalVariantAvailability).toHaveBeenCalledWith(mockQuery, {
|
||||
variant_ids: ["variant-1", "variant-2", "variant-3"],
|
||||
})
|
||||
})
|
||||
|
||||
it("should update inventory_quantity for variants with manage_inventory=true", async () => {
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 10 },
|
||||
"variant-2": { availability: 5 },
|
||||
"variant-3": { availability: 20 },
|
||||
}
|
||||
|
||||
;(getTotalVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithTotalInventoryQuantity(
|
||||
req as MedusaRequest,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(variants[0].inventory_quantity).toBe(10)
|
||||
expect(variants[1].inventory_quantity).toBe(5)
|
||||
expect(variants[2].inventory_quantity).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("wrapVariantsWithInventoryQuantityForSalesChannel", () => {
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
scope: {
|
||||
resolve: jest.fn().mockReturnValue(mockQuery),
|
||||
},
|
||||
publishable_key_context: {
|
||||
sales_channel_ids: ["sc-1"],
|
||||
},
|
||||
validatedQuery: {},
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw an error when multiple sales channels are available and no single one is specified", async () => {
|
||||
req.publishable_key_context.sales_channel_ids = ["sc-1", "sc-2"]
|
||||
req.validatedQuery = { sales_channel_id: ["sc-1", "sc-2"] }
|
||||
|
||||
await expect(
|
||||
wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
variants
|
||||
)
|
||||
).rejects.toThrow(MedusaError)
|
||||
})
|
||||
|
||||
it("should use sales channel from query when single channel is specified", async () => {
|
||||
req.validatedQuery = { sales_channel_id: ["sc-2"] }
|
||||
req.publishable_key_context = {
|
||||
key: "test-key",
|
||||
sales_channel_ids: ["sc-1", "sc-2"],
|
||||
}
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 7 },
|
||||
"variant-2": { availability: 3 },
|
||||
}
|
||||
|
||||
;(getVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(getVariantAvailability).toHaveBeenCalledWith(mockQuery, {
|
||||
variant_ids: ["variant-1", "variant-2", "variant-3"],
|
||||
sales_channel_id: "sc-2",
|
||||
})
|
||||
})
|
||||
|
||||
it("should use sales channel from publishable key when single channel is available", async () => {
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 12 },
|
||||
"variant-2": { availability: 8 },
|
||||
}
|
||||
|
||||
;(getVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(getVariantAvailability).toHaveBeenCalledWith(mockQuery, {
|
||||
variant_ids: ["variant-1", "variant-2", "variant-3"],
|
||||
sales_channel_id: "sc-1",
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle non-array sales_channel_id in query", async () => {
|
||||
req.validatedQuery = { sales_channel_id: "sc-2" }
|
||||
|
||||
const originalPublishableKeyContext = deepCopy(
|
||||
req.publishable_key_context
|
||||
)
|
||||
req.publishable_key_context = {
|
||||
key: "test-key",
|
||||
sales_channel_ids: ["sc-1", "sc-2"],
|
||||
}
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 7 },
|
||||
"variant-2": { availability: 3 },
|
||||
}
|
||||
|
||||
;(getVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(getVariantAvailability).toHaveBeenCalledWith(mockQuery, {
|
||||
variant_ids: ["variant-1", "variant-2", "variant-3"],
|
||||
sales_channel_id: "sc-2",
|
||||
})
|
||||
|
||||
req.publishable_key_context = originalPublishableKeyContext
|
||||
})
|
||||
|
||||
it("should update inventory_quantity for variants with manage_inventory=true", async () => {
|
||||
const mockAvailability = {
|
||||
"variant-1": { availability: 15 },
|
||||
"variant-2": { availability: 9 },
|
||||
"variant-3": { availability: 25 },
|
||||
}
|
||||
|
||||
;(getVariantAvailability as jest.Mock).mockResolvedValueOnce(
|
||||
mockAvailability
|
||||
)
|
||||
|
||||
await wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
variants
|
||||
)
|
||||
|
||||
expect(variants[0].inventory_quantity).toBe(15)
|
||||
expect(variants[1].inventory_quantity).toBe(9)
|
||||
expect(variants[2].inventory_quantity).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should not call getVariantAvailability when variants array is empty", async () => {
|
||||
await wrapVariantsWithInventoryQuantityForSalesChannel(
|
||||
req as MedusaStoreRequest<unknown>,
|
||||
[]
|
||||
)
|
||||
|
||||
expect(getVariantAvailability).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,48 +2,65 @@ import { MedusaStoreRequest } from "@medusajs/framework/http"
|
||||
import { arrayDifference, MedusaError } from "@medusajs/framework/utils"
|
||||
import { NextFunction } from "express"
|
||||
|
||||
/**
|
||||
* Transforms and validates the sales channel ids
|
||||
* @param req
|
||||
* @returns The transformed and validated sales channel ids
|
||||
*/
|
||||
export function transformAndValidateSalesChannelIds(
|
||||
req: MedusaStoreRequest
|
||||
): string[] {
|
||||
const { sales_channel_ids: idsFromPublishableKey = [] } =
|
||||
req.publishable_key_context
|
||||
|
||||
let { sales_channel_id: idsFromRequest = [] } = req.validatedQuery as {
|
||||
sales_channel_id: string | string[]
|
||||
}
|
||||
|
||||
idsFromRequest = Array.isArray(idsFromRequest)
|
||||
? idsFromRequest
|
||||
: [idsFromRequest]
|
||||
|
||||
// If all sales channel ids are not in the publishable key, we throw an error
|
||||
if (idsFromRequest.length) {
|
||||
const uniqueInParams = arrayDifference(
|
||||
idsFromRequest,
|
||||
idsFromPublishableKey
|
||||
)
|
||||
|
||||
if (uniqueInParams.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested sales channel is not part of the publishable key`
|
||||
)
|
||||
}
|
||||
|
||||
return idsFromRequest
|
||||
}
|
||||
|
||||
if (idsFromPublishableKey?.length) {
|
||||
return idsFromPublishableKey
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// Selection of sales channels happens in the following priority:
|
||||
// - If a publishable API key is passed, we take the sales channels attached to it and filter them down based on the query params
|
||||
// - If a sales channel id is passed through query params, we use that
|
||||
// - If not, we use the default sales channel for the store
|
||||
export function filterByValidSalesChannels() {
|
||||
return async (req: MedusaStoreRequest, _, next: NextFunction) => {
|
||||
const idsFromRequest = req.filterableFields.sales_channel_id
|
||||
const { sales_channel_ids: idsFromPublishableKey = [] } =
|
||||
req.publishable_key_context
|
||||
const salesChannelIds = transformAndValidateSalesChannelIds(req)
|
||||
|
||||
// If all sales channel ids are not in the publishable key, we throw an error
|
||||
if (Array.isArray(idsFromRequest) && idsFromRequest.length) {
|
||||
const uniqueInParams = arrayDifference(
|
||||
idsFromRequest,
|
||||
idsFromPublishableKey
|
||||
)
|
||||
|
||||
if (uniqueInParams.length) {
|
||||
return next(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested sales channel is not part of the publishable key`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
req.filterableFields.sales_channel_id = idsFromRequest
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
if (idsFromPublishableKey?.length) {
|
||||
req.filterableFields.sales_channel_id = idsFromPublishableKey
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
return next(
|
||||
new MedusaError(
|
||||
if (!salesChannelIds.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Publishable key needs to have a sales channel configured`
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
req.filterableFields.sales_channel_id = salesChannelIds
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
MedusaError,
|
||||
} from "@medusajs/framework/utils"
|
||||
import { MedusaRequest, MedusaStoreRequest } from "@medusajs/framework/http"
|
||||
import { transformAndValidateSalesChannelIds } from "./filter-by-valid-sales-channels"
|
||||
|
||||
export const wrapVariantsWithTotalInventoryQuantity = async (
|
||||
req: MedusaRequest,
|
||||
@@ -28,25 +29,21 @@ export const wrapVariantsWithInventoryQuantityForSalesChannel = async (
|
||||
req: MedusaStoreRequest<unknown>,
|
||||
variants: VariantInput[]
|
||||
) => {
|
||||
const salesChannelId = req.filterableFields.sales_channel_id as
|
||||
| string
|
||||
| string[]
|
||||
const { sales_channel_ids: idsFromPublishableKey = [] } =
|
||||
req.publishable_key_context
|
||||
const salesChannelIds = transformAndValidateSalesChannelIds(req)
|
||||
|
||||
let channelToUse: string | undefined
|
||||
if (salesChannelId && !Array.isArray(salesChannelId)) {
|
||||
channelToUse = salesChannelId
|
||||
}
|
||||
const publishableApiKeySalesChannelIds =
|
||||
req.publishable_key_context.sales_channel_ids ?? []
|
||||
|
||||
if (idsFromPublishableKey.length === 1) {
|
||||
channelToUse = idsFromPublishableKey[0]
|
||||
}
|
||||
let channelsToUse: string
|
||||
|
||||
if (!channelToUse) {
|
||||
if (publishableApiKeySalesChannelIds.length === 1) {
|
||||
channelsToUse = publishableApiKeySalesChannelIds[0]
|
||||
} else if (salesChannelIds.length === 1) {
|
||||
channelsToUse = salesChannelIds[0]
|
||||
} else {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Inventory availability cannot be calculated in the given context. Either provide a sales channel id or configure a single sales channel in the publishable key`
|
||||
`Inventory availability cannot be calculated in the given context. Either provide a single sales channel id or configure a single sales channel in the publishable key`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,7 +57,7 @@ export const wrapVariantsWithInventoryQuantityForSalesChannel = async (
|
||||
const query = req.scope.resolve(ContainerRegistrationKeys.QUERY)
|
||||
const availability = await getVariantAvailability(query, {
|
||||
variant_ids: variantIds,
|
||||
sales_channel_id: channelToUse,
|
||||
sales_channel_id: channelsToUse,
|
||||
})
|
||||
|
||||
wrapVariants(variants, availability)
|
||||
|
||||
Reference in New Issue
Block a user