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:
Oliver Windall Juhl authored and GitHub committed 2023-04-11 11:40:13 +02:00
1 parent 8f8816cbab
commit 7f6dc44beb
9 files changed
+129 -56

No files matched your search

+5
View File
@@ -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") },
}
)
}) })
}) })
+28 -20
View File
@@ -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,
}) })