fix(medusa): Idempotency workStage used within transaction (#2358)

This commit is contained in:
Adrien de Peretti authored and GitHub committed 2022-10-19 10:47:31 +02:00
1 parent 3c5e31c645
commit 9deec0fc3c
14 files changed
+567 -576

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": minor
---
fix(medusa): Idempotency workStage used within transaction
@@ -25,7 +25,7 @@ describe("Claims", () => {
beforeAll(async () => { beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..")) const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd }) dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd }) medusaProcess = await setupServer({ cwd, verbose: true })
}) })
afterAll(async () => { afterAll(async () => {
@@ -202,6 +202,54 @@ describe("Claims", () => {
expect(response.status).toEqual(200) 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 = {}) => { const createReturnableOrder = async (dbConnection, options = {}) => {
@@ -1,4 +1,3 @@
import { ClaimReason, ClaimType } from "../../../../models"
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
@@ -11,12 +10,13 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminOrdersFields, defaultAdminOrdersRelations } from "." 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 { 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" import { validator } from "../../../../utils/validator"
/** /**
@@ -223,150 +223,146 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const order = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) const order = await orderService
.retrieve(id, { .withTransaction(manager)
relations: [ .retrieve(id, {
"customer", relations: [
"shipping_address", "customer",
"region", "shipping_address",
"items", "region",
"items.tax_lines", "items",
"discounts", "items.tax_lines",
"discounts.rule", "discounts",
"claims", "discounts.rule",
"claims.additional_items", "claims",
"claims.additional_items.tax_lines", "claims.additional_items",
"swaps", "claims.additional_items.tax_lines",
"swaps.additional_items", "swaps",
"swaps.additional_items.tax_lines", "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({ return {
idempotency_key: idempotencyKey.idempotency_key, recovery_point: "claim_created",
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 { .catch((e) => {
recovery_point: "claim_created",
}
})
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "claim_created": { case "claim_created": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
let claim = await claimService.withTransaction(manager).list({ .workStage(idempotencyKey.idempotency_key, async (manager) => {
idempotency_key: idempotencyKey.idempotency_key, 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) { .catch((e) => {
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) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "refund_handled": { case "refund_handled": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
let order = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) let order = await orderService
.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
.withTransaction(manager) .withTransaction(manager)
.fulfill(claim.return_order.id) .retrieve(id, {
} relations: ["items", "discounts"],
})
order = await orderService.withTransaction(manager).retrieve(id, { let claim = await claimService.withTransaction(manager).list(
select: defaultAdminOrdersFields, {
relations: defaultAdminOrdersRelations, 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 { .catch((e) => {
response_code: 200,
response_body: { order },
}
})
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -200,71 +200,68 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const order = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) const order = await orderService
.retrieve(id, { .withTransaction(manager)
select: ["refunded_total", "total"], .retrieve(id, {
relations: [ select: ["refunded_total", "total"],
"items", relations: [
"items.tax_lines", "items",
"swaps", "items.tax_lines",
"swaps.additional_items", "swaps",
"swaps.additional_items.tax_lines", "swaps.additional_items",
], "swaps.additional_items.tax_lines",
}) ],
})
const swap = await swapService const swap = await swapService
.withTransaction(manager) .withTransaction(manager)
.create( .create(
order, order,
validated.return_items, validated.return_items,
validated.additional_items, validated.additional_items,
validated.return_shipping, validated.return_shipping,
{ {
idempotency_key: idempotencyKey.idempotency_key, idempotency_key: idempotencyKey.idempotency_key,
no_notification: validated.no_notification, no_notification: validated.no_notification,
allow_backorder: validated.allow_backorder, allow_backorder: validated.allow_backorder,
} }
) )
await swapService await swapService
.withTransaction(manager) .withTransaction(manager)
.createCart(swap.id, validated.custom_shipping_options) .createCart(swap.id, validated.custom_shipping_options)
const returnOrder = await returnService const returnOrder = await returnService
.withTransaction(manager) .withTransaction(manager)
.retrieveBySwap(swap.id) .retrieveBySwap(swap.id)
await returnService await returnService
.withTransaction(manager) .withTransaction(manager)
.fulfill(returnOrder.id) .fulfill(returnOrder.id)
return { return {
recovery_point: "swap_created", recovery_point: "swap_created",
} }
}) })
})
if (error) { .catch((e) => {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "swap_created": { case "swap_created": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (transactionManager: EntityManager) => {
const swaps = await swapService const swaps = await swapService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.list({ .list({
@@ -289,16 +286,12 @@ export default async (req, res) => {
response_code: 200, response_code: 200,
response_body: { order }, response_body: { order },
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -175,125 +175,121 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const returnObj: ReturnObj = { .workStage(idempotencyKey.idempotency_key, async (manager) => {
order_id: id, const returnObj: ReturnObj = {
idempotency_key: idempotencyKey.idempotency_key, order_id: id,
items: value.items, 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
} }
}
const order = await orderService if (value.return_shipping) {
.withTransaction(manager) returnObj.shipping_method = value.return_shipping
.retrieve(id) }
const evaluatedNoNotification = if (isDefined(value.refund) && value.refund < 0) {
value.no_notification !== undefined returnObj.refund_amount = 0
? value.no_notification } else {
: order.no_notification if (value.refund && value.refund >= 0) {
returnObj.no_notification = evaluatedNoNotification returnObj.refund_amount = value.refund
}
}
const createdReturn = await returnService const order = await orderService
.withTransaction(manager)
.create(returnObj)
if (value.return_shipping) {
await returnService
.withTransaction(manager) .withTransaction(manager)
.fulfill(createdReturn.id) .retrieve(id)
}
await eventBus const evaluatedNoNotification =
.withTransaction(manager) value.no_notification !== undefined
.emit("order.return_requested", { ? value.no_notification
id, : order.no_notification
return_id: createdReturn.id, returnObj.no_notification = evaluatedNoNotification
no_notification: evaluatedNoNotification,
})
return { const createdReturn = await returnService
recovery_point: "return_requested", .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 inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "return_requested": { case "return_requested": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
let order: Order | Return = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) let order: Order | Return = await orderService
.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
.withTransaction(manager) .withTransaction(manager)
.list({ .retrieve(id, { relations: ["returns"] })
idempotency_key: idempotencyKey.idempotency_key,
})
if (!returns.length) { /**
throw new MedusaError( * If we are ready to receive immediately, we find the newly created return
MedusaError.Types.INVALID_DATA, * and register it as received.
`Return not found` */
) 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 orderService
order = await returnService
.withTransaction(manager) .withTransaction(manager)
.receive(returnOrder.id, value.items, value.refund) .retrieve(id, {
} select: defaultAdminOrdersFields,
relations: defaultAdminOrdersRelations,
})
order = await orderService return {
.withTransaction(manager) response_code: 200,
.retrieve(id, { response_body: { order },
select: defaultAdminOrdersFields, }
relations: defaultAdminOrdersRelations, })
}) })
.catch((e) => {
return {
response_code: 200,
response_body: { order },
}
})
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -73,12 +73,11 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (manager: EntityManager) => {
const cart = await cartService const cart = await cartService
.withTransaction(manager) .withTransaction(manager)
.retrieveWithTotals(id, {}, { force_taxes: true }) .retrieveWithTotals(id, {}, { force_taxes: true })
@@ -87,16 +86,12 @@ export default async (req, res) => {
response_code: 200, response_code: 200,
response_body: { cart }, response_body: { cart },
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key!
}
})
break break
} }
@@ -79,37 +79,35 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(
async (stageManager) => { idempotencyKey.idempotency_key,
await cartService async (stageManager) => {
.withTransaction(stageManager) await cartService
.setPaymentSessions(id) .withTransaction(stageManager)
.setPaymentSessions(id)
const cart = await cartService const cart = await cartService
.withTransaction(stageManager) .withTransaction(stageManager)
.retrieveWithTotals(id, { .retrieveWithTotals(id, {
select: defaultStoreCartFields, select: defaultStoreCartFields,
relations: defaultStoreCartRelations, relations: defaultStoreCartRelations,
}) })
return { return {
response_code: 200, response_code: 200,
response_body: { cart }, response_body: { cart },
}
} }
} )
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -13,7 +13,6 @@ import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import EventBusService from "../../../../services/event-bus" import EventBusService from "../../../../services/event-bus"
import IdempotencyKeyService from "../../../../services/idempotency-key" import IdempotencyKeyService from "../../../../services/idempotency-key"
import OrderService from "../../../../services/order"
import ReturnService from "../../../../services/return" import ReturnService from "../../../../services/return"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -142,7 +141,6 @@ export default async (req, res) => {
res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key) res.setHeader("Idempotency-Key", idempotencyKey.idempotency_key)
try { try {
const orderService: OrderService = req.scope.resolve("orderService")
const returnService: ReturnService = req.scope.resolve("returnService") const returnService: ReturnService = req.scope.resolve("returnService")
const eventBus: EventBusService = req.scope.resolve("eventBusService") const eventBus: EventBusService = req.scope.resolve("eventBusService")
@@ -152,95 +150,84 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const order = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) const returnObj: any = {
.retrieve(returnDto.order_id, { order_id: returnDto.order_id,
select: ["refunded_total", "total"], idempotency_key: idempotencyKey.idempotency_key,
relations: ["items"], items: returnDto.items,
}) }
const returnObj: any = { if (returnDto.return_shipping) {
order_id: returnDto.order_id, returnObj.shipping_method = returnDto.return_shipping
idempotency_key: idempotencyKey.idempotency_key, }
items: returnDto.items,
}
if (returnDto.return_shipping) { const createdReturn = await returnService
returnObj.shipping_method = returnDto.return_shipping
}
const createdReturn = await returnService
.withTransaction(manager)
.create(returnObj)
if (returnDto.return_shipping) {
await returnService
.withTransaction(manager) .withTransaction(manager)
.fulfill(createdReturn.id) .create(returnObj)
}
await eventBus if (returnDto.return_shipping) {
.withTransaction(manager) await returnService
.emit("order.return_requested", { .withTransaction(manager)
id: returnDto.order_id, .fulfill(createdReturn.id)
return_id: createdReturn.id, }
})
return { await eventBus
recovery_point: "return_requested", .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 inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "return_requested": { case "return_requested": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const returnOrders = await returnService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) const returnOrders = await returnService
.list( .withTransaction(manager)
{ .list(
idempotency_key: idempotencyKey.idempotency_key, {
}, idempotency_key: idempotencyKey.idempotency_key,
{ },
relations: ["items", "items.reason"], {
} relations: ["items", "items.reason"],
) }
if (!returnOrders.length) { )
throw new MedusaError( if (!returnOrders.length) {
MedusaError.Types.INVALID_DATA, throw new MedusaError(
`Return not found` MedusaError.Types.INVALID_DATA,
) `Return not found`
} )
const returnOrder = returnOrders[0] }
const returnOrder = returnOrders[0]
return { return {
response_code: 200, response_code: 200,
response_body: { return: returnOrder }, response_body: { return: returnOrder },
} }
}) })
})
if (error) { .catch((e) => {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -173,74 +173,71 @@ export default async (req, res) => {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage(idempotencyKey.idempotency_key, async (manager) => { .withTransaction(transactionManager)
const order = await orderService .workStage(idempotencyKey.idempotency_key, async (manager) => {
.withTransaction(manager) const order = await orderService
.retrieve(swapDto.order_id, { .withTransaction(manager)
select: ["refunded_total", "total"], .retrieve(swapDto.order_id, {
relations: [ select: ["refunded_total", "total"],
"items", relations: [
"items.tax_lines", "items",
"swaps", "items.tax_lines",
"swaps.additional_items", "swaps",
"swaps.additional_items.tax_lines", "swaps.additional_items",
], "swaps.additional_items.tax_lines",
}) ],
})
let returnShipping let returnShipping
if (swapDto.return_shipping_option) { if (swapDto.return_shipping_option) {
returnShipping = { returnShipping = {
option_id: swapDto.return_shipping_option, 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,
} }
) }
await swapService.withTransaction(manager).createCart(swap.id) const swap = await swapService
const returnOrder = await returnService .withTransaction(manager)
.withTransaction(manager) .create(
.retrieveBySwap(swap.id) order,
swapDto.return_items,
swapDto.additional_items,
returnShipping,
{
idempotency_key: idempotencyKey.idempotency_key,
no_notification: true,
}
)
await returnService await swapService.withTransaction(manager).createCart(swap.id)
.withTransaction(manager) const returnOrder = await returnService
.fulfill(returnOrder.id) .withTransaction(manager)
.retrieveBySwap(swap.id)
return { await returnService
recovery_point: "swap_created", .withTransaction(manager)
} .fulfill(returnOrder.id)
})
if (error) { return {
recovery_point: "swap_created",
}
})
})
.catch((e) => {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
case "swap_created": { case "swap_created": {
await manager.transaction(async (transactionManager) => { await manager
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (transactionManager: EntityManager) => {
const swaps = await swapService const swaps = await swapService
.withTransaction(transactionManager) .withTransaction(transactionManager)
.list({ .list({
@@ -265,16 +262,12 @@ export default async (req, res) => {
response_code: 200, response_code: 200,
response_body: { swap }, response_body: { swap },
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key
}
})
break break
} }
@@ -1,7 +1,7 @@
import { MockManager } from "medusa-test-utils" import { MockManager } from "medusa-test-utils"
export const IdempotencyKeyService = { export const IdempotencyKeyService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
initializeRequest: jest.fn().mockImplementation(() => { initializeRequest: jest.fn().mockImplementation(() => {
@@ -18,15 +18,13 @@ export const IdempotencyKeyService = {
if (recovery_point) { if (recovery_point) {
return { return {
key: { recovery_point }, recovery_point,
} }
} else { } else {
return { return {
key: { recovery_point: "finished",
recovery_point: "finished", response_body,
response_body, response_code,
response_code,
},
} }
} }
} catch (err) { } catch (err) {
+18 -18
View File
@@ -1,16 +1,6 @@
import ClaimItemService from "./claim-item" import { MedusaError } from "medusa-core-utils"
import EventBusService from "./event-bus" import { DeepPartial, EntityManager } from "typeorm"
import FulfillmentProviderService from "./fulfillment-provider" import { TransactionBaseService } from "../interfaces"
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 { import {
ClaimFulfillmentStatus, ClaimFulfillmentStatus,
ClaimOrder, ClaimOrder,
@@ -20,15 +10,25 @@ import {
LineItem, LineItem,
ReturnItem, ReturnItem,
} from "../models" } from "../models"
import { AddressRepository } from "../repositories/address"
import { ClaimRepository } from "../repositories/claim" import { ClaimRepository } from "../repositories/claim"
import { DeepPartial, EntityManager } from "typeorm"
import { LineItemRepository } from "../repositories/line-item" import { LineItemRepository } from "../repositories/line-item"
import { MedusaError } from "medusa-core-utils"
import { ShippingMethodRepository } from "../repositories/shipping-method" 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 { 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 = { type InjectedDependencies = {
manager: EntityManager manager: EntityManager
+14 -19
View File
@@ -171,28 +171,23 @@ class IdempotencyKeyService extends TransactionBaseService {
} }
| never | never
> >
): Promise<{ key?: IdempotencyKey; error?: unknown }> { ): Promise<IdempotencyKey> {
try { return await this.atomicPhase_(async (manager) => {
return await this.atomicPhase_(async (manager) => { const { recovery_point, response_code, response_body } = await callback(
const { recovery_point, response_code, response_body } = await callback( manager
manager )
)
const data: DeepPartial<IdempotencyKey> = { const data: DeepPartial<IdempotencyKey> = {
recovery_point: recovery_point ?? "finished", recovery_point: recovery_point ?? "finished",
} }
if (!recovery_point) { if (!recovery_point) {
data.response_body = response_body data.response_body = response_body
data.response_code = response_code data.response_code = response_code
} }
const key = await this.update(idempotencyKey, data) return await this.update(idempotencyKey, data)
return { key } })
}, "SERIALIZABLE")
} catch (err) {
return { error: err }
}
} }
} }
@@ -13,21 +13,23 @@ const IdempotencyKeyServiceMock = {
if (recovery_point) { if (recovery_point) {
return { return {
key: { idempotency_key: key, recovery_point }, idempotency_key: key,
recovery_point,
} }
} else { } else {
return { return {
key: { recovery_point: "finished",
recovery_point: "finished", response_body,
response_body, response_code,
response_code,
},
} }
} }
} catch (err) { } catch (err) {
return { error: err } return { error: err }
} }
}), }),
update: jest.fn().mockImplementation((key, data) => {
return data
}),
} }
const toTest = [ const toTest = [
@@ -63,12 +63,11 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
while (inProgress) { while (inProgress) {
switch (idempotencyKey.recovery_point) { switch (idempotencyKey.recovery_point) {
case "started": { case "started": {
await this.manager_.transaction(async (transactionManager) => { await this.manager_
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (manager: EntityManager) => {
const cart = await cartService const cart = await cartService
.withTransaction(manager) .withTransaction(manager)
.retrieve(id) .retrieve(id)
@@ -89,25 +88,20 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
return { return {
recovery_point: "tax_lines_created", recovery_point: "tax_lines_created",
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key as IdempotencyKey
}
})
break break
} }
case "tax_lines_created": { case "tax_lines_created": {
await this.manager_.transaction(async (transactionManager) => { await this.manager_
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (manager: EntityManager) => {
const cart = await cartService const cart = await cartService
.withTransaction(manager) .withTransaction(manager)
.authorizePayment(id, { .authorizePayment(id, {
@@ -138,26 +132,21 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
return { return {
recovery_point: "payment_authorized", recovery_point: "payment_authorized",
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key as IdempotencyKey
}
})
break break
} }
case "payment_authorized": { case "payment_authorized": {
await this.manager_.transaction(async (transactionManager) => { await this.manager_
const { key, error } = await idempotencyKeyService .transaction("SERIALIZABLE", async (transactionManager) => {
.withTransaction(transactionManager) idempotencyKey = await idempotencyKeyService
.workStage( .withTransaction(transactionManager)
idempotencyKey.idempotency_key, .workStage(idempotencyKey.idempotency_key, async (manager) => {
async (manager: EntityManager) => {
const cart = await cartService const cart = await cartService
.withTransaction(manager) .withTransaction(manager)
.retrieveWithTotals(id, { .retrieveWithTotals(id, {
@@ -287,16 +276,12 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
} }
} }
} }
} })
) })
.catch((e) => {
if (error) {
inProgress = false inProgress = false
err = error err = e
} else { })
idempotencyKey = key as IdempotencyKey
}
})
break break
} }