diff --git a/.changeset/rich-ants-press.md b/.changeset/rich-ants-press.md new file mode 100644 index 0000000000..f5079fbbdb --- /dev/null +++ b/.changeset/rich-ants-press.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": minor +--- + +fix(medusa): Idempotency workStage used within transaction diff --git a/integration-tests/api/__tests__/claims/index.js b/integration-tests/api/__tests__/claims/index.js index 513bd4d970..1f7f4b0873 100644 --- a/integration-tests/api/__tests__/claims/index.js +++ b/integration-tests/api/__tests__/claims/index.js @@ -25,7 +25,7 @@ describe("Claims", () => { beforeAll(async () => { const cwd = path.resolve(path.join(__dirname, "..", "..")) dbConnection = await initDb({ cwd }) - medusaProcess = await setupServer({ cwd }) + medusaProcess = await setupServer({ cwd, verbose: true }) }) afterAll(async () => { @@ -202,6 +202,54 @@ describe("Claims", () => { expect(response.status).toEqual(200) }) + + test(" should throw and not have dangling claim order upon a claim creation without reasons on claim items", async () => { + const api = useApi() + + await adminSeeder(dbConnection) + + const order = await createReturnableOrder(dbConnection) + const option = await simpleShippingOptionFactory(dbConnection, { + region_id: "test-region", + }) + + const err = await api + .post( + `/admin/orders/${order.id}/claims`, + { + type: "replace", + shipping_methods: [ + { + option_id: option.id, + price: 0, + }, + ], + additional_items: [{ variant_id: "test-variant", quantity: 1 }], + claim_items: [ + { + item_id: "test-item", + quantity: 1, + }, + ], + }, + { + headers: { + authorization: "Bearer test_token", + }, + } + ) + .catch((e) => e) + + const claimOrders = await dbConnection.manager.query( + `SELECT * + FROM claim_order + WHERE order_id = '${order.id}'` + ) + + expect(err).toBeDefined() + expect(err.response.status).toBe(400) + expect(claimOrders).toEqual([]) + }) }) const createReturnableOrder = async (dbConnection, options = {}) => { diff --git a/packages/medusa/src/api/routes/admin/orders/create-claim.ts b/packages/medusa/src/api/routes/admin/orders/create-claim.ts index 5258af21c8..f6bc39469f 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-claim.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-claim.ts @@ -1,4 +1,3 @@ -import { ClaimReason, ClaimType } from "../../../../models" import { IsArray, IsBoolean, @@ -11,12 +10,13 @@ import { ValidateNested, } from "class-validator" import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "." +import { ClaimReason, ClaimType } from "../../../../models" -import { AddressPayload } from "../../../../types/common" -import { ClaimTypeValue } from "../../../../types/claim" -import { EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" import { Type } from "class-transformer" +import { MedusaError } from "medusa-core-utils" +import { EntityManager } from "typeorm" +import { ClaimTypeValue } from "../../../../types/claim" +import { AddressPayload } from "../../../../types/common" import { validator } from "../../../../utils/validator" /** @@ -223,150 +223,146 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(id, { - relations: [ - "customer", - "shipping_address", - "region", - "items", - "items.tax_lines", - "discounts", - "discounts.rule", - "claims", - "claims.additional_items", - "claims.additional_items.tax_lines", - "swaps", - "swaps.additional_items", - "swaps.additional_items.tax_lines", - ], + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(id, { + relations: [ + "customer", + "shipping_address", + "region", + "items", + "items.tax_lines", + "discounts", + "discounts.rule", + "claims", + "claims.additional_items", + "claims.additional_items.tax_lines", + "swaps", + "swaps.additional_items", + "swaps.additional_items.tax_lines", + ], + }) + + await claimService.withTransaction(manager).create({ + idempotency_key: idempotencyKey.idempotency_key, + order, + type: value.type, + shipping_address: value.shipping_address, + claim_items: value.claim_items, + return_shipping: value.return_shipping, + additional_items: value.additional_items, + shipping_methods: value.shipping_methods, + no_notification: value.no_notification, + metadata: value.metadata, }) - await claimService.withTransaction(manager).create({ - idempotency_key: idempotencyKey.idempotency_key, - order, - type: value.type, - shipping_address: value.shipping_address, - claim_items: value.claim_items, - return_shipping: value.return_shipping, - additional_items: value.additional_items, - shipping_methods: value.shipping_methods, - no_notification: value.no_notification, - metadata: value.metadata, + return { + recovery_point: "claim_created", + } }) - - return { - recovery_point: "claim_created", - } - }) - - if (error) { + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "claim_created": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - let claim = await claimService.withTransaction(manager).list({ - idempotency_key: idempotencyKey.idempotency_key, + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + let claim = await claimService.withTransaction(manager).list({ + idempotency_key: idempotencyKey.idempotency_key, + }) + + if (!claim.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Claim not found` + ) + } + + claim = claim[0] + + if (claim.type === "refund") { + await claimService + .withTransaction(manager) + .processRefund(claim.id) + } + + return { + recovery_point: "refund_handled", + } }) - - if (!claim.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Claim not found` - ) - } - - claim = claim[0] - - if (claim.type === "refund") { - await claimService - .withTransaction(manager) - .processRefund(claim.id) - } - - return { - recovery_point: "refund_handled", - } - }) - - if (error) { + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "refund_handled": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - let order = await orderService - .withTransaction(manager) - .retrieve(id, { - relations: ["items", "discounts"], - }) - - let claim = await claimService.withTransaction(manager).list( - { - idempotency_key: idempotencyKey.idempotency_key, - }, - { - relations: ["return_order"], - } - ) - - if (!claim.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Claim not found` - ) - } - - claim = claim[0] - - if (claim.return_order) { - await returnService + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + let order = await orderService .withTransaction(manager) - .fulfill(claim.return_order.id) - } + .retrieve(id, { + relations: ["items", "discounts"], + }) - order = await orderService.withTransaction(manager).retrieve(id, { - select: defaultAdminOrdersFields, - relations: defaultAdminOrdersRelations, + let claim = await claimService.withTransaction(manager).list( + { + idempotency_key: idempotencyKey.idempotency_key, + }, + { + relations: ["return_order"], + } + ) + + if (!claim.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Claim not found` + ) + } + + claim = claim[0] + + if (claim.return_order) { + await returnService + .withTransaction(manager) + .fulfill(claim.return_order.id) + } + + order = await orderService + .withTransaction(manager) + .retrieve(id, { + select: defaultAdminOrdersFields, + relations: defaultAdminOrdersRelations, + }) + + return { + response_code: 200, + response_body: { order }, + } }) - - return { - response_code: 200, - response_body: { order }, - } - }) - - if (error) { + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/admin/orders/create-swap.ts b/packages/medusa/src/api/routes/admin/orders/create-swap.ts index 4a161c5e08..057682ab55 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-swap.ts +++ b/packages/medusa/src/api/routes/admin/orders/create-swap.ts @@ -200,71 +200,68 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(id, { - select: ["refunded_total", "total"], - relations: [ - "items", - "items.tax_lines", - "swaps", - "swaps.additional_items", - "swaps.additional_items.tax_lines", - ], - }) + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(id, { + select: ["refunded_total", "total"], + relations: [ + "items", + "items.tax_lines", + "swaps", + "swaps.additional_items", + "swaps.additional_items.tax_lines", + ], + }) - const swap = await swapService - .withTransaction(manager) - .create( - order, - validated.return_items, - validated.additional_items, - validated.return_shipping, - { - idempotency_key: idempotencyKey.idempotency_key, - no_notification: validated.no_notification, - allow_backorder: validated.allow_backorder, - } - ) + const swap = await swapService + .withTransaction(manager) + .create( + order, + validated.return_items, + validated.additional_items, + validated.return_shipping, + { + idempotency_key: idempotencyKey.idempotency_key, + no_notification: validated.no_notification, + allow_backorder: validated.allow_backorder, + } + ) - await swapService - .withTransaction(manager) - .createCart(swap.id, validated.custom_shipping_options) + await swapService + .withTransaction(manager) + .createCart(swap.id, validated.custom_shipping_options) - const returnOrder = await returnService - .withTransaction(manager) - .retrieveBySwap(swap.id) + const returnOrder = await returnService + .withTransaction(manager) + .retrieveBySwap(swap.id) - await returnService - .withTransaction(manager) - .fulfill(returnOrder.id) + await returnService + .withTransaction(manager) + .fulfill(returnOrder.id) - return { - recovery_point: "swap_created", - } - }) - - if (error) { + return { + recovery_point: "swap_created", + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "swap_created": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (transactionManager: EntityManager) => { + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const swaps = await swapService .withTransaction(transactionManager) .list({ @@ -289,16 +286,12 @@ export default async (req, res) => { response_code: 200, response_body: { order }, } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/admin/orders/request-return.ts b/packages/medusa/src/api/routes/admin/orders/request-return.ts index bdb43e4b71..b9a15cc5d0 100644 --- a/packages/medusa/src/api/routes/admin/orders/request-return.ts +++ b/packages/medusa/src/api/routes/admin/orders/request-return.ts @@ -175,125 +175,121 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const returnObj: ReturnObj = { - order_id: id, - idempotency_key: idempotencyKey.idempotency_key, - items: value.items, - } - - if (value.return_shipping) { - returnObj.shipping_method = value.return_shipping - } - - if (isDefined(value.refund) && value.refund < 0) { - returnObj.refund_amount = 0 - } else { - if (value.refund && value.refund >= 0) { - returnObj.refund_amount = value.refund + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const returnObj: ReturnObj = { + order_id: id, + idempotency_key: idempotencyKey.idempotency_key, + items: value.items, } - } - const order = await orderService - .withTransaction(manager) - .retrieve(id) + if (value.return_shipping) { + returnObj.shipping_method = value.return_shipping + } - const evaluatedNoNotification = - value.no_notification !== undefined - ? value.no_notification - : order.no_notification - returnObj.no_notification = evaluatedNoNotification + if (isDefined(value.refund) && value.refund < 0) { + returnObj.refund_amount = 0 + } else { + if (value.refund && value.refund >= 0) { + returnObj.refund_amount = value.refund + } + } - const createdReturn = await returnService - .withTransaction(manager) - .create(returnObj) - - if (value.return_shipping) { - await returnService + const order = await orderService .withTransaction(manager) - .fulfill(createdReturn.id) - } + .retrieve(id) - await eventBus - .withTransaction(manager) - .emit("order.return_requested", { - id, - return_id: createdReturn.id, - no_notification: evaluatedNoNotification, - }) + const evaluatedNoNotification = + value.no_notification !== undefined + ? value.no_notification + : order.no_notification + returnObj.no_notification = evaluatedNoNotification - return { - recovery_point: "return_requested", - } - }) + const createdReturn = await returnService + .withTransaction(manager) + .create(returnObj) - if (error) { + if (value.return_shipping) { + await returnService + .withTransaction(manager) + .fulfill(createdReturn.id) + } + + await eventBus + .withTransaction(manager) + .emit("order.return_requested", { + id, + return_id: createdReturn.id, + no_notification: evaluatedNoNotification, + }) + + return { + recovery_point: "return_requested", + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "return_requested": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - let order: Order | Return = await orderService - .withTransaction(manager) - .retrieve(id, { relations: ["returns"] }) - - /** - * If we are ready to receive immediately, we find the newly created return - * and register it as received. - */ - if (value.receive_now) { - const returns = await returnService + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + let order: Order | Return = await orderService .withTransaction(manager) - .list({ - idempotency_key: idempotencyKey.idempotency_key, - }) + .retrieve(id, { relations: ["returns"] }) - if (!returns.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Return not found` - ) + /** + * If we are ready to receive immediately, we find the newly created return + * and register it as received. + */ + if (value.receive_now) { + const returns = await returnService + .withTransaction(manager) + .list({ + idempotency_key: idempotencyKey.idempotency_key, + }) + + if (!returns.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Return not found` + ) + } + + const returnOrder = returns[0] + + order = await returnService + .withTransaction(manager) + .receive(returnOrder.id, value.items, value.refund) } - const returnOrder = returns[0] - - order = await returnService + order = await orderService .withTransaction(manager) - .receive(returnOrder.id, value.items, value.refund) - } + .retrieve(id, { + select: defaultAdminOrdersFields, + relations: defaultAdminOrdersRelations, + }) - order = await orderService - .withTransaction(manager) - .retrieve(id, { - select: defaultAdminOrdersFields, - relations: defaultAdminOrdersRelations, - }) - - return { - response_code: 200, - response_body: { order }, - } - }) - - if (error) { + return { + response_code: 200, + response_body: { order }, + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts b/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts index 1f323e8a1c..bf845d7017 100644 --- a/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts +++ b/packages/medusa/src/api/routes/store/carts/calculate-taxes.ts @@ -73,12 +73,11 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager: EntityManager) => { + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const cart = await cartService .withTransaction(manager) .retrieveWithTotals(id, {}, { force_taxes: true }) @@ -87,16 +86,12 @@ export default async (req, res) => { response_code: 200, response_body: { cart }, } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key! - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts b/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts index 60213e35b4..b35ad5b032 100644 --- a/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts +++ b/packages/medusa/src/api/routes/store/carts/create-payment-sessions.ts @@ -79,37 +79,35 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (stageManager) => { - await cartService - .withTransaction(stageManager) - .setPaymentSessions(id) + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage( + idempotencyKey.idempotency_key, + async (stageManager) => { + await cartService + .withTransaction(stageManager) + .setPaymentSessions(id) - const cart = await cartService - .withTransaction(stageManager) - .retrieveWithTotals(id, { - select: defaultStoreCartFields, - relations: defaultStoreCartRelations, - }) + const cart = await cartService + .withTransaction(stageManager) + .retrieveWithTotals(id, { + select: defaultStoreCartFields, + relations: defaultStoreCartRelations, + }) - return { - response_code: 200, - response_body: { cart }, + return { + response_code: 200, + response_body: { cart }, + } } - } - ) - - if (error) { + ) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/store/returns/create-return.ts b/packages/medusa/src/api/routes/store/returns/create-return.ts index 354506f88e..a04b718542 100644 --- a/packages/medusa/src/api/routes/store/returns/create-return.ts +++ b/packages/medusa/src/api/routes/store/returns/create-return.ts @@ -13,7 +13,6 @@ import { MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import EventBusService from "../../../../services/event-bus" import IdempotencyKeyService from "../../../../services/idempotency-key" -import OrderService from "../../../../services/order" import ReturnService from "../../../../services/return" import { validator } from "../../../../utils/validator" @@ -142,7 +141,6 @@ export default async (req, res) => { res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key) try { - const orderService: OrderService = req.scope.resolve("orderService") const returnService: ReturnService = req.scope.resolve("returnService") const eventBus: EventBusService = req.scope.resolve("eventBusService") @@ -152,95 +150,84 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(returnDto.order_id, { - select: ["refunded_total", "total"], - relations: ["items"], - }) + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const returnObj: any = { + order_id: returnDto.order_id, + idempotency_key: idempotencyKey.idempotency_key, + items: returnDto.items, + } - const returnObj: any = { - order_id: returnDto.order_id, - idempotency_key: idempotencyKey.idempotency_key, - items: returnDto.items, - } + if (returnDto.return_shipping) { + returnObj.shipping_method = returnDto.return_shipping + } - if (returnDto.return_shipping) { - returnObj.shipping_method = returnDto.return_shipping - } - - const createdReturn = await returnService - .withTransaction(manager) - .create(returnObj) - - if (returnDto.return_shipping) { - await returnService + const createdReturn = await returnService .withTransaction(manager) - .fulfill(createdReturn.id) - } + .create(returnObj) - await eventBus - .withTransaction(manager) - .emit("order.return_requested", { - id: returnDto.order_id, - return_id: createdReturn.id, - }) + if (returnDto.return_shipping) { + await returnService + .withTransaction(manager) + .fulfill(createdReturn.id) + } - return { - recovery_point: "return_requested", - } - }) + await eventBus + .withTransaction(manager) + .emit("order.return_requested", { + id: returnDto.order_id, + return_id: createdReturn.id, + }) - if (error) { + return { + recovery_point: "return_requested", + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "return_requested": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const returnOrders = await returnService - .withTransaction(manager) - .list( - { - idempotency_key: idempotencyKey.idempotency_key, - }, - { - relations: ["items", "items.reason"], - } - ) - if (!returnOrders.length) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `Return not found` - ) - } - const returnOrder = returnOrders[0] + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const returnOrders = await returnService + .withTransaction(manager) + .list( + { + idempotency_key: idempotencyKey.idempotency_key, + }, + { + relations: ["items", "items.reason"], + } + ) + if (!returnOrders.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Return not found` + ) + } + const returnOrder = returnOrders[0] - return { - response_code: 200, - response_body: { return: returnOrder }, - } - }) - - if (error) { + return { + response_code: 200, + response_body: { return: returnOrder }, + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/api/routes/store/swaps/create-swap.ts b/packages/medusa/src/api/routes/store/swaps/create-swap.ts index aa80523c19..295f243a3c 100644 --- a/packages/medusa/src/api/routes/store/swaps/create-swap.ts +++ b/packages/medusa/src/api/routes/store/swaps/create-swap.ts @@ -173,74 +173,71 @@ export default async (req, res) => { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage(idempotencyKey.idempotency_key, async (manager) => { - const order = await orderService - .withTransaction(manager) - .retrieve(swapDto.order_id, { - select: ["refunded_total", "total"], - relations: [ - "items", - "items.tax_lines", - "swaps", - "swaps.additional_items", - "swaps.additional_items.tax_lines", - ], - }) + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { + const order = await orderService + .withTransaction(manager) + .retrieve(swapDto.order_id, { + select: ["refunded_total", "total"], + relations: [ + "items", + "items.tax_lines", + "swaps", + "swaps.additional_items", + "swaps.additional_items.tax_lines", + ], + }) - let returnShipping - if (swapDto.return_shipping_option) { - returnShipping = { - option_id: swapDto.return_shipping_option, - } - } - - const swap = await swapService - .withTransaction(manager) - .create( - order, - swapDto.return_items, - swapDto.additional_items, - returnShipping, - { - idempotency_key: idempotencyKey.idempotency_key, - no_notification: true, + let returnShipping + if (swapDto.return_shipping_option) { + returnShipping = { + option_id: swapDto.return_shipping_option, } - ) + } - await swapService.withTransaction(manager).createCart(swap.id) - const returnOrder = await returnService - .withTransaction(manager) - .retrieveBySwap(swap.id) + const swap = await swapService + .withTransaction(manager) + .create( + order, + swapDto.return_items, + swapDto.additional_items, + returnShipping, + { + idempotency_key: idempotencyKey.idempotency_key, + no_notification: true, + } + ) - await returnService - .withTransaction(manager) - .fulfill(returnOrder.id) + await swapService.withTransaction(manager).createCart(swap.id) + const returnOrder = await returnService + .withTransaction(manager) + .retrieveBySwap(swap.id) - return { - recovery_point: "swap_created", - } - }) + await returnService + .withTransaction(manager) + .fulfill(returnOrder.id) - if (error) { + return { + recovery_point: "swap_created", + } + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } case "swap_created": { - await manager.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (transactionManager: EntityManager) => { + await manager + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const swaps = await swapService .withTransaction(transactionManager) .list({ @@ -265,16 +262,12 @@ export default async (req, res) => { response_code: 200, response_body: { swap }, } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key - } - }) + err = e + }) break } diff --git a/packages/medusa/src/services/__mocks__/idempotency-key.js b/packages/medusa/src/services/__mocks__/idempotency-key.js index 51b26fdec2..cfed46ca23 100644 --- a/packages/medusa/src/services/__mocks__/idempotency-key.js +++ b/packages/medusa/src/services/__mocks__/idempotency-key.js @@ -1,7 +1,7 @@ import { MockManager } from "medusa-test-utils" export const IdempotencyKeyService = { - withTransaction: function() { + withTransaction: function () { return this }, initializeRequest: jest.fn().mockImplementation(() => { @@ -18,15 +18,13 @@ export const IdempotencyKeyService = { if (recovery_point) { return { - key: { recovery_point }, + recovery_point, } } else { return { - key: { - recovery_point: "finished", - response_body, - response_code, - }, + recovery_point: "finished", + response_body, + response_code, } } } catch (err) { diff --git a/packages/medusa/src/services/claim.ts b/packages/medusa/src/services/claim.ts index c9ac9b7f84..78c78f61ea 100644 --- a/packages/medusa/src/services/claim.ts +++ b/packages/medusa/src/services/claim.ts @@ -1,16 +1,6 @@ -import ClaimItemService from "./claim-item" -import EventBusService from "./event-bus" -import FulfillmentProviderService from "./fulfillment-provider" -import FulfillmentService from "./fulfillment" -import InventoryService from "./inventory" -import LineItemService from "./line-item" -import PaymentProviderService from "./payment-provider" -import RegionService from "./region" -import ReturnService from "./return" -import ShippingOptionService from "./shipping-option" -import TaxProviderService from "./tax-provider" -import TotalsService from "./totals" -import { AddressRepository } from "../repositories/address" +import { MedusaError } from "medusa-core-utils" +import { DeepPartial, EntityManager } from "typeorm" +import { TransactionBaseService } from "../interfaces" import { ClaimFulfillmentStatus, ClaimOrder, @@ -20,15 +10,25 @@ import { LineItem, ReturnItem, } from "../models" +import { AddressRepository } from "../repositories/address" import { ClaimRepository } from "../repositories/claim" -import { DeepPartial, EntityManager } from "typeorm" import { LineItemRepository } from "../repositories/line-item" -import { MedusaError } from "medusa-core-utils" import { ShippingMethodRepository } from "../repositories/shipping-method" -import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined, setMetadata } from "../utils" -import { FindConfig } from "../types/common" import { CreateClaimInput, UpdateClaimInput } from "../types/claim" +import { FindConfig } from "../types/common" +import { buildQuery, isDefined, setMetadata } from "../utils" +import ClaimItemService from "./claim-item" +import EventBusService from "./event-bus" +import FulfillmentService from "./fulfillment" +import FulfillmentProviderService from "./fulfillment-provider" +import InventoryService from "./inventory" +import LineItemService from "./line-item" +import PaymentProviderService from "./payment-provider" +import RegionService from "./region" +import ReturnService from "./return" +import ShippingOptionService from "./shipping-option" +import TaxProviderService from "./tax-provider" +import TotalsService from "./totals" type InjectedDependencies = { manager: EntityManager diff --git a/packages/medusa/src/services/idempotency-key.ts b/packages/medusa/src/services/idempotency-key.ts index 8bd7272d46..62d1254ebd 100644 --- a/packages/medusa/src/services/idempotency-key.ts +++ b/packages/medusa/src/services/idempotency-key.ts @@ -171,28 +171,23 @@ class IdempotencyKeyService extends TransactionBaseService { } | never > - ): Promise<{ key?: IdempotencyKey; error?: unknown }> { - try { - return await this.atomicPhase_(async (manager) => { - const { recovery_point, response_code, response_body } = await callback( - manager - ) + ): Promise { + return await this.atomicPhase_(async (manager) => { + const { recovery_point, response_code, response_body } = await callback( + manager + ) - const data: DeepPartial = { - recovery_point: recovery_point ?? "finished", - } + const data: DeepPartial = { + recovery_point: recovery_point ?? "finished", + } - if (!recovery_point) { - data.response_body = response_body - data.response_code = response_code - } + if (!recovery_point) { + data.response_body = response_body + data.response_code = response_code + } - const key = await this.update(idempotencyKey, data) - return { key } - }, "SERIALIZABLE") - } catch (err) { - return { error: err } - } + return await this.update(idempotencyKey, data) + }) } } diff --git a/packages/medusa/src/strategies/__tests__/cart-completion.js b/packages/medusa/src/strategies/__tests__/cart-completion.js index b2aff0672a..deb7cdac3e 100644 --- a/packages/medusa/src/strategies/__tests__/cart-completion.js +++ b/packages/medusa/src/strategies/__tests__/cart-completion.js @@ -13,21 +13,23 @@ const IdempotencyKeyServiceMock = { if (recovery_point) { return { - key: { idempotency_key: key, recovery_point }, + idempotency_key: key, + recovery_point, } } else { return { - key: { - recovery_point: "finished", - response_body, - response_code, - }, + recovery_point: "finished", + response_body, + response_code, } } } catch (err) { return { error: err } } }), + update: jest.fn().mockImplementation((key, data) => { + return data + }), } const toTest = [ diff --git a/packages/medusa/src/strategies/cart-completion.ts b/packages/medusa/src/strategies/cart-completion.ts index 2c5ae52e43..e8d2611f79 100644 --- a/packages/medusa/src/strategies/cart-completion.ts +++ b/packages/medusa/src/strategies/cart-completion.ts @@ -63,12 +63,11 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy { while (inProgress) { switch (idempotencyKey.recovery_point) { case "started": { - await this.manager_.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager: EntityManager) => { + await this.manager_ + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const cart = await cartService .withTransaction(manager) .retrieve(id) @@ -89,25 +88,20 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy { return { recovery_point: "tax_lines_created", } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key as IdempotencyKey - } - }) + err = e + }) break } case "tax_lines_created": { - await this.manager_.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager: EntityManager) => { + await this.manager_ + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const cart = await cartService .withTransaction(manager) .authorizePayment(id, { @@ -138,26 +132,21 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy { return { recovery_point: "payment_authorized", } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key as IdempotencyKey - } - }) + err = e + }) break } case "payment_authorized": { - await this.manager_.transaction(async (transactionManager) => { - const { key, error } = await idempotencyKeyService - .withTransaction(transactionManager) - .workStage( - idempotencyKey.idempotency_key, - async (manager: EntityManager) => { + await this.manager_ + .transaction("SERIALIZABLE", async (transactionManager) => { + idempotencyKey = await idempotencyKeyService + .withTransaction(transactionManager) + .workStage(idempotencyKey.idempotency_key, async (manager) => { const cart = await cartService .withTransaction(manager) .retrieveWithTotals(id, { @@ -287,16 +276,12 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy { } } } - } - ) - - if (error) { + }) + }) + .catch((e) => { inProgress = false - err = error - } else { - idempotencyKey = key as IdempotencyKey - } - }) + err = e + }) break }