Tests passing
This commit is contained in:
1 parent
21bc096b2e
commit
ee93256e69
17 files changed
+295
-229
No files matched your search
@@ -1,14 +0,0 @@
|
|||||||
class OrderSubscriber {
|
|
||||||
constructor({ klarnaProviderService, eventBusService }) {
|
|
||||||
this.klarnaProviderService_ = klarnaProviderService
|
|
||||||
|
|
||||||
this.eventBus_ = eventBusService
|
|
||||||
|
|
||||||
this.eventBus_.subscribe("order.completed", async (order) => {
|
|
||||||
const klarnaOrderId = order.payment_method.data.id
|
|
||||||
await this.klarnaProviderService_.acknowledgeOrder(klarnaOrderId)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OrderSubscriber
|
|
||||||
@@ -13,11 +13,6 @@ class CartSubscriber {
|
|||||||
this.eventBus_.subscribe("cart.customer_updated", async (cart) => {
|
this.eventBus_.subscribe("cart.customer_updated", async (cart) => {
|
||||||
await this.onCustomerUpdated(cart)
|
await this.onCustomerUpdated(cart)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.eventBus_.subscribe("order.completed", async (order) => {
|
|
||||||
const paymentData = order.payment_method.data
|
|
||||||
await this.stripeProviderService_.capturePayment(paymentData)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onCustomerUpdated(cart) {
|
async onCustomerUpdated(cart) {
|
||||||
|
|||||||
@@ -5,13 +5,57 @@ Object.defineProperty(exports, "__esModule", {
|
|||||||
});
|
});
|
||||||
exports["default"] = void 0;
|
exports["default"] = void 0;
|
||||||
|
|
||||||
var inventorySync = function inventorySync(container) {
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
||||||
var brightpearlService = container.resolve("brightpearlService");
|
|
||||||
var eventBus = container.resolve("eventBusService");
|
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
||||||
var pattern = "43 4,10,14,20 * * *"; // nice for tests "*/10 * * * * *"
|
|
||||||
|
var inventorySync = /*#__PURE__*/function () {
|
||||||
|
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(container) {
|
||||||
|
var brightpearlService, eventBus, client, pattern;
|
||||||
|
return regeneratorRuntime.wrap(function _callee$(_context) {
|
||||||
|
while (1) {
|
||||||
|
switch (_context.prev = _context.next) {
|
||||||
|
case 0:
|
||||||
|
brightpearlService = container.resolve("brightpearlService");
|
||||||
|
eventBus = container.resolve("eventBusService");
|
||||||
|
_context.prev = 2;
|
||||||
|
_context.next = 5;
|
||||||
|
return brightpearlService.getClient();
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
client = _context.sent;
|
||||||
|
pattern = "43 4,10,14,20 * * *"; // nice for tests "*/10 * * * * *"
|
||||||
|
|
||||||
eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory());
|
eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory());
|
||||||
|
_context.next = 15;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
_context.prev = 10;
|
||||||
|
_context.t0 = _context["catch"](2);
|
||||||
|
|
||||||
|
if (!(_context.t0.name === "not_allowed")) {
|
||||||
|
_context.next = 14;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.abrupt("return");
|
||||||
|
|
||||||
|
case 14:
|
||||||
|
throw _context.t0;
|
||||||
|
|
||||||
|
case 15:
|
||||||
|
case "end":
|
||||||
|
return _context.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _callee, null, [[2, 10]]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return function inventorySync(_x) {
|
||||||
|
return _ref.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
var _default = inventorySync;
|
var _default = inventorySync;
|
||||||
exports["default"] = _default;
|
exports["default"] = _default;
|
||||||
@@ -11,21 +11,45 @@ function _asyncToGenerator(fn) { return function () { var self = this, args = ar
|
|||||||
|
|
||||||
var webhookLoader = /*#__PURE__*/function () {
|
var webhookLoader = /*#__PURE__*/function () {
|
||||||
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(container) {
|
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(container) {
|
||||||
var brightpearlService;
|
var brightpearlService, client;
|
||||||
return regeneratorRuntime.wrap(function _callee$(_context) {
|
return regeneratorRuntime.wrap(function _callee$(_context) {
|
||||||
while (1) {
|
while (1) {
|
||||||
switch (_context.prev = _context.next) {
|
switch (_context.prev = _context.next) {
|
||||||
case 0:
|
case 0:
|
||||||
brightpearlService = container.resolve("brightpearlService");
|
brightpearlService = container.resolve("brightpearlService");
|
||||||
_context.next = 3;
|
_context.prev = 1;
|
||||||
|
_context.next = 4;
|
||||||
|
return brightpearlService.getClient();
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
client = _context.sent;
|
||||||
|
_context.next = 7;
|
||||||
return brightpearlService.verifyWebhooks();
|
return brightpearlService.verifyWebhooks();
|
||||||
|
|
||||||
case 3:
|
case 7:
|
||||||
|
_context.next = 14;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
_context.prev = 9;
|
||||||
|
_context.t0 = _context["catch"](1);
|
||||||
|
|
||||||
|
if (!(_context.t0.name === "not_allowed")) {
|
||||||
|
_context.next = 13;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.abrupt("return");
|
||||||
|
|
||||||
|
case 13:
|
||||||
|
throw _context.t0;
|
||||||
|
|
||||||
|
case 14:
|
||||||
case "end":
|
case "end":
|
||||||
return _context.stop();
|
return _context.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, _callee);
|
}, _callee, null, [[1, 9]]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return function webhookLoader(_x) {
|
return function webhookLoader(_x) {
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
const inventorySync = container => {
|
const inventorySync = async (container) => {
|
||||||
const brightpearlService = container.resolve("brightpearlService")
|
const brightpearlService = container.resolve("brightpearlService")
|
||||||
const eventBus = container.resolve("eventBusService")
|
const eventBus = container.resolve("eventBusService")
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = await brightpearlService.getClient()
|
||||||
const pattern = "43 4,10,14,20 * * *" // nice for tests "*/10 * * * * *"
|
const pattern = "43 4,10,14,20 * * *" // nice for tests "*/10 * * * * *"
|
||||||
eventBus.createCronJob("inventory-sync", {}, pattern, brightpearlService.syncInventory())
|
eventBus.createCronJob(
|
||||||
|
"inventory-sync",
|
||||||
|
{},
|
||||||
|
pattern,
|
||||||
|
brightpearlService.syncInventory()
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === "not_allowed") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default inventorySync
|
export default inventorySync
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
const webhookLoader = async (container) => {
|
const webhookLoader = async (container) => {
|
||||||
const brightpearlService = container.resolve("brightpearlService")
|
const brightpearlService = container.resolve("brightpearlService")
|
||||||
|
try {
|
||||||
|
const client = await brightpearlService.getClient()
|
||||||
await brightpearlService.verifyWebhooks()
|
await brightpearlService.verifyWebhooks()
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === "not_allowed") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default webhookLoader
|
export default webhookLoader
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
import { BaseService } from "medusa-interfaces"
|
import { BaseService } from "medusa-interfaces"
|
||||||
import Brightpearl from "../utils/brightpearl"
|
import Brightpearl from "../utils/brightpearl"
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,7 @@ describe("POST /admin/orders/:id/return", () => {
|
|||||||
payload: {
|
payload: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("existingLine"),
|
item_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,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -51,20 +38,7 @@ describe("POST /admin/orders/:id/return", () => {
|
|||||||
IdMap.getId("test-order"),
|
IdMap.getId("test-order"),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("existingLine"),
|
item_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,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ export default async (req, res) => {
|
|||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
|
||||||
const schema = Validator.object().keys({
|
const schema = Validator.object().keys({
|
||||||
items: Validator.array().required(),
|
items: Validator.array()
|
||||||
|
.items({
|
||||||
|
item_id: Validator.string().required(),
|
||||||
|
quantity: Validator.number().required(),
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
refund: Validator.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { value, error } = schema.validate(req.body)
|
const { value, error } = schema.validate(req.body)
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ export const orders = {
|
|||||||
customer_id: IdMap.getId("test-customer"),
|
customer_id: IdMap.getId("test-customer"),
|
||||||
payment_method: {
|
payment_method: {
|
||||||
provider_id: "default_provider",
|
provider_id: "default_provider",
|
||||||
|
data: {
|
||||||
|
hi: "hi",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
shipping_methods: [
|
shipping_methods: [
|
||||||
{
|
{
|
||||||
@@ -133,6 +136,7 @@ export const orders = {
|
|||||||
orderToRefund: {
|
orderToRefund: {
|
||||||
_id: IdMap.getId("refund-order"),
|
_id: IdMap.getId("refund-order"),
|
||||||
email: "oliver@test.dk",
|
email: "oliver@test.dk",
|
||||||
|
tax_rate: 0.25,
|
||||||
billing_address: {
|
billing_address: {
|
||||||
first_name: "Oli",
|
first_name: "Oli",
|
||||||
last_name: "Medusa",
|
last_name: "Medusa",
|
||||||
|
|||||||
@@ -36,5 +36,6 @@ 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 },
|
||||||
|
returned_quantity: { type: Number, default: 0 },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
})
|
})
|
||||||
@@ -22,7 +22,7 @@ describe("EventBusService", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("creates bull queue", () => {
|
it("creates bull queue", () => {
|
||||||
expect(Bull).toHaveBeenCalledTimes(1)
|
expect(Bull).toHaveBeenCalledTimes(2)
|
||||||
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", "testhost")
|
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", "testhost")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { IdMap } from "medusa-test-utils"
|
|||||||
import { OrderModelMock, orders } from "../../models/__mocks__/order"
|
import { OrderModelMock, orders } from "../../models/__mocks__/order"
|
||||||
import { carts } from "../../models/__mocks__/cart"
|
import { carts } from "../../models/__mocks__/cart"
|
||||||
import OrderService from "../order"
|
import OrderService from "../order"
|
||||||
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
|
import {
|
||||||
|
PaymentProviderServiceMock,
|
||||||
|
DefaultProviderMock,
|
||||||
|
} from "../__mocks__/payment-provider"
|
||||||
import { DiscountServiceMock } from "../__mocks__/discount"
|
import { DiscountServiceMock } from "../__mocks__/discount"
|
||||||
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
||||||
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
import { ShippingProfileServiceMock } from "../__mocks__/shipping-profile"
|
||||||
@@ -53,6 +56,7 @@ describe("OrderService", () => {
|
|||||||
...carts.completeCart,
|
...carts.completeCart,
|
||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
cart_id: carts.completeCart._id,
|
cart_id: carts.completeCart._id,
|
||||||
|
tax_rate: 0.25,
|
||||||
}
|
}
|
||||||
delete order._id
|
delete order._id
|
||||||
delete order.payment_sessions
|
delete order.payment_sessions
|
||||||
@@ -112,6 +116,7 @@ describe("OrderService", () => {
|
|||||||
],
|
],
|
||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
cart_id: carts.withGiftCard._id,
|
cart_id: carts.withGiftCard._id,
|
||||||
|
tax_rate: 0.25,
|
||||||
}
|
}
|
||||||
|
|
||||||
delete order._id
|
delete order._id
|
||||||
@@ -359,6 +364,7 @@ describe("OrderService", () => {
|
|||||||
const orderService = new OrderService({
|
const orderService = new OrderService({
|
||||||
orderModel: OrderModelMock,
|
orderModel: OrderModelMock,
|
||||||
paymentProviderService: PaymentProviderServiceMock,
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -439,6 +445,7 @@ describe("OrderService", () => {
|
|||||||
orderModel: OrderModelMock,
|
orderModel: OrderModelMock,
|
||||||
paymentProviderService: PaymentProviderServiceMock,
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
totalsService: TotalsServiceMock,
|
totalsService: TotalsServiceMock,
|
||||||
|
eventBusService: EventBusServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -448,20 +455,7 @@ describe("OrderService", () => {
|
|||||||
it("calls order model functions", async () => {
|
it("calls order model functions", async () => {
|
||||||
await orderService.return(IdMap.getId("processed-order"), [
|
await orderService.return(IdMap.getId("processed-order"), [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("existingLine"),
|
item_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,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -489,31 +483,75 @@ describe("OrderService", () => {
|
|||||||
returned_quantity: 10,
|
returned_quantity: 10,
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
title: "merge line",
|
title: "merge line",
|
||||||
|
returned: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
fulfillment_status: "returned",
|
fulfillment_status: "returned",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(DefaultProviderMock.refundPayment).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DefaultProviderMock.refundPayment).toHaveBeenCalledWith(
|
||||||
|
{ hi: "hi" },
|
||||||
|
1230
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls order model functions and sets partially_fulfilled", async () => {
|
it("return with custom refund", async () => {
|
||||||
await orderService.return(IdMap.getId("order-refund"), [
|
await orderService.return(
|
||||||
|
IdMap.getId("processed-order"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
|
quantity: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
102
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
|
{ _id: IdMap.getId("processed-order") },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("existingLine"),
|
_id: IdMap.getId("existingLine"),
|
||||||
title: "merge line",
|
|
||||||
description: "This is a new line",
|
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
|
||||||
content: {
|
content: {
|
||||||
unit_price: 100,
|
|
||||||
variant: {
|
|
||||||
_id: IdMap.getId("eur-8-us-10"),
|
|
||||||
},
|
|
||||||
product: {
|
product: {
|
||||||
_id: IdMap.getId("product"),
|
_id: IdMap.getId("validId"),
|
||||||
},
|
},
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
unit_price: 123,
|
||||||
|
variant: {
|
||||||
|
_id: IdMap.getId("can-cover"),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
description: "This is a new line",
|
||||||
|
quantity: 10,
|
||||||
|
returned_quantity: 10,
|
||||||
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
|
title: "merge line",
|
||||||
|
returned: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fulfillment_status: "returned",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(DefaultProviderMock.refundPayment).toHaveBeenCalledTimes(1)
|
||||||
|
expect(DefaultProviderMock.refundPayment).toHaveBeenCalledWith(
|
||||||
|
{ hi: "hi" },
|
||||||
|
102
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls order model functions and sets partially_returned", async () => {
|
||||||
|
await orderService.return(IdMap.getId("order-refund"), [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("existingLine"),
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@@ -538,6 +576,7 @@ describe("OrderService", () => {
|
|||||||
},
|
},
|
||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
|
returned: false,
|
||||||
returned_quantity: 2,
|
returned_quantity: 2,
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
title: "merge line",
|
title: "merge line",
|
||||||
@@ -560,7 +599,7 @@ describe("OrderService", () => {
|
|||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
fulfillment_status: "partially_fulfilled",
|
fulfillment_status: "partially_returned",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -568,7 +607,7 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
it("throws if payment is already processed", async () => {
|
it("throws if payment is already processed", async () => {
|
||||||
try {
|
try {
|
||||||
await orderService.return(IdMap.getId("fulfilled-order"))
|
await orderService.return(IdMap.getId("fulfilled-order"), [])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toEqual(
|
expect(error.message).toEqual(
|
||||||
"Can't return an order with payment unprocessed"
|
"Can't return an order with payment unprocessed"
|
||||||
@@ -578,7 +617,7 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
it("throws if return is attempted on unfulfilled order", async () => {
|
it("throws if return is attempted on unfulfilled order", async () => {
|
||||||
try {
|
try {
|
||||||
await orderService.return(IdMap.getId("not-fulfilled-order"))
|
await orderService.return(IdMap.getId("not-fulfilled-order"), [])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toEqual(
|
expect(error.message).toEqual(
|
||||||
"Can't return an unfulfilled or already returned order"
|
"Can't return an unfulfilled or already returned order"
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ describe("TotalsService", () => {
|
|||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
content: {
|
content: {
|
||||||
unit_price: 123,
|
unit_price: 100,
|
||||||
variant: {
|
variant: {
|
||||||
_id: IdMap.getId("can-cover"),
|
_id: IdMap.getId("can-cover"),
|
||||||
},
|
},
|
||||||
@@ -206,7 +206,7 @@ describe("TotalsService", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(res).toEqual(1107)
|
expect(res).toEqual(1125)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calculates refund with total fixed discount", async () => {
|
it("calculates refund with total fixed discount", async () => {
|
||||||
@@ -218,7 +218,7 @@ describe("TotalsService", () => {
|
|||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
content: {
|
content: {
|
||||||
unit_price: 123,
|
unit_price: 100,
|
||||||
variant: {
|
variant: {
|
||||||
_id: IdMap.getId("can-cover"),
|
_id: IdMap.getId("can-cover"),
|
||||||
},
|
},
|
||||||
@@ -231,7 +231,7 @@ describe("TotalsService", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(res).toEqual(359)
|
expect(res).toEqual(373.125)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calculates refund with item fixed discount", async () => {
|
it("calculates refund with item fixed discount", async () => {
|
||||||
@@ -243,7 +243,7 @@ describe("TotalsService", () => {
|
|||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
content: {
|
content: {
|
||||||
unit_price: 123,
|
unit_price: 100,
|
||||||
variant: {
|
variant: {
|
||||||
_id: IdMap.getId("eur-8-us-10"),
|
_id: IdMap.getId("eur-8-us-10"),
|
||||||
},
|
},
|
||||||
@@ -256,7 +256,7 @@ describe("TotalsService", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(res).toEqual(363)
|
expect(res).toEqual(367.5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calculates refund with item percentage discount", async () => {
|
it("calculates refund with item percentage discount", async () => {
|
||||||
@@ -268,7 +268,7 @@ describe("TotalsService", () => {
|
|||||||
description: "This is a new line",
|
description: "This is a new line",
|
||||||
thumbnail: "test-img-yeah.com/thumb",
|
thumbnail: "test-img-yeah.com/thumb",
|
||||||
content: {
|
content: {
|
||||||
unit_price: 123,
|
unit_price: 100,
|
||||||
variant: {
|
variant: {
|
||||||
_id: IdMap.getId("eur-8-us-10"),
|
_id: IdMap.getId("eur-8-us-10"),
|
||||||
},
|
},
|
||||||
@@ -281,7 +281,7 @@ describe("TotalsService", () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(res).toEqual(332.1)
|
expect(res).toEqual(337.5)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws if line items to return is not in order", async () => {
|
it("throws if line items to return is not in order", async () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class OrderService extends BaseService {
|
|||||||
GIFT_CARD_CREATED: "order.gift_card_created",
|
GIFT_CARD_CREATED: "order.gift_card_created",
|
||||||
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",
|
||||||
PLACED: "order.placed",
|
PLACED: "order.placed",
|
||||||
UPDATED: "order.updated",
|
UPDATED: "order.updated",
|
||||||
CANCELLED: "order.cancelled",
|
CANCELLED: "order.cancelled",
|
||||||
@@ -218,28 +219,28 @@ class OrderService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async completeOrder(orderId) {
|
async completeOrder(orderId) {
|
||||||
const order = await this.retrieve(orderId)
|
const order = await this.retrieve(orderId)
|
||||||
this.orderModel_
|
|
||||||
|
// Capture the payment
|
||||||
|
await this.capturePayment(orderId)
|
||||||
|
|
||||||
|
// Run all other registered events
|
||||||
|
const completeOrderJob = await this.eventBus_.emit(
|
||||||
|
OrderService.Events.COMPLETED,
|
||||||
|
result
|
||||||
|
)
|
||||||
|
|
||||||
|
await completeOrderJob.finished().catch(error => {
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.orderModel_
|
||||||
.updateOne(
|
.updateOne(
|
||||||
{ _id: order._id },
|
{ _id: order._id },
|
||||||
{
|
{
|
||||||
$set: { status: "completed" },
|
$set: { status: "completed" },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(async result => {
|
.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
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -672,9 +673,25 @@ class OrderService extends BaseService {
|
|||||||
* @param {string[]} lineItems - the line items to return
|
* @param {string[]} lineItems - the line items to return
|
||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
*/
|
*/
|
||||||
async return(orderId, lineItems) {
|
async return(orderId, lineItems, refundAmount) {
|
||||||
const order = await this.retrieve(orderId)
|
const order = await this.retrieve(orderId)
|
||||||
|
|
||||||
|
// Find the lines to return
|
||||||
|
const returnLines = lineItems.map(({ item_id, quantity }) => {
|
||||||
|
const item = order.items.find(i => i._id.equals(item_id))
|
||||||
|
if (!item) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Return contains invalid line item"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
quantity,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
order.fulfillment_status === "not_fulfilled" ||
|
order.fulfillment_status === "not_fulfilled" ||
|
||||||
order.fulfillment_status === "returned"
|
order.fulfillment_status === "returned"
|
||||||
@@ -697,31 +714,50 @@ class OrderService extends BaseService {
|
|||||||
provider_id
|
provider_id
|
||||||
)
|
)
|
||||||
|
|
||||||
const amount = this.totalsService_.getRefundTotal(order, lineItems)
|
const amount =
|
||||||
|
refundAmount || this.totalsService_.getRefundTotal(order, returnLines)
|
||||||
await paymentProvider.refundPayment(data, amount)
|
await paymentProvider.refundPayment(data, amount)
|
||||||
|
|
||||||
lineItems.map(item => {
|
let isFullReturn = true
|
||||||
const returnedItem = order.items.find(({ _id }) => _id === item._id)
|
const newItems = order.items.map(i => {
|
||||||
if (returnedItem) {
|
const isReturn = returnLines.find(r => r._id.equals(i._id))
|
||||||
returnedItem.returned_quantity = item.quantity
|
if (isReturn) {
|
||||||
|
let returned = false
|
||||||
|
if (i.quantity === isReturn.quantity) {
|
||||||
|
returned = true
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
returned_quantity: isReturn.quantity,
|
||||||
|
returned,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isFullReturn = false
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const fullReturn = order.items.every(
|
return this.orderModel_
|
||||||
item => item.quantity === item.returned_quantity
|
.updateOne(
|
||||||
)
|
|
||||||
|
|
||||||
return this.orderModel_.updateOne(
|
|
||||||
{
|
{
|
||||||
_id: orderId,
|
_id: orderId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
items: order.items,
|
items: newItems,
|
||||||
fulfillment_status: fullReturn ? "returned" : "partially_fulfilled",
|
fulfillment_status: isFullReturn
|
||||||
|
? "returned"
|
||||||
|
: "partially_returned",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.then(result => {
|
||||||
|
this.eventBus_.emit(OrderService.Events.ITEMS_RETURNED, {
|
||||||
|
order: result,
|
||||||
|
items: returnLines,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -768,6 +804,14 @@ class OrderService extends BaseService {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
o.items = o.items.map(i => {
|
||||||
|
return {
|
||||||
|
...i,
|
||||||
|
refundable: this.totalsService_.getLineItemRefund(o, i),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,32 @@ class TotalsService extends BaseService {
|
|||||||
return (subtotal - discountTotal + shippingTotal) * tax_rate
|
return (subtotal - discountTotal + shippingTotal) * tax_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLineItemRefund(order, lineItem) {
|
||||||
|
const { tax_rate, discounts } = order
|
||||||
|
const taxRate = tax_rate || 0
|
||||||
|
|
||||||
|
const discount = discounts.find(
|
||||||
|
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!discount) {
|
||||||
|
return lineItem.content.unit_price * lineItem.quantity * (1 + taxRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineDiscounts = this.getLineDiscounts(order, discount)
|
||||||
|
const discountedLine = lineDiscounts.find(line =>
|
||||||
|
line.item._id.equals(lineItem._id)
|
||||||
|
)
|
||||||
|
|
||||||
|
const discountAmount =
|
||||||
|
(discountedLine.amount / discountedLine.item.quantity) * lineItem.quantity
|
||||||
|
|
||||||
|
return (
|
||||||
|
(lineItem.content.unit_price * lineItem.quantity - discountAmount) *
|
||||||
|
(1 + taxRate)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates refund total of line items.
|
* Calculates refund total of line items.
|
||||||
* If any of the items to return have been discounted, we need to
|
* If any of the items to return have been discounted, we need to
|
||||||
@@ -88,101 +114,9 @@ class TotalsService extends BaseService {
|
|||||||
* @param {[LineItem]} lineItems -
|
* @param {[LineItem]} lineItems -
|
||||||
* @return {int} the calculated subtotal
|
* @return {int} the calculated subtotal
|
||||||
*/
|
*/
|
||||||
async getRefundTotal(order, lineItems) {
|
getRefundTotal(order, lineItems) {
|
||||||
const discount = order.discounts.find(
|
const refunds = lineItems.map(i => this.getLineItemRefund(order, i))
|
||||||
({ discount_rule }) => discount_rule.type !== "free_shipping"
|
return refunds.reduce((acc, next) => acc + next, 0)
|
||||||
)
|
|
||||||
|
|
||||||
if (_.differenceBy(lineItems, order.items, "_id").length !== 0) {
|
|
||||||
throw new MedusaError(
|
|
||||||
MedusaError.Types.INVALID_ARGUMENT,
|
|
||||||
"Line items does not exist on order"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const subtotal = this.getSubtotal({ items: lineItems })
|
|
||||||
|
|
||||||
const region = await this.regionService_.retrieve(order.region_id)
|
|
||||||
|
|
||||||
// if nothing is discounted, return the subtotal of line items
|
|
||||||
if (!discount) {
|
|
||||||
return subtotal * (1 + region.tax_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { value, type, allocation } = discount.discount_rule
|
|
||||||
|
|
||||||
if (type === "percentage" && allocation === "total") {
|
|
||||||
const discountTotal = (subtotal / 100) * value
|
|
||||||
return subtotal - discountTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "fixed" && allocation === "total") {
|
|
||||||
return subtotal - value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "percentage" && allocation === "item") {
|
|
||||||
// Find discounted items
|
|
||||||
const itemPercentageDiscounts = await this.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
{ items: lineItems },
|
|
||||||
"percentage"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Find discount total by taking each discounted item, reducing it by
|
|
||||||
// its discount value. Then summing all those items together.
|
|
||||||
const discountRefundTotal = _.sumBy(
|
|
||||||
itemPercentageDiscounts,
|
|
||||||
d => d.lineItem.content.unit_price * d.lineItem.quantity - d.amount
|
|
||||||
)
|
|
||||||
|
|
||||||
// Find the items that weren't discounted
|
|
||||||
const notDiscountedItems = _.differenceBy(
|
|
||||||
lineItems,
|
|
||||||
Array.from(itemPercentageDiscounts, el => el.lineItem),
|
|
||||||
"_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
// If all items were discounted, we return the total of the discounted
|
|
||||||
// items
|
|
||||||
if (!notDiscountedItems) {
|
|
||||||
return discountRefundTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we find the total those not discounted
|
|
||||||
const notDiscRefundTotal = this.getSubtotal({ items: notDiscountedItems })
|
|
||||||
|
|
||||||
// Finally, return the sum of discounted and not discounted items
|
|
||||||
return notDiscRefundTotal + discountRefundTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
// See immediate `if`-statement above for a elaboration on the following
|
|
||||||
// calculations. This time with fixed discount type.
|
|
||||||
if (type === "fixed" && allocation === "item") {
|
|
||||||
const itemPercentageDiscounts = await this.getAllocationItemDiscounts(
|
|
||||||
discount,
|
|
||||||
{ items: lineItems },
|
|
||||||
"fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
const discountRefundTotal = _.sumBy(
|
|
||||||
itemPercentageDiscounts,
|
|
||||||
d => d.lineItem.content.unit_price * d.lineItem.quantity - d.amount
|
|
||||||
)
|
|
||||||
|
|
||||||
const notDiscountedItems = _.differenceBy(
|
|
||||||
lineItems,
|
|
||||||
Array.from(itemPercentageDiscounts, el => el.lineItem),
|
|
||||||
"_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!notDiscountedItems) {
|
|
||||||
return notDiscRefundTotal
|
|
||||||
}
|
|
||||||
|
|
||||||
const notDiscRefundTotal = this.getSubtotal({ items: notDiscountedItems })
|
|
||||||
|
|
||||||
return notDiscRefundTotal + discountRefundTotal
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,7 +159,7 @@ class TotalsService extends BaseService {
|
|||||||
* @return {[{ string, string, int }]} array of triples of lineitem, variant
|
* @return {[{ string, string, int }]} array of triples of lineitem, variant
|
||||||
* and applied discount
|
* and applied discount
|
||||||
*/
|
*/
|
||||||
async getAllocationItemDiscounts(discount, cart) {
|
getAllocationItemDiscounts(discount, cart) {
|
||||||
const discounts = []
|
const discounts = []
|
||||||
for (const item of cart.items) {
|
for (const item of cart.items) {
|
||||||
if (discount.discount_rule.valid_for.length > 0) {
|
if (discount.discount_rule.valid_for.length > 0) {
|
||||||
@@ -252,7 +186,7 @@ class TotalsService extends BaseService {
|
|||||||
return discounts
|
return discounts
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLineDiscounts(cart, discount) {
|
getLineDiscounts(cart, discount) {
|
||||||
const subtotal = this.getSubtotal(cart)
|
const subtotal = this.getSubtotal(cart)
|
||||||
const { type, allocation, value } = discount.discount_rule
|
const { type, allocation, value } = discount.discount_rule
|
||||||
if (allocation === "total") {
|
if (allocation === "total") {
|
||||||
|
|||||||
@@ -19,14 +19,6 @@ class OrderSubscriber {
|
|||||||
|
|
||||||
this.eventBus_ = eventBusService
|
this.eventBus_ = eventBusService
|
||||||
|
|
||||||
this.eventBus_.subscribe("order.completed", async order => {
|
|
||||||
const paymentProvider = this.paymentProviderService_.retrieveProvider(
|
|
||||||
order.payment_method.provider_id
|
|
||||||
)
|
|
||||||
|
|
||||||
await paymentProvider.capturePayment(order._id)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.eventBus_.subscribe("order.placed", async order => {
|
this.eventBus_.subscribe("order.placed", async order => {
|
||||||
await this.customerService_.addOrder(order.customer_id, order._id)
|
await this.customerService_.addOrder(order.customer_id, order._id)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user