diff --git a/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap b/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap index 3d843b87ed..dbf6afc647 100644 --- a/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap +++ b/integration-tests/api/__tests__/store/__snapshots__/cart.js.snap @@ -24,7 +24,7 @@ Object { } `; -exports[`/store/carts shipping address + region updates updates region only - single to multipe countries 1`] = ` +exports[`/store/carts shipping address + region updates updates region only - single to multiple countries 1`] = ` Object { "address_1": null, "address_2": null, diff --git a/integration-tests/api/__tests__/store/cart.js b/integration-tests/api/__tests__/store/cart.js index c89c8caa7f..5bcb854bd2 100644 --- a/integration-tests/api/__tests__/store/cart.js +++ b/integration-tests/api/__tests__/store/cart.js @@ -924,19 +924,19 @@ describe("/store/carts", () => { }) it("fails on apply discount if limit has been reached", async () => { - expect.assertions(2) const api = useApi() - await api + const err = await api .post("/store/carts/test-cart", { discounts: [{ code: "SPENT" }], }) - .catch((error) => { - expect(error.response.status).toEqual(400) - expect(error.response.data.message).toEqual( - "Discount has been used maximum allowed times" - ) - }) + .catch((err) => err) + + expect(err).toBeTruthy() + expect(err.response.status).toEqual(400) + expect(err.response.data.message).toEqual( + "Discount has been used maximum allowed times" + ) }) it("successfully passes customer conditions with `in` operator and applies discount", async () => { @@ -1755,7 +1755,7 @@ describe("/store/carts", () => { }), ]) ) - expect(cartWithGiftcard.data.cart.total).toBe(1900) // 1000 (giftcard) + 900 (standard item with 10% discount) + expect(cartWithGiftcard.data.cart.total).toBe(2900) // 1000 (giftcard) + 900 (standard item with 10% discount) + 1000 Shipping expect(cartWithGiftcard.data.cart.discount_total).toBe(100) expect(cartWithGiftcard.status).toEqual(200) }) @@ -1925,7 +1925,7 @@ describe("/store/carts", () => { await doAfterEach() }) - it("updates region only - single to multipe countries", async () => { + it("updates region only - single to multiple countries", async () => { const api = useApi() const { data, status } = await api @@ -1947,7 +1947,7 @@ describe("/store/carts", () => { }) }) - it("updates region only - single to multipe countries", async () => { + it("should reset the shipping_address on null value", async () => { const api = useApi() const { data, status } = await api @@ -1962,7 +1962,5 @@ describe("/store/carts", () => { expect(status).toEqual(200) expect(data.cart.shipping_address).toEqual(null) }) - - // it("updates cart.customer_id on cart retrieval if cart.customer_id differ from session customer", async () => {}) }) }) diff --git a/packages/medusa-interfaces/src/base-service.js b/packages/medusa-interfaces/src/base-service.js index 17db039018..25ac7f71a9 100644 --- a/packages/medusa-interfaces/src/base-service.js +++ b/packages/medusa-interfaces/src/base-service.js @@ -228,8 +228,7 @@ class BaseService { } try { - const result = await this.manager_.transaction((m) => doWork(m)) - return result + return await this.manager_.transaction((m) => doWork(m)) } catch (error) { if (errorHandler) { const result = await errorHandler(error) diff --git a/packages/medusa/src/api/routes/store/carts/update-cart.ts b/packages/medusa/src/api/routes/store/carts/update-cart.ts index 405d5bf1cf..6fc264f1a8 100644 --- a/packages/medusa/src/api/routes/store/carts/update-cart.ts +++ b/packages/medusa/src/api/routes/store/carts/update-cart.ts @@ -90,23 +90,20 @@ export default async (req, res) => { // Update the cart const { shipping_address, billing_address, ...rest } = validated - const toUpdate: CartUpdateProps = { - ...rest, - } - + const cartDataToUpdate: CartUpdateProps = { ...rest }; if (typeof shipping_address === "string") { - toUpdate.shipping_address_id = shipping_address + cartDataToUpdate.shipping_address_id = shipping_address } else { - toUpdate.shipping_address = shipping_address + cartDataToUpdate.shipping_address = shipping_address } if (typeof billing_address === "string") { - toUpdate.billing_address_id = billing_address + cartDataToUpdate.billing_address_id = billing_address } else { - toUpdate.billing_address = billing_address + cartDataToUpdate.billing_address = billing_address } - await cartService.update(id, toUpdate) + await cartService.update(id, cartDataToUpdate) // If the cart has payment sessions update these const updated = await cartService.retrieve(id, { diff --git a/packages/medusa/src/loaders/search-index.ts b/packages/medusa/src/loaders/search-index.ts index e03be7c491..ea8937cfef 100644 --- a/packages/medusa/src/loaders/search-index.ts +++ b/packages/medusa/src/loaders/search-index.ts @@ -4,7 +4,9 @@ import { MedusaContainer } from "../types/global" import DefaultSearchService from "../services/search" import { Logger } from "../types/global" -async function loadProductsIntoSearchEngine(container: MedusaContainer): Promise { +async function loadProductsIntoSearchEngine( + container: MedusaContainer +): Promise { const searchService = container.resolve("searchService") const productService = container.resolve("productService") @@ -61,7 +63,11 @@ async function loadProductsIntoSearchEngine(container: MedusaContainer): Promise } } -export default async ({ container }: { container: MedusaContainer }): Promise => { +export default async ({ + container, +}: { + container: MedusaContainer +}): Promise => { const searchService = container.resolve("searchService") const logger = container.resolve("logger") if (searchService.isDefault) { diff --git a/packages/medusa/src/repositories/cart.ts b/packages/medusa/src/repositories/cart.ts index c0b640219b..a9a955970b 100644 --- a/packages/medusa/src/repositories/cart.ts +++ b/packages/medusa/src/repositories/cart.ts @@ -5,7 +5,7 @@ import { Cart } from "../models/cart" @EntityRepository(Cart) export class CartRepository extends Repository { public async findWithRelations( - relations: Array = [], + relations: string[] = [], optionsWithoutRelations: Omit, "relations"> = {} ): Promise { const entities = await this.find(optionsWithoutRelations) @@ -38,7 +38,7 @@ export class CartRepository extends Repository { } public async findOneWithRelations( - relations: Array = [], + relations: string[] = [], optionsWithoutRelations: Omit, "relations"> = {} ): Promise { // Limit 1 diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index 991da99d99..571b7c26a7 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -193,6 +193,9 @@ describe("CartService", () => { describe("create", () => { const regionService = { + withTransaction: function() { + return this + }, retrieve: () => { return { id: IdMap.getId("testRegion"), @@ -324,7 +327,7 @@ describe("CartService", () => { } const shippingOptionService = { - deleteShippingMethod: jest.fn(), + deleteShippingMethods: jest.fn(), withTransaction: function() { return this }, @@ -378,6 +381,7 @@ describe("CartService", () => { totalsService, cartRepository, lineItemService, + lineItemRepository: MockRepository(), eventBusService, shippingOptionService, inventoryService, @@ -476,7 +480,7 @@ describe("CartService", () => { await cartService.addLineItem(IdMap.getId("cartWithLine"), lineItem) - expect(lineItemService.update).toHaveBeenCalledTimes(2) + expect(lineItemService.update).toHaveBeenCalledTimes(1) expect(lineItemService.update).toHaveBeenCalledWith( IdMap.getId("merger"), { @@ -581,7 +585,7 @@ describe("CartService", () => { }) const shippingOptionService = { - deleteShippingMethod: jest.fn(), + deleteShippingMethods: jest.fn(), withTransaction: function() { return this }, @@ -592,6 +596,7 @@ describe("CartService", () => { totalsService, cartRepository, lineItemService, + lineItemRepository: MockRepository(), shippingOptionService, eventBusService, lineItemAdjustmentService: LineItemAdjustmentServiceMock, @@ -641,15 +646,15 @@ describe("CartService", () => { IdMap.getId("itemToRemove") ) - expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledTimes( + expect(shippingOptionService.deleteShippingMethods).toHaveBeenCalledTimes( 1 ) - expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledWith({ + expect(shippingOptionService.deleteShippingMethods).toHaveBeenCalledWith([{ id: IdMap.getId("ship-method"), shipping_option: { profile_id: IdMap.getId("prevPro"), }, - }) + }]) expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledTimes(1) expect(LineItemAdjustmentServiceMock.delete).toHaveBeenCalledWith({ @@ -1095,6 +1100,9 @@ describe("CartService", () => { countries: [{ iso_2: "us" }], }) ), + withTransaction: function() { + return this + }, } const cartRepository = MockRepository({ findOneWithRelations: () => @@ -1131,6 +1139,9 @@ describe("CartService", () => { } const priceSelectionStrat = { + withTransaction: function() { + return this + }, calculateVariantPrice: async (variantId, context) => { if (variantId === IdMap.getId("fail")) { throw new MedusaError( @@ -1152,6 +1163,7 @@ describe("CartService", () => { lineItemService, productVariantService, eventBusService, + paymentSessionRepository: MockRepository(), priceSelectionStrategy: priceSelectionStrat, }) @@ -1192,7 +1204,7 @@ describe("CartService", () => { shipping_address: { country_code: "us", }, - items: [IdMap.getId("testitem"), null], + items: [IdMap.getId("testitem")], payment_session: null, payment_sessions: [], gift_cards: [], @@ -1513,13 +1525,16 @@ describe("CartService", () => { }, }) }), - deleteShippingMethod: jest.fn(), + deleteShippingMethods: jest.fn(), withTransaction: function() { return this }, } const customShippingOptionService = { + withTransaction: function() { + return this + }, list: jest.fn().mockImplementation(({ cart_id }) => { if (cart_id === IdMap.getId("cart-with-custom-so")) { return [ @@ -1575,7 +1590,7 @@ describe("CartService", () => { expect( shippingOptionService.createShippingMethod ).toHaveBeenCalledWith(IdMap.getId("profile1"), data, { cart: cart2 }) - expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledWith({ + expect(shippingOptionService.deleteShippingMethods).toHaveBeenCalledWith({ id: IdMap.getId("ship1"), shipping_option: { profile_id: IdMap.getId("profile1"), @@ -1594,7 +1609,7 @@ describe("CartService", () => { data ) - expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledTimes( + expect(shippingOptionService.deleteShippingMethods).toHaveBeenCalledTimes( 0 ) expect(shippingOptionService.createShippingMethod).toHaveBeenCalledTimes( @@ -1616,7 +1631,7 @@ describe("CartService", () => { data ) - expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledTimes( + expect(shippingOptionService.deleteShippingMethods).toHaveBeenCalledTimes( 0 ) expect(shippingOptionService.createShippingMethod).toHaveBeenCalledTimes( @@ -1738,6 +1753,9 @@ describe("CartService", () => { }) const discountService = { + withTransaction: function () { + return this + }, retrieveByCode: jest.fn().mockImplementation((code) => { if (code === "US10") { return Promise.resolve({ diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 37f13a038e..66bb41f46f 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -1228,7 +1228,7 @@ describe("OrderService", () => { .mockImplementation((optionId, data, config) => Promise.resolve({ shipping_option: { profile_id: optionId } }) ), - deleteShippingMethod: jest + deleteShippingMethods: jest .fn() .mockImplementation(() => Promise.resolve({})), @@ -1276,7 +1276,7 @@ describe("OrderService", () => { } ) - expect(optionService.deleteShippingMethod).not.toHaveBeenCalled() + expect(optionService.deleteShippingMethods).not.toHaveBeenCalled() }) it("successfully removes shipping method if same option profile", async () => { @@ -1305,8 +1305,8 @@ describe("OrderService", () => { } ) - expect(optionService.deleteShippingMethod).toHaveBeenCalledTimes(1) - expect(optionService.deleteShippingMethod).toHaveBeenCalledWith({ + expect(optionService.deleteShippingMethods).toHaveBeenCalledTimes(1) + expect(optionService.deleteShippingMethods).toHaveBeenCalledWith({ shipping_option: { profile_id: IdMap.getId("method1"), }, diff --git a/packages/medusa/src/services/cart.ts b/packages/medusa/src/services/cart.ts index 64a114a781..bcdde39bae 100644 --- a/packages/medusa/src/services/cart.ts +++ b/packages/medusa/src/services/cart.ts @@ -1,7 +1,7 @@ import _ from "lodash" import { MedusaError, Validator } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" -import { DeepPartial, EntityManager } from "typeorm" +import { DeepPartial, EntityManager, In } from "typeorm" import { IPriceSelectionStrategy } from "../interfaces/price-selection-strategy" import { Address } from "../models/address" import { Cart } from "../models/cart" @@ -36,13 +36,15 @@ import TotalsService from "./totals" import InventoryService from "./inventory" import CustomShippingOptionService from "./custom-shipping-option" import LineItemAdjustmentService from "./line-item-adjustment" +import { LineItemRepository } from "../repositories/line-item" -type CartConstructorProps = { +type InjectedDependencies = { manager: EntityManager cartRepository: typeof CartRepository shippingMethodRepository: typeof ShippingMethodRepository addressRepository: typeof AddressRepository paymentSessionRepository: typeof PaymentSessionRepository + lineItemRepository: typeof LineItemRepository eventBusService: EventBusService taxProviderService: TaxProviderService paymentProviderService: PaymentProviderService @@ -75,32 +77,34 @@ class CartService extends BaseService { UPDATED: "cart.updated", } - private manager_: EntityManager - private shippingMethodRepository_: typeof ShippingMethodRepository - private cartRepository_: typeof CartRepository - private eventBus_: EventBusService - private productVariantService_: ProductVariantService - private productService_: ProductService - private regionService_: RegionService - private lineItemService_: LineItemService - private paymentProviderService_: PaymentProviderService - private customerService_: CustomerService - private shippingOptionService_: ShippingOptionService - private discountService_: DiscountService - private giftCardService_: GiftCardService - private taxProviderService_: TaxProviderService - private totalsService_: TotalsService - private addressRepository_: typeof AddressRepository - private paymentSessionRepository_: typeof PaymentSessionRepository - private inventoryService_: InventoryService - private customShippingOptionService_: CustomShippingOptionService - private lineItemAdjustmentService_: LineItemAdjustmentService - private priceSelectionStrategy_: IPriceSelectionStrategy + protected readonly manager_: EntityManager + protected readonly shippingMethodRepository_: typeof ShippingMethodRepository + protected readonly cartRepository_: typeof CartRepository + protected readonly addressRepository_: typeof AddressRepository + protected readonly paymentSessionRepository_: typeof PaymentSessionRepository + protected readonly lineItemRepository_: typeof LineItemRepository + protected readonly eventBus_: EventBusService + protected readonly productVariantService_: ProductVariantService + protected readonly productService_: ProductService + protected readonly regionService_: RegionService + protected readonly lineItemService_: LineItemService + protected readonly paymentProviderService_: PaymentProviderService + protected readonly customerService_: CustomerService + protected readonly shippingOptionService_: ShippingOptionService + protected readonly discountService_: DiscountService + protected readonly giftCardService_: GiftCardService + protected readonly taxProviderService_: TaxProviderService + protected readonly totalsService_: TotalsService + protected readonly inventoryService_: InventoryService + protected readonly customShippingOptionService_: CustomShippingOptionService + protected readonly priceSelectionStrategy_: IPriceSelectionStrategy + protected readonly lineItemAdjustmentService_: LineItemAdjustmentService constructor({ manager, cartRepository, shippingMethodRepository, + lineItemRepository, eventBusService, paymentProviderService, productService, @@ -119,12 +123,13 @@ class CartService extends BaseService { customShippingOptionService, lineItemAdjustmentService, priceSelectionStrategy, - }: CartConstructorProps) { + }: InjectedDependencies) { super() this.manager_ = manager this.shippingMethodRepository_ = shippingMethodRepository this.cartRepository_ = cartRepository + this.lineItemRepository_ = lineItemRepository this.eventBus_ = eventBusService this.productVariantService_ = productVariantService this.productService_ = productService @@ -154,6 +159,7 @@ class CartService extends BaseService { manager: transactionManager, taxProviderService: this.taxProviderService_, cartRepository: this.cartRepository_, + lineItemRepository: this.lineItemRepository_, eventBusService: this.eventBus_, paymentProviderService: this.paymentProviderService_, paymentSessionRepository: this.paymentSessionRepository_, @@ -179,7 +185,7 @@ class CartService extends BaseService { return cloned } - transformQueryForTotals_( + protected transformQueryForTotals_( config: FindConfig ): FindConfig & { totalsToSelect: TotalField[] } { let { select, relations } = config @@ -229,7 +235,7 @@ class CartService extends BaseService { } } - async decorateTotals_( + protected async decorateTotals_( cart: Cart, totalsToSelect: TotalField[], options: TotalsConfig = { force_taxes: false } @@ -280,10 +286,16 @@ class CartService extends BaseService { selector: FilterableCartProps, config: FindConfig = {} ): Promise { - const cartRepo = this.manager_.getCustomRepository(this.cartRepository_) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ + ) - const query = this.buildQuery_(selector, config) - return await cartRepo.find(query) + const query = this.buildQuery_(selector, config) + return await cartRepo.find(query) + } + ) } /** @@ -298,40 +310,46 @@ class CartService extends BaseService { options: FindConfig = {}, totalsConfig: TotalsConfig = {} ): Promise { - const cartRepo = this.manager_.getCustomRepository(this.cartRepository_) - const validatedId = this.validateId_(cartId) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ + ) + const validatedId = this.validateId_(cartId) - const { select, relations, totalsToSelect } = - this.transformQueryForTotals_(options) + const { select, relations, totalsToSelect } = + this.transformQueryForTotals_(options) - const query = this.buildQuery_( - { id: validatedId }, - { ...options, select, relations } + const query = this.buildQuery_( + { id: validatedId }, + { ...options, select, relations } + ) + + if (relations && relations.length > 0) { + query.relations = relations + } + + if (select && select.length > 0) { + query.select = select + } else { + query.select = undefined + } + + const queryRelations = query.relations + query.relations = undefined + + const raw = await cartRepo.findOneWithRelations(queryRelations, query) + + if (!raw) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Cart with ${cartId} was not found` + ) + } + + return await this.decorateTotals_(raw, totalsToSelect, totalsConfig) + } ) - - if (relations && relations.length > 0) { - query.relations = relations - } - - if (select && select.length > 0) { - query.select = select - } else { - delete query.select - } - - const rels = query.relations - delete query.relations - - const raw = await cartRepo.findOneWithRelations(rels, query) - - if (!raw) { - throw new MedusaError( - MedusaError.Types.NOT_FOUND, - `Cart with ${cartId} was not found` - ) - } - - return await this.decorateTotals_(raw, totalsToSelect, totalsConfig) } /** @@ -340,88 +358,106 @@ class CartService extends BaseService { * @return the result of the create operation */ async create(data: CartCreateProps): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cartRepo = manager.getCustomRepository(this.cartRepository_) - const addressRepo = manager.getCustomRepository(this.addressRepository_) - - const { region_id } = data - if (!region_id) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - `A region_id must be provided when creating a cart` + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ + ) + const addressRepo = transactionManager.getCustomRepository( + this.addressRepository_ ) - } - const region = await this.regionService_.retrieve(region_id, { - relations: ["countries"], - }) - const regCountries = region.countries.map(({ iso_2 }) => iso_2) - - const toCreate: DeepPartial = {} - toCreate.region_id = region.id - - if (typeof data.email !== "undefined") { - const customer = await this.createOrFetchUserFromEmail_(data.email) - toCreate.customer = customer - toCreate.customer_id = customer.id - toCreate.email = customer.email - } - - if (typeof data.shipping_address_id !== "undefined") { - const addr = await addressRepo.findOne(data.shipping_address_id) - if (addr && !regCountries.includes(addr.country_code)) { + const { region_id } = data + if (!region_id) { throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "Shipping country not in region" + MedusaError.Types.INVALID_DATA, + `A region_id must be provided when creating a cart` ) } - toCreate.shipping_address = addr - } + const rawCart: DeepPartial = {} - if (!data.shipping_address) { - if (region.countries.length === 1) { - // Preselect the country if the region only has 1 - // and create address entity - toCreate.shipping_address = addressRepo.create({ - country_code: regCountries[0], + if (data.email) { + const customer = await this.createOrFetchUserFromEmail_(data.email) + rawCart.customer = customer + rawCart.customer_id = customer.id + rawCart.email = customer.email + } + + const region = await this.regionService_ + .withTransaction(transactionManager) + .retrieve(region_id, { + relations: ["countries"], }) - } - } else { - if (!regCountries.includes(data.shipping_address.country_code)) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "Shipping country not in region" - ) + const regCountries = region.countries.map(({ iso_2 }) => iso_2) + + rawCart.region_id = region.id + + if (data.shipping_address_id !== undefined) { + const shippingAddress = data.shipping_address_id + ? await addressRepo.findOne(data.shipping_address_id) + : null + + if ( + shippingAddress && + !regCountries.includes(shippingAddress.country_code) + ) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Shipping country not in region" + ) + } + + rawCart.shipping_address = shippingAddress } - toCreate.shipping_address = data.shipping_address + if (!data.shipping_address) { + if (region.countries.length === 1) { + // Preselect the country if the region only has 1 + // and create address entity + rawCart.shipping_address = addressRepo.create({ + country_code: regCountries[0], + }) + } + } else { + if (!regCountries.includes(data.shipping_address.country_code)) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Shipping country not in region" + ) + } + + rawCart.shipping_address = data.shipping_address + } + + const remainingFields: (keyof Cart)[] = [ + "billing_address_id", + "context", + "type", + "metadata", + "discounts", + "gift_cards", + ] + + for (const remainingField of remainingFields) { + if ( + typeof data[remainingField] !== "undefined" && + remainingField !== "object" + ) { + rawCart[remainingField] = data[remainingField] + } + } + + const createdCart = cartRepo.create(rawCart) + const cart = await cartRepo.save(createdCart) + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.CREATED, { + id: cart.id, + }) + return cart } - - const remainingFields: (keyof Cart)[] = [ - "billing_address_id", - "context", - "type", - "metadata", - "discounts", - "gift_cards", - ] - - for (const k of remainingFields) { - if (typeof data[k] !== "undefined" && k !== "object") { - toCreate[k] = data[k] - } - } - - const inProgress = cartRepo.create(toCreate) - const result = await cartRepo.save(inProgress) - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.CREATED, { - id: result.id, - }) - return result - }) + ) } /** @@ -431,52 +467,61 @@ class CartService extends BaseService { * @return the result of the update operation */ async removeLineItem(cartId: string, lineItemId: string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: [ - "items", - "items.variant", - "items.variant.product", - "payment_sessions", - ], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: [ + "items", + "items.variant", + "items.variant.product", + "payment_sessions", + ], + }) - const lineItem = cart.items.find((li) => li.id === lineItemId) - if (!lineItem) { - return cart - } - - // Remove shipping methods if they are not needed - if (cart.shipping_methods && cart.shipping_methods.length) { - for (const method of cart.shipping_methods) { - await this.shippingOptionService_ - .withTransaction(manager) - .deleteShippingMethod(method) + const lineItem = cart.items.find((item) => item.id === lineItemId) + if (!lineItem) { + return cart } - } - for (const itm of cart.items) { - await this.lineItemService_.withTransaction(manager).update(itm.id, { - has_shipping: false, + // Remove shipping methods if they are not needed + if (cart.shipping_methods?.length) { + await this.shippingOptionService_ + .withTransaction(transactionManager) + .deleteShippingMethods(cart.shipping_methods) + } + + const lineItemRepository = transactionManager.getCustomRepository( + this.lineItemRepository_ + ) + await lineItemRepository.update( + { + id: In(cart.items.map((item) => item.id)), + }, + { + has_shipping: false, + } + ) + + await this.lineItemService_ + .withTransaction(transactionManager) + .delete(lineItem.id) + + const result = await this.retrieve(cartId, { + relations: ["items", "discounts", "discounts.rule"], }) + + await this.refreshAdjustments_(result) + + // Notify subscribers + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, { + id: cart.id, + }) + + return this.retrieve(cartId) } - - await this.lineItemService_.withTransaction(manager).delete(lineItem.id) - - const result = await this.retrieve(cartId, { - relations: ["items", "discounts", "discounts.rule"], - }) - - await this.refreshAdjustments_(result) - - // Notify subscribers - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, { - id: result.id, - }) - return result - }) + ) } /** @@ -487,7 +532,7 @@ class CartService extends BaseService { * @param lineItem - the line item * @return boolean representing wheter shipping method is validated */ - validateLineItemShipping_( + protected validateLineItemShipping_( shippingMethods: ShippingMethod[], lineItem: LineItem ): boolean { @@ -518,84 +563,83 @@ class CartService extends BaseService { * @return the result of the update operation */ async addLineItem(cartId: string, lineItem: LineItem): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: [ - "shipping_methods", - "items", - "items.adjustments", - "payment_sessions", - "items.variant", - "items.variant.product", - "discounts", - "discounts.rule", - ], - }) - - let currentItem: LineItem | undefined - if (lineItem.should_merge) { - currentItem = cart.items.find((line) => { - if (line.should_merge && line.variant_id === lineItem.variant_id) { - return _.isEqual(line.metadata, lineItem.metadata) - } - return false + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: [ + "shipping_methods", + "items", + "items.adjustments", + "payment_sessions", + "items.variant", + "items.variant.product", + "discounts", + "discounts.rule", + ], }) - } - // If content matches one of the line items currently in the cart we can - // simply update the quantity of the existing line item - if (currentItem) { - const newQuantity = currentItem.quantity + lineItem.quantity - // Confirm inventory or throw error - await this.inventoryService_ - .withTransaction(manager) - .confirmInventory(lineItem.variant_id, newQuantity) - - await this.lineItemService_ - .withTransaction(manager) - .update(currentItem.id, { - quantity: newQuantity, + let currentItem: LineItem | undefined + if (lineItem.should_merge) { + currentItem = cart.items.find((item) => { + if (item.should_merge && item.variant_id === lineItem.variant_id) { + return _.isEqual(item.metadata, lineItem.metadata) + } + return false }) - } else { + } + + // If content matches one of the line items currently in the cart we can + // simply update the quantity of the existing line item + const quantity = currentItem + ? (currentItem.quantity += lineItem.quantity) + : lineItem.quantity + // Confirm inventory or throw error await this.inventoryService_ - .withTransaction(manager) - .confirmInventory(lineItem.variant_id, lineItem.quantity) + .withTransaction(transactionManager) + .confirmInventory(lineItem.variant_id, quantity) - await this.lineItemService_.withTransaction(manager).create({ - ...lineItem, - has_shipping: false, - cart_id: cartId, - }) - } - - for (const itm of cart.items) { - await this.lineItemService_.withTransaction(manager).update(itm.id, { - has_shipping: false, - }) - } - - // Remove shipping methods - if (cart.shipping_methods && cart.shipping_methods.length) { - for (const method of cart.shipping_methods) { - await this.shippingOptionService_ - .withTransaction(manager) - .deleteShippingMethod(method) + if (currentItem) { + await this.lineItemService_ + .withTransaction(transactionManager) + .update(currentItem.id, { + quantity: currentItem.quantity, + }) + } else { + await this.lineItemService_ + .withTransaction(transactionManager) + .create({ + ...lineItem, + has_shipping: false, + cart_id: cartId, + }) } + + const lineItemRepository = transactionManager.getCustomRepository( + this.lineItemRepository_ + ) + await lineItemRepository.update( + { + id: In(cart.items.map((item) => item.id)), + }, + { + has_shipping: false, + } + ) + + const result = await this.retrieve(cartId, { + relations: ["items", "discounts", "discounts.rule"], + }) + + await this.refreshAdjustments_(result) + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, result) + + return result } - - const result = await this.retrieve(cartId, { - relations: ["items", "discounts", "discounts.rule"], - }) - - await this.refreshAdjustments_(result) - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - - return result - }) + ) } /** @@ -610,49 +654,55 @@ class CartService extends BaseService { lineItemId: string, lineItemUpdate: LineItemUpdate ): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: ["items", "items.adjustments", "payment_sessions"], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: ["items", "items.adjustments", "payment_sessions"], + }) - // Ensure that the line item exists in the cart - const lineItemExists = cart.items.find((i) => i.id === lineItemId) - if (!lineItemExists) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "A line item with the provided id doesn't exist in the cart" - ) - } - - if (lineItemUpdate.quantity) { - const hasInventory = await this.inventoryService_ - .withTransaction(manager) - .confirmInventory(lineItemExists.variant_id, lineItemUpdate.quantity) - - if (!hasInventory) { + // Ensure that the line item exists in the cart + const lineItemExists = cart.items.find((i) => i.id === lineItemId) + if (!lineItemExists) { throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "Inventory doesn't cover the desired quantity" + MedusaError.Types.INVALID_DATA, + "A line item with the provided id doesn't exist in the cart" ) } + + if (lineItemUpdate.quantity) { + const hasInventory = await this.inventoryService_ + .withTransaction(transactionManager) + .confirmInventory( + lineItemExists.variant_id, + lineItemUpdate.quantity + ) + + if (!hasInventory) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Inventory doesn't cover the desired quantity" + ) + } + } + + await this.lineItemService_ + .withTransaction(transactionManager) + .update(lineItemId, lineItemUpdate) + + const updatedCart = await this.retrieve(cartId, { + relations: ["items", "discounts", "discounts.rule"], + }) + + await this.refreshAdjustments_(updatedCart) + + // Update the line item + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + + return updatedCart } - - await this.lineItemService_ - .withTransaction(manager) - .update(lineItemId, lineItemUpdate) - - const result = await this.retrieve(cartId, { - relations: ["items", "discounts", "discounts.rule"], - }) - - await this.refreshAdjustments_(result) - - // Update the line item - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }) + ) } /** @@ -666,185 +716,180 @@ class CartService extends BaseService { */ async adjustFreeShipping_(cart: Cart, shouldAdd: boolean): Promise { if (cart.shipping_methods?.length) { + const shippingMethodRepository = + this.transactionManager_.getCustomRepository( + this.shippingMethodRepository_ + ) + // if any free shipping discounts, we ensure to update shipping method amount if (shouldAdd) { - await Promise.all( - cart.shipping_methods.map(async (sm) => { - const smRepo = this.manager_.getCustomRepository( - this.shippingMethodRepository_ - ) - - const method = await smRepo.findOne({ where: { id: sm.id } }) - - if (method) { - method.price = 0 - await smRepo.save(method) - } - }) + return shippingMethodRepository.update( + { + id: In( + cart.shipping_methods.map((shippingMethod) => shippingMethod.id) + ), + }, + { + price: 0, + } ) } else { await Promise.all( - cart.shipping_methods.map(async (sm) => { - const smRepo = this.manager_.getCustomRepository( - this.shippingMethodRepository_ - ) - + cart.shipping_methods.map(async (shippingMethod) => { // if free shipping discount is removed, we adjust the shipping // back to its original amount - sm.price = sm.shipping_option.amount - await smRepo.save(sm) + shippingMethod.price = shippingMethod.shipping_option.amount + return shippingMethodRepository.save(shippingMethod) }) ) } } } - async update(cartId: string, update: CartUpdateProps): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cartRepo = manager.getCustomRepository(this.cartRepository_) - const cart = await this.retrieve(cartId, { - select: [ - "subtotal", - "tax_total", - "shipping_total", - "discount_total", - "total", - ], - relations: [ - "items", - "shipping_methods", - "shipping_address", - "billing_address", - "gift_cards", - "customer", - "region", - "payment_sessions", - "region.countries", - "discounts", - "discounts.rule", - "discounts.regions", - ], - }) + async update(cartId: string, data: CartUpdateProps): Promise { + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ + ) + const cart = await this.retrieve(cartId, { + select: [ + "subtotal", + "tax_total", + "shipping_total", + "discount_total", + "total", + ], + relations: [ + "items", + "shipping_methods", + "shipping_address", + "billing_address", + "gift_cards", + "customer", + "region", + "payment_sessions", + "region.countries", + "discounts", + "discounts.rule", + "discounts.regions", + ], + }) - if (typeof update.customer_id !== "undefined") { - await this.updateCustomerId_(cart, update.customer_id) - } else { - if (typeof update.email !== "undefined") { - const customer = await this.createOrFetchUserFromEmail_(update.email) - cart.customer = customer - cart.customer_id = customer.id - cart.email = customer.email - } - } - - if (typeof update.region_id !== "undefined") { - const countryCode = - (update.country_code || update.shipping_address?.country_code) ?? null - await this.setRegion_(cart, update.region_id, countryCode) - } - - if ( - typeof update.customer_id !== "undefined" || - typeof update.region_id !== "undefined" - ) { - await this.updateUnitPrices(cart, update.region_id, update.customer_id) - } - - const addrRepo = manager.getCustomRepository(this.addressRepository_) - if ("shipping_address_id" in update || "shipping_address" in update) { - let address: string | Partial
| undefined - if (typeof update.shipping_address_id !== "undefined") { - address = update.shipping_address_id - } else if (typeof update.shipping_address !== "undefined") { - address = update.shipping_address + if (data.customer_id) { + await this.updateCustomerId_(cart, data.customer_id) + } else { + if (typeof data.email !== "undefined") { + const customer = await this.createOrFetchUserFromEmail_(data.email) + cart.customer = customer + cart.customer_id = customer.id + cart.email = customer.email + } } - if (typeof address !== "undefined") { - await this.updateShippingAddress_(cart, address, addrRepo) - } - } - - if ("billing_address_id" in update || "billing_address" in update) { - let address: string | Partial
| undefined - if (typeof update.billing_address_id !== "undefined") { - address = update.billing_address_id - } else if (typeof update.billing_address !== "undefined") { - address = update.billing_address + if (typeof data.region_id !== "undefined") { + const countryCode = + (data.country_code || data.shipping_address?.country_code) ?? null + await this.setRegion_(cart, data.region_id, countryCode) } - if (typeof address !== "undefined") { - await this.updateBillingAddress_(cart, address, addrRepo) - } - } - - if (typeof update.discounts !== "undefined") { - const previousDiscounts = cart.discounts - cart.discounts = [] - - for (const { code } of update.discounts) { - await this.applyDiscount(cart, code) + if ( + typeof data.customer_id !== "undefined" || + typeof data.region_id !== "undefined" + ) { + await this.updateUnitPrices_(cart, data.region_id, data.customer_id) } - const hasFreeShipping = cart.discounts.some( - ({ rule }) => rule.type === "free_shipping" + const addrRepo = transactionManager.getCustomRepository( + this.addressRepository_ ) - // if we previously had a free shipping discount and then removed it, - // we need to update shipping methods to original price - if ( - previousDiscounts.some(({ rule }) => rule.type === "free_shipping") && - !hasFreeShipping - ) { - await this.adjustFreeShipping_(cart, false) + const billingAddress = data.billing_address_id ?? data.billing_address + if (billingAddress !== undefined) { + await this.updateBillingAddress_(cart, billingAddress, addrRepo) } - if (hasFreeShipping) { - await this.adjustFreeShipping_(cart, true) + const shippingAddress = + data.shipping_address_id ?? data.shipping_address + if (shippingAddress !== undefined) { + await this.updateShippingAddress_(cart, shippingAddress, addrRepo) } - } - if ("gift_cards" in update) { - cart.gift_cards = [] + if (data.discounts?.length) { + const previousDiscounts = [...cart.discounts] + cart.discounts.length = 0 - for (const { code } of update.gift_cards!) { - await this.applyGiftCard_(cart, code) + await Promise.all( + data.discounts.map(({ code }) => { + return this.applyDiscount(cart, code) + }) + ) + + const hasFreeShipping = cart.discounts.some( + ({ rule }) => rule?.type === "free_shipping" + ) + + // if we previously had a free shipping discount and then removed it, + // we need to update shipping methods to original price + if ( + previousDiscounts.some( + ({ rule }) => rule?.type === "free_shipping" + ) && + !hasFreeShipping + ) { + await this.adjustFreeShipping_(cart, false) + } + + if (hasFreeShipping) { + await this.adjustFreeShipping_(cart, true) + } } - } - if ("metadata" in update) { - cart.metadata = this.setMetadata_(cart, update.metadata) - } + if ("gift_cards" in data) { + cart.gift_cards = [] - if ("context" in update) { - const prevContext = cart.context || {} - cart.context = { - ...prevContext, - ...update.context, + await Promise.all( + (data.gift_cards ?? []).map(({ code }) => { + return this.applyGiftCard_(cart, code) + }) + ) } - } - if ("completed_at" in update) { - cart.completed_at = update.completed_at! - } + if ("metadata" in data) { + cart.metadata = this.setMetadata_(cart, data.metadata) + } - if ("payment_authorized_at" in update) { - cart.payment_authorized_at = update.payment_authorized_at! - } + if ("context" in data) { + const prevContext = cart.context || {} + cart.context = { + ...prevContext, + ...data.context, + } + } - const result = await cartRepo.save(cart) + if ("completed_at" in data) { + cart.completed_at = data.completed_at! + } + + if ("payment_authorized_at" in data) { + cart.payment_authorized_at = data.payment_authorized_at! + } + + const updatedCart = await cartRepo.save(cart) + + if ("email" in data || "customer_id" in data) { + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.CUSTOMER_UPDATED, updatedCart.id) + } - if ("email" in update || "customer_id" in update) { await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.CUSTOMER_UPDATED, result.id) + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + + return updatedCart } - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - - return result - }) + ) } /** @@ -853,7 +898,10 @@ class CartService extends BaseService { * @param customerId - the customer to add to cart * @return the result of the update operation */ - async updateCustomerId_(cart: Cart, customerId: string): Promise { + protected async updateCustomerId_( + cart: Cart, + customerId: string + ): Promise { const customer = await this.customerService_ .withTransaction(this.transactionManager_) .retrieve(customerId) @@ -868,7 +916,9 @@ class CartService extends BaseService { * @param email - the email to use * @return the resultign customer object */ - async createOrFetchUserFromEmail_(email: string): Promise { + protected async createOrFetchUserFromEmail_( + email: string + ): Promise { const schema = Validator.string().email().required() const { value, error } = schema.validate(email.toLowerCase()) if (error) { @@ -899,7 +949,7 @@ class CartService extends BaseService { * @param addrRepo - the repository to use for address updates * @return the result of the update operation */ - async updateBillingAddress_( + protected async updateBillingAddress_( cart: Cart, addressOrId: Partial
| string, addrRepo: AddressRepository @@ -916,8 +966,7 @@ class CartService extends BaseService { address.country_code = address.country_code?.toLowerCase() ?? null if (address.id) { - const updated = await addrRepo.save(address) - cart.billing_address = updated + cart.billing_address = await addrRepo.save(address) } else { if (cart.billing_address_id) { const addr = await addrRepo.findOne({ @@ -926,11 +975,9 @@ class CartService extends BaseService { await addrRepo.save({ ...addr, ...address }) } else { - const created = addrRepo.create({ + cart.billing_address = addrRepo.create({ ...address, }) - - cart.billing_address = created } } } @@ -942,7 +989,7 @@ class CartService extends BaseService { * @param addrRepo - the repository to use for address updates * @return the result of the update operation */ - async updateShippingAddress_( + protected async updateShippingAddress_( cart: Cart, addressOrId: Partial
| string, addrRepo: AddressRepository @@ -975,8 +1022,7 @@ class CartService extends BaseService { } if (address.id) { - const updated = await addrRepo.save(address) - cart.shipping_address = updated + cart.shipping_address = await addrRepo.save(address) } else { if (cart.shipping_address_id) { const addr = await addrRepo.findOne({ @@ -985,17 +1031,17 @@ class CartService extends BaseService { await addrRepo.save({ ...addr, ...address }) } else { - const created = addrRepo.create({ + cart.shipping_address = addrRepo.create({ ...address, }) - - cart.shipping_address = created } } } - async applyGiftCard_(cart: Cart, code: string): Promise { - const giftCard = await this.giftCardService_.retrieveByCode(code) + protected async applyGiftCard_(cart: Cart, code: string): Promise { + const giftCard = await this.giftCardService_ + .withTransaction(this.transactionManager_) + .retrieveByCode(code) if (giftCard.is_disabled) { throw new MedusaError( @@ -1013,7 +1059,7 @@ class CartService extends BaseService { // if discount is already there, we simply resolve if (cart.gift_cards.find(({ id }) => id === giftCard.id)) { - return Promise.resolve() + return } cart.gift_cards = [...cart.gift_cards, giftCard] @@ -1029,51 +1075,57 @@ class CartService extends BaseService { * @return the result of the update operation */ async applyDiscount(cart: Cart, discountCode: string): Promise { - return this.atomicPhase_(async (manager) => { - const discount = await this.discountService_.retrieveByCode( - discountCode, - ["rule", "regions"] - ) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const discount = await this.discountService_ + .withTransaction(transactionManager) + .retrieveByCode(discountCode, ["rule", "regions"]) - await this.discountService_.validateDiscountForCartOrThrow(cart, discount) + await this.discountService_ + .withTransaction(transactionManager) + .validateDiscountForCartOrThrow(cart, discount) - const rule = discount.rule + const rule = discount.rule - // if discount is already there, we simply resolve - if (cart.discounts.find(({ id }) => id === discount.id)) { - return Promise.resolve() - } + // if discount is already there, we simply resolve + if (cart.discounts.find(({ id }) => id === discount.id)) { + return + } - const toParse = [...cart.discounts, discount] + const toParse = [...cart.discounts, discount] - let sawNotShipping = false - const newDiscounts = toParse.map((d) => { - const drule = d.rule - switch (drule.type) { - case "free_shipping": - if (d.rule.type === rule.type) { - return discount - } - return d - default: - if (!sawNotShipping) { - sawNotShipping = true - if (rule.type !== "free_shipping") { + let sawNotShipping = false + const newDiscounts = toParse.map((discountToParse) => { + switch (discountToParse.rule?.type) { + case "free_shipping": + if (discountToParse.rule.type === rule.type) { return discount } - return d - } - return null + return discountToParse + default: + if (!sawNotShipping) { + sawNotShipping = true + if (rule?.type !== "free_shipping") { + return discount + } + return discountToParse + } + return null + } + }) + + cart.discounts = newDiscounts.filter( + (newDiscount): newDiscount is Discount => { + return !!newDiscount + } + ) + + // ignore if free shipping + if (rule?.type !== "free_shipping" && cart?.items) { + await this.refreshAdjustments_(cart) } - }) - - cart.discounts = newDiscounts.filter(Boolean) as Discount[] - - // ignore if free shipping - if (rule.type !== "free_shipping" && cart?.items) { - await this.refreshAdjustments_(cart) } - }) + ) } /** @@ -1083,36 +1135,41 @@ class CartService extends BaseService { * @return the resulting cart */ async removeDiscount(cartId: string, discountCode: string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: [ - "discounts", - "discounts.rule", - "payment_sessions", - "shipping_methods", - ], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: [ + "discounts", + "discounts.rule", + "payment_sessions", + "shipping_methods", + ], + }) - if (cart.discounts.some(({ rule }) => rule.type === "free_shipping")) { - await this.adjustFreeShipping_(cart, false) + if (cart.discounts.some(({ rule }) => rule.type === "free_shipping")) { + await this.adjustFreeShipping_(cart, false) + } + + cart.discounts = cart.discounts.filter( + (discount) => discount.code !== discountCode + ) + + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ + ) + const updatedCart = await cartRepo.save(cart) + + if (updatedCart.payment_sessions?.length) { + await this.setPaymentSessions(cartId) + } + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + + return updatedCart } - - cart.discounts = cart.discounts.filter((d) => d.code !== discountCode) - - const cartRepo = manager.getCustomRepository(this.cartRepository_) - - const result = await cartRepo.save(cart) - - if (cart.payment_sessions?.length) { - await this.setPaymentSessions(cartId) - } - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - - return result - }) + ) } /** @@ -1122,25 +1179,26 @@ class CartService extends BaseService { * @return the resulting cart */ async updatePaymentSession(cartId: string, update: object): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: ["payment_sessions"], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: ["payment_sessions"], + }) - if (cart.payment_session) { - await this.paymentProviderService_.updateSessionData( - cart.payment_session, - update - ) + if (cart.payment_session) { + await this.paymentProviderService_ + .withTransaction(transactionManager) + .updateSessionData(cart.payment_session, update) + } + + const updatedCart = await this.retrieve(cart.id) + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart } - - const result = await this.retrieve(cart.id) - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }) + ) } /** @@ -1159,58 +1217,60 @@ class CartService extends BaseService { cartId: string, context: Record = {} ): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cartRepository = manager.getCustomRepository(this.cartRepository_) - - const cart = await this.retrieve(cartId, { - select: ["total"], - relations: ["region", "payment_sessions"], - }) - - if (typeof cart.total === "undefined") { - throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "cart.total should be defined" + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepository = transactionManager.getCustomRepository( + this.cartRepository_ ) + + const cart = await this.retrieve(cartId, { + select: ["total"], + relations: ["region", "payment_sessions"], + }) + + if (typeof cart.total === "undefined") { + throw new MedusaError( + MedusaError.Types.UNEXPECTED_STATE, + "cart.total should be defined" + ) + } + + // If cart total is 0, we don't perform anything payment related + if (cart.total <= 0) { + cart.payment_authorized_at = new Date() + return cartRepository.save(cart) + } + + if (!cart.payment_session) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "You cannot complete a cart without a payment session." + ) + } + + const session = await this.paymentProviderService_ + .withTransaction(transactionManager) + .authorizePayment(cart.payment_session, context) + + const freshCart = await this.retrieve(cart.id, { + select: ["total"], + relations: ["payment_sessions"], + }) + + if (session.status === "authorized") { + freshCart.payment = await this.paymentProviderService_ + .withTransaction(transactionManager) + .createPayment(freshCart) + freshCart.payment_authorized_at = new Date() + } + + const updatedCart = await cartRepository.save(freshCart) + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart } - - // If cart total is 0, we don't perform anything payment related - if (cart.total <= 0) { - cart.payment_authorized_at = new Date() - return cartRepository.save(cart) - } - - if (!cart.payment_session) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "You cannot complete a cart without a payment session." - ) - } - - const session = await this.paymentProviderService_ - .withTransaction(manager) - .authorizePayment(cart.payment_session, context) - - const freshCart = await this.retrieve(cart.id, { - select: ["total"], - relations: ["payment_sessions"], - }) - - if (session.status === "authorized") { - const payment = await this.paymentProviderService_ - .withTransaction(manager) - .createPayment(freshCart) - - freshCart.payment = payment - freshCart.payment_authorized_at = new Date() - } - - const updated = await cartRepository.save(freshCart) - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, updated) - return updated - }) + ) } /** @@ -1220,62 +1280,67 @@ class CartService extends BaseService { * @return result of update operation */ async setPaymentSession(cartId: string, providerId: string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const psRepo = manager.getCustomRepository(this.paymentSessionRepository_) - - const cart = await this.retrieve(cartId, { - select: [ - "total", - "subtotal", - "tax_total", - "discount_total", - "gift_card_total", - ], - relations: ["region", "region.payment_providers", "payment_sessions"], - }) - - // The region must have the provider id in its providers array - if ( - providerId !== "system" && - !( - cart.region.payment_providers.length && - cart.region.payment_providers.find(({ id }) => providerId === id) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const psRepo = transactionManager.getCustomRepository( + this.paymentSessionRepository_ ) - ) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - `The payment method is not available in this region` - ) - } - await Promise.all( - cart.payment_sessions.map(async (ps) => { - return await psRepo.save({ ...ps, is_selected: null }) + const cart = await this.retrieve(cartId, { + select: [ + "total", + "subtotal", + "tax_total", + "discount_total", + "gift_card_total", + ], + relations: ["region", "region.payment_providers", "payment_sessions"], }) - ) - const sess = cart.payment_sessions.find( - (ps) => ps.provider_id === providerId - ) + // The region must have the provider id in its providers array + if ( + providerId !== "system" && + !( + cart.region.payment_providers.length && + cart.region.payment_providers.find(({ id }) => providerId === id) + ) + ) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + `The payment method is not available in this region` + ) + } - if (!sess) { - throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "Could not find payment session" + await Promise.all( + cart.payment_sessions.map(async (paymentSession) => { + return psRepo.save({ ...paymentSession, is_selected: null }) + }) ) - } - sess.is_selected = true + const paymentSession = cart.payment_sessions.find( + (ps) => ps.provider_id === providerId + ) - await psRepo.save(sess) + if (!paymentSession) { + throw new MedusaError( + MedusaError.Types.UNEXPECTED_STATE, + "Could not find payment session" + ) + } - const result = await this.retrieve(cartId) + paymentSession.is_selected = true - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }, "SERIALIZABLE") + await psRepo.save(paymentSession) + + const updatedCart = await this.retrieve(cartId) + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart + }, + "SERIALIZABLE" + ) } /** @@ -1288,94 +1353,103 @@ class CartService extends BaseService { * @return the result of the update operation. */ async setPaymentSessions(cartOrCartId: Cart | string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const psRepo = manager.getCustomRepository(this.paymentSessionRepository_) - - const cartId = - typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id - - const cart = await this.retrieve( - cartId, - { - select: [ - "total", - "subtotal", - "tax_total", - "discount_total", - "shipping_total", - "gift_card_total", - ], - relations: [ - "items", - "discounts", - "discounts.rule", - "gift_cards", - "shipping_methods", - "billing_address", - "shipping_address", - "region", - "region.tax_rates", - "region.payment_providers", - "payment_sessions", - "customer", - ], - }, - { force_taxes: true } - ) - - const { total, region } = cart - - if (typeof total === "undefined") { - throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "cart.total must be defined" + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const psRepo = transactionManager.getCustomRepository( + this.paymentSessionRepository_ ) - } - // If there are existing payment sessions ensure that these are up to date - const seen: string[] = [] - if (cart.payment_sessions && cart.payment_sessions.length) { - for (const session of cart.payment_sessions) { - if ( - total <= 0 || - !region.payment_providers.find( - ({ id }) => id === session.provider_id - ) - ) { - await this.paymentProviderService_ - .withTransaction(manager) - .deleteSession(session) + const cartId = + typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id + + const cart = await this.retrieve( + cartId, + { + select: [ + "total", + "subtotal", + "tax_total", + "discount_total", + "shipping_total", + "gift_card_total", + ], + relations: [ + "items", + "discounts", + "discounts.rule", + "gift_cards", + "shipping_methods", + "billing_address", + "shipping_address", + "region", + "region.tax_rates", + "region.payment_providers", + "payment_sessions", + "customer", + ], + }, + { force_taxes: true } + ) + + const { total, region } = cart + + if (typeof total === "undefined") { + throw new MedusaError( + MedusaError.Types.UNEXPECTED_STATE, + "cart.total must be defined" + ) + } + + // If there are existing payment sessions ensure that these are up to date + const seen: string[] = [] + if (cart.payment_sessions?.length) { + await Promise.all( + cart.payment_sessions.map(async (paymentSession) => { + if ( + total <= 0 || + !region.payment_providers.find( + ({ id }) => id === paymentSession.provider_id + ) + ) { + return this.paymentProviderService_ + .withTransaction(transactionManager) + .deleteSession(paymentSession) + } else { + seen.push(paymentSession.provider_id) + return this.paymentProviderService_ + .withTransaction(transactionManager) + .updateSession(paymentSession, cart) + } + }) + ) + } + + if (total > 0) { + // If only one payment session exists, we preselect it + if (region.payment_providers.length === 1 && !cart.payment_session) { + const paymentProvider = region.payment_providers[0] + const paymentSession = await this.paymentProviderService_ + .withTransaction(transactionManager) + .createSession(paymentProvider.id, cart) + + paymentSession.is_selected = true + + await psRepo.save(paymentSession) } else { - seen.push(session.provider_id) - await this.paymentProviderService_ - .withTransaction(manager) - .updateSession(session, cart) + await Promise.all( + region.payment_providers.map(async (paymentProvider) => { + if (!seen.includes(paymentProvider.id)) { + return this.paymentProviderService_ + .withTransaction(transactionManager) + .createSession(paymentProvider.id, cart) + } + return + }) + ) } } } - - if (total > 0) { - // If only one payment session exists, we preselect it - if (region.payment_providers.length === 1 && !cart.payment_session) { - const p = region.payment_providers[0] - const sess = await this.paymentProviderService_ - .withTransaction(manager) - .createSession(p.id, cart) - - sess.is_selected = true - - await psRepo.save(sess) - } else { - for (const provider of region.payment_providers) { - if (!seen.includes(provider.id)) { - await this.paymentProviderService_ - .withTransaction(manager) - .createSession(provider.id, cart) - } - } - } - } - }) + ) } /** @@ -1389,37 +1463,41 @@ class CartService extends BaseService { cartId: string, providerId: string ): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: ["payment_sessions"], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: ["payment_sessions"], + }) - const cartRepo = manager.getCustomRepository(this.cartRepository_) - - if (cart.payment_sessions) { - const session = cart.payment_sessions.find( - ({ provider_id }) => provider_id === providerId + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ ) - cart.payment_sessions = cart.payment_sessions.filter( - ({ provider_id }) => provider_id !== providerId - ) + if (cart.payment_sessions) { + const paymentSession = cart.payment_sessions.find( + ({ provider_id }) => provider_id === providerId + ) - if (session) { - // Delete the session with the provider - await this.paymentProviderService_ - .withTransaction(manager) - .deleteSession(session) + cart.payment_sessions = cart.payment_sessions.filter( + ({ provider_id }) => provider_id !== providerId + ) + + if (paymentSession) { + // Delete the session with the provider + await this.paymentProviderService_ + .withTransaction(transactionManager) + .deleteSession(paymentSession) + } } + + await cartRepo.save(cart) + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, cart) + return cart } - - await cartRepo.save(cart) - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, cart) - return cart - }) + ) } /** @@ -1433,31 +1511,33 @@ class CartService extends BaseService { cartId: string, providerId: string ): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: ["payment_sessions"], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: ["payment_sessions"], + }) - if (cart.payment_sessions) { - const session = cart.payment_sessions.find( - ({ provider_id }) => provider_id === providerId - ) + if (cart.payment_sessions) { + const paymentSession = cart.payment_sessions.find( + ({ provider_id }) => provider_id === providerId + ) - if (session) { - // Delete the session with the provider - await this.paymentProviderService_ - .withTransaction(manager) - .refreshSession(session, cart) + if (paymentSession) { + // Delete the session with the provider + await this.paymentProviderService_ + .withTransaction(transactionManager) + .refreshSession(paymentSession, cart) + } } + + const updatedCart = await this.retrieve(cartId) + + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart } - - const result = await this.retrieve(cartId) - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }) + ) } /** @@ -1476,83 +1556,96 @@ class CartService extends BaseService { optionId: string, data: Record = {} ): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - select: ["subtotal"], - relations: [ - "shipping_methods", - "discounts", - "discounts.rule", - "shipping_methods.shipping_option", - "items", - "items.variant", - "payment_sessions", - "items.variant.product", - ], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + select: ["subtotal"], + relations: [ + "shipping_methods", + "discounts", + "discounts.rule", + "shipping_methods.shipping_option", + "items", + "items.variant", + "payment_sessions", + "items.variant.product", + ], + }) - const cartCustomShippingOptions = - await this.customShippingOptionService_.list({ cart_id: cart.id }) + const cartCustomShippingOptions = + await this.customShippingOptionService_ + .withTransaction(transactionManager) + .list({ cart_id: cart.id }) - const customShippingOption = this.findCustomShippingOption( - cartCustomShippingOptions, - optionId - ) + const customShippingOption = this.findCustomShippingOption( + cartCustomShippingOptions, + optionId + ) - const { shipping_methods } = cart + const { shipping_methods } = cart - /** - * If we have a custom shipping option configured we want the price - * override to take effect and do not want `validateCartOption` to check - * if requirements are met, hence we are not passing the entire cart, but - * just the id. - */ - const shippingMethodConfig = customShippingOption - ? { cart_id: cart.id, price: customShippingOption.price } - : { - cart, - } + /** + * If we have a custom shipping option configured we want the price + * override to take effect and do not want `validateCartOption` to check + * if requirements are met, hence we are not passing the entire cart, but + * just the id. + */ + const shippingMethodConfig = customShippingOption + ? { cart_id: cart.id, price: customShippingOption.price } + : { cart } - const newMethod = await this.shippingOptionService_ - .withTransaction(manager) - .createShippingMethod(optionId, data, shippingMethodConfig) + const newShippingMethod = await this.shippingOptionService_ + .withTransaction(transactionManager) + .createShippingMethod(optionId, data, shippingMethodConfig) - const methods = [newMethod] - if (shipping_methods.length) { - for (const sm of shipping_methods) { - if ( - sm.shipping_option.profile_id === - newMethod.shipping_option.profile_id - ) { - await this.shippingOptionService_ - .withTransaction(manager) - .deleteShippingMethod(sm) - } else { - methods.push(sm) + const methods = [newShippingMethod] + if (shipping_methods?.length) { + for (const shippingMethod of shipping_methods) { + if ( + shippingMethod.shipping_option.profile_id === + newShippingMethod.shipping_option.profile_id + ) { + await this.shippingOptionService_ + .withTransaction(transactionManager) + .deleteShippingMethods(shippingMethod) + } else { + methods.push(shippingMethod) + } } } - } - for (const item of cart.items) { - await this.lineItemService_.withTransaction(manager).update(item.id, { - has_shipping: this.validateLineItemShipping_(methods, item), + if (cart.items?.length) { + await Promise.all( + cart.items.map(async (item) => { + return this.lineItemService_ + .withTransaction(transactionManager) + .update(item.id, { + has_shipping: this.validateLineItemShipping_(methods, item), + }) + }) + ) + } + + const updatedCart = await this.retrieve(cartId, { + relations: ["discounts", "discounts.rule", "shipping_methods"], }) - } - const result = await this.retrieve(cartId, { - relations: ["discounts", "discounts.rule", "shipping_methods"], - }) + // if cart has freeshipping, adjust price + if ( + updatedCart.discounts.some( + ({ rule }) => rule.type === "free_shipping" + ) + ) { + await this.adjustFreeShipping_(updatedCart, true) + } - // if cart has freeshipping, adjust price - if (result.discounts.some(({ rule }) => rule.type === "free_shipping")) { - await this.adjustFreeShipping_(result, true) - } - - await this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }, "SERIALIZABLE") + await this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart + }, + "SERIALIZABLE" + ) } /** @@ -1581,7 +1674,7 @@ class CartService extends BaseService { return customOption } - async updateUnitPrices( + protected async updateUnitPrices_( cart: Cart, regionId?: string, customer_id?: string @@ -1589,18 +1682,18 @@ class CartService extends BaseService { // If the cart contains items, we update the price of the items // to match the updated region or customer id (keeping the old // value if it exists) - if (cart.items.length) { - const region = await this.regionService_.retrieve( - regionId || cart.region_id, - { + if (cart.items?.length) { + const region = await this.regionService_ + .withTransaction(this.transactionManager_) + .retrieve(regionId || cart.region_id, { relations: ["countries"], - } - ) + }) - cart.items = await Promise.all( - cart.items - .map(async (item) => { + cart.items = ( + await Promise.all( + cart.items.map(async (item) => { const availablePrice = await this.priceSelectionStrategy_ + .withTransaction(this.transactionManager_) .calculateVariantPrice(item.variant_id, { region_id: region.id, currency_code: region.currency_code, @@ -1627,8 +1720,8 @@ class CartService extends BaseService { return null } }) - .filter(Boolean) - ) + ) + ).filter((item): item is LineItem => item) } } @@ -1639,7 +1732,7 @@ class CartService extends BaseService { * @param countryCode - the country code to set the country to * @return the result of the update operation */ - async setRegion_( + protected async setRegion_( cart: Cart, regionId: string, countryCode: string | null @@ -1652,10 +1745,14 @@ class CartService extends BaseService { } // Set the new region for the cart - const region = await this.regionService_.retrieve(regionId, { - relations: ["countries"], - }) - const addrRepo = this.manager_.getCustomRepository(this.addressRepository_) + const region = await this.regionService_ + .withTransaction(this.transactionManager_) + .retrieve(regionId, { + relations: ["countries"], + }) + const addrRepo = this.transactionManager_.getCustomRepository( + this.addressRepository_ + ) cart.region = region cart.region_id = region.id @@ -1730,34 +1827,29 @@ class CartService extends BaseService { // Shipping methods are determined by region so the user needs to find a // new shipping method if (cart.shipping_methods && cart.shipping_methods.length) { - const smRepo = this.manager_.getCustomRepository( - this.shippingMethodRepository_ - ) - await smRepo.remove(cart.shipping_methods) + await this.shippingOptionService_ + .withTransaction(this.transactionManager_) + .deleteShippingMethods(cart.shipping_methods) } if (cart.discounts && cart.discounts.length) { - const newDiscounts = cart.discounts.map((d) => { - if (d.regions.find(({ id }) => id === regionId)) { - return d - } - return null + cart.discounts = cart.discounts.filter((discount) => { + return discount.regions.find(({ id }) => id === regionId) }) - - cart.discounts = newDiscounts.filter(Boolean) as Discount[] } cart.gift_cards = [] if (cart.payment_sessions && cart.payment_sessions.length) { - await Promise.all( - cart.payment_sessions.map((ps) => - this.paymentProviderService_ - .withTransaction(this.manager_) - .deleteSession(ps) - ) + const paymentSessionRepo = this.transactionManager_.getCustomRepository( + this.paymentSessionRepository_ ) - cart.payment_sessions = [] + await paymentSessionRepo.delete({ + id: In( + cart.payment_sessions.map((paymentSession) => paymentSession.id) + ), + }) + cart.payment_sessions.length = 0 cart.payment_session = null } } @@ -1768,28 +1860,37 @@ class CartService extends BaseService { * @return the deleted cart or undefined if the cart was not found. */ async delete(cartId: string): Promise { - return await this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(cartId, { - relations: ["items", "discounts", "discounts.rule", "payment_sessions"], - }) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(cartId, { + relations: [ + "items", + "discounts", + "discounts.rule", + "payment_sessions", + ], + }) - if (cart.completed_at) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "Completed carts cannot be deleted" + if (cart.completed_at) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Completed carts cannot be deleted" + ) + } + + if (cart.payment_authorized_at) { + throw new MedusaError( + MedusaError.Types.NOT_ALLOWED, + "Can't delete a cart with an authorized payment" + ) + } + + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ ) + return cartRepo.remove(cart) } - - if (cart.payment_authorized_at) { - throw new MedusaError( - MedusaError.Types.NOT_ALLOWED, - "Can't delete a cart with an authorized payment" - ) - } - - const cartRepo = manager.getCustomRepository(this.cartRepository_) - return cartRepo.remove(cart) - }) + ) } /** @@ -1806,70 +1907,86 @@ class CartService extends BaseService { key: string, value: string | number ): Promise { - return await this.atomicPhase_(async (manager: EntityManager) => { - const cartRepo = manager.getCustomRepository(this.cartRepository_) - - const validatedId = this.validateId_(cartId) - if (typeof key !== "string") { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Key type is invalid. Metadata keys must be strings" + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ ) + + const validatedId = this.validateId_(cartId) + if (typeof key !== "string") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Key type is invalid. Metadata keys must be strings" + ) + } + + const cart = await cartRepo.findOne(validatedId) + if (!cart) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + "Unable to find the cart with the given id" + ) + } + + const existing = cart.metadata || {} + cart.metadata = { + ...existing, + [key]: value, + } + + const updatedCart = await cartRepo.save(cart) + this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart } - - const cart = (await cartRepo.findOne(validatedId)) as Cart - - const existing = cart.metadata || {} - cart.metadata = { - ...existing, - [key]: value, - } - - const result = await cartRepo.save(cart) - this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }) + ) } async createTaxLines(id: string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cart = await this.retrieve(id, { - relations: [ - "items", - "gift_cards", - "discounts", - "discounts.rule", - "shipping_methods", - "region", - "region.tax_rates", - ], - }) - const calculationContext = this.totalsService_.getCalculationContext(cart) + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cart = await this.retrieve(id, { + relations: [ + "items", + "gift_cards", + "discounts", + "discounts.rule", + "shipping_methods", + "region", + "region.tax_rates", + ], + }) + const calculationContext = this.totalsService_ + .withTransaction(transactionManager) + .getCalculationContext(cart) - const txTaxProvider = this.taxProviderService_.withTransaction(manager) - await txTaxProvider.createTaxLines(cart, calculationContext) + await this.taxProviderService_ + .withTransaction(transactionManager) + .createTaxLines(cart, calculationContext) - return cart - }) + return cart + } + ) } - async refreshAdjustments_(cart: Cart): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const nonReturnLines = cart.items.filter((item) => !item.is_return) - const nonReturnLineIDs = nonReturnLines.map((i) => i.id) + protected async refreshAdjustments_(cart: Cart): Promise { + const nonReturnLineIDs = cart.items + .filter((item) => !item.is_return) + .map((i) => i.id) - // delete all old non return line item adjustments - await this.lineItemAdjustmentService_.withTransaction(manager).delete({ + // delete all old non return line item adjustments + await this.lineItemAdjustmentService_ + .withTransaction(this.transactionManager_) + .delete({ item_id: nonReturnLineIDs, }) - // potentially create/update line item adjustments - await this.lineItemAdjustmentService_ - .withTransaction(manager) - .createAdjustments(cart) - }) + // potentially create/update line item adjustments + await this.lineItemAdjustmentService_ + .withTransaction(this.transactionManager_) + .createAdjustments(cart) } /** @@ -1879,35 +1996,39 @@ class CartService extends BaseService { * @return resolves to the updated result. */ async deleteMetadata(cartId: string, key: string): Promise { - return this.atomicPhase_(async (manager: EntityManager) => { - const cartRepo = manager.getCustomRepository(this.cartRepository_) - const validatedId = this.validateId_(cartId) - - if (typeof key !== "string") { - throw new MedusaError( - MedusaError.Types.INVALID_ARGUMENT, - "Key type is invalid. Metadata keys must be strings" + return await this.atomicPhase_( + async (transactionManager: EntityManager) => { + const cartRepo = transactionManager.getCustomRepository( + this.cartRepository_ ) + const validatedId = this.validateId_(cartId) + + if (typeof key !== "string") { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Key type is invalid. Metadata keys must be strings" + ) + } + + const cart = await cartRepo.findOne(validatedId) + if (!cart) { + throw new MedusaError( + MedusaError.Types.NOT_FOUND, + `Cart with id: ${validatedId} was not found` + ) + } + + const updated = cart.metadata || {} + delete updated[key] + cart.metadata = updated + + const updatedCart = await cartRepo.save(cart) + this.eventBus_ + .withTransaction(transactionManager) + .emit(CartService.Events.UPDATED, updatedCart) + return updatedCart } - - const cart = await cartRepo.findOne(validatedId) - if (!cart) { - throw new MedusaError( - MedusaError.Types.NOT_FOUND, - `Cart with id: ${validatedId} was not found` - ) - } - - const updated = cart.metadata || {} - delete updated[key] - cart.metadata = updated - - const result = await cartRepo.save(cart) - this.eventBus_ - .withTransaction(manager) - .emit(CartService.Events.UPDATED, result) - return result - }) + ) } } diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index bc09f91787..0f916e2f13 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -873,7 +873,7 @@ class OrderService extends BaseService { ) { await this.shippingOptionService_ .withTransaction(manager) - .deleteShippingMethod(sm) + .deleteShippingMethods(sm) } else { methods.push(sm) } diff --git a/packages/medusa/src/services/shipping-option.js b/packages/medusa/src/services/shipping-option.js index dc13aee7af..f732bbc758 100644 --- a/packages/medusa/src/services/shipping-option.js +++ b/packages/medusa/src/services/shipping-option.js @@ -208,12 +208,16 @@ class ShippingOptionService extends BaseService { /** * Removes a given shipping method - * @param {ShippingMethod} sm - the shipping method to remove + * @param {ShippingMethod | Array} shippingMethods - the shipping method to remove */ - async deleteShippingMethod(sm) { + async deleteShippingMethods(shippingMethods) { + if (!Array.isArray(shippingMethods)) { + shippingMethods = [shippingMethods] + } + return this.atomicPhase_(async (manager) => { const methodRepo = manager.getCustomRepository(this.methodRepository_) - return methodRepo.remove(sm) + return methodRepo.remove(shippingMethods) }) } diff --git a/packages/medusa/src/types/global.ts b/packages/medusa/src/types/global.ts index 5653cce373..002975d8b7 100644 --- a/packages/medusa/src/types/global.ts +++ b/packages/medusa/src/types/global.ts @@ -1,10 +1,14 @@ -import { asFunction, asValue, AwilixContainer } from "awilix" +import { AwilixContainer } from "awilix" import { Logger as _Logger } from "winston" export type ClassConstructor = { - new (...args: any[]): T; -}; + new (...args: unknown[]): T +} -export type MedusaContainer = AwilixContainer & { registerAdd: (name: string, registration: T ) => MedusaContainer } +export type MedusaContainer = AwilixContainer & { + registerAdd: (name: string, registration: T) => MedusaContainer +} -export type Logger = _Logger & { progress: (activityId: string, msg: string) => void } +export type Logger = _Logger & { + progress: (activityId: string, msg: string) => void +}