feat(medusa): Respond with order when cart is already completed (#5766)

This commit is contained in:
Oli Juhl authored and GitHub committed 2023-12-01 09:17:12 +01:00
1 parent c808317044
commit 428331a653
6 files changed
+106 -61

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/medusa": minor
---
feat(medusa): Respond with order when cart is already completed
@@ -24,14 +24,6 @@ Object {
} }
`; `;
exports[`/store/carts POST /store/carts/:id returns early, if cart is already completed 1`] = `
Object {
"code": "cart_incompatible_state",
"message": "Cart has already been completed",
"type": "not_allowed",
}
`;
exports[`/store/carts shipping address + region updates updates region only - single to multiple countries 1`] = ` exports[`/store/carts shipping address + region updates updates region only - single to multiple countries 1`] = `
Object { Object {
"address_1": null, "address_1": null,
@@ -2234,24 +2234,27 @@ describe("/store/carts", () => {
expect(createdOrder.status).toEqual(200) expect(createdOrder.status).toEqual(200)
}) })
it("returns early, if cart is already completed", async () => { it("should return early, if cart is already completed", async () => {
const manager = dbConnection.manager
const api = useApi() const api = useApi()
await manager.query(
`UPDATE "cart" const completedCart = await api.post(
SET completed_at=current_timestamp `/store/carts/test-cart-2/complete-cart`
WHERE id = 'test-cart-2'`
) )
try {
await api.post(`/store/carts/test-cart-2/complete-cart`) expect(completedCart.status).toEqual(200)
} catch (error) {
expect(error.response.data).toMatchSnapshot({ const alreadyCompletedCart = await api.post(
type: "not_allowed", `/store/carts/test-cart-2/complete-cart`
message: "Cart has already been completed", )
code: "cart_incompatible_state",
expect(alreadyCompletedCart.data.data).toEqual(
expect.objectContaining({
cart_id: "test-cart-2",
id: expect.any(String),
}) })
expect(error.response.status).toEqual(409) )
} expect(alreadyCompletedCart.data.type).toEqual("order")
expect(alreadyCompletedCart.status).toEqual(200)
}) })
it("fails to complete cart with items inventory not/partially covered", async () => { it("fails to complete cart with items inventory not/partially covered", async () => {
@@ -2354,6 +2357,32 @@ describe("/store/carts", () => {
expect(res.data.cart.completed_at).not.toBe(null) expect(res.data.cart.completed_at).not.toBe(null)
}) })
it("should return the swap when cart is already completed", async () => {
const manager = dbConnection.manager
await manager.query(
"UPDATE swap SET cart_id='swap-cart' where id='test-swap'"
)
await manager.query("DELETE FROM payment where swap_id='test-swap'")
const api = useApi()
await api.post(`/store/carts/swap-cart/complete-cart`)
const alreadyCompletedCart = await api.post(
`/store/carts/swap-cart/complete-cart`
)
expect(alreadyCompletedCart.data.data).toEqual(
expect.objectContaining({
cart_id: "swap-cart",
id: expect.any(String),
})
)
expect(alreadyCompletedCart.data.type).toEqual("swap")
expect(alreadyCompletedCart.status).toEqual(200)
})
it("completes cart with a non-customer and for a customer with the same email created later the order doesn't show up", async () => { it("completes cart with a non-customer and for a customer with the same email created later the order doesn't show up", async () => {
const api = useApi() const api = useApi()
const customerEmail = "test-email-for-non-existent-customer@test.com" const customerEmail = "test-email-for-non-existent-customer@test.com"
@@ -456,7 +456,7 @@ describe("/store/carts", () => {
await db.teardown() await db.teardown()
}) })
it("should fails on cart already completed", async () => { it("should return an order on cart already completed", async () => {
const api = useApi() const api = useApi()
const manager = dbConnection.manager const manager = dbConnection.manager
@@ -501,17 +501,16 @@ describe("/store/carts", () => {
}) })
) )
const responseFail = await api const successRes = await api.post(`/store/carts/${cartId}/complete`)
.post(`/store/carts/${cartId}/complete`)
.catch((err) => {
return err.response
})
expect(responseFail.status).toEqual(409) expect(successRes.status).toEqual(200)
expect(responseFail.data.code).toEqual("cart_incompatible_state") expect(successRes.data.data).toEqual(
expect(responseFail.data.message).toEqual( expect.objectContaining({
"Cart has already been completed" cart_id: cartId,
id: expect.any(String),
})
) )
expect(successRes.data.type).toEqual("order")
}) })
}) })
@@ -88,7 +88,7 @@ const toTest = [
}, },
], ],
[ [
"returns 409", "succeeds",
{ {
cart: { cart: {
id: "test-cart", id: "test-cart",
@@ -104,12 +104,15 @@ const toTest = [
idempotency_key: "ikey", idempotency_key: "ikey",
recovery_point: "started", recovery_point: "started",
}, },
validate: function (value) { validate: function (value, { orderServiceMock }) {
expect(value.response_code).toEqual(409) expect(value.response_code).toEqual(200)
expect(value.response_body).toEqual({ expect(
code: "cart_incompatible_state", orderServiceMock.retrieveByCartIdWithTotals
message: "Cart has already been completed", ).toHaveBeenCalledTimes(1)
type: "not_allowed", expect(
orderServiceMock.retrieveByCartIdWithTotals
).toHaveBeenCalledWith("test-cart", {
relations: ["shipping_address", "items", "payments"],
}) })
}, },
}, },
@@ -204,6 +207,7 @@ describe("CartCompletionStrategy", () => {
createFromCart: jest.fn(() => Promise.resolve(cart)), createFromCart: jest.fn(() => Promise.resolve(cart)),
retrieve: jest.fn(() => Promise.resolve({})), retrieve: jest.fn(() => Promise.resolve({})),
retrieveWithTotals: jest.fn(() => Promise.resolve({})), retrieveWithTotals: jest.fn(() => Promise.resolve({})),
retrieveByCartIdWithTotals: jest.fn(() => Promise.resolve({})),
newTotalsService: newTotalsServiceMock, newTotalsService: newTotalsServiceMock,
} }
const swapServiceMock = { const swapServiceMock = {
@@ -1,28 +1,28 @@
import {
AbstractCartCompletionStrategy,
CartCompletionResponse,
} from "../interfaces"
import { import {
IEventBusService, IEventBusService,
IInventoryService, IInventoryService,
ReservationItemDTO, ReservationItemDTO,
} from "@medusajs/types" } from "@medusajs/types"
import {
AbstractCartCompletionStrategy,
CartCompletionResponse,
} from "../interfaces"
import { IdempotencyKey, Order } from "../models" import { IdempotencyKey, Order } from "../models"
import OrderService, {
ORDER_CART_ALREADY_EXISTS_ERROR,
} from "../services/order"
import { import {
PaymentProviderService, PaymentProviderService,
ProductVariantInventoryService, ProductVariantInventoryService,
} from "../services" } from "../services"
import OrderService, {
ORDER_CART_ALREADY_EXISTS_ERROR,
} from "../services/order"
import CartService from "../services/cart"
import { EntityManager } from "typeorm"
import IdempotencyKeyService from "../services/idempotency-key"
import { MedusaError } from "medusa-core-utils"
import { RequestContext } from "../types/request"
import SwapService from "../services/swap"
import { promiseAll } from "@medusajs/utils" import { promiseAll } from "@medusajs/utils"
import { MedusaError } from "medusa-core-utils"
import { EntityManager } from "typeorm"
import CartService from "../services/cart"
import IdempotencyKeyService from "../services/idempotency-key"
import SwapService from "../services/swap"
import { RequestContext } from "../types/request"
type InjectedDependencies = { type InjectedDependencies = {
productVariantInventoryService: ProductVariantInventoryService productVariantInventoryService: ProductVariantInventoryService
@@ -201,13 +201,29 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
}) })
if (cart.completed_at) { if (cart.completed_at) {
if (cart.type === "swap") {
const swapId = cart.metadata?.swap_id as string
const swapServiceTx = this.swapService_.withTransaction(manager)
const swap = await swapServiceTx.retrieve(swapId, {
relations: ["shipping_address"],
})
return {
response_code: 200,
response_body: { data: swap, type: "swap" },
}
}
const order = await this.orderService_
.withTransaction(manager)
.retrieveByCartIdWithTotals(id, {
relations: ["shipping_address", "items", "payments"],
})
return { return {
response_code: 409, response_code: 200,
response_body: { response_body: { data: order, type: "order" },
code: MedusaError.Codes.CART_INCOMPATIBLE_STATE,
message: "Cart has already been completed",
type: MedusaError.Types.NOT_ALLOWED,
},
} }
} }
@@ -449,8 +465,8 @@ class CartCompletionStrategy extends AbstractCartCompletionStrategy {
await this.removeReservations(reservations) await this.removeReservations(reservations)
if (error && error.message === ORDER_CART_ALREADY_EXISTS_ERROR) { if (error && error.message === ORDER_CART_ALREADY_EXISTS_ERROR) {
order = await orderServiceTx.retrieveByCartId(id, { order = await orderServiceTx.retrieveByCartIdWithTotals(id, {
relations: ["shipping_address", "payments"], relations: ["shipping_address", "items", "payments"],
}) })
return { return {