fix/cancel-order (#120)
* fix: adds ability to cancel order * passing tests * chore: clean up unused code
This commit is contained in:
1 parent
4bbb2a2367
commit
11bedf8c6f
29 files changed
+240
-97
No files matched your search
@@ -35,6 +35,10 @@ class ManualFulfillmentService extends FulfillmentService {
|
|||||||
// No data is being sent anywhere
|
// No data is being sent anywhere
|
||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancelFulfillment() {
|
||||||
|
return Promise.resolve({})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ManualFulfillmentService
|
export default ManualFulfillmentService
|
||||||
@@ -236,6 +236,25 @@ class WebshipperFulfillmentService extends FulfillmentService {
|
|||||||
url: link,
|
url: link,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels a fulfillment. If the fulfillment has already been canceled this
|
||||||
|
* is idemptotent. Can only cancel pending orders.
|
||||||
|
* @param {object} data - the fulfilment data
|
||||||
|
* @return {Promise<object>} the result of the cancellation
|
||||||
|
*/
|
||||||
|
async cancelFulfillment(data) {
|
||||||
|
const order = await this.client_.orders.retrieve(data.id)
|
||||||
|
|
||||||
|
if (order.attributes.status !== "pending") {
|
||||||
|
if (order.attributes.status === "cancelled") {
|
||||||
|
return Promise.resolve(order)
|
||||||
|
}
|
||||||
|
throw new Error("Cannot cancel order")
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client_.orders.delete(data.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WebshipperFulfillmentService
|
export default WebshipperFulfillmentService
|
||||||
@@ -83,6 +83,13 @@ class Webshipper {
|
|||||||
},
|
},
|
||||||
}).then(({ data }) => data)
|
}).then(({ data }) => data)
|
||||||
},
|
},
|
||||||
|
delete: async (id) => {
|
||||||
|
const path = `/v2/orders/${id}`
|
||||||
|
return this.client_({
|
||||||
|
method: "DELETE",
|
||||||
|
url: path,
|
||||||
|
}).then(({ data }) => data)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class StripeProviderService extends PaymentService {
|
|||||||
status = "succeeded"
|
status = "succeeded"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paymentIntent.status === "cancelled") {
|
if (paymentIntent.status === "canceled") {
|
||||||
status = "cancelled"
|
status = "canceled"
|
||||||
}
|
}
|
||||||
|
|
||||||
return status
|
return status
|
||||||
@@ -228,8 +228,13 @@ class StripeProviderService extends PaymentService {
|
|||||||
async cancelPayment(paymentData) {
|
async cancelPayment(paymentData) {
|
||||||
const { id } = paymentData
|
const { id } = paymentData
|
||||||
try {
|
try {
|
||||||
return this.stripe_.paymentIntents.cancel(id)
|
const result = await this.stripe_.paymentIntents.cancel(id)
|
||||||
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.payment_intent.status === "canceled") {
|
||||||
|
return error.payment_intent
|
||||||
|
}
|
||||||
|
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ export default () => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(statusCode).json(err.message)
|
res.status(statusCode).json({
|
||||||
|
name: err.name,
|
||||||
|
message: err.message,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,14 @@ describe("POST /admin/orders/:id/fulfillment", () => {
|
|||||||
"POST",
|
"POST",
|
||||||
`/admin/orders/${IdMap.getId("test-order")}/fulfillment`,
|
`/admin/orders/${IdMap.getId("test-order")}/fulfillment`,
|
||||||
{
|
{
|
||||||
|
payload: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("line1"),
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
adminSession: {
|
adminSession: {
|
||||||
jwt: {
|
jwt: {
|
||||||
userId: IdMap.getId("admin_user"),
|
userId: IdMap.getId("admin_user"),
|
||||||
@@ -27,7 +35,14 @@ describe("POST /admin/orders/:id/fulfillment", () => {
|
|||||||
it("calls OrderService createFulfillment", () => {
|
it("calls OrderService createFulfillment", () => {
|
||||||
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledTimes(1)
|
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledTimes(1)
|
||||||
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledWith(
|
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledWith(
|
||||||
IdMap.getId("test-order")
|
IdMap.getId("test-order"),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
item_id: IdMap.getId("line1"),
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
undefined
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ describe("POST /admin/orders/:id/return", () => {
|
|||||||
item_id: IdMap.getId("existingLine"),
|
item_id: IdMap.getId("existingLine"),
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
undefined
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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.cancel(id)
|
let order = await orderService.cancel(id)
|
||||||
|
order = await orderService.decorate(order, [], ["region"])
|
||||||
res.json({ order })
|
res.json({ order })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -14,17 +14,21 @@ describe("GET /store/carts", () => {
|
|||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls get product from productSerice", () => {
|
it("calls retrieve from CartService", () => {
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
||||||
IdMap.getId("emptyCart")
|
IdMap.getId("emptyCart")
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns products", () => {
|
it("returns cart", () => {
|
||||||
expect(subject.body.cart._id).toEqual(IdMap.getId("emptyCart"))
|
expect(subject.body.cart._id).toEqual(IdMap.getId("emptyCart"))
|
||||||
expect(subject.body.cart.decorated).toEqual(true)
|
expect(subject.body.cart.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("returns 200 status", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("returns 404 on undefined cart", () => {
|
describe("returns 404 on undefined cart", () => {
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ describe("POST /store/customers/:id", () => {
|
|||||||
expect(subject.body.customer.first_name).toEqual("LeBron")
|
expect(subject.body.customer.first_name).toEqual("LeBron")
|
||||||
expect(subject.body.customer.decorated).toEqual(true)
|
expect(subject.body.customer.decorated).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("status code 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fails if not authenticated", () => {
|
describe("fails if not authenticated", () => {
|
||||||
|
|||||||
@@ -1,38 +1,14 @@
|
|||||||
import { createContainer, asValue } from "awilix"
|
import { createContainer, asValue } from "awilix"
|
||||||
import express from "express"
|
import express from "express"
|
||||||
|
import cookieParser from "cookie-parser"
|
||||||
import supertest from "supertest"
|
import supertest from "supertest"
|
||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
import sessions from "client-sessions"
|
import session from "express-session"
|
||||||
import cookie from "cookie"
|
|
||||||
import servicesLoader from "../loaders/services"
|
import servicesLoader from "../loaders/services"
|
||||||
import expressLoader from "../loaders/express"
|
|
||||||
import apiLoader from "../loaders/api"
|
import apiLoader from "../loaders/api"
|
||||||
import passportLoader from "../loaders/passport"
|
import passportLoader from "../loaders/passport"
|
||||||
import config from "../config"
|
import config from "../config"
|
||||||
|
|
||||||
const testApp = express()
|
|
||||||
|
|
||||||
const container = createContainer()
|
|
||||||
container.register({
|
|
||||||
logger: asValue({
|
|
||||||
error: () => {},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
servicesLoader({ container })
|
|
||||||
expressLoader({ app: testApp })
|
|
||||||
passportLoader({ app: testApp, container })
|
|
||||||
|
|
||||||
// Add the registered services to the request scope
|
|
||||||
testApp.use((req, res, next) => {
|
|
||||||
req.scope = container.createScope()
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
apiLoader({ container, rootDirectory: ".", app: testApp })
|
|
||||||
|
|
||||||
const supertestRequest = supertest(testApp)
|
|
||||||
|
|
||||||
let adminSessionOpts = {
|
let adminSessionOpts = {
|
||||||
cookieName: "session",
|
cookieName: "session",
|
||||||
secret: "test",
|
secret: "test",
|
||||||
@@ -45,9 +21,44 @@ let clientSessionOpts = {
|
|||||||
}
|
}
|
||||||
export { clientSessionOpts }
|
export { clientSessionOpts }
|
||||||
|
|
||||||
|
const testApp = express()
|
||||||
|
|
||||||
|
const container = createContainer()
|
||||||
|
container.register({
|
||||||
|
logger: asValue({
|
||||||
|
error: () => {},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
testApp.set("trust proxy", 1)
|
||||||
|
testApp.use((req, res, next) => {
|
||||||
|
req.session = {}
|
||||||
|
const data = req.get("Cookie")
|
||||||
|
if (data) {
|
||||||
|
req.session = {
|
||||||
|
...req.session,
|
||||||
|
...JSON.parse(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
servicesLoader({ container })
|
||||||
|
passportLoader({ app: testApp, container })
|
||||||
|
|
||||||
|
testApp.use((req, res, next) => {
|
||||||
|
req.scope = container.createScope()
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
apiLoader({ container, rootDirectory: ".", app: testApp })
|
||||||
|
|
||||||
|
const supertestRequest = supertest(testApp)
|
||||||
|
|
||||||
export async function request(method, url, opts = {}) {
|
export async function request(method, url, opts = {}) {
|
||||||
let { payload, headers } = opts
|
let { payload, headers } = opts
|
||||||
|
|
||||||
|
let req = supertestRequest[method.toLowerCase()](url)
|
||||||
headers = headers || {}
|
headers = headers || {}
|
||||||
headers.Cookie = headers.Cookie || ""
|
headers.Cookie = headers.Cookie || ""
|
||||||
if (opts.adminSession) {
|
if (opts.adminSession) {
|
||||||
@@ -60,13 +71,7 @@ export async function request(method, url, opts = {}) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
headers.Cookie = JSON.stringify(opts.adminSession) || ""
|
||||||
headers.Cookie +=
|
|
||||||
adminSessionOpts.cookieName +
|
|
||||||
"=" +
|
|
||||||
sessions.util.encode(adminSessionOpts, opts.adminSession) +
|
|
||||||
"; "
|
|
||||||
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
|
||||||
}
|
}
|
||||||
if (opts.clientSession) {
|
if (opts.clientSession) {
|
||||||
if (opts.clientSession.jwt) {
|
if (opts.clientSession.jwt) {
|
||||||
@@ -79,16 +84,9 @@ export async function request(method, url, opts = {}) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.Cookie +=
|
headers.Cookie = JSON.stringify(opts.clientSession) || ""
|
||||||
clientSessionOpts.cookieName +
|
|
||||||
"=" +
|
|
||||||
sessions.util.encode(clientSessionOpts, opts.clientSession) +
|
|
||||||
"; "
|
|
||||||
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let req = supertestRequest[method.toLowerCase()](url)
|
|
||||||
|
|
||||||
for (let name in headers) {
|
for (let name in headers) {
|
||||||
req.set(name, headers[name])
|
req.set(name, headers[name])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import config from "../config"
|
|||||||
export default async ({ app, configModule }) => {
|
export default async ({ app, configModule }) => {
|
||||||
let sameSite = false
|
let sameSite = false
|
||||||
let secure = false
|
let secure = false
|
||||||
if (process.env.NODE_ENV === "production" || process.env.NODE_ENV === "staging") {
|
if (
|
||||||
|
process.env.NODE_ENV === "production" ||
|
||||||
|
process.env.NODE_ENV === "staging"
|
||||||
|
) {
|
||||||
secure = true
|
secure = true
|
||||||
sameSite = "none"
|
sameSite = "none"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export const orders = {
|
|||||||
},
|
},
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
|
fulfilled_quantity: 0,
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -56,6 +57,12 @@ export const orders = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
fulfillments: [
|
||||||
|
{
|
||||||
|
provider_id: "default_provider",
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
fulfillment_status: "not_fulfilled",
|
fulfillment_status: "not_fulfilled",
|
||||||
payment_status: "awaiting",
|
payment_status: "awaiting",
|
||||||
status: "pending",
|
status: "pending",
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ export default new mongoose.Schema({
|
|||||||
data: { type: [mongoose.Schema.Types.Mixed], default: {} },
|
data: { type: [mongoose.Schema.Types.Mixed], default: {} },
|
||||||
tracking_numbers: { type: [String], default: [] },
|
tracking_numbers: { type: [String], default: [] },
|
||||||
shipped_at: { type: String },
|
shipped_at: { type: String },
|
||||||
|
is_canceled: { type: Boolean, default: false },
|
||||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import bcrypt from "bcrypt"
|
import Scrypt from "scrypt-kdf"
|
||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
|
||||||
export const CustomerServiceMock = {
|
export const CustomerServiceMock = {
|
||||||
@@ -43,9 +43,10 @@ export const CustomerServiceMock = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (email === "oliver@test.dk") {
|
if (email === "oliver@test.dk") {
|
||||||
return bcrypt
|
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
|
||||||
.hash("123456789", 10)
|
email,
|
||||||
.then(hash => ({ email, password_hash: hash }))
|
password_hash: hash.toString("base64"),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ export const DiscountServiceMock = {
|
|||||||
list: jest.fn().mockImplementation(data => {
|
list: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve([])
|
return Promise.resolve([])
|
||||||
}),
|
}),
|
||||||
|
decorate: jest.fn().mockImplementation(data => {
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}),
|
||||||
addRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
addRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export const DefaultProviderMock = {
|
|||||||
|
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}),
|
}),
|
||||||
|
cancelFulfillment: jest.fn().mockImplementation(data => {
|
||||||
|
return {}
|
||||||
|
}),
|
||||||
calculatePrice: jest.fn().mockImplementation(data => {
|
calculatePrice: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const DefaultProviderMock = {
|
|||||||
}),
|
}),
|
||||||
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PaymentProviderServiceMock = {
|
export const PaymentProviderServiceMock = {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const ProductServiceMock = {
|
|||||||
|
|
||||||
return Promise.resolve({ ...data })
|
return Promise.resolve({ ...data })
|
||||||
}),
|
}),
|
||||||
|
count: jest.fn().mockReturnValue(4),
|
||||||
publish: jest.fn().mockImplementation(_ => {
|
publish: jest.fn().mockImplementation(_ => {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
_id: IdMap.getId("publish"),
|
_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()),
|
addShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
|
removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
addProduct: 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 { IdMap } from "medusa-test-utils"
|
||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
|
|
||||||
@@ -92,9 +92,10 @@ export const UserServiceMock = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (email === "oliver@test.dk") {
|
if (email === "oliver@test.dk") {
|
||||||
return bcrypt
|
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
|
||||||
.hash("123456789", 10)
|
email,
|
||||||
.then(hash => ({ email, password_hash: hash }))
|
password_hash: hash.toString("base64"),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -228,12 +228,26 @@ describe("CartService", () => {
|
|||||||
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("cartWithLine"),
|
_id: IdMap.getId("cartWithLine"),
|
||||||
"items._id": IdMap.getId("existingLine"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$push: {
|
||||||
"items.$.quantity": 20,
|
items: {
|
||||||
"items.$.has_shipping": false,
|
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: {
|
$set: {
|
||||||
region_id: IdMap.getId("region-us"),
|
region_id: IdMap.getId("region-us"),
|
||||||
shipping_methods: [],
|
shipping_methods: [],
|
||||||
|
shipping_address: {
|
||||||
|
country_code: "US",
|
||||||
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
_id: IdMap.getId("line"),
|
_id: IdMap.getId("line"),
|
||||||
|
|||||||
@@ -141,9 +141,7 @@ describe("CustomerService", () => {
|
|||||||
first_name: "Oliver",
|
first_name: "Oliver",
|
||||||
last_name: "Juhl",
|
last_name: "Juhl",
|
||||||
has_account: true,
|
has_account: true,
|
||||||
password_hash: expect.stringMatching(
|
password_hash: expect.stringMatching(/^.{128}$/),
|
||||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -259,9 +257,7 @@ describe("CustomerService", () => {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
has_account: true,
|
has_account: true,
|
||||||
password_hash: expect.stringMatching(
|
password_hash: expect.stringMatching(/^.{128}$/),
|
||||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ runValidators: true }
|
{ runValidators: true }
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ describe("EventBusService", () => {
|
|||||||
|
|
||||||
it("creates bull queue", () => {
|
it("creates bull queue", () => {
|
||||||
expect(Bull).toHaveBeenCalledTimes(2)
|
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",
|
title: "test",
|
||||||
description: "EUR10US-12",
|
description: "EUR10US-12",
|
||||||
thumbnail: "test.1234",
|
thumbnail: "test.1234",
|
||||||
|
should_merge: true,
|
||||||
content: {
|
content: {
|
||||||
unit_price: 10,
|
unit_price: 10,
|
||||||
variant: {
|
variant: {
|
||||||
@@ -41,6 +42,7 @@ describe("LineItemService", () => {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
quantity: 2,
|
quantity: 2,
|
||||||
|
metadata: {},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -88,6 +90,7 @@ describe("LineItemService", () => {
|
|||||||
name: "Test Name",
|
name: "Test Name",
|
||||||
},
|
},
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
|
should_merge: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ describe("OrderService", () => {
|
|||||||
discountService: DiscountServiceMock,
|
discountService: DiscountServiceMock,
|
||||||
regionService: RegionServiceMock,
|
regionService: RegionServiceMock,
|
||||||
eventBusService: EventBusServiceMock,
|
eventBusService: EventBusServiceMock,
|
||||||
counterService: CounterServiceMock
|
counterService: CounterServiceMock,
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -135,6 +135,7 @@ describe("OrderService", () => {
|
|||||||
tax_rate: 0.25,
|
tax_rate: 0.25,
|
||||||
email: "test",
|
email: "test",
|
||||||
giftcard: expect.any(Object),
|
giftcard: expect.any(Object),
|
||||||
|
line_item: expect.any(Object),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -328,6 +329,8 @@ describe("OrderService", () => {
|
|||||||
|
|
||||||
describe("cancel", () => {
|
describe("cancel", () => {
|
||||||
const orderService = new OrderService({
|
const orderService = new OrderService({
|
||||||
|
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||||
|
paymentProviderService: PaymentProviderServiceMock,
|
||||||
orderModel: OrderModelMock,
|
orderModel: OrderModelMock,
|
||||||
eventBusService: EventBusServiceMock,
|
eventBusService: EventBusServiceMock,
|
||||||
})
|
})
|
||||||
@@ -342,7 +345,24 @@ describe("OrderService", () => {
|
|||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
|
||||||
{ _id: IdMap.getId("not-fulfilled-order") },
|
{ _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"))
|
await orderService.cancel(IdMap.getId("payed-order"))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.message).toEqual(
|
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,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
|
fulfilled_quantity: 0,
|
||||||
quantity: 10,
|
quantity: 10,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
orders.testOrder
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||||
@@ -467,6 +489,7 @@ describe("OrderService", () => {
|
|||||||
},
|
},
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
|
fulfilled_quantity: 0,
|
||||||
quantity: 10,
|
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(
|
await expect(
|
||||||
orderService.createFulfillment(IdMap.getId("fulfilled-order"), [
|
orderService.createFulfillment(IdMap.getId("test-order"), [
|
||||||
{
|
{
|
||||||
item_id: IdMap.getId("existingLine"),
|
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({
|
expect(UserModelMock.create).toHaveBeenCalledWith({
|
||||||
email: "oliver@test.dk",
|
email: "oliver@test.dk",
|
||||||
name: "Oliver",
|
name: "Oliver",
|
||||||
password_hash: expect.stringMatching(
|
password_hash: expect.stringMatching(/.{128}$/),
|
||||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -134,9 +132,7 @@ describe("UserService", () => {
|
|||||||
$set: {
|
$set: {
|
||||||
// Since bcrypt hashing always varies, we are testing the password
|
// Since bcrypt hashing always varies, we are testing the password
|
||||||
// match by using a regular expression.
|
// match by using a regular expression.
|
||||||
password_hash: expect.stringMatching(
|
password_hash: expect.stringMatching(/^.{128}$/),
|
||||||
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class OrderService extends BaseService {
|
|||||||
REFUND_CREATED: "order.refund_created",
|
REFUND_CREATED: "order.refund_created",
|
||||||
PLACED: "order.placed",
|
PLACED: "order.placed",
|
||||||
UPDATED: "order.updated",
|
UPDATED: "order.updated",
|
||||||
CANCELLED: "order.cancelled",
|
CANCELED: "order.canceled",
|
||||||
COMPLETED: "order.completed",
|
COMPLETED: "order.completed",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,21 +542,35 @@ class OrderService extends BaseService {
|
|||||||
async cancel(orderId) {
|
async cancel(orderId) {
|
||||||
const order = await this.retrieve(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") {
|
if (order.payment_status !== "awaiting") {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.NOT_ALLOWED,
|
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_
|
return this.orderModel_
|
||||||
.updateOne(
|
.updateOne(
|
||||||
@@ -564,12 +578,21 @@ class OrderService extends BaseService {
|
|||||||
_id: orderId,
|
_id: orderId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: { status: "cancelled" },
|
$set: {
|
||||||
|
status: "canceled",
|
||||||
|
fulfillment_status: "canceled",
|
||||||
|
payment_status: "canceled",
|
||||||
|
fulfillments,
|
||||||
|
payment_method: {
|
||||||
|
...order.payment_method,
|
||||||
|
data: payData,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
// Notify subscribers
|
// Notify subscribers
|
||||||
this.eventBus_.emit(OrderService.Events.CANCELLED, result)
|
this.eventBus_.emit(OrderService.Events.CANCELED, result)
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ class UserService extends BaseService {
|
|||||||
async setPassword(userId, password) {
|
async setPassword(userId, password) {
|
||||||
const user = await this.retrieve(userId)
|
const user = await this.retrieve(userId)
|
||||||
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10)
|
const hashedPassword = await this.hashPassword_(password)
|
||||||
if (!hashedPassword) {
|
if (!hashedPassword) {
|
||||||
throw new MedusaError(
|
throw new MedusaError(
|
||||||
MedusaError.Types.DB_ERROR,
|
MedusaError.Types.DB_ERROR,
|
||||||
|
|||||||
Reference in new issue
Block a user