fix(medusa): migrate cart service to typescript (#884)
* fix: migrate cart service to typescript * fix: jsdoc inventory service * fix: revert route unit test change * fix: typo * fix: revert integration test packages * fix: cleanup * fix: tests * fix: integration tests * fix: create props type guards * fix: move total field to common types
This commit is contained in:
@@ -95,7 +95,7 @@ Object {
|
||||
"payment_authorized_at": null,
|
||||
"payment_id": null,
|
||||
"region_id": "test-region",
|
||||
"shipping_address_id": "test-shipping-address",
|
||||
"shipping_address_id": StringMatching /\\^addr_\\*/,
|
||||
"type": "swap",
|
||||
"updated_at": Any<String>,
|
||||
},
|
||||
@@ -266,7 +266,7 @@ Object {
|
||||
"payment_authorized_at": null,
|
||||
"payment_id": null,
|
||||
"region_id": "test-region",
|
||||
"shipping_address_id": "test-shipping-address",
|
||||
"shipping_address_id": StringMatching /\\^addr_\\*/,
|
||||
"type": "swap",
|
||||
"updated_at": Any<String>,
|
||||
},
|
||||
|
||||
@@ -137,6 +137,7 @@ describe("/store/carts", () => {
|
||||
type: "swap",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
shipping_address_id: expect.stringMatching(/^addr_*/),
|
||||
metadata: {
|
||||
swap_id: expect.stringMatching(/^swap_*/),
|
||||
},
|
||||
@@ -220,6 +221,7 @@ describe("/store/carts", () => {
|
||||
cart: {
|
||||
id: expect.stringMatching(/^cart_*/),
|
||||
billing_address_id: "test-billing-address",
|
||||
shipping_address_id: expect.stringMatching(/^addr_*/),
|
||||
type: "swap",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
|
||||
@@ -10,6 +10,7 @@ export const MedusaErrorTypes = {
|
||||
INVALID_DATA: "invalid_data",
|
||||
NOT_FOUND: "not_found",
|
||||
NOT_ALLOWED: "not_allowed",
|
||||
UNEXPECTED_STATE: "unexpected_state",
|
||||
}
|
||||
|
||||
export const MedusaErrorCodes = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Router } from "express"
|
||||
import { DraftOrder, Order } from "../../../.."
|
||||
import { DraftOrder, Order, Cart } from "../../../.."
|
||||
import middlewares from "../../../middlewares"
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
|
||||
@@ -62,7 +62,7 @@ export const defaultAdminDraftOrdersCartRelations = [
|
||||
"discounts.rule",
|
||||
]
|
||||
|
||||
export const defaultAdminDraftOrdersCartFields = [
|
||||
export const defaultAdminDraftOrdersCartFields: (keyof Cart)[] = [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
defaultAdminDraftOrdersFields,
|
||||
} from "."
|
||||
import { DraftOrder } from "../../../.."
|
||||
import { LineItemUpdate } from "../../../../types/cart"
|
||||
import { CartService, DraftOrderService } from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
/**
|
||||
@@ -112,15 +113,6 @@ export default async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
class LineItemUpdate {
|
||||
title?: string
|
||||
unit_price?: number
|
||||
quantity?: number
|
||||
metadata?: object = {}
|
||||
region_id?: string
|
||||
variant_id?: string
|
||||
}
|
||||
|
||||
export class AdminPostDraftOrdersDraftOrderLineItemsItemReq {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
SwapService,
|
||||
} from "../../../../services"
|
||||
|
||||
import { Order } from "../../../../models/order"
|
||||
|
||||
/**
|
||||
* @oas [post] /carts/{id}/complete
|
||||
* summary: "Complete a Cart"
|
||||
@@ -144,7 +146,7 @@ export default async (req, res) => {
|
||||
relations: ["payment", "payment_sessions"],
|
||||
})
|
||||
|
||||
let order
|
||||
let order: Order
|
||||
|
||||
// If cart is part of swap, we register swap as complete
|
||||
switch (cart.type) {
|
||||
@@ -183,6 +185,15 @@ export default async (req, res) => {
|
||||
}
|
||||
// case "payment_link":
|
||||
default: {
|
||||
if (typeof cart.total === "undefined") {
|
||||
return {
|
||||
response_code: 500,
|
||||
response_body: {
|
||||
message: "Unexpected state",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!cart.payment && cart.total > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import reqIp from "request-ip"
|
||||
import { EntityManager } from "typeorm"
|
||||
|
||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||
import { CartService, LineItemService } from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { AddressPayload } from "../../../../types/common"
|
||||
|
||||
/**
|
||||
* @oas [post] /carts
|
||||
@@ -74,8 +76,11 @@ export default async (req, res) => {
|
||||
|
||||
await entityManager.transaction(async (manager) => {
|
||||
// Add a default region if no region has been specified
|
||||
let regionId = validated.region_id
|
||||
if (!validated.region_id) {
|
||||
let regionId: string
|
||||
|
||||
if (typeof validated.region_id !== "undefined") {
|
||||
regionId = validated.region_id
|
||||
} else {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const regions = await regionService.withTransaction(manager).list({})
|
||||
|
||||
@@ -90,11 +95,11 @@ export default async (req, res) => {
|
||||
}
|
||||
|
||||
const toCreate: {
|
||||
region_id: string | undefined
|
||||
region_id: string
|
||||
context: object
|
||||
customer_id?: string
|
||||
email?: string
|
||||
shipping_address?: object
|
||||
shipping_address?: Partial<AddressPayload>
|
||||
} = {
|
||||
region_id: regionId,
|
||||
context: {
|
||||
@@ -117,11 +122,11 @@ export default async (req, res) => {
|
||||
country_code: validated.country_code.toLowerCase(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let cart = await cartService.withTransaction(manager).create(toCreate)
|
||||
if (validated.items) {
|
||||
await Promise.all(
|
||||
validated.items.map(async i => {
|
||||
validated.items.map(async (i) => {
|
||||
const lineItem = await lineItemService
|
||||
.withTransaction(manager)
|
||||
.generate(i.variant_id, regionId, i.quantity)
|
||||
|
||||
@@ -97,7 +97,7 @@ export default (app, container) => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultStoreCartFields = [
|
||||
export const defaultStoreCartFields: (keyof Cart)[] = [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||
import { CartService } from "../../../../services"
|
||||
import { AddressPayload } from "../../../../types/common"
|
||||
import { CartUpdateProps } from "../../../../types/cart"
|
||||
import { IsType } from "../../../../utils/validators/is-type"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
@@ -87,7 +88,25 @@ export default async (req, res) => {
|
||||
const cartService: CartService = req.scope.resolve("cartService")
|
||||
|
||||
// Update the cart
|
||||
await cartService.update(id, validated)
|
||||
const { shipping_address, billing_address, ...rest } = validated
|
||||
|
||||
const toUpdate: CartUpdateProps = {
|
||||
...rest,
|
||||
}
|
||||
|
||||
if (typeof shipping_address === "string") {
|
||||
toUpdate.shipping_address_id = shipping_address
|
||||
} else {
|
||||
toUpdate.shipping_address = shipping_address
|
||||
}
|
||||
|
||||
if (typeof billing_address === "string") {
|
||||
toUpdate.billing_address_id = billing_address
|
||||
} else {
|
||||
toUpdate.billing_address = billing_address
|
||||
}
|
||||
|
||||
await cartService.update(id, toUpdate)
|
||||
|
||||
// If the cart has payment sessions update these
|
||||
const updated = await cartService.retrieve(id, {
|
||||
|
||||
@@ -50,46 +50,46 @@ export class Address {
|
||||
id: string
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
customer_id: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
customer_id: string | null
|
||||
|
||||
@ManyToOne(() => Customer)
|
||||
@JoinColumn({ name: "customer_id" })
|
||||
customer: Customer
|
||||
customer: Customer | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
company: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
company: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
first_name: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
first_name: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
last_name: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
last_name: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
address_1: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
address_1: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
address_2: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
address_2: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
city: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
city: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
country_code: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
country_code: string | null
|
||||
|
||||
@ManyToOne(() => Country)
|
||||
@JoinColumn({ name: "country_code", referencedColumnName: "iso_2" })
|
||||
country: Country
|
||||
country: Country | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
province: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
province: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
postal_code: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
postal_code: string | null
|
||||
|
||||
@Column({ nullable: true })
|
||||
phone: string
|
||||
@Column({ type: "text", nullable: true })
|
||||
phone: string | null
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
@@ -98,7 +98,7 @@ export class Address {
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||
deleted_at: Date
|
||||
deleted_at: Date | null
|
||||
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@@ -145,7 +145,7 @@ export class Cart {
|
||||
cascade: ["insert", "remove", "soft-remove"],
|
||||
})
|
||||
@JoinColumn({ name: "shipping_address_id" })
|
||||
shipping_address: Address
|
||||
shipping_address: Address | null
|
||||
|
||||
@OneToMany(() => LineItem, (lineItem) => lineItem.cart, {
|
||||
cascade: ["insert", "remove"],
|
||||
@@ -172,7 +172,7 @@ export class Cart {
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
discounts: Discount
|
||||
discounts: Discount[]
|
||||
|
||||
@ManyToMany(() => GiftCard)
|
||||
@JoinTable({
|
||||
@@ -186,7 +186,7 @@ export class Cart {
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
gift_cards: GiftCard
|
||||
gift_cards: GiftCard[]
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
@@ -196,7 +196,7 @@ export class Cart {
|
||||
@JoinColumn({ name: "customer_id" })
|
||||
customer: Customer
|
||||
|
||||
payment_session: PaymentSession
|
||||
payment_session: PaymentSession | null
|
||||
|
||||
@OneToMany(() => PaymentSession, (paymentSession) => paymentSession.cart, {
|
||||
cascade: true,
|
||||
@@ -217,7 +217,7 @@ export class Cart {
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@DbAwareColumn({ type: "enum", enum: CartType, default: "default" })
|
||||
type: boolean
|
||||
type: CartType
|
||||
|
||||
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||
completed_at: Date
|
||||
@@ -243,15 +243,14 @@ export class Cart {
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
context: any
|
||||
|
||||
// Total fields
|
||||
shipping_total: number
|
||||
discount_total: number
|
||||
tax_total: number
|
||||
refunded_total: number
|
||||
total: number
|
||||
subtotal: number
|
||||
refundable_amount: number
|
||||
gift_card_total: number
|
||||
shipping_total?: number
|
||||
discount_total?: number
|
||||
tax_total?: number
|
||||
refunded_total?: number
|
||||
total?: number
|
||||
subtotal?: number
|
||||
refundable_amount?: number
|
||||
gift_card_total?: number
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert(): undefined | void {
|
||||
|
||||
@@ -32,10 +32,7 @@ export class PaymentSession {
|
||||
@Column()
|
||||
cart_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => Cart,
|
||||
cart => cart.payment_sessions
|
||||
)
|
||||
@ManyToOne(() => Cart, (cart) => cart.payment_sessions)
|
||||
@JoinColumn({ name: "cart_id" })
|
||||
cart: Cart
|
||||
|
||||
@@ -43,8 +40,8 @@ export class PaymentSession {
|
||||
@Column()
|
||||
provider_id: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
is_selected: boolean
|
||||
@Column({ type: "boolean", nullable: true })
|
||||
is_selected: boolean | null
|
||||
|
||||
@DbAwareColumn({ type: "enum", enum: PaymentSessionStatus })
|
||||
status: string
|
||||
|
||||
@@ -1,7 +1,64 @@
|
||||
import _ from "lodash"
|
||||
import { EntityManager, DeepPartial } from "typeorm"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||
import { CartRepository } from "../repositories/cart"
|
||||
import { AddressRepository } from "../repositories/address"
|
||||
import { PaymentSessionRepository } from "../repositories/payment-session"
|
||||
|
||||
import { Address } from "../models/address"
|
||||
import { Discount } from "../models/discount"
|
||||
import { Cart } from "../models/cart"
|
||||
import { Customer } from "../models/customer"
|
||||
import { LineItem } from "../models/line-item"
|
||||
import { ShippingMethod } from "../models/shipping-method"
|
||||
import { CustomShippingOption } from "../models/custom-shipping-option"
|
||||
|
||||
import { TotalField, FindConfig } from "../types/common"
|
||||
import {
|
||||
FilterableCartProps,
|
||||
LineItemUpdate,
|
||||
CartUpdateProps,
|
||||
CartCreateProps,
|
||||
} from "../types/cart"
|
||||
|
||||
import EventBusService from "./event-bus"
|
||||
import ProductVariantService from "./product-variant"
|
||||
import ProductService from "./product"
|
||||
import RegionService from "./region"
|
||||
import LineItemService from "./line-item"
|
||||
import PaymentProviderService from "./payment-provider"
|
||||
import ShippingOptionService from "./shipping-option"
|
||||
import CustomerService from "./customer"
|
||||
import DiscountService from "./discount"
|
||||
import GiftCardService from "./gift-card"
|
||||
import TotalsService from "./totals"
|
||||
import InventoryService from "./inventory"
|
||||
import CustomShippingOptionService from "./custom-shipping-option"
|
||||
|
||||
type CartConstructorProps = {
|
||||
manager: EntityManager
|
||||
cartRepository: typeof CartRepository
|
||||
shippingMethodRepository: typeof ShippingMethodRepository
|
||||
addressRepository: typeof AddressRepository
|
||||
paymentSessionRepository: typeof PaymentSessionRepository
|
||||
eventBusService: EventBusService
|
||||
paymentProviderService: PaymentProviderService
|
||||
productService: ProductService
|
||||
productVariantService: ProductVariantService
|
||||
regionService: RegionService
|
||||
lineItemService: LineItemService
|
||||
shippingOptionService: ShippingOptionService
|
||||
customerService: CustomerService
|
||||
discountService: DiscountService
|
||||
giftCardService: GiftCardService
|
||||
totalsService: TotalsService
|
||||
inventoryService: InventoryService
|
||||
customShippingOptionService: CustomShippingOptionService
|
||||
}
|
||||
|
||||
/* Provides layer to manipulate carts.
|
||||
* @implements BaseService
|
||||
*/
|
||||
@@ -12,6 +69,25 @@ 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 totalsService_: TotalsService
|
||||
private addressRepository_: typeof AddressRepository
|
||||
private paymentSessionRepository_: typeof PaymentSessionRepository
|
||||
private inventoryService_: InventoryService
|
||||
private customShippingOptionService_: CustomShippingOptionService
|
||||
|
||||
constructor({
|
||||
manager,
|
||||
cartRepository,
|
||||
@@ -31,7 +107,7 @@ class CartService extends BaseService {
|
||||
paymentSessionRepository,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
}) {
|
||||
}: CartConstructorProps) {
|
||||
super()
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
@@ -89,7 +165,7 @@ class CartService extends BaseService {
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
withTransaction(transactionManager: EntityManager): CartService {
|
||||
if (!transactionManager) {
|
||||
return this
|
||||
}
|
||||
@@ -139,7 +215,9 @@ class CartService extends BaseService {
|
||||
* @typedef {LineItemContent[]} LineItemContentArray
|
||||
*/
|
||||
|
||||
transformQueryForTotals_(config) {
|
||||
transformQueryForTotals_(
|
||||
config: FindConfig<Cart>
|
||||
): FindConfig<Cart> & { totalsToSelect: TotalField[] } {
|
||||
let { select, relations } = config
|
||||
|
||||
if (!select) {
|
||||
@@ -159,7 +237,9 @@ class CartService extends BaseService {
|
||||
"total",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
const totalsToSelect = select.filter((v) =>
|
||||
totalFields.includes(v)
|
||||
) as TotalField[]
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
@@ -184,26 +264,40 @@ class CartService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async decorateTotals_(cart, totalsFields = []) {
|
||||
if (totalsFields.includes("shipping_total")) {
|
||||
cart.shipping_total = await this.totalsService_.getShippingTotal(cart)
|
||||
async decorateTotals_(
|
||||
cart: Cart,
|
||||
totalsToSelect: TotalField[]
|
||||
): Promise<Cart> {
|
||||
const totals: { [K in TotalField]?: number } = {}
|
||||
|
||||
for (const key of totalsToSelect) {
|
||||
switch (key) {
|
||||
case "total": {
|
||||
totals.total = await this.totalsService_.getTotal(cart)
|
||||
break
|
||||
}
|
||||
case "shipping_total": {
|
||||
totals.shipping_total = this.totalsService_.getShippingTotal(cart)
|
||||
break
|
||||
}
|
||||
case "discount_total":
|
||||
totals.discount_total = this.totalsService_.getDiscountTotal(cart)
|
||||
break
|
||||
case "tax_total":
|
||||
totals.tax_total = await this.totalsService_.getTaxTotal(cart)
|
||||
break
|
||||
case "gift_card_total":
|
||||
totals.gift_card_total = this.totalsService_.getGiftCardTotal(cart)
|
||||
break
|
||||
case "subtotal":
|
||||
totals.subtotal = this.totalsService_.getSubtotal(cart)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if (totalsFields.includes("discount_total")) {
|
||||
cart.discount_total = await this.totalsService_.getDiscountTotal(cart)
|
||||
}
|
||||
if (totalsFields.includes("tax_total")) {
|
||||
cart.tax_total = await this.totalsService_.getTaxTotal(cart)
|
||||
}
|
||||
if (totalsFields.includes("gift_card_total")) {
|
||||
cart.gift_card_total = await this.totalsService_.getGiftCardTotal(cart)
|
||||
}
|
||||
if (totalsFields.includes("subtotal")) {
|
||||
cart.subtotal = await this.totalsService_.getSubtotal(cart)
|
||||
}
|
||||
if (totalsFields.includes("total")) {
|
||||
cart.total = await this.totalsService_.getTotal(cart)
|
||||
}
|
||||
return cart
|
||||
|
||||
return Object.assign(cart, totals)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,22 +305,14 @@ class CartService extends BaseService {
|
||||
* @param {Object} config - config object
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
list(selector, config = {}) {
|
||||
async list(
|
||||
selector: FilterableCartProps,
|
||||
config: FindConfig<Cart> = {}
|
||||
): Promise<Cart[]> {
|
||||
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const query = {
|
||||
where: selector,
|
||||
}
|
||||
|
||||
if (config.select) {
|
||||
query.select = config.select
|
||||
}
|
||||
|
||||
if (config.relations) {
|
||||
query.relations = config.relations
|
||||
}
|
||||
|
||||
return cartRepo.find(query)
|
||||
const query = this.buildQuery_(selector, config)
|
||||
return await cartRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,16 +321,20 @@ class CartService extends BaseService {
|
||||
* @param {Object} options - the options to get a cart
|
||||
* @return {Promise<Cart>} the cart document.
|
||||
*/
|
||||
async retrieve(cartId, options = {}) {
|
||||
async retrieve(
|
||||
cartId: string,
|
||||
options: FindConfig<Cart> = {}
|
||||
): Promise<Cart> {
|
||||
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(options)
|
||||
|
||||
const query = {
|
||||
where: { id: validatedId },
|
||||
}
|
||||
const query = this.buildQuery_(
|
||||
{ id: validatedId },
|
||||
{ ...options, select, relations }
|
||||
)
|
||||
|
||||
if (relations && relations.length > 0) {
|
||||
query.relations = relations
|
||||
@@ -252,10 +342,13 @@ class CartService extends BaseService {
|
||||
|
||||
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) {
|
||||
@@ -265,8 +358,7 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const cart = await this.decorateTotals_(raw, totalsToSelect)
|
||||
return cart
|
||||
return await this.decorateTotals_(raw, totalsToSelect)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,10 +366,11 @@ class CartService extends BaseService {
|
||||
* @param {Object} data - the data to create the cart with
|
||||
* @return {Promise} the result of the create operation
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async create(data: CartCreateProps): Promise<Cart> {
|
||||
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(
|
||||
@@ -289,26 +382,35 @@ class CartService extends BaseService {
|
||||
const region = await this.regionService_.retrieve(region_id, {
|
||||
relations: ["countries"],
|
||||
})
|
||||
|
||||
const regCountries = region.countries.map(({ iso_2 }) => iso_2)
|
||||
|
||||
if (data.email) {
|
||||
const toCreate: DeepPartial<Cart> = {}
|
||||
toCreate.region_id = region.id
|
||||
|
||||
if (typeof data.email !== "undefined") {
|
||||
const customer = await this.createOrFetchUserFromEmail_(data.email)
|
||||
data.customer = customer
|
||||
data.customer_id = customer.id
|
||||
data.email = customer.email
|
||||
toCreate.customer = customer
|
||||
toCreate.customer_id = customer.id
|
||||
toCreate.email = customer.email
|
||||
}
|
||||
|
||||
if (data.shipping_address_id) {
|
||||
if (typeof data.shipping_address_id !== "undefined") {
|
||||
const addr = await addressRepo.findOne(data.shipping_address_id)
|
||||
data.shipping_address = addr
|
||||
if (addr && !regCountries.includes(addr.country_code)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Shipping country not in region"
|
||||
)
|
||||
}
|
||||
|
||||
toCreate.shipping_address = addr
|
||||
}
|
||||
|
||||
if (!data.shipping_address) {
|
||||
if (region.countries.length === 1) {
|
||||
// Preselect the country if the region only has 1
|
||||
// and create address entity
|
||||
data.shipping_address = addressRepo.create({
|
||||
toCreate.shipping_address = addressRepo.create({
|
||||
country_code: regCountries[0],
|
||||
})
|
||||
}
|
||||
@@ -319,14 +421,26 @@ class CartService extends BaseService {
|
||||
"Shipping country not in region"
|
||||
)
|
||||
}
|
||||
|
||||
toCreate.shipping_address = data.shipping_address
|
||||
}
|
||||
|
||||
const toCreate = {
|
||||
...data,
|
||||
region_id: region.id,
|
||||
const remainingFields: (keyof Cart)[] = [
|
||||
"billing_address_id",
|
||||
"context",
|
||||
"type",
|
||||
"metadata",
|
||||
"discounts",
|
||||
"gift_cards",
|
||||
]
|
||||
|
||||
for (const k of remainingFields) {
|
||||
if (typeof data[k] !== "undefined") {
|
||||
toCreate[k] = data[k]
|
||||
}
|
||||
}
|
||||
|
||||
const inProgress = await cartRepo.create(toCreate)
|
||||
const inProgress = cartRepo.create(toCreate)
|
||||
const result = await cartRepo.save(inProgress)
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
@@ -343,8 +457,8 @@ class CartService extends BaseService {
|
||||
* @param {LineItem} lineItemId - the line item to remove.
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeLineItem(cartId, lineItemId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async removeLineItem(cartId: string, lineItemId: string): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"items",
|
||||
@@ -395,7 +509,10 @@ class CartService extends BaseService {
|
||||
* @param {LineItem} lineItem - the line item
|
||||
* @return {boolean}
|
||||
*/
|
||||
validateLineItemShipping_(shippingMethods, lineItem) {
|
||||
validateLineItemShipping_(
|
||||
shippingMethods: ShippingMethod[],
|
||||
lineItem: LineItem
|
||||
): boolean {
|
||||
if (!lineItem.variant_id) {
|
||||
return true
|
||||
}
|
||||
@@ -422,8 +539,8 @@ class CartService extends BaseService {
|
||||
* @param {LineItem} lineItem - the line item to add.
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addLineItem(cartId, lineItem) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async addLineItem(cartId: string, lineItem: LineItem): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
@@ -434,12 +551,13 @@ class CartService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
let currentItem
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -501,8 +619,12 @@ class CartService extends BaseService {
|
||||
* include an id field.
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateLineItem(cartId, lineItemId, lineItemUpdate) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async updateLineItem(
|
||||
cartId: string,
|
||||
lineItemId: string,
|
||||
lineItemUpdate: LineItemUpdate
|
||||
): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["items", "payment_sessions"],
|
||||
})
|
||||
@@ -550,7 +672,7 @@ class CartService extends BaseService {
|
||||
* @param {Cart} cart - the the cart to adjust free shipping for
|
||||
* @param {boolean} shouldAdd - flag to indicate, if we should add or remove
|
||||
*/
|
||||
async adjustFreeShipping_(cart, shouldAdd) {
|
||||
async adjustFreeShipping_(cart: Cart, shouldAdd: boolean): Promise<void> {
|
||||
if (cart.shipping_methods?.length) {
|
||||
// if any free shipping discounts, we ensure to update shipping method amount
|
||||
if (shouldAdd) {
|
||||
@@ -585,8 +707,8 @@ class CartService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async update(cartId, update) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async update(cartId: string, update: CartUpdateProps): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: [
|
||||
@@ -613,16 +735,16 @@ class CartService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
if ("region_id" in update) {
|
||||
if (typeof update.region_id !== "undefined") {
|
||||
const countryCode =
|
||||
update.country_code || update.shipping_address?.country_code
|
||||
(update.country_code || update.shipping_address?.country_code) ?? null
|
||||
await this.setRegion_(cart, update.region_id, countryCode)
|
||||
}
|
||||
|
||||
if ("customer_id" in update) {
|
||||
if (typeof update.customer_id !== "undefined") {
|
||||
await this.updateCustomerId_(cart, update.customer_id)
|
||||
} else {
|
||||
if ("email" in update) {
|
||||
if (typeof update.email !== "undefined") {
|
||||
const customer = await this.createOrFetchUserFromEmail_(update.email)
|
||||
cart.customer = customer
|
||||
cart.customer_id = customer.id
|
||||
@@ -632,16 +754,32 @@ class CartService extends BaseService {
|
||||
|
||||
const addrRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
if ("shipping_address_id" in update || "shipping_address" in update) {
|
||||
const address = update.shipping_address_id || update.shipping_address
|
||||
await this.updateShippingAddress_(cart, address, addrRepo)
|
||||
let address: string | Partial<Address> | 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 (typeof address !== "undefined") {
|
||||
await this.updateShippingAddress_(cart, address, addrRepo)
|
||||
}
|
||||
}
|
||||
|
||||
if ("billing_address_id" in update || "billing_address" in update) {
|
||||
const address = update.billing_address_id || update.billing_address
|
||||
await this.updateBillingAddress_(cart, address, addrRepo)
|
||||
let address: string | Partial<Address> | 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 address !== "undefined") {
|
||||
await this.updateBillingAddress_(cart, address, addrRepo)
|
||||
}
|
||||
}
|
||||
|
||||
if ("discounts" in update) {
|
||||
if (typeof update.discounts !== "undefined") {
|
||||
const previousDiscounts = cart.discounts
|
||||
cart.discounts = []
|
||||
|
||||
@@ -670,7 +808,7 @@ class CartService extends BaseService {
|
||||
if ("gift_cards" in update) {
|
||||
cart.gift_cards = []
|
||||
|
||||
for (const { code } of update.gift_cards) {
|
||||
for (const { code } of update.gift_cards!) {
|
||||
await this.applyGiftCard_(cart, code)
|
||||
}
|
||||
}
|
||||
@@ -688,11 +826,11 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
if ("completed_at" in update) {
|
||||
cart.completed_at = update.completed_at
|
||||
cart.completed_at = update.completed_at!
|
||||
}
|
||||
|
||||
if ("payment_authorized_at" in update) {
|
||||
cart.payment_authorized_at = update.payment_authorized_at
|
||||
cart.payment_authorized_at = update.payment_authorized_at!
|
||||
}
|
||||
|
||||
const result = await cartRepo.save(cart)
|
||||
@@ -717,7 +855,7 @@ class CartService extends BaseService {
|
||||
* @param {string} customerId - the customer to add to cart
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateCustomerId_(cart, customerId) {
|
||||
async updateCustomerId_(cart: Cart, customerId: string): Promise<void> {
|
||||
const customer = await this.customerService_
|
||||
.withTransaction(this.transactionManager_)
|
||||
.retrieve(customerId)
|
||||
@@ -732,7 +870,7 @@ class CartService extends BaseService {
|
||||
* @param {string} email - the email to use
|
||||
* @return {Promise} the resultign customer object
|
||||
*/
|
||||
async createOrFetchUserFromEmail_(email) {
|
||||
async createOrFetchUserFromEmail_(email: string): Promise<Customer> {
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email.toLowerCase())
|
||||
if (error) {
|
||||
@@ -765,17 +903,24 @@ class CartService extends BaseService {
|
||||
* updates
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateBillingAddress_(cart, addressOrId, addrRepo) {
|
||||
async updateBillingAddress_(
|
||||
cart: Cart,
|
||||
addressOrId: Partial<Address> | string,
|
||||
addrRepo: AddressRepository
|
||||
): Promise<void> {
|
||||
let address: Address
|
||||
if (typeof addressOrId === `string`) {
|
||||
addressOrId = await addrRepo.findOne({
|
||||
address = (await addrRepo.findOne({
|
||||
where: { id: addressOrId },
|
||||
})
|
||||
})) as Address
|
||||
} else {
|
||||
address = addressOrId as Address
|
||||
}
|
||||
|
||||
addressOrId.country_code = addressOrId.country_code.toLowerCase()
|
||||
address.country_code = address.country_code?.toLowerCase() ?? null
|
||||
|
||||
if (addressOrId.id) {
|
||||
const updated = await addrRepo.save(addressOrId)
|
||||
if (address.id) {
|
||||
const updated = await addrRepo.save(address)
|
||||
cart.billing_address = updated
|
||||
} else {
|
||||
if (cart.billing_address_id) {
|
||||
@@ -783,10 +928,10 @@ class CartService extends BaseService {
|
||||
where: { id: cart.billing_address_id },
|
||||
})
|
||||
|
||||
await addrRepo.save({ ...addr, ...addressOrId })
|
||||
await addrRepo.save({ ...addr, ...address })
|
||||
} else {
|
||||
const created = addrRepo.create({
|
||||
...addressOrId,
|
||||
...address,
|
||||
})
|
||||
|
||||
cart.billing_address = created
|
||||
@@ -803,25 +948,31 @@ class CartService extends BaseService {
|
||||
* updates
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateShippingAddress_(cart, addressOrId, addrRepo) {
|
||||
async updateShippingAddress_(
|
||||
cart: Cart,
|
||||
addressOrId: Partial<Address> | string,
|
||||
addrRepo: AddressRepository
|
||||
): Promise<void> {
|
||||
let address: Address
|
||||
|
||||
if (addressOrId === null) {
|
||||
cart.shipping_address = null
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof addressOrId === `string`) {
|
||||
addressOrId = await addrRepo.findOne({
|
||||
address = (await addrRepo.findOne({
|
||||
where: { id: addressOrId },
|
||||
})
|
||||
})) as Address
|
||||
} else {
|
||||
address = addressOrId as Address
|
||||
}
|
||||
|
||||
addressOrId.country_code = addressOrId.country_code?.toLowerCase() ?? null
|
||||
address.country_code = address.country_code?.toLowerCase() ?? null
|
||||
|
||||
if (
|
||||
addressOrId.country_code &&
|
||||
!cart.region.countries.find(
|
||||
({ iso_2 }) => addressOrId.country_code === iso_2
|
||||
)
|
||||
address.country_code &&
|
||||
!cart.region.countries.find(({ iso_2 }) => address.country_code === iso_2)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -829,8 +980,8 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
if (addressOrId.id) {
|
||||
const updated = await addrRepo.save(addressOrId)
|
||||
if (address.id) {
|
||||
const updated = await addrRepo.save(address)
|
||||
cart.shipping_address = updated
|
||||
} else {
|
||||
if (cart.shipping_address_id) {
|
||||
@@ -838,10 +989,10 @@ class CartService extends BaseService {
|
||||
where: { id: cart.shipping_address_id },
|
||||
})
|
||||
|
||||
await addrRepo.save({ ...addr, ...addressOrId })
|
||||
await addrRepo.save({ ...addr, ...address })
|
||||
} else {
|
||||
const created = addrRepo.create({
|
||||
...addressOrId,
|
||||
...address,
|
||||
})
|
||||
|
||||
cart.shipping_address = created
|
||||
@@ -849,7 +1000,7 @@ class CartService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
async applyGiftCard_(cart, code) {
|
||||
async applyGiftCard_(cart: Cart, code: string): Promise<void> {
|
||||
const giftCard = await this.giftCardService_.retrieveByCode(code)
|
||||
|
||||
if (giftCard.is_disabled) {
|
||||
@@ -883,7 +1034,7 @@ class CartService extends BaseService {
|
||||
* @param {string} discountCode - the discount code
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async applyDiscount(cart, discountCode) {
|
||||
async applyDiscount(cart: Cart, discountCode: string): Promise<void> {
|
||||
const discount = await this.discountService_.retrieveByCode(discountCode, [
|
||||
"rule",
|
||||
"rule.valid_for",
|
||||
@@ -982,8 +1133,8 @@ class CartService extends BaseService {
|
||||
* @param {string} discountCode - the discount code to remove
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async removeDiscount(cartId, discountCode) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async removeDiscount(cartId: string, discountCode: string): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"discounts",
|
||||
@@ -1030,8 +1181,8 @@ class CartService extends BaseService {
|
||||
* @param {string} cartId - the id of the cart to update the payment session for
|
||||
* @param {object} update - the data to update the payment session with
|
||||
*/
|
||||
async updatePaymentSession(cartId, update) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async updatePaymentSession(cartId: string, update: object): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1064,8 +1215,8 @@ class CartService extends BaseService {
|
||||
* this could be IP address or similar for fraud handling.
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async authorizePayment(cartId, context = {}) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async authorizePayment(cartId: string, context: object = {}): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cartRepository = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1073,6 +1224,13 @@ class CartService extends BaseService {
|
||||
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()
|
||||
@@ -1111,8 +1269,8 @@ class CartService extends BaseService {
|
||||
* @param {string} providerId - the id of the provider to be set to the cart
|
||||
* @return {Promise} result of update operation
|
||||
*/
|
||||
async setPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async setPaymentSession(cartId: string, providerId: string): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1141,8 +1299,8 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
cart.payment_sessions.map((ps) => {
|
||||
return psRepo.save({ ...ps, is_selected: null })
|
||||
cart.payment_sessions.map(async (ps) => {
|
||||
return await psRepo.save({ ...ps, is_selected: null })
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1150,6 +1308,13 @@ class CartService extends BaseService {
|
||||
(ps) => ps.provider_id === providerId
|
||||
)
|
||||
|
||||
if (!sess) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
"Could not find payment session"
|
||||
)
|
||||
}
|
||||
|
||||
sess.is_selected = true
|
||||
|
||||
await psRepo.save(sess)
|
||||
@@ -1173,12 +1338,13 @@ class CartService extends BaseService {
|
||||
* session for
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async setPaymentSessions(cartOrCartId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async setPaymentSessions(cartOrCartId: Cart | string): Promise<Cart> {
|
||||
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: [
|
||||
"gift_card_total",
|
||||
@@ -1205,8 +1371,15 @@ class CartService extends BaseService {
|
||||
|
||||
const region = cart.region
|
||||
|
||||
if (typeof cart.total === "undefined") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNEXPECTED_STATE,
|
||||
"cart.total should be defined"
|
||||
)
|
||||
}
|
||||
|
||||
// If there are existing payment sessions ensure that these are up to date
|
||||
const seen = []
|
||||
const seen: string[] = []
|
||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||
for (const session of cart.payment_sessions) {
|
||||
if (
|
||||
@@ -1258,8 +1431,11 @@ class CartService extends BaseService {
|
||||
* should be removed.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async deletePaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async deletePaymentSession(
|
||||
cartId: string,
|
||||
providerId: string
|
||||
): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1299,8 +1475,11 @@ class CartService extends BaseService {
|
||||
* should be removed.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async refreshPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async refreshPaymentSession(
|
||||
cartId: string,
|
||||
providerId: string
|
||||
): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1338,8 +1517,12 @@ class CartService extends BaseService {
|
||||
* @param {Object} data - the fulmillment data for the method
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addShippingMethod(cartId, optionId, data) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async addShippingMethod(
|
||||
cartId: string,
|
||||
optionId: string,
|
||||
data: object = {}
|
||||
): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
@@ -1431,7 +1614,10 @@ class CartService extends BaseService {
|
||||
* @param {string} optionId - id of the normal or custom shipping option to find in the cartCustomShippingOptions
|
||||
* @return {CustomShippingOption | undefined}
|
||||
*/
|
||||
findCustomShippingOption(cartCustomShippingOptions, optionId) {
|
||||
findCustomShippingOption(
|
||||
cartCustomShippingOptions: CustomShippingOption[],
|
||||
optionId: string
|
||||
): CustomShippingOption | undefined {
|
||||
const customOption = cartCustomShippingOptions?.find(
|
||||
(cso) => cso.shipping_option_id === optionId
|
||||
)
|
||||
@@ -1454,7 +1640,11 @@ class CartService extends BaseService {
|
||||
* @param {string} countryCode - the country code to set the country to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setRegion_(cart, regionId, countryCode) {
|
||||
async setRegion_(
|
||||
cart: Cart,
|
||||
regionId: string,
|
||||
countryCode: string | null
|
||||
): Promise<void> {
|
||||
if (cart.completed_at || cart.payment_authorized_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
@@ -1506,18 +1696,18 @@ class CartService extends BaseService {
|
||||
* First check if there is an existing shipping address on the cart if so
|
||||
* fetch the entire thing so we can modify the shipping country
|
||||
*/
|
||||
let shippingAddress = {}
|
||||
let shippingAddress: Partial<Address> = {}
|
||||
if (cart.shipping_address_id) {
|
||||
shippingAddress = await addrRepo.findOne({
|
||||
shippingAddress = (await addrRepo.findOne({
|
||||
where: { id: cart.shipping_address_id },
|
||||
})
|
||||
})) as Address
|
||||
}
|
||||
|
||||
/*
|
||||
* If the client has specified which country code we are updating to check
|
||||
* that that country is in fact in the country and perform the update.
|
||||
*/
|
||||
if (countryCode !== undefined) {
|
||||
if (countryCode !== null) {
|
||||
if (
|
||||
!region.countries.find(
|
||||
({ iso_2 }) => iso_2 === countryCode.toLowerCase()
|
||||
@@ -1580,9 +1770,10 @@ class CartService extends BaseService {
|
||||
if (d.regions.find(({ id }) => id === regionId)) {
|
||||
return d
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
cart.discounts = newDiscounts.filter((d) => !!d)
|
||||
cart.discounts = newDiscounts.filter(Boolean) as Discount[]
|
||||
}
|
||||
|
||||
cart.gift_cards = []
|
||||
@@ -1603,11 +1794,11 @@ class CartService extends BaseService {
|
||||
/**
|
||||
* Deletes a cart from the database. Completed carts cannot be deleted.
|
||||
* @param {string} cartId - the id of the cart to delete
|
||||
* @return {Promise<Cart?>} the deleted cart or undefined if the cart was
|
||||
* @return {Promise<string>} the deleted cart or undefined if the cart was
|
||||
* not found.
|
||||
*/
|
||||
async delete(cartId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async delete(cartId: string): Promise<string> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"items",
|
||||
@@ -1633,7 +1824,7 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
return cartRepo.remove(cartId)
|
||||
return cartRepo.remove(cart)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1646,8 +1837,12 @@ class CartService extends BaseService {
|
||||
* @param {string} value - value for metadata field.
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async setMetadata(cartId, key, value) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async setMetadata(
|
||||
cartId: string,
|
||||
key: string,
|
||||
value: string | number
|
||||
): Promise<Cart> {
|
||||
return await this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const validatedId = this.validateId_(cartId)
|
||||
@@ -1658,7 +1853,7 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const cart = await cartRepo.findOne(validatedId)
|
||||
const cart = (await cartRepo.findOne(validatedId)) as Cart
|
||||
|
||||
const existing = cart.metadata || {}
|
||||
cart.metadata = {
|
||||
@@ -1680,8 +1875,8 @@ class CartService extends BaseService {
|
||||
* @param {string} key - key for metadata field
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async deleteMetadata(cartId, key) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
async deleteMetadata(cartId: string, key: string): Promise<Cart> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
@@ -57,7 +57,7 @@ class InventoryService extends BaseService {
|
||||
* allows backorders or if the inventory quantity is greater than `quantity`.
|
||||
* @param {string} variantId - the id of the variant to check
|
||||
* @param {number} quantity - the number of units to check availability for
|
||||
* @return {boolean} true if the inventory covers the quantity
|
||||
* @return {Promise<boolean>} true if the inventory covers the quantity
|
||||
*/
|
||||
async confirmInventory(variantId, quantity) {
|
||||
// if variantId is undefined then confirm inventory as it
|
||||
|
||||
@@ -201,7 +201,7 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Removes a given shipping method
|
||||
* @param {string} sm - the shipping method to remove
|
||||
* @param {ShippingMethod} sm - the shipping method to remove
|
||||
*/
|
||||
async deleteShippingMethod(sm) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
|
||||
70
packages/medusa/src/types/cart.ts
Normal file
70
packages/medusa/src/types/cart.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ValidateNested } from "class-validator"
|
||||
import { IsType } from "../utils/validators/is-type"
|
||||
import { CartType } from "../models/cart"
|
||||
import {
|
||||
AddressPayload,
|
||||
DateComparisonOperator,
|
||||
StringComparisonOperator,
|
||||
} from "./common"
|
||||
|
||||
export class FilterableCartProps {
|
||||
@ValidateNested()
|
||||
@IsType([String, [String], StringComparisonOperator])
|
||||
id?: string | string[] | StringComparisonOperator
|
||||
|
||||
@IsType([DateComparisonOperator])
|
||||
created_at?: DateComparisonOperator
|
||||
|
||||
@IsType([DateComparisonOperator])
|
||||
updated_at?: DateComparisonOperator
|
||||
}
|
||||
|
||||
// TODO: Probably worth moving to `./line-item` instead
|
||||
export type LineItemUpdate = {
|
||||
title?: string
|
||||
unit_price?: number
|
||||
quantity?: number
|
||||
metadata?: object
|
||||
region_id?: string
|
||||
variant_id?: string
|
||||
}
|
||||
|
||||
class GiftCard {
|
||||
code: string
|
||||
}
|
||||
|
||||
class Discount {
|
||||
code: string
|
||||
}
|
||||
|
||||
export type CartCreateProps = {
|
||||
region_id: string
|
||||
email?: string
|
||||
billing_address_id?: string
|
||||
billing_address?: Partial<AddressPayload>
|
||||
shipping_address_id?: string
|
||||
shipping_address?: Partial<AddressPayload>
|
||||
gift_cards?: GiftCard[]
|
||||
discounts?: Discount[]
|
||||
customer_id?: string
|
||||
type?: CartType
|
||||
context?: object
|
||||
metadata?: object
|
||||
}
|
||||
|
||||
export type CartUpdateProps = {
|
||||
region_id?: string
|
||||
country_code?: string
|
||||
email?: string
|
||||
shipping_address_id?: string
|
||||
billing_address_id?: string
|
||||
billing_address?: AddressPayload
|
||||
shipping_address?: AddressPayload
|
||||
completed_at?: Date
|
||||
payment_authorized_at?: Date
|
||||
gift_cards?: GiftCard[]
|
||||
discounts?: Discount[]
|
||||
customer_id?: string
|
||||
context?: object
|
||||
metadata?: object
|
||||
}
|
||||
@@ -8,6 +8,16 @@ export type PartialPick<T, K extends keyof T> = {
|
||||
[P in K]?: T[P]
|
||||
}
|
||||
|
||||
export type TotalField =
|
||||
| "shipping_total"
|
||||
| "discount_total"
|
||||
| "tax_total"
|
||||
| "refunded_total"
|
||||
| "total"
|
||||
| "subtotal"
|
||||
| "refundable_amount"
|
||||
| "gift_card_total"
|
||||
|
||||
export interface FindConfig<Entity> {
|
||||
select?: (keyof Entity)[]
|
||||
skip?: number
|
||||
|
||||
Reference in New Issue
Block a user