From db9483140adae7a2f9f147b86cea98c6360395fc Mon Sep 17 00:00:00 2001 From: Riqwan Thamir Date: Thu, 24 Apr 2025 15:25:22 +0200 Subject: [PATCH] fix(medusa): Fix store return API (#12284) what: - fixes the middleware to point to the right API route - adds a test to store return API --- .../__tests__/order/store/returns.spec.ts | 246 ++++++++++++++++++ packages/medusa/src/api/middlewares.ts | 4 +- packages/medusa/src/api/store/middlewares.ts | 5 +- .../store/{return => returns}/middlewares.ts | 4 +- .../store/{return => returns}/query-config.ts | 0 .../api/store/{return => returns}/route.ts | 0 .../store/{return => returns}/validators.ts | 4 +- 7 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 integration-tests/http/__tests__/order/store/returns.spec.ts rename packages/medusa/src/api/store/{return => returns}/middlewares.ts (89%) rename packages/medusa/src/api/store/{return => returns}/query-config.ts (100%) rename packages/medusa/src/api/store/{return => returns}/route.ts (100%) rename packages/medusa/src/api/store/{return => returns}/validators.ts (97%) diff --git a/integration-tests/http/__tests__/order/store/returns.spec.ts b/integration-tests/http/__tests__/order/store/returns.spec.ts new file mode 100644 index 0000000000..bf3173a96b --- /dev/null +++ b/integration-tests/http/__tests__/order/store/returns.spec.ts @@ -0,0 +1,246 @@ +import { medusaIntegrationTestRunner } from "@medusajs/test-utils" +import { Modules, RuleOperator } from "@medusajs/utils" +import { + adminHeaders, + createAdminUser, + generatePublishableKey, + generateStoreHeaders, +} from "../../../../helpers/create-admin-user" +import { setupTaxStructure } from "../../../../modules/__tests__/fixtures" +import { createAuthenticatedCustomer } from "../../../../modules/helpers/create-authenticated-customer" +import { createOrderSeeder } from "../../fixtures/order" + +jest.setTimeout(300000) + +medusaIntegrationTestRunner({ + testSuite: ({ dbConnection, getContainer, api }) => { + let order + let shippingProfile + let fulfillmentSet + let location + let item + let returnShippingOption + let storeHeadersWithCustomer, storeHeaders + + const shippingProviderId = "manual_test-provider" + + beforeEach(async () => { + const container = getContainer() + + await setupTaxStructure(container.resolve(Modules.TAX)) + await createAdminUser(dbConnection, adminHeaders, container) + + const publishableKey = await generatePublishableKey(container) + storeHeaders = generateStoreHeaders({ publishableKey }) + const result = await createAuthenticatedCustomer(api, storeHeaders, { + first_name: "tony", + last_name: "stark", + email: "tony@stark-industries.com", + }) + storeHeadersWithCustomer = { + headers: { + ...storeHeaders.headers, + authorization: `Bearer ${result.jwt}`, + }, + } + + const inventoryItemOverride = ( + await api.post( + `/admin/inventory-items`, + { sku: "test-variant", requires_shipping: false }, + adminHeaders + ) + ).data.inventory_item + + const seeders = await createOrderSeeder({ + api, + container, + inventoryItemOverride, + withoutShipping: true, + }) + order = seeders.order + + shippingProfile = ( + await api.post( + `/admin/shipping-profiles`, + { name: "Test", type: "default" }, + adminHeaders + ) + ).data.shipping_profile + + location = ( + await api.post( + `/admin/stock-locations`, + { name: "Test location" }, + adminHeaders + ) + ).data.stock_location + + location = ( + await api.post( + `/admin/stock-locations/${location.id}/fulfillment-sets?fields=*fulfillment_sets`, + { name: "Test", type: "test-type" }, + adminHeaders + ) + ).data.stock_location + + fulfillmentSet = ( + await api.post( + `/admin/fulfillment-sets/${location.fulfillment_sets[0].id}/service-zones`, + { + name: "Test", + geo_zones: [{ type: "country", country_code: "us" }], + }, + adminHeaders + ) + ).data.fulfillment_set + + const inventoryItem = ( + await api.get(`/admin/inventory-items?sku=test-variant`, adminHeaders) + ).data.inventory_items[0] + + await api.post( + `/admin/inventory-items/${inventoryItem.id}/location-levels`, + { + location_id: location.id, + stocked_quantity: 10, + }, + adminHeaders + ) + + await api.post( + `/admin/stock-locations/${location.id}/fulfillment-providers`, + { add: [shippingProviderId] }, + adminHeaders + ) + + const shippingOptionPayload = { + name: "Return shipping", + service_zone_id: fulfillmentSet.service_zones[0].id, + shipping_profile_id: shippingProfile.id, + provider_id: shippingProviderId, + price_type: "flat", + type: { + label: "Test type", + description: "Test description", + code: "test-code", + }, + prices: [ + { + currency_code: "usd", + amount: 15, + }, + ], + rules: [ + { + operator: RuleOperator.EQ, + attribute: "is_return", + value: "true", + }, + ], + } + + returnShippingOption = ( + await api.post( + "/admin/shipping-options", + shippingOptionPayload, + adminHeaders + ) + ).data.shipping_option + + item = order.items[0] + }) + + describe("POST /store/returns", () => { + it("should request a return", async () => { + let orderResult = ( + await api.get(`/admin/orders/${order.id}`, adminHeaders) + ).data.order + + const returnReason = ( + await api.post( + "/admin/return-reasons", + { + value: "return-reason-test", + label: "Test return reason", + }, + adminHeaders + ) + ).data.return_reason + + let fulfillableItem = orderResult.items.find( + (item) => item.detail.fulfilled_quantity < item.detail.quantity + ) + + await api.post( + `/admin/orders/${order.id}/fulfillments`, + { + location_id: location.id, + items: [ + { + id: fulfillableItem.id, + quantity: item.detail.quantity - item.detail.fulfilled_quantity, + }, + ], + }, + adminHeaders + ) + + orderResult = (await api.get(`/admin/orders/${order.id}`, adminHeaders)) + .data.order + + fulfillableItem = orderResult.items.find( + (item) => item.detail.fulfilled_quantity < item.detail.quantity + ) + + expect(fulfillableItem).toBeUndefined() + + orderResult = (await api.get(`/admin/orders/${order.id}`, adminHeaders)) + .data.order + + const returnPayload = { + order_id: order.id, + items: [ + { + id: order.items[0].id, + quantity: 1, + reason_id: returnReason.id, + note: "This is a test note", + }, + ], + return_shipping: { + option_id: returnShippingOption.id, + price: 100, + }, + } + + const returnResponse = ( + await api.post( + "/store/returns", + returnPayload, + storeHeadersWithCustomer + ) + ).data.return + + expect(returnResponse).toEqual( + expect.objectContaining({ + order_id: order.id, + items: expect.arrayContaining([ + expect.objectContaining({ + quantity: 1, + reason_id: returnReason.id, + note: "This is a test note", + }), + ]), + shipping_methods: [ + expect.objectContaining({ + shipping_option_id: returnShippingOption.id, + amount: 100, + }), + ], + }) + ) + }) + }) + }, +}) diff --git a/packages/medusa/src/api/middlewares.ts b/packages/medusa/src/api/middlewares.ts index 6d07da645e..5f53fc39d8 100644 --- a/packages/medusa/src/api/middlewares.ts +++ b/packages/medusa/src/api/middlewares.ts @@ -53,11 +53,10 @@ import { storeOrderRoutesMiddlewares } from "./store/orders/middlewares" import { storePaymentCollectionsMiddlewares } from "./store/payment-collections/middlewares" import { storePaymentProvidersMiddlewares } from "./store/payment-providers/middlewares" import { storeProductCategoryRoutesMiddlewares } from "./store/product-categories/middlewares" -import { storeProductRoutesMiddlewares } from "./store/products/middlewares" import { storeProductTagRoutesMiddlewares } from "./store/product-tags/middlewares" import { storeProductTypeRoutesMiddlewares } from "./store/product-types/middlewares" +import { storeProductRoutesMiddlewares } from "./store/products/middlewares" import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares" -import { storeReturnRoutesMiddlewares } from "./store/return/middlewares" import { storeReturnReasonRoutesMiddlewares } from "./store/return-reasons/middlewares" import { storeShippingOptionRoutesMiddlewares } from "./store/shipping-options/middlewares" @@ -119,7 +118,6 @@ export default defineMiddlewares([ ...adminReturnReasonRoutesMiddlewares, ...adminClaimRoutesMiddlewares, ...adminRefundReasonsRoutesMiddlewares, - ...storeReturnRoutesMiddlewares, ...adminExchangeRoutesMiddlewares, ...adminProductVariantRoutesMiddlewares, ...adminOrderEditRoutesMiddlewares, diff --git a/packages/medusa/src/api/store/middlewares.ts b/packages/medusa/src/api/store/middlewares.ts index 8466e48552..c20bd00997 100644 --- a/packages/medusa/src/api/store/middlewares.ts +++ b/packages/medusa/src/api/store/middlewares.ts @@ -1,3 +1,6 @@ import { MiddlewareRoute } from "@medusajs/framework/http" +import { storeReturnsRoutesMiddlewares } from "./returns/middlewares" -export const storeRoutesMiddlewares: MiddlewareRoute[] = [] +export const storeRoutesMiddlewares: MiddlewareRoute[] = [ + ...storeReturnsRoutesMiddlewares, +] diff --git a/packages/medusa/src/api/store/return/middlewares.ts b/packages/medusa/src/api/store/returns/middlewares.ts similarity index 89% rename from packages/medusa/src/api/store/return/middlewares.ts rename to packages/medusa/src/api/store/returns/middlewares.ts index bd73cf6e52..a1f202fefd 100644 --- a/packages/medusa/src/api/store/return/middlewares.ts +++ b/packages/medusa/src/api/store/returns/middlewares.ts @@ -1,12 +1,12 @@ -import { MiddlewareRoute } from "@medusajs/framework/http" import { validateAndTransformBody, validateAndTransformQuery, } from "@medusajs/framework" +import { MiddlewareRoute } from "@medusajs/framework/http" import * as QueryConfig from "./query-config" import { ReturnsParams, StorePostReturnsReqSchema } from "./validators" -export const storeReturnRoutesMiddlewares: MiddlewareRoute[] = [ +export const storeReturnsRoutesMiddlewares: MiddlewareRoute[] = [ { method: ["POST"], matcher: "/store/returns", diff --git a/packages/medusa/src/api/store/return/query-config.ts b/packages/medusa/src/api/store/returns/query-config.ts similarity index 100% rename from packages/medusa/src/api/store/return/query-config.ts rename to packages/medusa/src/api/store/returns/query-config.ts diff --git a/packages/medusa/src/api/store/return/route.ts b/packages/medusa/src/api/store/returns/route.ts similarity index 100% rename from packages/medusa/src/api/store/return/route.ts rename to packages/medusa/src/api/store/returns/route.ts diff --git a/packages/medusa/src/api/store/return/validators.ts b/packages/medusa/src/api/store/returns/validators.ts similarity index 97% rename from packages/medusa/src/api/store/return/validators.ts rename to packages/medusa/src/api/store/returns/validators.ts index 08deed770b..71fbc37bbe 100644 --- a/packages/medusa/src/api/store/return/validators.ts +++ b/packages/medusa/src/api/store/returns/validators.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { createFindParams, createSelectParams } from "../../utils/validators" import { applyAndAndOrOperators } from "../../utils/common-validators" +import { createFindParams, createSelectParams } from "../../utils/validators" export type ReturnParamsType = z.infer export const ReturnParams = createSelectParams() @@ -28,7 +28,7 @@ const ItemSchema = z.object({ }) export const StorePostReturnsReqSchema = z.object({ - order_id: z.string(), + order_id: z.string().min(1), items: z.array(ItemSchema), return_shipping: ReturnShippingSchema, note: z.string().nullish(),