fix(medusa): Add totals when retrieving order by cart id (#3777)
* fix(medusa): Add totals when retrieving order by cart id * fix: Unit test * Create .changeset/stupid-rockets-smile.md
This commit is contained in:
1 parent
8f8816cbab
commit
7f6dc44beb
9 files changed
+129
-56
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/medusa": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(medusa): Add totals when retrieving order by cart id
|
||||||
@@ -19,6 +19,7 @@ const {
|
|||||||
simpleProductFactory,
|
simpleProductFactory,
|
||||||
simpleCartFactory,
|
simpleCartFactory,
|
||||||
simpleShippingOptionFactory,
|
simpleShippingOptionFactory,
|
||||||
|
simpleOrderFactory,
|
||||||
} = require("../../factories")
|
} = require("../../factories")
|
||||||
const { MedusaError } = require("medusa-core-utils")
|
const { MedusaError } = require("medusa-core-utils")
|
||||||
|
|
||||||
@@ -157,6 +158,56 @@ describe("/store/carts", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("retrieves an order by cart id, with totals", async () => {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
const region = await simpleRegionFactory(dbConnection)
|
||||||
|
const product = await simpleProductFactory(dbConnection)
|
||||||
|
|
||||||
|
const cartRes = await api.post("/store/carts", {
|
||||||
|
region_id: region.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cartId = cartRes.data.cart.id
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cartId}/line-items`, {
|
||||||
|
variant_id: product.variants[0].id,
|
||||||
|
quantity: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cartId}`, {
|
||||||
|
email: "testmailer@medusajs.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
await api.post(`/store/carts/${cartId}/payment-sessions`).catch((err) => {
|
||||||
|
console.error("Error creating payment session: ", err.response.data)
|
||||||
|
return err.response
|
||||||
|
})
|
||||||
|
|
||||||
|
const responseSuccess = await api.post(`/store/carts/${cartId}/complete`)
|
||||||
|
|
||||||
|
const orderId = responseSuccess.data.data.id
|
||||||
|
|
||||||
|
const response = await api.get("/store/orders/cart/" + cartId)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.order).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: orderId,
|
||||||
|
cart_id: cartId,
|
||||||
|
total: 100,
|
||||||
|
gift_card_total: 0,
|
||||||
|
gift_card_tax_total: 0,
|
||||||
|
tax_total: 0,
|
||||||
|
subtotal: 100,
|
||||||
|
discount_total: 0,
|
||||||
|
shipping_total: 0,
|
||||||
|
refunded_total: 0,
|
||||||
|
paid_total: 100,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("lookup order response contains only fields defined with `fields` param", async () => {
|
it("lookup order response contains only fields defined with `fields` param", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { DataSource } from "typeorm"
|
|
||||||
import faker from "faker"
|
|
||||||
import {
|
import {
|
||||||
Discount,
|
Discount,
|
||||||
FulfillmentStatus,
|
FulfillmentStatus,
|
||||||
@@ -7,31 +5,33 @@ import {
|
|||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
Refund,
|
Refund,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import {
|
import faker from "faker"
|
||||||
DiscountFactoryData,
|
import { DataSource } from "typeorm"
|
||||||
simpleDiscountFactory,
|
|
||||||
} from "./simple-discount-factory"
|
|
||||||
import { RegionFactoryData, simpleRegionFactory } from "./simple-region-factory"
|
|
||||||
import {
|
|
||||||
LineItemFactoryData,
|
|
||||||
simpleLineItemFactory,
|
|
||||||
} from "./simple-line-item-factory"
|
|
||||||
import {
|
import {
|
||||||
AddressFactoryData,
|
AddressFactoryData,
|
||||||
simpleAddressFactory,
|
simpleAddressFactory,
|
||||||
} from "./simple-address-factory"
|
} from "./simple-address-factory"
|
||||||
import {
|
import {
|
||||||
ShippingMethodFactoryData,
|
CustomerFactoryData,
|
||||||
simpleShippingMethodFactory,
|
simpleCustomerFactory,
|
||||||
} from "./simple-shipping-method-factory"
|
} from "./simple-customer-factory"
|
||||||
|
import {
|
||||||
|
DiscountFactoryData,
|
||||||
|
simpleDiscountFactory,
|
||||||
|
} from "./simple-discount-factory"
|
||||||
|
import {
|
||||||
|
LineItemFactoryData,
|
||||||
|
simpleLineItemFactory,
|
||||||
|
} from "./simple-line-item-factory"
|
||||||
|
import { RegionFactoryData, simpleRegionFactory } from "./simple-region-factory"
|
||||||
import {
|
import {
|
||||||
SalesChannelFactoryData,
|
SalesChannelFactoryData,
|
||||||
simpleSalesChannelFactory,
|
simpleSalesChannelFactory,
|
||||||
} from "./simple-sales-channel-factory"
|
} from "./simple-sales-channel-factory"
|
||||||
import {
|
import {
|
||||||
CustomerFactoryData,
|
ShippingMethodFactoryData,
|
||||||
simpleCustomerFactory,
|
simpleShippingMethodFactory,
|
||||||
} from "./simple-customer-factory"
|
} from "./simple-shipping-method-factory"
|
||||||
|
|
||||||
export type OrderFactoryData = {
|
export type OrderFactoryData = {
|
||||||
id?: string
|
id?: string
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ describe("GET /store/orders", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls orderService retrieve", () => {
|
it("calls orderService retrieve", () => {
|
||||||
expect(OrderServiceMock.retrieveByCartId).toHaveBeenCalledTimes(1)
|
expect(OrderServiceMock.retrieveByCartIdWithTotals).toHaveBeenCalledTimes(
|
||||||
expect(OrderServiceMock.retrieveByCartId).toHaveBeenCalledWith(
|
1
|
||||||
|
)
|
||||||
|
expect(OrderServiceMock.retrieveByCartIdWithTotals).toHaveBeenCalledWith(
|
||||||
IdMap.getId("test-cart"),
|
IdMap.getId("test-cart"),
|
||||||
{
|
{
|
||||||
select: defaultStoreOrdersFields,
|
select: defaultStoreOrdersFields,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { defaultStoreOrdersFields, defaultStoreOrdersRelations } from "."
|
|
||||||
|
|
||||||
import { OrderService } from "../../../../services"
|
import { OrderService } from "../../../../services"
|
||||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||||
|
|
||||||
@@ -50,10 +48,11 @@ export default async (req, res) => {
|
|||||||
const { cart_id } = req.params
|
const { cart_id } = req.params
|
||||||
|
|
||||||
const orderService: OrderService = req.scope.resolve("orderService")
|
const orderService: OrderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.retrieveByCartId(cart_id, {
|
|
||||||
select: defaultStoreOrdersFields,
|
const order = await orderService.retrieveByCartIdWithTotals(
|
||||||
relations: defaultStoreOrdersRelations,
|
cart_id,
|
||||||
})
|
req.retrieveConfig
|
||||||
|
)
|
||||||
|
|
||||||
res.json({ order: cleanResponseData(order, []) })
|
res.json({ order: cleanResponseData(order, []) })
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Router } from "express"
|
import { Router } from "express"
|
||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import { Order } from "../../../.."
|
import { Order } from "../../../.."
|
||||||
|
import { FindParams } from "../../../../types/common"
|
||||||
import middlewares, {
|
import middlewares, {
|
||||||
transformBody,
|
transformBody,
|
||||||
transformStoreQuery,
|
transformStoreQuery,
|
||||||
} from "../../../middlewares"
|
} from "../../../middlewares"
|
||||||
import requireCustomerAuthentication from "../../../middlewares/require-customer-authentication"
|
import requireCustomerAuthentication from "../../../middlewares/require-customer-authentication"
|
||||||
import { StorePostCustomersCustomerOrderClaimReq } from "./request-order"
|
|
||||||
import { StorePostCustomersCustomerAcceptClaimReq } from "./confirm-order-request"
|
import { StorePostCustomersCustomerAcceptClaimReq } from "./confirm-order-request"
|
||||||
import { StoreGetOrderParams } from "./get-order"
|
import { StoreGetOrderParams } from "./get-order"
|
||||||
import { StoreGetOrdersParams } from "./lookup-order"
|
import { StoreGetOrdersParams } from "./lookup-order"
|
||||||
import { FindParams } from "../../../../types/common"
|
import { StorePostCustomersCustomerOrderClaimReq } from "./request-order"
|
||||||
|
|
||||||
const route = Router()
|
const route = Router()
|
||||||
|
|
||||||
@@ -219,6 +219,6 @@ export type StoreOrdersRes = {
|
|||||||
order: Order
|
order: Order
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from "./lookup-order"
|
|
||||||
export * from "./confirm-order-request"
|
export * from "./confirm-order-request"
|
||||||
|
export * from "./lookup-order"
|
||||||
export * from "./request-order"
|
export * from "./request-order"
|
||||||
@@ -187,6 +187,9 @@ export const OrderServiceMock = {
|
|||||||
retrieveByCartId: jest.fn().mockImplementation((cartId) => {
|
retrieveByCartId: jest.fn().mockImplementation((cartId) => {
|
||||||
return Promise.resolve({ id: IdMap.getId("test-order") })
|
return Promise.resolve({ id: IdMap.getId("test-order") })
|
||||||
}),
|
}),
|
||||||
|
retrieveByCartIdWithTotals: jest.fn().mockImplementation((cartId) => {
|
||||||
|
return Promise.resolve({ id: IdMap.getId("test-order") })
|
||||||
|
}),
|
||||||
decorate: jest.fn().mockImplementation((order) => {
|
decorate: jest.fn().mockImplementation((order) => {
|
||||||
order.decorated = true
|
order.decorated = true
|
||||||
return order
|
return order
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||||
import OrderService from "../order"
|
|
||||||
import { LineItemServiceMock } from "../__mocks__/line-item"
|
import { LineItemServiceMock } from "../__mocks__/line-item"
|
||||||
import { newTotalsServiceMock } from "../__mocks__/new-totals"
|
import { newTotalsServiceMock } from "../__mocks__/new-totals"
|
||||||
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
|
import { ProductVariantInventoryServiceMock } from "../__mocks__/product-variant-inventory"
|
||||||
import { taxProviderServiceMock } from "../__mocks__/tax-provider"
|
import { taxProviderServiceMock } from "../__mocks__/tax-provider"
|
||||||
|
import OrderService from "../order"
|
||||||
|
|
||||||
describe("OrderService", () => {
|
describe("OrderService", () => {
|
||||||
const totalsService = {
|
const totalsService = {
|
||||||
@@ -519,12 +519,13 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("retrieveByCartId", () => {
|
describe("retrieveByCartIdWithTotals", () => {
|
||||||
const orderRepo = MockRepository({
|
const orderRepo = MockRepository({
|
||||||
findOne: (q) => {
|
findOneWithRelations: (q) => {
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const orderService = new OrderService({
|
const orderService = new OrderService({
|
||||||
totalsService,
|
totalsService,
|
||||||
newTotalsService: newTotalsServiceMock,
|
newTotalsService: newTotalsServiceMock,
|
||||||
@@ -537,11 +538,15 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls order model functions", async () => {
|
it("calls order model functions", async () => {
|
||||||
await orderService.retrieveByCartId(IdMap.getId("test-cart"))
|
await orderService.retrieveByCartIdWithTotals(IdMap.getId("test-cart"))
|
||||||
expect(orderRepo.findOne).toHaveBeenCalledTimes(1)
|
|
||||||
expect(orderRepo.findOne).toHaveBeenCalledWith({
|
expect(orderRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||||
where: { cart_id: IdMap.getId("test-cart") },
|
expect(orderRepo.findOneWithRelations).toHaveBeenCalledWith(
|
||||||
})
|
expect.any(Object),
|
||||||
|
{
|
||||||
|
where: { cart_id: IdMap.getId("test-cart") },
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -487,35 +487,43 @@ class OrderService extends TransactionBaseService {
|
|||||||
cartId: string,
|
cartId: string,
|
||||||
config: FindConfig<Order> = {}
|
config: FindConfig<Order> = {}
|
||||||
): Promise<Order> {
|
): Promise<Order> {
|
||||||
const orderRepo = this.activeManager_.withRepository(this.orderRepository_)
|
if (!isDefined(cartId)) {
|
||||||
|
throw new MedusaError(
|
||||||
const { select, relations, totalsToSelect } =
|
MedusaError.Types.NOT_FOUND,
|
||||||
this.transformQueryForTotals(config)
|
`"cartId" must be defined`
|
||||||
|
)
|
||||||
const query = {
|
|
||||||
where: { cart_id: cartId },
|
|
||||||
} as FindConfig<Order>
|
|
||||||
|
|
||||||
if (relations && relations.length > 0) {
|
|
||||||
query.relations = relations
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query.select = select?.length ? select : undefined
|
const orderRepo = this.activeManager_.withRepository(this.orderRepository_)
|
||||||
|
|
||||||
const raw = await orderRepo.findOne(query)
|
const query = buildQuery({ cart_id: cartId }, config)
|
||||||
|
|
||||||
|
if (!(config.select || []).length) {
|
||||||
|
query.select = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryRelations = { ...query.relations }
|
||||||
|
delete query.relations
|
||||||
|
|
||||||
|
const raw = await orderRepo.findOneWithRelations(queryRelations, query)
|
||||||
|
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_FOUND,
|
MedusaError.Types.NOT_FOUND,
|
||||||
`Order with cart id: ${cartId} was not found`
|
`Order with cart id ${cartId} was not found`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!totalsToSelect?.length) {
|
return raw
|
||||||
return raw
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return await this.decorateTotals(raw, totalsToSelect)
|
async retrieveByCartIdWithTotals(
|
||||||
|
cartId: string,
|
||||||
|
options: FindConfig<Order> = {}
|
||||||
|
): Promise<Order> {
|
||||||
|
const relations = this.getTotalsRelations(options)
|
||||||
|
const order = await this.retrieveByCartId(cartId, { ...options, relations })
|
||||||
|
return await this.decorateTotals(order, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -659,7 +667,7 @@ class OrderService extends TransactionBaseService {
|
|||||||
// Is the cascade insert really used? Also, is it really necessary to pass the entire entities when creating or updating?
|
// Is the cascade insert really used? Also, is it really necessary to pass the entire entities when creating or updating?
|
||||||
// We normally should only pass what is needed?
|
// We normally should only pass what is needed?
|
||||||
const shippingMethods = cart.shipping_methods.map((method) => {
|
const shippingMethods = cart.shipping_methods.map((method) => {
|
||||||
;(method.tax_lines as any) = undefined
|
(method.tax_lines as any) = undefined
|
||||||
return method
|
return method
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -767,7 +775,7 @@ class OrderService extends TransactionBaseService {
|
|||||||
// TODO: Due to cascade insert we have to remove the tax_lines that have been added by the cart decorate totals.
|
// TODO: Due to cascade insert we have to remove the tax_lines that have been added by the cart decorate totals.
|
||||||
// Is the cascade insert really used? Also, is it really necessary to pass the entire entities when creating or updating?
|
// Is the cascade insert really used? Also, is it really necessary to pass the entire entities when creating or updating?
|
||||||
// We normally should only pass what is needed?
|
// We normally should only pass what is needed?
|
||||||
;(method.tax_lines as any) = undefined
|
(method.tax_lines as any) = undefined
|
||||||
return shippingOptionServiceTx.updateShippingMethod(method.id, {
|
return shippingOptionServiceTx.updateShippingMethod(method.id, {
|
||||||
order_id: order.id,
|
order_id: order.id,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user