fix/cancel-order (#120)
* fix: adds ability to cancel order * passing tests * chore: clean up unused code
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import bcrypt from "bcrypt"
|
||||
import Scrypt from "scrypt-kdf"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const CustomerServiceMock = {
|
||||
@@ -43,9 +43,10 @@ export const CustomerServiceMock = {
|
||||
})
|
||||
}
|
||||
if (email === "oliver@test.dk") {
|
||||
return bcrypt
|
||||
.hash("123456789", 10)
|
||||
.then(hash => ({ email, password_hash: hash }))
|
||||
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
|
||||
email,
|
||||
password_hash: hash.toString("base64"),
|
||||
}))
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
|
||||
@@ -42,6 +42,9 @@ export const DiscountServiceMock = {
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve([])
|
||||
}),
|
||||
decorate: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve(data)
|
||||
}),
|
||||
addRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
|
||||
@@ -13,6 +13,9 @@ export const DefaultProviderMock = {
|
||||
|
||||
return Promise.resolve(false)
|
||||
}),
|
||||
cancelFulfillment: jest.fn().mockImplementation(data => {
|
||||
return {}
|
||||
}),
|
||||
calculatePrice: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
|
||||
@@ -15,6 +15,7 @@ export const DefaultProviderMock = {
|
||||
}),
|
||||
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||
}
|
||||
|
||||
export const PaymentProviderServiceMock = {
|
||||
|
||||
@@ -37,6 +37,7 @@ export const ProductServiceMock = {
|
||||
|
||||
return Promise.resolve({ ...data })
|
||||
}),
|
||||
count: jest.fn().mockReturnValue(4),
|
||||
publish: jest.fn().mockImplementation(_ => {
|
||||
return Promise.resolve({
|
||||
_id: IdMap.getId("publish"),
|
||||
|
||||
@@ -126,6 +126,7 @@ export const ShippingProfileServiceMock = {
|
||||
])
|
||||
}
|
||||
}),
|
||||
decorate: jest.fn().mockImplementation(d => Promise.resolve(d)),
|
||||
addShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
addProduct: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import bcrypt from "bcrypt"
|
||||
import Scrypt from "scrypt-kdf"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import _ from "lodash"
|
||||
|
||||
@@ -92,9 +92,10 @@ export const UserServiceMock = {
|
||||
})
|
||||
}
|
||||
if (email === "oliver@test.dk") {
|
||||
return bcrypt
|
||||
.hash("123456789", 10)
|
||||
.then(hash => ({ email, password_hash: hash }))
|
||||
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
|
||||
email,
|
||||
password_hash: hash.toString("base64"),
|
||||
}))
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
|
||||
@@ -228,12 +228,26 @@ describe("CartService", () => {
|
||||
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("cartWithLine"),
|
||||
"items._id": IdMap.getId("existingLine"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
"items.$.quantity": 20,
|
||||
"items.$.has_shipping": false,
|
||||
$push: {
|
||||
items: {
|
||||
title: "merge line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
has_shipping: false,
|
||||
content: {
|
||||
unit_price: 123,
|
||||
variant: {
|
||||
_id: IdMap.getId("can-cover"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
quantity: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -736,6 +750,9 @@ describe("CartService", () => {
|
||||
$set: {
|
||||
region_id: IdMap.getId("region-us"),
|
||||
shipping_methods: [],
|
||||
shipping_address: {
|
||||
country_code: "US",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
_id: IdMap.getId("line"),
|
||||
|
||||
@@ -141,9 +141,7 @@ describe("CustomerService", () => {
|
||||
first_name: "Oliver",
|
||||
last_name: "Juhl",
|
||||
has_account: true,
|
||||
password_hash: expect.stringMatching(
|
||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||
),
|
||||
password_hash: expect.stringMatching(/^.{128}$/),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -259,9 +257,7 @@ describe("CustomerService", () => {
|
||||
{
|
||||
$set: {
|
||||
has_account: true,
|
||||
password_hash: expect.stringMatching(
|
||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||
),
|
||||
password_hash: expect.stringMatching(/^.{128}$/),
|
||||
},
|
||||
},
|
||||
{ runValidators: true }
|
||||
|
||||
@@ -23,7 +23,9 @@ describe("EventBusService", () => {
|
||||
|
||||
it("creates bull queue", () => {
|
||||
expect(Bull).toHaveBeenCalledTimes(2)
|
||||
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", "testhost")
|
||||
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", {
|
||||
createClient: expect.any(Function),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ describe("LineItemService", () => {
|
||||
title: "test",
|
||||
description: "EUR10US-12",
|
||||
thumbnail: "test.1234",
|
||||
should_merge: true,
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
@@ -41,6 +42,7 @@ describe("LineItemService", () => {
|
||||
quantity: 1,
|
||||
},
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -88,6 +90,7 @@ describe("LineItemService", () => {
|
||||
name: "Test Name",
|
||||
},
|
||||
quantity: 1,
|
||||
should_merge: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("OrderService", () => {
|
||||
discountService: DiscountServiceMock,
|
||||
regionService: RegionServiceMock,
|
||||
eventBusService: EventBusServiceMock,
|
||||
counterService: CounterServiceMock
|
||||
counterService: CounterServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -135,6 +135,7 @@ describe("OrderService", () => {
|
||||
tax_rate: 0.25,
|
||||
email: "test",
|
||||
giftcard: expect.any(Object),
|
||||
line_item: expect.any(Object),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -328,6 +329,8 @@ describe("OrderService", () => {
|
||||
|
||||
describe("cancel", () => {
|
||||
const orderService = new OrderService({
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
orderModel: OrderModelMock,
|
||||
eventBusService: EventBusServiceMock,
|
||||
})
|
||||
@@ -342,7 +345,24 @@ describe("OrderService", () => {
|
||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{ _id: IdMap.getId("not-fulfilled-order") },
|
||||
{ $set: { status: "cancelled" } }
|
||||
{
|
||||
$set: {
|
||||
status: "canceled",
|
||||
fulfillment_status: "canceled",
|
||||
payment_status: "canceled",
|
||||
fulfillments: [
|
||||
{
|
||||
data: {},
|
||||
is_canceled: true,
|
||||
provider_id: "default_provider",
|
||||
},
|
||||
],
|
||||
payment_method: {
|
||||
data: {},
|
||||
provider_id: "default_provider",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -359,7 +379,7 @@ describe("OrderService", () => {
|
||||
await orderService.cancel(IdMap.getId("payed-order"))
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual(
|
||||
"Can't cancel an order with payment processed"
|
||||
"Can't cancel an order with a processed payment"
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -435,9 +455,11 @@ describe("OrderService", () => {
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
fulfilled_quantity: 0,
|
||||
quantity: 10,
|
||||
},
|
||||
]
|
||||
],
|
||||
orders.testOrder
|
||||
)
|
||||
|
||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
@@ -467,6 +489,7 @@ describe("OrderService", () => {
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
fulfilled_quantity: 0,
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
@@ -504,15 +527,15 @@ describe("OrderService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("throws if payment is already processed", async () => {
|
||||
it("throws if too many items are requested fulfilled", async () => {
|
||||
await expect(
|
||||
orderService.createFulfillment(IdMap.getId("fulfilled-order"), [
|
||||
orderService.createFulfillment(IdMap.getId("test-order"), [
|
||||
{
|
||||
item_id: IdMap.getId("existingLine"),
|
||||
quantity: 10,
|
||||
quantity: 11,
|
||||
},
|
||||
])
|
||||
).rejects.toThrow("Order is already fulfilled")
|
||||
).rejects.toThrow("Cannot fulfill more items than have been purchased")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -53,9 +53,7 @@ describe("UserService", () => {
|
||||
expect(UserModelMock.create).toHaveBeenCalledWith({
|
||||
email: "oliver@test.dk",
|
||||
name: "Oliver",
|
||||
password_hash: expect.stringMatching(
|
||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||
),
|
||||
password_hash: expect.stringMatching(/.{128}$/),
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -134,9 +132,7 @@ describe("UserService", () => {
|
||||
$set: {
|
||||
// Since bcrypt hashing always varies, we are testing the password
|
||||
// match by using a regular expression.
|
||||
password_hash: expect.stringMatching(
|
||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
||||
),
|
||||
password_hash: expect.stringMatching(/^.{128}$/),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ class OrderService extends BaseService {
|
||||
REFUND_CREATED: "order.refund_created",
|
||||
PLACED: "order.placed",
|
||||
UPDATED: "order.updated",
|
||||
CANCELLED: "order.cancelled",
|
||||
CANCELED: "order.canceled",
|
||||
COMPLETED: "order.completed",
|
||||
}
|
||||
|
||||
@@ -542,21 +542,35 @@ class OrderService extends BaseService {
|
||||
async cancel(orderId) {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.fulfillment_status !== "not_fulfilled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't cancel a fulfilled order"
|
||||
)
|
||||
}
|
||||
|
||||
if (order.payment_status !== "awaiting") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't cancel an order with payment processed"
|
||||
"Can't cancel an order with a processed payment"
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: cancel payment method
|
||||
const fulfillments = await Promise.all(
|
||||
order.fulfillments.map(async fulfillment => {
|
||||
const { provider_id, data } = fulfillment
|
||||
const provider = await this.fulfillmentProviderService_.retrieveProvider(
|
||||
provider_id
|
||||
)
|
||||
const newData = await provider.cancelFulfillment(data)
|
||||
return {
|
||||
...fulfillment,
|
||||
is_canceled: true,
|
||||
data: newData,
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const { provider_id, data } = order.payment_method
|
||||
const paymentProvider = await this.paymentProviderService_.retrieveProvider(
|
||||
provider_id
|
||||
)
|
||||
|
||||
// Cancel payment with payment provider
|
||||
const payData = await paymentProvider.cancelPayment(data)
|
||||
|
||||
return this.orderModel_
|
||||
.updateOne(
|
||||
@@ -564,12 +578,21 @@ class OrderService extends BaseService {
|
||||
_id: orderId,
|
||||
},
|
||||
{
|
||||
$set: { status: "cancelled" },
|
||||
$set: {
|
||||
status: "canceled",
|
||||
fulfillment_status: "canceled",
|
||||
payment_status: "canceled",
|
||||
fulfillments,
|
||||
payment_method: {
|
||||
...order.payment_method,
|
||||
data: payData,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(result => {
|
||||
// Notify subscribers
|
||||
this.eventBus_.emit(OrderService.Events.CANCELLED, result)
|
||||
this.eventBus_.emit(OrderService.Events.CANCELED, result)
|
||||
return result
|
||||
})
|
||||
.catch(err => {
|
||||
|
||||
@@ -231,7 +231,7 @@ class UserService extends BaseService {
|
||||
async setPassword(userId, password) {
|
||||
const user = await this.retrieve(userId)
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 10)
|
||||
const hashedPassword = await this.hashPassword_(password)
|
||||
if (!hashedPassword) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DB_ERROR,
|
||||
|
||||
Reference in New Issue
Block a user