Brightpearl integration sales flow
This commit is contained in:
1 parent
ee93256e69
commit
ab0c51819c
29 files changed
+781
-130
No files matched your search
@@ -33,7 +33,7 @@ class ManualFulfillmentService extends FulfillmentService {
|
|||||||
|
|
||||||
createOrder() {
|
createOrder() {
|
||||||
// No data is being sent anywhere
|
// No data is being sent anywhere
|
||||||
return
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ class StripeProviderService extends PaymentService {
|
|||||||
const { customer_id, region_id } = cart
|
const { customer_id, region_id } = cart
|
||||||
const { currency_code } = await this.regionService_.retrieve(region_id)
|
const { currency_code } = await this.regionService_.retrieve(region_id)
|
||||||
|
|
||||||
|
console.log(customer_id)
|
||||||
|
|
||||||
let stripeCustomerId
|
let stripeCustomerId
|
||||||
if (!customer_id) {
|
if (!customer_id) {
|
||||||
const { id } = await this.stripe_.customers.create({
|
const { id } = await this.stripe_.customers.create({
|
||||||
@@ -104,7 +106,8 @@ class StripeProviderService extends PaymentService {
|
|||||||
stripeCustomerId = id
|
stripeCustomerId = id
|
||||||
} else {
|
} else {
|
||||||
const customer = await this.customerService_.retrieve(customer_id)
|
const customer = await this.customerService_.retrieve(customer_id)
|
||||||
if (!customer.metadata.stripe_id) {
|
console.log(customer)
|
||||||
|
if (!(customer.metadata && customer.metadata.stripe_id)) {
|
||||||
const { id } = await this.stripe_.customers.create({
|
const { id } = await this.stripe_.customers.create({
|
||||||
email: customer.email,
|
email: customer.email,
|
||||||
})
|
})
|
||||||
@@ -151,7 +154,7 @@ class StripeProviderService extends PaymentService {
|
|||||||
const { id } = data
|
const { id } = data
|
||||||
const amount = this.totalsService_.getTotal(cart)
|
const amount = this.totalsService_.getTotal(cart)
|
||||||
return this.stripe_.paymentIntents.update(id, {
|
return this.stripe_.paymentIntents.update(id, {
|
||||||
amount,
|
amount: amount * 100,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
@@ -212,7 +215,7 @@ class StripeProviderService extends PaymentService {
|
|||||||
const { id } = paymentData
|
const { id } = paymentData
|
||||||
try {
|
try {
|
||||||
return this.stripe_.refunds.create({
|
return this.stripe_.refunds.create({
|
||||||
amount,
|
amount: amount * 100,
|
||||||
payment_intent: id,
|
payment_intent: id,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -4,12 +4,27 @@ import bodyParser from "body-parser"
|
|||||||
export default (container) => {
|
export default (container) => {
|
||||||
const app = Router()
|
const app = Router()
|
||||||
|
|
||||||
app.post("/brightpearl/inventory-update", bodyParser.json(), async (req, res) => {
|
app.post("/brightpearl/goods-out", bodyParser.json(), async (req, res) => {
|
||||||
|
const { id, lifecycle_event } = req.body
|
||||||
|
const brightpearlService = req.scope.resolve("brightpearlService")
|
||||||
|
|
||||||
|
if (lifecycle_event === "created") {
|
||||||
|
await brightpearlService.createFulfillmentFromGoodsOut(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
"/brightpearl/inventory-update",
|
||||||
|
bodyParser.json(),
|
||||||
|
async (req, res) => {
|
||||||
const { id } = req.body
|
const { id } = req.body
|
||||||
const brightpearlService = req.scope.resolve("brightpearlService")
|
const brightpearlService = req.scope.resolve("brightpearlService")
|
||||||
await brightpearlService.updateInventory(id)
|
await brightpearlService.updateInventory(id)
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@@ -41,24 +41,52 @@ class BrightpearlService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = new Brightpearl({
|
const client = new Brightpearl({
|
||||||
|
account: this.options.account,
|
||||||
url: data.api_domain,
|
url: data.api_domain,
|
||||||
auth_type: data.token_type,
|
auth_type: data.token_type,
|
||||||
access_token: data.access_token,
|
access_token: data.access_token,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.authData_ = data
|
||||||
this.brightpearlClient_ = client
|
this.brightpearlClient_ = client
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAuthData() {
|
||||||
|
if (this.authData_) {
|
||||||
|
return this.authData_
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await this.oauthService_.retrieveByName("brightpearl")
|
||||||
|
if (!data || !data.access_token) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"You must authenticate the Brightpearl app in settings before continuing"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.authData_ = data
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
async verifyWebhooks() {
|
async verifyWebhooks() {
|
||||||
const brightpearl = await this.getClient()
|
const brightpearl = await this.getClient()
|
||||||
const hooks = [
|
const hooks = [
|
||||||
|
{
|
||||||
|
subscribeTo: "goods-out-note.created",
|
||||||
|
httpMethod: "POST",
|
||||||
|
uriTemplate: `${this.options.backend_url}/brightpearl/goods-out`,
|
||||||
|
bodyTemplate:
|
||||||
|
'{"account": "${account-code}", "lifecycle_event": "${lifecycle-event}", "resource_type": "${resource-type}", "id": "${resource-id}" }',
|
||||||
|
contentType: "application/json",
|
||||||
|
idSetAccepted: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
subscribeTo: "product.modified.on-hand-modified",
|
subscribeTo: "product.modified.on-hand-modified",
|
||||||
httpMethod: "POST",
|
httpMethod: "POST",
|
||||||
uriTemplate: `${this.options.backend_url}/brightpearl/inventory-update`,
|
uriTemplate: `${this.options.backend_url}/brightpearl/inventory-update`,
|
||||||
bodyTemplate:
|
bodyTemplate:
|
||||||
'{"account": "${account-code}", "lifecycleEvent": "${lifecycle-event}", "resourceType": "${resource-type}", "id": "${resource-id}" }',
|
'{"account": "${account-code}", "lifecycle_event": "${lifecycle-event}", "resource_type": "${resource-type}", "id": "${resource-id}" }',
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
idSetAccepted: false,
|
idSetAccepted: false,
|
||||||
},
|
},
|
||||||
@@ -107,9 +135,12 @@ class BrightpearlService extends BaseService {
|
|||||||
|
|
||||||
async updateInventory(productId) {
|
async updateInventory(productId) {
|
||||||
const client = await this.getClient()
|
const client = await this.getClient()
|
||||||
const brightpearlProduct = await client.products.retrieve(productId)
|
const availability = await client.products
|
||||||
const availability = await client.products.retrieveAvailability(productId)
|
.retrieveAvailability(productId)
|
||||||
|
.catch(() => null)
|
||||||
|
|
||||||
|
if (availability) {
|
||||||
|
const brightpearlProduct = await client.products.retrieve(productId)
|
||||||
const onHand = availability[productId].total.onHand
|
const onHand = availability[productId].total.onHand
|
||||||
|
|
||||||
const sku = brightpearlProduct.identity.sku
|
const sku = brightpearlProduct.identity.sku
|
||||||
@@ -121,6 +152,7 @@ class BrightpearlService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createGoodsOutNote(fromOrder, shipment) {
|
async createGoodsOutNote(fromOrder, shipment) {
|
||||||
const client = await this.getClient()
|
const client = await this.getClient()
|
||||||
@@ -174,11 +206,158 @@ class BrightpearlService extends BaseService {
|
|||||||
return client.warehouses.updateGoodsOutNote(noteId, {
|
return client.warehouses.updateGoodsOutNote(noteId, {
|
||||||
priority: false,
|
priority: false,
|
||||||
shipping: {
|
shipping: {
|
||||||
reference: shipment.tracking_number,
|
reference: shipment.tracking_numbers.join(", "),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createRefundCredit(fromOrder, fromRefund) {
|
||||||
|
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||||
|
const client = await this.getClient()
|
||||||
|
const authData = await this.getAuthData()
|
||||||
|
const orderId = fromOrder.metadata.brightpearl_sales_order_id
|
||||||
|
if (orderId) {
|
||||||
|
let accountingCode = "4000"
|
||||||
|
if (
|
||||||
|
fromRefund.reason === "discount" &&
|
||||||
|
this.options.discount_account_code
|
||||||
|
) {
|
||||||
|
accountingCode = this.options.discount_account_code
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentSo = await client.orders.retrieve(orderId)
|
||||||
|
const order = {
|
||||||
|
currency: parentSo.currency,
|
||||||
|
ref: parentSo.ref,
|
||||||
|
externalRef: `${parentSo.externalRef}.${fromOrder.refunds.length}`,
|
||||||
|
channelId: this.options.channel_id || `1`,
|
||||||
|
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||||
|
customer: parentSo.customer,
|
||||||
|
delivery: parentSo.delivery,
|
||||||
|
parentId: orderId,
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
name: `${fromRefund.reason}: ${fromRefund.note}`,
|
||||||
|
quantity: 1,
|
||||||
|
taxCode: region.tax_code,
|
||||||
|
net: fromRefund.amount / (1 + fromOrder.tax_rate),
|
||||||
|
tax:
|
||||||
|
fromRefund.amount - fromRefund.amount / (1 + fromOrder.tax_rate),
|
||||||
|
nominalCode: accountingCode,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.orders
|
||||||
|
.createCredit(order)
|
||||||
|
.then(async (creditId) => {
|
||||||
|
const paymentMethod = fromOrder.payment_method
|
||||||
|
const paymentType = "PAYMENT"
|
||||||
|
const payment = {
|
||||||
|
transactionRef: `${paymentMethod._id}.${fromOrder.refunds.length}`,
|
||||||
|
transactionCode: fromOrder._id,
|
||||||
|
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||||
|
orderId: creditId,
|
||||||
|
currencyIsoCode: fromOrder.currency_code,
|
||||||
|
amountPaid: fromRefund.amount,
|
||||||
|
paymentDate: new Date(),
|
||||||
|
paymentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = fromOrder.metadata.brightpearl_credit_ids || []
|
||||||
|
const newIds = [...existing, creditId]
|
||||||
|
|
||||||
|
await client.payments.create(payment)
|
||||||
|
|
||||||
|
return this.orderService_.setMetadata(
|
||||||
|
fromOrder._id,
|
||||||
|
"brightpearl_credit_ids",
|
||||||
|
newIds
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err.response.data.errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSalesCredit(fromOrder, fromReturn) {
|
||||||
|
const region = await this.regionService_.retrieve(fromOrder.region_id)
|
||||||
|
const client = await this.getClient()
|
||||||
|
const authData = await this.getAuthData()
|
||||||
|
const orderId = fromOrder.metadata.brightpearl_sales_order_id
|
||||||
|
if (orderId) {
|
||||||
|
const parentSo = await client.orders.retrieve(orderId)
|
||||||
|
const order = {
|
||||||
|
currency: parentSo.currency,
|
||||||
|
ref: parentSo.ref,
|
||||||
|
externalRef: `${parentSo.externalRef}.${fromOrder.refunds.length}`,
|
||||||
|
channelId: this.options.channel_id || `1`,
|
||||||
|
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||||
|
customer: parentSo.customer,
|
||||||
|
delivery: parentSo.delivery,
|
||||||
|
parentId: orderId,
|
||||||
|
rows: fromReturn.items.map((i) => {
|
||||||
|
const parentRow = parentSo.rows.find((row) => {
|
||||||
|
return row.externalRef === i.item_id
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
net: (parentRow.net / parentRow.quantity) * i.quantity,
|
||||||
|
tax: (parentRow.tax / parentRow.quantity) * i.quantity,
|
||||||
|
productId: parentRow.productId,
|
||||||
|
taxCode: parentRow.taxCode,
|
||||||
|
externalRef: parentRow.externalRef,
|
||||||
|
nominalCode: parentRow.nominalCode,
|
||||||
|
quantity: i.quantity,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = order.rows.reduce((acc, next) => {
|
||||||
|
return acc + next.net + next.tax
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
const difference = fromReturn.refund_amount - total
|
||||||
|
if (difference) {
|
||||||
|
order.rows.push({
|
||||||
|
name: "Difference",
|
||||||
|
quantity: 1,
|
||||||
|
taxCode: region.tax_code,
|
||||||
|
net: difference / (1 + fromOrder.tax_rate),
|
||||||
|
tax: difference - difference / (1 + fromOrder.tax_rate),
|
||||||
|
nominalCode: this.options.sales_account_code || "4000",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.orders
|
||||||
|
.createCredit(order)
|
||||||
|
.then(async (creditId) => {
|
||||||
|
const paymentMethod = fromOrder.payment_method
|
||||||
|
const paymentType = "PAYMENT"
|
||||||
|
const payment = {
|
||||||
|
transactionRef: `${paymentMethod._id}.${fromOrder.refunds.length}`,
|
||||||
|
transactionCode: fromOrder._id,
|
||||||
|
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||||
|
orderId: creditId,
|
||||||
|
currencyIsoCode: fromOrder.currency_code,
|
||||||
|
amountPaid: fromReturn.refund_amount,
|
||||||
|
paymentDate: new Date(),
|
||||||
|
paymentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = fromOrder.metadata.brightpearl_credit_ids || []
|
||||||
|
const newIds = [...existing, creditId]
|
||||||
|
|
||||||
|
await client.payments.create(payment)
|
||||||
|
|
||||||
|
return this.orderService_.setMetadata(
|
||||||
|
fromOrder._id,
|
||||||
|
"brightpearl_credit_ids",
|
||||||
|
newIds
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((err) => console.log(err.response.data.errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createSalesOrder(fromOrder) {
|
async createSalesOrder(fromOrder) {
|
||||||
const client = await this.getClient()
|
const client = await this.getClient()
|
||||||
let customer = await this.retrieveCustomerByEmail(fromOrder.email)
|
let customer = await this.retrieveCustomerByEmail(fromOrder.email)
|
||||||
@@ -188,12 +367,17 @@ class BrightpearlService extends BaseService {
|
|||||||
customer = await this.createCustomer(fromOrder)
|
customer = await this.createCustomer(fromOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authData = await this.getAuthData()
|
||||||
|
|
||||||
const { shipping_address } = fromOrder
|
const { shipping_address } = fromOrder
|
||||||
const order = {
|
const order = {
|
||||||
currency: {
|
currency: {
|
||||||
code: fromOrder.currency_code,
|
code: fromOrder.currency_code,
|
||||||
},
|
},
|
||||||
|
ref: fromOrder._id,
|
||||||
externalRef: fromOrder._id,
|
externalRef: fromOrder._id,
|
||||||
|
channelId: this.options.channel_id || `1`,
|
||||||
|
installedIntegrationInstanceId: authData.installation_instance_id,
|
||||||
customer: {
|
customer: {
|
||||||
id: customer.contactId,
|
id: customer.contactId,
|
||||||
address: {
|
address: {
|
||||||
@@ -237,7 +421,7 @@ class BrightpearlService extends BaseService {
|
|||||||
const payment = {
|
const payment = {
|
||||||
transactionRef: `${paymentMethod._id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
transactionRef: `${paymentMethod._id}.${paymentType}`, // Brightpearl cannot accept an auth and capture with same ref
|
||||||
transactionCode: fromOrder._id,
|
transactionCode: fromOrder._id,
|
||||||
paymentMethodCode: "1220",
|
paymentMethodCode: this.options.payment_method_code || "1220",
|
||||||
orderId: salesOrderId,
|
orderId: salesOrderId,
|
||||||
currencyIsoCode: fromOrder.currency_code,
|
currencyIsoCode: fromOrder.currency_code,
|
||||||
paymentDate: new Date(),
|
paymentDate: new Date(),
|
||||||
@@ -368,6 +552,35 @@ class BrightpearlService extends BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createFulfillmentFromGoodsOut(id) {
|
||||||
|
const client = await this.getClient()
|
||||||
|
|
||||||
|
// Get goods out and associated order
|
||||||
|
const goodsOut = await client.warehouses.retrieveGoodsOutNote(id)
|
||||||
|
const order = await client.orders.retrieve(goodsOut.orderId)
|
||||||
|
|
||||||
|
console.log(order)
|
||||||
|
|
||||||
|
// Combine the line items that we are going to create a fulfillment for
|
||||||
|
const lines = Object.keys(goodsOut.orderRows)
|
||||||
|
.map((key) => {
|
||||||
|
const row = order.rows.find((r) => r.id == key)
|
||||||
|
if (row) {
|
||||||
|
return {
|
||||||
|
item_id: row.externalRef,
|
||||||
|
quantity: goodsOut.orderRows[key][0].quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter((i) => !!i)
|
||||||
|
|
||||||
|
return this.orderService_.createFulfillment(order.ref, lines, {
|
||||||
|
goods_out_note: id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async createCustomer(fromOrder) {
|
async createCustomer(fromOrder) {
|
||||||
const client = await this.getClient()
|
const client = await this.getClient()
|
||||||
const address = await client.addresses.create({
|
const address = await client.addresses.create({
|
||||||
|
|||||||
@@ -3,29 +3,53 @@ class OrderSubscriber {
|
|||||||
this.orderService_ = orderService
|
this.orderService_ = orderService
|
||||||
this.brightpearlService_ = brightpearlService
|
this.brightpearlService_ = brightpearlService
|
||||||
|
|
||||||
|
eventBusService.subscribe("order.refund_created", this.registerRefund)
|
||||||
|
|
||||||
|
eventBusService.subscribe("order.items_returned", this.registerReturn)
|
||||||
|
|
||||||
eventBusService.subscribe("order.placed", this.sendToBrightpearl)
|
eventBusService.subscribe("order.placed", this.sendToBrightpearl)
|
||||||
eventBusService.subscribe("order.payment_captured", this.registerCapturedPayment)
|
|
||||||
|
eventBusService.subscribe(
|
||||||
|
"order.payment_captured",
|
||||||
|
this.registerCapturedPayment
|
||||||
|
)
|
||||||
|
|
||||||
eventBusService.subscribe("order.shipment_created", this.registerShipment)
|
eventBusService.subscribe("order.shipment_created", this.registerShipment)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToBrightpearl = order => {
|
sendToBrightpearl = (order) => {
|
||||||
return this.brightpearlService_.createSalesOrder(order)
|
return this.brightpearlService_.createSalesOrder(order)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCapturedPayment = order => {
|
registerCapturedPayment = (order) => {
|
||||||
return this.brightpearlService_.createCapturedPayment(order)
|
return this.brightpearlService_.createCapturedPayment(order)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerShipment = async (data) => {
|
registerShipment = async (data) => {
|
||||||
const { order_id, shipment } = data
|
const { order_id, shipment } = data
|
||||||
const order = await this.orderService_.retrieve(order_id)
|
const noteId = shipment.metadata.goods_out_note
|
||||||
const notes = await this.brightpearlService_.createGoodsOutNote(order, shipment)
|
if (noteId) {
|
||||||
if (notes.length) {
|
await this.brightpearlService_.registerGoodsOutTrackingNumber(
|
||||||
const noteId = notes[0]
|
noteId,
|
||||||
await this.brightpearlService_.registerGoodsOutTrackingNumber(noteId, shipment)
|
shipment
|
||||||
|
)
|
||||||
await this.brightpearlService_.registerGoodsOutShipped(noteId, shipment)
|
await this.brightpearlService_.registerGoodsOutShipped(noteId, shipment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerReturn = (data) => {
|
||||||
|
const { order, return: fromReturn } = data
|
||||||
|
return this.brightpearlService_
|
||||||
|
.createSalesCredit(order, fromReturn)
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
registerRefund = (data) => {
|
||||||
|
const { order, refund } = data
|
||||||
|
return this.brightpearlService_
|
||||||
|
.createRefundCredit(order, refund)
|
||||||
|
.catch((err) => console.log(err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrderSubscriber
|
export default OrderSubscriber
|
||||||
@@ -23,7 +23,7 @@ class BrightpearlClient {
|
|||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.client_ = axios.create({
|
this.client_ = axios.create({
|
||||||
baseURL: `${options.url}/public-api/${options.account}`,
|
baseURL: `https://${options.url}/public-api/${options.account}`,
|
||||||
headers: {
|
headers: {
|
||||||
"brightpearl-app-ref": "medusa-dev",
|
"brightpearl-app-ref": "medusa-dev",
|
||||||
"brightpearl-dev-ref": "sebrindom",
|
"brightpearl-dev-ref": "sebrindom",
|
||||||
@@ -97,6 +97,14 @@ class BrightpearlClient {
|
|||||||
})
|
})
|
||||||
.then(({ data }) => data.response)
|
.then(({ data }) => data.response)
|
||||||
},
|
},
|
||||||
|
retrieveGoodsOutNote: (id) => {
|
||||||
|
return this.client_
|
||||||
|
.request({
|
||||||
|
url: `/warehouse-service/order/*/goods-note/goods-out/${id}`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
.then(({ data }) => data.response && data.response[id])
|
||||||
|
},
|
||||||
createGoodsOutNote: (orderId, data) => {
|
createGoodsOutNote: (orderId, data) => {
|
||||||
return this.client_
|
return this.client_
|
||||||
.request({
|
.request({
|
||||||
@@ -160,6 +168,15 @@ class BrightpearlClient {
|
|||||||
})
|
})
|
||||||
.then(({ data }) => data.response)
|
.then(({ data }) => data.response)
|
||||||
},
|
},
|
||||||
|
createCredit: (salesCredit) => {
|
||||||
|
return this.client_
|
||||||
|
.request({
|
||||||
|
url: `/order-service/sales-credit`,
|
||||||
|
method: "POST",
|
||||||
|
data: salesCredit,
|
||||||
|
})
|
||||||
|
.then(({ data }) => data.response)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,13 +98,22 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
retrieveGoodsOutNote: function retrieveGoodsOutNote(id) {
|
||||||
|
return _this.client_.request({
|
||||||
|
url: "/warehouse-service/order/*/goods-note/goods-out/".concat(id),
|
||||||
|
method: "GET"
|
||||||
|
}).then(function (_ref5) {
|
||||||
|
var data = _ref5.data;
|
||||||
|
return data.response && data.response[id];
|
||||||
|
});
|
||||||
|
},
|
||||||
createGoodsOutNote: function createGoodsOutNote(orderId, data) {
|
createGoodsOutNote: function createGoodsOutNote(orderId, data) {
|
||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/warehouse-service/order/".concat(orderId, "/goods-note/goods-out"),
|
url: "/warehouse-service/order/".concat(orderId, "/goods-note/goods-out"),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: data
|
data: data
|
||||||
}).then(function (_ref5) {
|
}).then(function (_ref6) {
|
||||||
var data = _ref5.data;
|
var data = _ref6.data;
|
||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -137,8 +146,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
data: {
|
data: {
|
||||||
products: data
|
products: data
|
||||||
}
|
}
|
||||||
}).then(function (_ref6) {
|
}).then(function (_ref7) {
|
||||||
var data = _ref6.data;
|
var data = _ref7.data;
|
||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -151,8 +160,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/order-service/sales-order/".concat(orderId),
|
url: "/order-service/sales-order/".concat(orderId),
|
||||||
method: "GET"
|
method: "GET"
|
||||||
}).then(function (_ref7) {
|
}).then(function (_ref8) {
|
||||||
var data = _ref7.data;
|
var data = _ref8.data;
|
||||||
return data.response.length && data.response[0];
|
return data.response.length && data.response[0];
|
||||||
})["catch"](function (err) {
|
})["catch"](function (err) {
|
||||||
return console.log(err);
|
return console.log(err);
|
||||||
@@ -163,8 +172,18 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
url: "/order-service/sales-order",
|
url: "/order-service/sales-order",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: order
|
data: order
|
||||||
}).then(function (_ref8) {
|
}).then(function (_ref9) {
|
||||||
var data = _ref8.data;
|
var data = _ref9.data;
|
||||||
|
return data.response;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createCredit: function createCredit(salesCredit) {
|
||||||
|
return _this.client_.request({
|
||||||
|
url: "/order-service/sales-credit",
|
||||||
|
method: "POST",
|
||||||
|
data: salesCredit
|
||||||
|
}).then(function (_ref10) {
|
||||||
|
var data = _ref10.data;
|
||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -178,8 +197,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
url: "/contact-service/postal-address",
|
url: "/contact-service/postal-address",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: address
|
data: address
|
||||||
}).then(function (_ref9) {
|
}).then(function (_ref11) {
|
||||||
var data = _ref9.data;
|
var data = _ref11.data;
|
||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -191,24 +210,24 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
retrieveAvailability: function retrieveAvailability(productId) {
|
retrieveAvailability: function retrieveAvailability(productId) {
|
||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/warehouse-service/product-availability/".concat(productId)
|
url: "/warehouse-service/product-availability/".concat(productId)
|
||||||
}).then(function (_ref10) {
|
}).then(function (_ref12) {
|
||||||
var data = _ref10.data;
|
var data = _ref12.data;
|
||||||
return data.response && data.response;
|
return data.response && data.response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
retrieve: function retrieve(productId) {
|
retrieve: function retrieve(productId) {
|
||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/product-service/product/".concat(productId)
|
url: "/product-service/product/".concat(productId)
|
||||||
}).then(function (_ref11) {
|
}).then(function (_ref13) {
|
||||||
var data = _ref11.data;
|
var data = _ref13.data;
|
||||||
return data.response && data.response[0];
|
return data.response && data.response[0];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
retrieveBySKU: function retrieveBySKU(sku) {
|
retrieveBySKU: function retrieveBySKU(sku) {
|
||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/product-service/product-search?SKU=".concat(sku)
|
url: "/product-service/product-search?SKU=".concat(sku)
|
||||||
}).then(function (_ref12) {
|
}).then(function (_ref14) {
|
||||||
var data = _ref12.data;
|
var data = _ref14.data;
|
||||||
return _this.buildSearchResults_(data.response);
|
return _this.buildSearchResults_(data.response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -220,8 +239,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
retrieveByEmail: function retrieveByEmail(email) {
|
retrieveByEmail: function retrieveByEmail(email) {
|
||||||
return _this.client_.request({
|
return _this.client_.request({
|
||||||
url: "/contact-service/contact-search?primaryEmail=".concat(email)
|
url: "/contact-service/contact-search?primaryEmail=".concat(email)
|
||||||
}).then(function (_ref13) {
|
}).then(function (_ref15) {
|
||||||
var data = _ref13.data;
|
var data = _ref15.data;
|
||||||
return _this.buildSearchResults_(data.response);
|
return _this.buildSearchResults_(data.response);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -230,8 +249,8 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
url: "/contact-service/contact",
|
url: "/contact-service/contact",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: customerData
|
data: customerData
|
||||||
}).then(function (_ref14) {
|
}).then(function (_ref16) {
|
||||||
var data = _ref14.data;
|
var data = _ref16.data;
|
||||||
return data.response;
|
return data.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -239,7 +258,7 @@ var BrightpearlClient = /*#__PURE__*/function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.client_ = _axios["default"].create({
|
this.client_ = _axios["default"].create({
|
||||||
baseURL: "".concat(options.url, "/public-api/").concat(options.account),
|
baseURL: "https://".concat(options.url, "/public-api/").concat(options.account),
|
||||||
headers: {
|
headers: {
|
||||||
"brightpearl-app-ref": "medusa-dev",
|
"brightpearl-app-ref": "medusa-dev",
|
||||||
"brightpearl-dev-ref": "sebrindom",
|
"brightpearl-dev-ref": "sebrindom",
|
||||||
|
|||||||
@@ -7,14 +7,44 @@ import errorHandler from "./middlewares/error-handler"
|
|||||||
export default (container, config) => {
|
export default (container, config) => {
|
||||||
const app = Router()
|
const app = Router()
|
||||||
|
|
||||||
app.post("/create-shipment/:order_id", async (req, res) => {
|
app.post("/create-shipment/:order_id/:fulfillment_id", async (req, res) => {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const eventBus = req.scope.resolve("eventBusService")
|
const eventBus = req.scope.resolve("eventBusService")
|
||||||
const order = await orderService.retrieve(req.params.order_id)
|
const order = await orderService.retrieve(req.params.order_id)
|
||||||
|
|
||||||
await orderService.createShipment(order._id, {
|
await orderService.createShipment(order._id, req.params.fulfillment_id, [
|
||||||
item_ids: order.items.map(({ _id }) => `${_id}`),
|
"1234",
|
||||||
tracking_number: "1234",
|
])
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/run-hook/:order_id/refund", async (req, res) => {
|
||||||
|
const orderService = req.scope.resolve("orderService")
|
||||||
|
const eventBus = req.scope.resolve("eventBusService")
|
||||||
|
const order = await orderService.retrieve(req.params.order_id)
|
||||||
|
const returnToSend = order.refunds[order.refunds.length - 1]
|
||||||
|
|
||||||
|
eventBus.emit("order.refund_created", {
|
||||||
|
order,
|
||||||
|
refund: returnToSend,
|
||||||
|
})
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post("/run-hook/:order_id/return", async (req, res) => {
|
||||||
|
const orderService = req.scope.resolve("orderService")
|
||||||
|
const eventBus = req.scope.resolve("eventBusService")
|
||||||
|
const order = await orderService.retrieve(req.params.order_id)
|
||||||
|
const returnToSend = order.returns[0]
|
||||||
|
|
||||||
|
eventBus.emit("order.items_returned", {
|
||||||
|
order,
|
||||||
|
return: {
|
||||||
|
...returnToSend,
|
||||||
|
refund_amount: 1000,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.capturePayment(id)
|
let order = await orderService.capturePayment(id)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
res.json({ order })
|
res.json({ order })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.completeOrder(id)
|
let order = await orderService.completeOrder(id)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
res.json({ order })
|
res.json({ order })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.createFulfillment(id)
|
let order = await orderService.createFulfillment(id)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
res.json({ order })
|
res.json({ order })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.create(value)
|
let order = await orderService.create(value)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({ order })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export default app => {
|
|||||||
middlewares.wrap(require("./complete-order").default)
|
middlewares.wrap(require("./complete-order").default)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
route.post(
|
||||||
|
"/:id/refund",
|
||||||
|
middlewares.wrap(require("./refund-payment").default)
|
||||||
|
)
|
||||||
route.post(
|
route.post(
|
||||||
"/:id/capture",
|
"/:id/capture",
|
||||||
middlewares.wrap(require("./capture-payment").default)
|
middlewares.wrap(require("./capture-payment").default)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
amount: Validator.number().required(),
|
||||||
|
reason: Validator.string().required(),
|
||||||
|
note: Validator.string()
|
||||||
|
.allow("")
|
||||||
|
.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { value, error } = schema.validate(req.body)
|
||||||
|
if (error) {
|
||||||
|
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const orderService = req.scope.resolve("orderService")
|
||||||
|
let order = await orderService.createRefund(
|
||||||
|
id,
|
||||||
|
value.amount,
|
||||||
|
value.reason,
|
||||||
|
value.note
|
||||||
|
)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
|
|
||||||
|
res.status(200).json({ order })
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.return(id, value.items)
|
let order = await orderService.return(id, value.items)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({ order })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const orderService = req.scope.resolve("orderService")
|
const orderService = req.scope.resolve("orderService")
|
||||||
const order = await orderService.update(id, value)
|
let order = await orderService.update(id, value)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
|
|
||||||
res.status(200).json({ order })
|
res.status(200).json({ order })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
res.status(200).json({ cart })
|
res.status(200).json({ cart })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err.response.data)
|
console.log(err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,13 +51,9 @@ export const orders = {
|
|||||||
price: 100,
|
price: 100,
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
profile_id: IdMap.getId("default"),
|
profile_id: IdMap.getId("default"),
|
||||||
|
data: {
|
||||||
|
extra: "hi",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
_id: IdMap.getId("freeShipping"),
|
|
||||||
name: "Free Shipping",
|
|
||||||
price: 10,
|
|
||||||
provider_id: "default_provider",
|
|
||||||
profile_id: IdMap.getId("profile1"),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
fulfillment_status: "not_fulfilled",
|
fulfillment_status: "not_fulfilled",
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import ShippingMethodSchema from "./schemas/shipping-method"
|
|||||||
import AddressSchema from "./schemas/address"
|
import AddressSchema from "./schemas/address"
|
||||||
import DiscountSchema from "./schemas/discount"
|
import DiscountSchema from "./schemas/discount"
|
||||||
import ShipmentSchema from "./schemas/shipment"
|
import ShipmentSchema from "./schemas/shipment"
|
||||||
|
import ReturnSchema from "./schemas/return"
|
||||||
|
import RefundSchema from "./schemas/refund"
|
||||||
|
import FulfillmentSchema from "./schemas/fulfillment"
|
||||||
|
|
||||||
class OrderModel extends BaseModel {
|
class OrderModel extends BaseModel {
|
||||||
static modelName = "Order"
|
static modelName = "Order"
|
||||||
@@ -26,6 +29,9 @@ class OrderModel extends BaseModel {
|
|||||||
currency_code: { type: String, required: true },
|
currency_code: { type: String, required: true },
|
||||||
tax_rate: { type: Number, required: true },
|
tax_rate: { type: Number, required: true },
|
||||||
shipments: { type: [ShipmentSchema], default: [] },
|
shipments: { type: [ShipmentSchema], default: [] },
|
||||||
|
fulfillments: { type: [FulfillmentSchema], default: [] },
|
||||||
|
returns: { type: [ReturnSchema], default: [] },
|
||||||
|
refunds: { type: [RefundSchema], default: [] },
|
||||||
region_id: { type: String, required: true },
|
region_id: { type: String, required: true },
|
||||||
discounts: { type: [DiscountSchema], default: [] },
|
discounts: { type: [DiscountSchema], default: [] },
|
||||||
customer_id: { type: String },
|
customer_id: { type: String },
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import mongoose from "mongoose"
|
||||||
|
|
||||||
|
export default new mongoose.Schema({
|
||||||
|
created: { type: String, default: Date.now },
|
||||||
|
provider_id: { type: String, required: true },
|
||||||
|
items: { type: [mongoose.Schema.Types.Mixed], required: true },
|
||||||
|
data: { type: [mongoose.Schema.Types.Mixed], default: {} },
|
||||||
|
tracking_numbers: { type: [String], default: [] },
|
||||||
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
|
})
|
||||||
@@ -36,6 +36,8 @@ export default new mongoose.Schema({
|
|||||||
content: { type: mongoose.Schema.Types.Mixed, required: true },
|
content: { type: mongoose.Schema.Types.Mixed, required: true },
|
||||||
quantity: { type: Number, required: true },
|
quantity: { type: Number, required: true },
|
||||||
returned: { type: Boolean, default: false },
|
returned: { type: Boolean, default: false },
|
||||||
|
fulfilled: { type: Boolean, default: false },
|
||||||
|
fulfilled_quantity: { type: Number, default: 0 },
|
||||||
returned_quantity: { type: Number, default: 0 },
|
returned_quantity: { type: Number, default: 0 },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import mongoose from "mongoose"
|
||||||
|
|
||||||
|
export default new mongoose.Schema({
|
||||||
|
created: { type: String, default: Date.now },
|
||||||
|
note: { type: String, default: "" },
|
||||||
|
reason: { type: String, default: "" },
|
||||||
|
amount: { type: Number, required: true },
|
||||||
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import mongoose from "mongoose"
|
||||||
|
|
||||||
|
export default new mongoose.Schema({
|
||||||
|
created: { type: String, default: Date.now },
|
||||||
|
refund_amount: { type: Number, required: true },
|
||||||
|
items: { type: [mongoose.Schema.Types.Mixed], required: true },
|
||||||
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
|
})
|
||||||
@@ -17,7 +17,7 @@ export const DefaultProviderMock = {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
createOrder: jest.fn().mockImplementation(data => {
|
createOrder: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve()
|
return Promise.resolve(data)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,15 @@ export const OrderServiceMock = {
|
|||||||
createFromCart: jest.fn().mockImplementation(data => {
|
createFromCart: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve(orders.testOrder)
|
return Promise.resolve(orders.testOrder)
|
||||||
}),
|
}),
|
||||||
update: jest.fn().mockImplementation(data => Promise.resolve()),
|
update: jest.fn().mockImplementation(data => {
|
||||||
|
if (data === IdMap.getId("test-order")) {
|
||||||
|
return Promise.resolve(orders.testOrder)
|
||||||
|
}
|
||||||
|
if (data === IdMap.getId("processed-order")) {
|
||||||
|
return Promise.resolve(orders.processedOrder)
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined)
|
||||||
|
}),
|
||||||
retrieve: jest.fn().mockImplementation(orderId => {
|
retrieve: jest.fn().mockImplementation(orderId => {
|
||||||
if (orderId === IdMap.getId("test-order")) {
|
if (orderId === IdMap.getId("test-order")) {
|
||||||
return Promise.resolve(orders.testOrder)
|
return Promise.resolve(orders.testOrder)
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
DefaultProviderMock,
|
DefaultProviderMock,
|
||||||
} from "../__mocks__/payment-provider"
|
} from "../__mocks__/payment-provider"
|
||||||
import { DiscountServiceMock } from "../__mocks__/discount"
|
import { DiscountServiceMock } from "../__mocks__/discount"
|
||||||
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
import {
|
||||||
|
FulfillmentProviderServiceMock,
|
||||||
|
DefaultProviderMock as FulfillmentProviderMock,
|
||||||
|
} from "../__mocks__/fulfillment-provider"
|
||||||
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
||||||
import { TotalsServiceMock } from "../__mocks__/totals"
|
import { TotalsServiceMock } from "../__mocks__/totals"
|
||||||
import { RegionServiceMock } from "../__mocks__/region"
|
import { RegionServiceMock } from "../__mocks__/region"
|
||||||
@@ -402,40 +405,110 @@ describe("OrderService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls order model functions", async () => {
|
it("calls order model functions", async () => {
|
||||||
await orderService.createFulfillment(IdMap.getId("test-order"))
|
await orderService.createFulfillment(IdMap.getId("test-order"), [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(FulfillmentProviderMock.createOrder).toHaveBeenCalledTimes(1)
|
||||||
|
expect(FulfillmentProviderMock.createOrder).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
extra: "hi",
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("validId"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("test-order") },
|
{ _id: IdMap.getId("test-order") },
|
||||||
{
|
{
|
||||||
$set: {
|
$push: {
|
||||||
fulfillment_status: "fulfilled",
|
fulfillments: {
|
||||||
shipping_methods: [
|
$each: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("expensiveShipping"),
|
data: {
|
||||||
items: [],
|
extra: "hi",
|
||||||
name: "Expensive Shipping",
|
|
||||||
price: 100,
|
|
||||||
profile_id: IdMap.getId("default"),
|
|
||||||
provider_id: "default_provider",
|
|
||||||
},
|
},
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("freeShipping"),
|
_id: IdMap.getId("existingLine"),
|
||||||
items: [],
|
title: "merge line",
|
||||||
name: "Free Shipping",
|
description: "This is a new line",
|
||||||
price: 10,
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
profile_id: IdMap.getId("profile1"),
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("validId"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
$set: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("existingLine"),
|
||||||
|
title: "merge line",
|
||||||
|
description: "This is a new line",
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
content: {
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
|
},
|
||||||
|
product: {
|
||||||
|
_id: IdMap.getId("validId"),
|
||||||
|
},
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
quantity: 10,
|
||||||
|
fulfilled_quantity: 10,
|
||||||
|
fulfilled: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fulfillment_status: "fulfilled",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if payment is already processed", async () => {
|
it("throws if payment is already processed", async () => {
|
||||||
await expect(
|
await expect(
|
||||||
orderService.createFulfillment(IdMap.getId("fulfilled-order"))
|
orderService.createFulfillment(IdMap.getId("fulfilled-order"), [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
])
|
||||||
).rejects.toThrow("Order is already fulfilled")
|
).rejects.toThrow("Order is already fulfilled")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -464,6 +537,20 @@ describe("OrderService", () => {
|
|||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("processed-order") },
|
{ _id: IdMap.getId("processed-order") },
|
||||||
{
|
{
|
||||||
|
$push: {
|
||||||
|
refunds: {
|
||||||
|
amount: 1230,
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
refund_amount: 1230,
|
||||||
|
},
|
||||||
|
},
|
||||||
$set: {
|
$set: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -514,6 +601,20 @@ describe("OrderService", () => {
|
|||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("processed-order") },
|
{ _id: IdMap.getId("processed-order") },
|
||||||
{
|
{
|
||||||
|
$push: {
|
||||||
|
refunds: {
|
||||||
|
amount: 102,
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
refund_amount: 102,
|
||||||
|
},
|
||||||
|
},
|
||||||
$set: {
|
$set: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -560,6 +661,20 @@ describe("OrderService", () => {
|
|||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("order-refund") },
|
{ _id: IdMap.getId("order-refund") },
|
||||||
{
|
{
|
||||||
|
$push: {
|
||||||
|
refunds: {
|
||||||
|
amount: 0,
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
refund_amount: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
$set: {
|
$set: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ describe("TotalsService", () => {
|
|||||||
it("calculates shipping", async () => {
|
it("calculates shipping", async () => {
|
||||||
res = await totalsService.getShippingTotal(orders.testOrder)
|
res = await totalsService.getShippingTotal(orders.testOrder)
|
||||||
|
|
||||||
expect(res).toEqual(110)
|
expect(res).toEqual(100)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe("getTaxTotal", () => {
|
describe("getTaxTotal", () => {
|
||||||
@@ -341,7 +341,7 @@ describe("TotalsService", () => {
|
|||||||
it("calculates tax", async () => {
|
it("calculates tax", async () => {
|
||||||
res = await totalsService.getTaxTotal(orders.testOrder)
|
res = await totalsService.getTaxTotal(orders.testOrder)
|
||||||
|
|
||||||
expect(res).toEqual(335)
|
expect(res).toEqual(332.5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -358,8 +358,7 @@ describe("TotalsService", () => {
|
|||||||
|
|
||||||
it("calculates total", async () => {
|
it("calculates total", async () => {
|
||||||
res = await totalsService.getTotal(orders.testOrder)
|
res = await totalsService.getTotal(orders.testOrder)
|
||||||
|
expect(res).toEqual(1230 + 332.5 + 100)
|
||||||
expect(res).toEqual(1230 + 335 + 110)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -8,6 +8,7 @@ class OrderService extends BaseService {
|
|||||||
PAYMENT_CAPTURED: "order.payment_captured",
|
PAYMENT_CAPTURED: "order.payment_captured",
|
||||||
SHIPMENT_CREATED: "order.shipment_created",
|
SHIPMENT_CREATED: "order.shipment_created",
|
||||||
ITEMS_RETURNED: "order.items_returned",
|
ITEMS_RETURNED: "order.items_returned",
|
||||||
|
REFUND_CREATED: "order.refund_created",
|
||||||
PLACED: "order.placed",
|
PLACED: "order.placed",
|
||||||
UPDATED: "order.updated",
|
UPDATED: "order.updated",
|
||||||
CANCELLED: "order.cancelled",
|
CANCELLED: "order.cancelled",
|
||||||
@@ -373,47 +374,33 @@ class OrderService extends BaseService {
|
|||||||
/**
|
/**
|
||||||
* Adds a shipment to the order to indicate that an order has left the warehouse
|
* Adds a shipment to the order to indicate that an order has left the warehouse
|
||||||
*/
|
*/
|
||||||
async createShipment(orderId, shipment) {
|
async createShipment(orderId, fulfillmentId, trackingNumbers, metadata = {}) {
|
||||||
const order = await this.retrieve(orderId)
|
const order = await this.retrieve(orderId)
|
||||||
|
|
||||||
console.log(order)
|
let shipment
|
||||||
if (order.fulfillment_status === "shipped") {
|
const updated = order.fulfillments.map(f => {
|
||||||
throw new MedusaError(
|
if (f._id.equals(fulfillmentId)) {
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
shipment = {
|
||||||
"Order has already been shipped"
|
...f,
|
||||||
)
|
tracking_numbers: trackingNumbers,
|
||||||
}
|
metadata: {
|
||||||
|
...f.metadata,
|
||||||
const shipmentSchema = Validator.object({
|
...metadata,
|
||||||
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",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
})
|
||||||
|
|
||||||
// Add the shipment to the order
|
// Add the shipment to the order
|
||||||
return this.orderModel_.updateOne({ _id: orderId }, update).then(result => {
|
return this.orderModel_
|
||||||
|
.updateOne(
|
||||||
|
{ _id: orderId },
|
||||||
|
{
|
||||||
|
$set: { fulfillments: updated },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(result => {
|
||||||
this.eventBus_.emit(OrderService.Events.SHIPMENT_CREATED, {
|
this.eventBus_.emit(OrderService.Events.SHIPMENT_CREATED, {
|
||||||
order_id: orderId,
|
order_id: orderId,
|
||||||
shipment,
|
shipment,
|
||||||
@@ -614,9 +601,33 @@ class OrderService extends BaseService {
|
|||||||
* @param {string} orderId - id of order to cancel.
|
* @param {string} orderId - id of order to cancel.
|
||||||
* @return {Promise} result of the update operation.
|
* @return {Promise} result of the update operation.
|
||||||
*/
|
*/
|
||||||
async createFulfillment(orderId) {
|
async createFulfillment(orderId, itemsToFulfill, metadata = {}) {
|
||||||
const order = await this.retrieve(orderId)
|
const order = await this.retrieve(orderId)
|
||||||
|
|
||||||
|
const lineItems = itemsToFulfill
|
||||||
|
.map(({ item_id, quantity }) => {
|
||||||
|
const item = order.items.find(i => i._id.equals(item_id))
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
// This will in most cases be called by a webhook so to ensure that
|
||||||
|
// things go through smoothly in instances where extra items outside
|
||||||
|
// of Medusa are added we allow unknown items
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity > item.quantity - item.fulfilled_quantity) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Cannot fulfill more items than have been purchased"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
quantity,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(i => !!i)
|
||||||
|
|
||||||
if (order.fulfillment_status !== "not_fulfilled") {
|
if (order.fulfillment_status !== "not_fulfilled") {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
MedusaError.Types.INVALID_ARGUMENT,
|
||||||
@@ -624,7 +635,7 @@ class OrderService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { shipping_methods, items } = order
|
const { shipping_methods } = order
|
||||||
|
|
||||||
// prepare update object
|
// prepare update object
|
||||||
const updateFields = { fulfillment_status: "fulfilled" }
|
const updateFields = { fulfillment_status: "fulfilled" }
|
||||||
@@ -634,26 +645,55 @@ class OrderService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// partition order items to their dedicated shipping method
|
// partition order items to their dedicated shipping method
|
||||||
updateFields.shipping_methods = await this.partitionItems_(
|
const fulfillments = await this.partitionItems_(shipping_methods, lineItems)
|
||||||
shipping_methods,
|
|
||||||
items
|
|
||||||
)
|
|
||||||
|
|
||||||
await Promise.all(
|
let successfullyFulfilled = []
|
||||||
updateFields.shipping_methods.map(method => {
|
const results = await Promise.all(
|
||||||
|
fulfillments.map(async method => {
|
||||||
const provider = this.fulfillmentProviderService_.retrieveProvider(
|
const provider = this.fulfillmentProviderService_.retrieveProvider(
|
||||||
method.provider_id
|
method.provider_id
|
||||||
)
|
)
|
||||||
return provider.createOrder(method.data, method.items)
|
|
||||||
|
const data = await provider
|
||||||
|
.createOrder(method.data, method.items)
|
||||||
|
.then(res => {
|
||||||
|
successfullyFulfilled = [...successfullyFulfilled, ...method.items]
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
provider_id: method.provider_id,
|
||||||
|
items: method.items,
|
||||||
|
data,
|
||||||
|
metadata,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Reflect the fulfillments in the items
|
||||||
|
updateFields.items = order.items.map(i => {
|
||||||
|
const ful = successfullyFulfilled.find(f => i._id.equals(f._id))
|
||||||
|
if (ful) {
|
||||||
|
if (i.quantity === ful.quantity) {
|
||||||
|
i.fulfilled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
fulfilled_quantity: ful.quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
})
|
||||||
|
|
||||||
return this.orderModel_
|
return this.orderModel_
|
||||||
.updateOne(
|
.updateOne(
|
||||||
{
|
{
|
||||||
_id: orderId,
|
_id: orderId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
$push: { fulfillments: { $each: results } },
|
||||||
$set: updateFields,
|
$set: updateFields,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -676,6 +716,16 @@ class OrderService extends BaseService {
|
|||||||
async return(orderId, lineItems, refundAmount) {
|
async return(orderId, lineItems, refundAmount) {
|
||||||
const order = await this.retrieve(orderId)
|
const order = await this.retrieve(orderId)
|
||||||
|
|
||||||
|
const total = await this.totalsService_.getTotal(order)
|
||||||
|
const refunded = await this.totalsService_.getRefundedTotal(order)
|
||||||
|
|
||||||
|
if (refundAmount > total - refunded) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Cannot refund more than the original payment"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Find the lines to return
|
// Find the lines to return
|
||||||
const returnLines = lineItems.map(({ item_id, quantity }) => {
|
const returnLines = lineItems.map(({ item_id, quantity }) => {
|
||||||
const item = order.items.find(i => i._id.equals(item_id))
|
const item = order.items.find(i => i._id.equals(item_id))
|
||||||
@@ -686,6 +736,14 @@ class OrderService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const returnable = item.quantity - item.returned_quantity
|
||||||
|
if (quantity > returnable) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Cannot return more items than have been purchased"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
quantity,
|
quantity,
|
||||||
@@ -722,17 +780,22 @@ class OrderService extends BaseService {
|
|||||||
const newItems = order.items.map(i => {
|
const newItems = order.items.map(i => {
|
||||||
const isReturn = returnLines.find(r => r._id.equals(i._id))
|
const isReturn = returnLines.find(r => r._id.equals(i._id))
|
||||||
if (isReturn) {
|
if (isReturn) {
|
||||||
|
const returnedQuantity = i.returned_quantity + isReturn.quantity
|
||||||
let returned = false
|
let returned = false
|
||||||
if (i.quantity === isReturn.quantity) {
|
if (i.quantity === returnedQuantity) {
|
||||||
returned = true
|
returned = true
|
||||||
|
} else {
|
||||||
|
isFullReturn = false
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
returned_quantity: isReturn.quantity,
|
returned_quantity: returnedQuantity
|
||||||
returned,
|
returned
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!i.returned) {
|
||||||
isFullReturn = false
|
isFullReturn = false
|
||||||
|
}
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -743,6 +806,15 @@ class OrderService extends BaseService {
|
|||||||
_id: orderId,
|
_id: orderId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
$push: {
|
||||||
|
refunds: {
|
||||||
|
amount,
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
items: lineItems,
|
||||||
|
refund_amount: amount,
|
||||||
|
},
|
||||||
|
},
|
||||||
$set: {
|
$set: {
|
||||||
items: newItems,
|
items: newItems,
|
||||||
fulfillment_status: isFullReturn
|
fulfillment_status: isFullReturn
|
||||||
@@ -754,7 +826,10 @@ class OrderService extends BaseService {
|
|||||||
.then(result => {
|
.then(result => {
|
||||||
this.eventBus_.emit(OrderService.Events.ITEMS_RETURNED, {
|
this.eventBus_.emit(OrderService.Events.ITEMS_RETURNED, {
|
||||||
order: result,
|
order: result,
|
||||||
items: returnLines,
|
return: {
|
||||||
|
items: lineItems,
|
||||||
|
refund_amount: amount,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
@@ -786,6 +861,56 @@ class OrderService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refunds a given amount back to the customer.
|
||||||
|
*/
|
||||||
|
async createRefund(orderId, refundAmount, reason, note) {
|
||||||
|
const order = await this.retrieve(orderId)
|
||||||
|
const total = await this.totalsService_.getTotal(order)
|
||||||
|
const refunded = await this.totalsService_.getRefundedTotal(order)
|
||||||
|
|
||||||
|
if (refundAmount > total - refunded) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.NOT_ALLOWED,
|
||||||
|
"Cannot refund more than original order amount"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { provider_id, data } = order.payment_method
|
||||||
|
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
||||||
|
provider_id
|
||||||
|
)
|
||||||
|
|
||||||
|
await paymentProvider.refundPayment(data, refundAmount)
|
||||||
|
|
||||||
|
return this.orderModel_
|
||||||
|
.updateOne(
|
||||||
|
{
|
||||||
|
_id: orderId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$push: {
|
||||||
|
refunds: {
|
||||||
|
amount: refundAmount,
|
||||||
|
reason,
|
||||||
|
note,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(result => {
|
||||||
|
this.eventBus_.emit(OrderService.Events.REFUND_CREATED, {
|
||||||
|
order: result,
|
||||||
|
refund: {
|
||||||
|
amount: refundAmount,
|
||||||
|
reason,
|
||||||
|
note,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorates an order.
|
* Decorates an order.
|
||||||
* @param {Order} order - the order to decorate.
|
* @param {Order} order - the order to decorate.
|
||||||
@@ -800,6 +925,8 @@ class OrderService extends BaseService {
|
|||||||
o.tax_total = await this.totalsService_.getTaxTotal(order)
|
o.tax_total = await this.totalsService_.getTaxTotal(order)
|
||||||
o.subtotal = await this.totalsService_.getSubtotal(order)
|
o.subtotal = await this.totalsService_.getSubtotal(order)
|
||||||
o.total = await this.totalsService_.getTotal(order)
|
o.total = await this.totalsService_.getTotal(order)
|
||||||
|
o.refunded_total = await this.totalsService_.getRefundedTotal(order)
|
||||||
|
o.refundable_amount = o.total - o.refunded_total
|
||||||
o.created = order._id.getTimestamp()
|
o.created = order._id.getTimestamp()
|
||||||
if (expandFields.includes("region")) {
|
if (expandFields.includes("region")) {
|
||||||
o.region = await this.regionService_.retrieve(order.region_id)
|
o.region = await this.regionService_.retrieve(order.region_id)
|
||||||
@@ -808,7 +935,10 @@ class OrderService extends BaseService {
|
|||||||
o.items = o.items.map(i => {
|
o.items = o.items.map(i => {
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
refundable: this.totalsService_.getLineItemRefund(o, i),
|
refundable: this.totalsService_.getLineItemRefund(o, {
|
||||||
|
...i,
|
||||||
|
quantity: i.quantity - i.returned_quantity,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ class TotalsService extends BaseService {
|
|||||||
return (subtotal - discountTotal + shippingTotal) * tax_rate
|
return (subtotal - discountTotal + shippingTotal) * tax_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRefundedTotal(object) {
|
||||||
|
const total = object.refunds.reduce((acc, next) => acc + next.amount, 0)
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
getLineItemRefund(order, lineItem) {
|
getLineItemRefund(order, lineItem) {
|
||||||
const { tax_rate, discounts } = order
|
const { tax_rate, discounts } = order
|
||||||
const taxRate = tax_rate || 0
|
const taxRate = tax_rate || 0
|
||||||
|
|||||||
Reference in new issue
Block a user