827 lines
23 KiB
JavaScript
827 lines
23 KiB
JavaScript
import _ from "lodash"
|
|
import { Validator, MedusaError } from "medusa-core-utils"
|
|
import { BaseService } from "medusa-interfaces"
|
|
|
|
class OrderService extends BaseService {
|
|
static Events = {
|
|
GIFT_CARD_CREATED: "order.gift_card_created",
|
|
PAYMENT_CAPTURED: "order.payment_captured",
|
|
SHIPMENT_CREATED: "order.shipment_created",
|
|
PLACED: "order.placed",
|
|
UPDATED: "order.updated",
|
|
CANCELLED: "order.cancelled",
|
|
COMPLETED: "order.completed",
|
|
}
|
|
|
|
constructor({
|
|
orderModel,
|
|
paymentProviderService,
|
|
shippingProfileService,
|
|
discountService,
|
|
fulfillmentProviderService,
|
|
lineItemService,
|
|
totalsService,
|
|
regionService,
|
|
eventBusService,
|
|
}) {
|
|
super()
|
|
|
|
/** @private @const {OrderModel} */
|
|
this.orderModel_ = orderModel
|
|
|
|
/** @private @const {PaymentProviderService} */
|
|
this.paymentProviderService_ = paymentProviderService
|
|
|
|
/** @private @const {ShippingProvileService} */
|
|
this.shippingProfileService_ = shippingProfileService
|
|
|
|
/** @private @const {FulfillmentProviderService} */
|
|
this.fulfillmentProviderService_ = fulfillmentProviderService
|
|
|
|
/** @private @const {LineItemService} */
|
|
this.lineItemService_ = lineItemService
|
|
|
|
/** @private @const {TotalsService} */
|
|
this.totalsService_ = totalsService
|
|
|
|
/** @private @const {RegionService} */
|
|
this.regionService_ = regionService
|
|
|
|
/** @private @const {DiscountService} */
|
|
this.discountService_ = discountService
|
|
|
|
/** @private @const {EventBus} */
|
|
this.eventBus_ = eventBusService
|
|
}
|
|
|
|
/**
|
|
* Used to validate order ids. Throws an error if the cast fails
|
|
* @param {string} rawId - the raw order id to validate.
|
|
* @return {string} the validated id
|
|
*/
|
|
validateId_(rawId) {
|
|
const schema = Validator.objectId()
|
|
const { value, error } = schema.validate(rawId.toString())
|
|
if (error) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"The order id could not be casted to an ObjectId"
|
|
)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
/**
|
|
* Used to validate order addresses. Can be used to both
|
|
* validate shipping and billing address.
|
|
* @param {Address} address - the address to validate
|
|
* @return {Address} the validated address
|
|
*/
|
|
validateAddress_(address) {
|
|
const { value, error } = Validator.address().validate(address)
|
|
if (error) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_DATA,
|
|
"The address is not valid"
|
|
)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
/**
|
|
* Used to validate email.
|
|
* @param {string} email - the email to vaildate
|
|
* @return {string} the validate email
|
|
*/
|
|
validateEmail_(email) {
|
|
const schema = Validator.string().email()
|
|
const { value, error } = schema.validate(email)
|
|
if (error) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"The email is not valid"
|
|
)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
async partitionItems_(shipping_methods, items) {
|
|
let updatedMethods = []
|
|
// partition order items to their dedicated shipping method
|
|
await Promise.all(
|
|
shipping_methods.map(async method => {
|
|
const { profile_id } = method
|
|
const profile = await this.shippingProfileService_.retrieve(profile_id)
|
|
// for each method find the items in the order, that are associated
|
|
// with the profile on the current shipping method
|
|
if (shipping_methods.length === 1) {
|
|
method.items = items
|
|
} else {
|
|
method.items = items.filter(({ content }) => {
|
|
if (Array.isArray(content)) {
|
|
// we require bundles to have same shipping method, therefore:
|
|
return profile.products.includes(content[0].product._id)
|
|
} else {
|
|
return profile.products.includes(content.product._id)
|
|
}
|
|
})
|
|
}
|
|
updatedMethods.push(method)
|
|
})
|
|
)
|
|
return updatedMethods
|
|
}
|
|
|
|
/**
|
|
* @param {Object} selector - the query object for find
|
|
* @return {Promise} the result of the find operation
|
|
*/
|
|
list(selector) {
|
|
return this.orderModel_.find(selector)
|
|
}
|
|
|
|
/**
|
|
* Gets an order by id.
|
|
* @param {string} orderId - id of order to retrieve
|
|
* @return {Promise<Order>} the order document
|
|
*/
|
|
async retrieve(orderId) {
|
|
const validatedId = this.validateId_(orderId)
|
|
const order = await this.orderModel_
|
|
.findOne({ _id: validatedId })
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
|
|
if (!order) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Order with ${orderId} was not found`
|
|
)
|
|
}
|
|
return order
|
|
}
|
|
|
|
/**
|
|
* Gets an order by cart id.
|
|
* @param {string} cartId - cart id to find order
|
|
* @return {Promise<Order>} the order document
|
|
*/
|
|
async retrieveByCartId(cartId) {
|
|
const order = await this.orderModel_
|
|
.findOne({ cart_id: cartId })
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
|
|
if (!order) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_FOUND,
|
|
`Order with cart id ${cartId} was not found`
|
|
)
|
|
}
|
|
return order
|
|
}
|
|
|
|
/**
|
|
* Checks the existence of an order by cart id.
|
|
* @param {string} cartId - cart id to find order
|
|
* @return {Promise<Order>} the order document
|
|
*/
|
|
async existsByCartId(cartId) {
|
|
const order = await this.orderModel_
|
|
.findOne({ metadata: { cart_id: cartId } })
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
|
|
if (!order) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* @param {Object} selector - the query object for find
|
|
* @return {Promise} the result of the find operation
|
|
*/
|
|
list(selector) {
|
|
return this.orderModel_.find(selector)
|
|
}
|
|
|
|
/**
|
|
* @param {string} orderId - id of the order to complete
|
|
* @return {Promise} the result of the find operation
|
|
*/
|
|
async completeOrder(orderId) {
|
|
const order = await this.retrieve(orderId)
|
|
this.orderModel_
|
|
.updateOne(
|
|
{ _id: order._id },
|
|
{
|
|
$set: { status: "completed" },
|
|
}
|
|
)
|
|
.then(async result => {
|
|
const completeOrderJob = await this.eventBus_.emit(
|
|
OrderService.Events.COMPLETED,
|
|
result
|
|
)
|
|
|
|
return completeOrderJob
|
|
.finished()
|
|
.then(async () => {
|
|
return this.retrieve(order._id)
|
|
})
|
|
.catch(error => {
|
|
throw error
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Creates an order from a cart
|
|
* @param {object} order - the order to create
|
|
* @return {Promise} resolves to the creation result.
|
|
*/
|
|
async createFromCart(cart) {
|
|
// Create DB session for transaction
|
|
const dbSession = await this.orderModel_.startSession()
|
|
|
|
// Initialize DB transaction
|
|
return dbSession
|
|
.withTransaction(async () => {
|
|
// Check if order from cart already exists
|
|
// If so, this function throws
|
|
const exists = await this.existsByCartId(cart._id)
|
|
if (exists) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Order from cart already exists"
|
|
)
|
|
}
|
|
|
|
// Throw if payment method does not exist
|
|
if (!cart.payment_method) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Cart does not contain a payment method"
|
|
)
|
|
}
|
|
|
|
const { payment_method, payment_sessions } = cart
|
|
|
|
if (!payment_sessions || !payment_sessions.length) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"cart must have payment sessions"
|
|
)
|
|
}
|
|
|
|
let paymentSession = payment_sessions.find(
|
|
ps => ps.provider_id === payment_method.provider_id
|
|
)
|
|
|
|
// Throw if payment method does not exist
|
|
if (!paymentSession) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Cart does not have an authorized payment session"
|
|
)
|
|
}
|
|
|
|
const region = await this.regionService_.retrieve(cart.region_id)
|
|
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
|
paymentSession.provider_id
|
|
)
|
|
const paymentStatus = await paymentProvider.getStatus(
|
|
paymentSession.data
|
|
)
|
|
|
|
// If payment status is not authorized, we throw
|
|
if (paymentStatus !== "authorized" && paymentStatus !== "succeeded") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Payment method is not authorized"
|
|
)
|
|
}
|
|
|
|
const paymentData = await paymentProvider.retrievePayment(
|
|
paymentSession.data
|
|
)
|
|
|
|
// Generate gift cards if in cart
|
|
const items = await Promise.all(
|
|
cart.items.map(async i => {
|
|
if (i.is_giftcard) {
|
|
const giftcard = await this.discountService_
|
|
.generateGiftCard(i.content.unit_price, region._id)
|
|
.then(result => {
|
|
this.eventBus_.emit(OrderService.Events.GIFT_CARD_CREATED, {
|
|
currency_code: region.currency_code,
|
|
tax_rate: region.tax_rate,
|
|
giftcard: result,
|
|
email: cart.email,
|
|
})
|
|
return result
|
|
})
|
|
return {
|
|
...i,
|
|
metadata: {
|
|
...i.metadata,
|
|
giftcard: giftcard._id,
|
|
},
|
|
}
|
|
}
|
|
return i
|
|
})
|
|
)
|
|
|
|
const o = {
|
|
payment_method: {
|
|
provider_id: paymentSession.provider_id,
|
|
data: paymentData,
|
|
},
|
|
discounts: cart.discounts,
|
|
shipping_methods: cart.shipping_methods,
|
|
items,
|
|
shipping_address: cart.shipping_address,
|
|
billing_address: cart.shipping_address,
|
|
region_id: cart.region_id,
|
|
email: cart.email,
|
|
customer_id: cart.customer_id,
|
|
cart_id: cart._id,
|
|
tax_rate: region.tax_rate,
|
|
currency_code: region.currency_code,
|
|
}
|
|
|
|
const orderDocument = await this.orderModel_.create([o], {
|
|
session: dbSession,
|
|
})
|
|
|
|
// Emit and return
|
|
this.eventBus_.emit(OrderService.Events.PLACED, orderDocument[0])
|
|
return orderDocument[0]
|
|
})
|
|
.then(() => this.orderModel_.findOne({ cart_id: cart._id }))
|
|
}
|
|
|
|
/**
|
|
* Adds a shipment to the order to indicate that an order has left the warehouse
|
|
*/
|
|
async createShipment(orderId, shipment) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
console.log(order)
|
|
if (order.fulfillment_status === "shipped") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Order has already been shipped"
|
|
)
|
|
}
|
|
|
|
const shipmentSchema = Validator.object({
|
|
item_ids: Validator.array()
|
|
.items(Validator.string())
|
|
.required(),
|
|
tracking_number: Validator.string().required(),
|
|
})
|
|
|
|
const { value, error } = shipmentSchema.validate(shipment)
|
|
if (error) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_DATA,
|
|
`Shipment not valid: ${error}`
|
|
)
|
|
}
|
|
|
|
const existing = order.shipments || []
|
|
const shipments = [...existing, value]
|
|
const allCovered = order.items.every(
|
|
i => !!shipments.find(s => s.item_ids.includes(`${i._id}`))
|
|
)
|
|
|
|
const update = {
|
|
$push: { shipments: value },
|
|
$set: {
|
|
fulfillment_status: allCovered ? "shipped" : "partially_shipped",
|
|
},
|
|
}
|
|
|
|
// Add the shipment to the order
|
|
return this.orderModel_.updateOne({ _id: orderId }, update).then(result => {
|
|
this.eventBus_.emit(OrderService.Events.SHIPMENT_CREATED, {
|
|
order_id: orderId,
|
|
shipment,
|
|
})
|
|
return result
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Creates an order
|
|
* @param {object} order - the order to create
|
|
* @return {Promise} resolves to the creation result.
|
|
*/
|
|
async create(order) {
|
|
return this.orderModel_
|
|
.create(order)
|
|
.then(result => {
|
|
// Notify subscribers
|
|
this.eventBus_.emit(OrderService.Events.PLACED, result)
|
|
return result
|
|
})
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Updates an order. Metadata updates should
|
|
* use dedicated method, e.g. `setMetadata` etc. The function
|
|
* will throw errors if metadata updates are attempted.
|
|
* @param {string} orderId - the id of the order. Must be a string that
|
|
* can be casted to an ObjectId
|
|
* @param {object} update - an object with the update values.
|
|
* @return {Promise} resolves to the update result.
|
|
*/
|
|
async update(orderId, update) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (
|
|
(update.shipping_address ||
|
|
update.billing_address ||
|
|
update.payment_method ||
|
|
update.items) &&
|
|
(order.fulfillment_status !== "not_fulfilled" ||
|
|
order.payment_status !== "awaiting" ||
|
|
order.status !== "pending")
|
|
) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't update shipping, billing, items and payment method when order is processed"
|
|
)
|
|
}
|
|
|
|
if (update.metadata) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_DATA,
|
|
"Use setMetadata to update metadata fields"
|
|
)
|
|
}
|
|
|
|
if (update.status || update.fulfillment_status || update.payment_status) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't update order statuses. This will happen automatically. Use metadata in order for additional statuses"
|
|
)
|
|
}
|
|
|
|
const updateFields = { ...update }
|
|
|
|
if (update.shipping_address) {
|
|
updateFields.shipping_address = this.validateAddress_(
|
|
update.shipping_address
|
|
)
|
|
}
|
|
|
|
if (update.billing_address) {
|
|
updateFields.billing_address = this.validateAddress_(
|
|
update.billing_address
|
|
)
|
|
}
|
|
|
|
if (update.items) {
|
|
updateFields.items = update.items.map(item =>
|
|
this.lineItemService_.validate(item)
|
|
)
|
|
}
|
|
|
|
return this.orderModel_
|
|
.updateOne(
|
|
{ _id: order._id },
|
|
{ $set: updateFields },
|
|
{ runValidators: true }
|
|
)
|
|
.then(result => {
|
|
// Notify subscribers
|
|
this.eventBus_.emit(OrderService.Events.UPDATED, result)
|
|
return result
|
|
})
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Cancels an order.
|
|
* Throws if fulfillment process has been initiated.
|
|
* Throws if payment process has been initiated.
|
|
* @param {string} orderId - id of order to cancel.
|
|
* @return {Promise} result of the update operation.
|
|
*/
|
|
async cancel(orderId) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (order.fulfillment_status !== "not_fulfilled") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't cancel a fulfilled order"
|
|
)
|
|
}
|
|
|
|
if (order.payment_status !== "awaiting") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't cancel an order with payment processed"
|
|
)
|
|
}
|
|
|
|
// TODO: cancel payment method
|
|
|
|
return this.orderModel_
|
|
.updateOne(
|
|
{
|
|
_id: orderId,
|
|
},
|
|
{
|
|
$set: { status: "cancelled" },
|
|
}
|
|
)
|
|
.then(result => {
|
|
// Notify subscribers
|
|
this.eventBus_.emit(OrderService.Events.CANCELLED, result)
|
|
return result
|
|
})
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Captures payment for an order.
|
|
* @param {string} orderId - id of order to capture payment for.
|
|
* @return {Promise} result of the update operation.
|
|
*/
|
|
async capturePayment(orderId) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (order.payment_status !== "awaiting") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Payment already captured"
|
|
)
|
|
}
|
|
|
|
// prepare update object
|
|
const updateFields = { payment_status: "captured" }
|
|
const completed = order.fulfillment_status !== "not_fulfilled"
|
|
if (completed) {
|
|
updateFields.status = "completed"
|
|
}
|
|
|
|
const { provider_id, data } = order.payment_method
|
|
const paymentProvider = await this.paymentProviderService_.retrieveProvider(
|
|
provider_id
|
|
)
|
|
|
|
await paymentProvider.capturePayment(data)
|
|
|
|
return this.orderModel_
|
|
.updateOne(
|
|
{
|
|
_id: orderId,
|
|
},
|
|
{
|
|
$set: updateFields,
|
|
}
|
|
)
|
|
.then(result => {
|
|
this.eventBus_.emit(OrderService.Events.PAYMENT_CAPTURED, result)
|
|
return result
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Creates fulfillments for an order.
|
|
* In a situation where the order has more than one shipping method,
|
|
* we need to partition the order items, such that they can be sent
|
|
* to their respective fulfillment provider.
|
|
* @param {string} orderId - id of order to cancel.
|
|
* @return {Promise} result of the update operation.
|
|
*/
|
|
async createFulfillment(orderId) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (order.fulfillment_status !== "not_fulfilled") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Order is already fulfilled"
|
|
)
|
|
}
|
|
|
|
const { shipping_methods, items } = order
|
|
|
|
// prepare update object
|
|
const updateFields = { fulfillment_status: "fulfilled" }
|
|
const completed = order.payment_status !== "awaiting"
|
|
if (completed) {
|
|
updateFields.status = "completed"
|
|
}
|
|
|
|
// partition order items to their dedicated shipping method
|
|
updateFields.shipping_methods = await this.partitionItems_(
|
|
shipping_methods,
|
|
items
|
|
)
|
|
|
|
await Promise.all(
|
|
updateFields.shipping_methods.map(method => {
|
|
const provider = this.fulfillmentProviderService_.retrieveProvider(
|
|
method.provider_id
|
|
)
|
|
return provider.createOrder(method.data, method.items)
|
|
})
|
|
)
|
|
|
|
return this.orderModel_
|
|
.updateOne(
|
|
{
|
|
_id: orderId,
|
|
},
|
|
{
|
|
$set: updateFields,
|
|
}
|
|
)
|
|
.then(result => {
|
|
// Notify subscribers
|
|
this.eventBus_.emit(OrderService.Events.UPDATED, result)
|
|
return result
|
|
})
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Return either the entire or part of an order.
|
|
* @param {string} orderId - the order to return.
|
|
* @param {string[]} lineItems - the line items to return
|
|
* @return {Promise} the result of the update operation
|
|
*/
|
|
async return(orderId, lineItems) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (
|
|
order.fulfillment_status === "not_fulfilled" ||
|
|
order.fulfillment_status === "returned"
|
|
) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't return an unfulfilled or already returned order"
|
|
)
|
|
}
|
|
|
|
if (order.payment_status !== "captured") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't return an order with payment unprocessed"
|
|
)
|
|
}
|
|
|
|
const { provider_id, data } = order.payment_method
|
|
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
|
provider_id
|
|
)
|
|
|
|
const amount = this.totalsService_.getRefundTotal(order, lineItems)
|
|
await paymentProvider.refundPayment(data, amount)
|
|
|
|
lineItems.map(item => {
|
|
const returnedItem = order.items.find(({ _id }) => _id === item._id)
|
|
if (returnedItem) {
|
|
returnedItem.returned_quantity = item.quantity
|
|
}
|
|
})
|
|
|
|
const fullReturn = order.items.every(
|
|
item => item.quantity === item.returned_quantity
|
|
)
|
|
|
|
return this.orderModel_.updateOne(
|
|
{
|
|
_id: orderId,
|
|
},
|
|
{
|
|
$set: {
|
|
items: order.items,
|
|
fulfillment_status: fullReturn ? "returned" : "partially_fulfilled",
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Archives an order. It only alloved, if the order has been fulfilled
|
|
* and payment has been captured.
|
|
* @param {string} orderId - the order to archive
|
|
* @return {Promise} the result of the update operation
|
|
*/
|
|
async archive(orderId) {
|
|
const order = await this.retrieve(orderId)
|
|
|
|
if (order.status !== ("completed" || "refunded")) {
|
|
throw new MedusaError(
|
|
MedusaError.Types.NOT_ALLOWED,
|
|
"Can't archive an unprocessed order"
|
|
)
|
|
}
|
|
|
|
return this.orderModel_.updateOne(
|
|
{
|
|
_id: orderId,
|
|
},
|
|
{
|
|
$set: { status: "archived" },
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Decorates an order.
|
|
* @param {Order} order - the order to decorate.
|
|
* @param {string[]} fields - the fields to include.
|
|
* @param {string[]} expandFields - fields to expand.
|
|
* @return {Order} return the decorated order.
|
|
*/
|
|
async decorate(order, fields, expandFields = []) {
|
|
const o = order
|
|
o.shipping_total = await this.totalsService_.getShippingTotal(order)
|
|
o.discount_total = await this.totalsService_.getDiscountTotal(order)
|
|
o.tax_total = await this.totalsService_.getTaxTotal(order)
|
|
o.subtotal = await this.totalsService_.getSubtotal(order)
|
|
o.total = await this.totalsService_.getTotal(order)
|
|
o.created = order._id.getTimestamp()
|
|
if (expandFields.includes("region")) {
|
|
o.region = await this.regionService_.retrieve(order.region_id)
|
|
}
|
|
return o
|
|
}
|
|
|
|
/**
|
|
* Dedicated method to set metadata for an order.
|
|
* To ensure that plugins does not overwrite each
|
|
* others metadata fields, setMetadata is provided.
|
|
* @param {string} orderId - the order to decorate.
|
|
* @param {string} key - key for metadata field
|
|
* @param {string} value - value for metadata field.
|
|
* @return {Promise} resolves to the updated result.
|
|
*/
|
|
async setMetadata(orderId, key, value) {
|
|
const validatedId = this.validateId_(orderId)
|
|
|
|
if (typeof key !== "string") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Key type is invalid. Metadata keys must be strings"
|
|
)
|
|
}
|
|
|
|
const keyPath = `metadata.${key}`
|
|
return this.orderModel_
|
|
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Dedicated method to delete metadata for an order.
|
|
* @param {string} orderId - the order to delete metadata from.
|
|
* @param {string} key - key for metadata field
|
|
* @return {Promise} resolves to the updated result.
|
|
*/
|
|
async deleteMetadata(orderId, key) {
|
|
const validatedId = this.validateId_(orderId)
|
|
|
|
if (typeof key !== "string") {
|
|
throw new MedusaError(
|
|
MedusaError.Types.INVALID_ARGUMENT,
|
|
"Key type is invalid. Metadata keys must be strings"
|
|
)
|
|
}
|
|
|
|
const keyPath = `metadata.${key}`
|
|
return this.orderModel_
|
|
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
|
.catch(err => {
|
|
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
|
})
|
|
}
|
|
}
|
|
|
|
export default OrderService
|