Adds Regions (#33)
Regions are a collection of countries that share some common functionality, e.g., currency, available fulfillment and shipping providers and a taxrate. The store operator can have as many Regions as needed.
This commit is contained in:
@@ -3,6 +3,7 @@ import middlewares from "../../middlewares"
|
||||
import authRoutes from "./auth"
|
||||
import productRoutes from "./products"
|
||||
import productVariantRoutes from "./product-variants"
|
||||
import regionRoutes from "./regions"
|
||||
import shippingOptionRoutes from "./shipping-options"
|
||||
import shippingProfileRoutes from "./shipping-profiles"
|
||||
|
||||
@@ -18,6 +19,7 @@ export default app => {
|
||||
route.use(middlewares.authenticate())
|
||||
|
||||
productRoutes(route)
|
||||
regionRoutes(route)
|
||||
shippingOptionRoutes(route)
|
||||
shippingProfileRoutes(route)
|
||||
// productVariantRoutes(route)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("POST /admin/regions/:region_id/countries", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request("POST", `/admin/regions/${id}/countries`, {
|
||||
payload: {
|
||||
country_code: "se",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addCountry).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addCountry).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"se"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("POST /admin/regions/:region_id/fulfillment-providers", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/regions/${id}/fulfillment-providers`,
|
||||
{
|
||||
payload: {
|
||||
provider_id: "default_provider",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addFulfillmentProvider).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addFulfillmentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("POST /admin/regions/:region_id/payment-providers", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/regions/${id}/payment-providers`,
|
||||
{
|
||||
payload: {
|
||||
provider_id: "default_provider",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addPaymentProvider).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addPaymentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("POST /admin/regions", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/regions", {
|
||||
payload: {
|
||||
name: "New Region",
|
||||
currency_code: "dkk",
|
||||
countries: ["dk"],
|
||||
tax_rate: 0.3,
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service create", () => {
|
||||
expect(RegionServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.create).toHaveBeenCalledWith({
|
||||
name: "New Region",
|
||||
currency_code: "dkk",
|
||||
countries: ["dk"],
|
||||
tax_rate: 0.3,
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("DELETE /admin/regions/:region_id", () => {
|
||||
describe("successful deletion", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request("DELETE", `/admin/regions/${id}`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.delete).toHaveBeenCalledWith(
|
||||
IdMap.getId("region")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("GET /admin/regions/:region_id", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request("GET", `/admin/regions/${id}`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("region")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("GET /admin/regions", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("GET", `/admin/regions`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.list).toHaveBeenCalledWith({})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("DELETE /admin/regions/:region_id/countries/:country_code", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request("DELETE", `/admin/regions/${id}/countries/DK`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.removeCountry).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.removeCountry).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"DK"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,38 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("DELETE /admin/regions/:region_id/fulfillment-providers/:provider_id", () => {
|
||||
describe("successful deletion", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request(
|
||||
"DELETE",
|
||||
`/admin/regions/${id}/fulfillment-providers/default_provider`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.removeFulfillmentProvider).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(RegionServiceMock.removeFulfillmentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("DELETE /admin/regions/:region_id/payment-providers/:provider_id", () => {
|
||||
describe("successful deletion", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request(
|
||||
"DELETE",
|
||||
`/admin/regions/${id}/payment-providers/default_provider`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.removePaymentProvider).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.removePaymentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { RegionServiceMock } from "../../../../../services/__mocks__/region"
|
||||
|
||||
describe("POST /admin/regions/:region_id", () => {
|
||||
describe("successful deletion", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
subject = await request("POST", `/admin/regions/${id}`, {
|
||||
payload: {
|
||||
name: "Updated Region",
|
||||
currency_code: "dkk",
|
||||
countries: ["dk"],
|
||||
tax_rate: 0.3,
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
{
|
||||
name: "Updated Region",
|
||||
currency_code: "dkk",
|
||||
countries: ["dk"],
|
||||
tax_rate: 0.3,
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
23
packages/medusa/src/api/routes/admin/regions/add-country.js
Normal file
23
packages/medusa/src/api/routes/admin/regions/add-country.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
country_code: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.addCountry(region_id, value.country_code)
|
||||
|
||||
const data = await regionService.retrieve(region_id)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
provider_id: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.addFulfillmentProvider(region_id, value.provider_id)
|
||||
|
||||
const data = await regionService.retrieve(region_id)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
provider_id: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.addPaymentProvider(region_id, value.provider_id)
|
||||
|
||||
const data = await regionService.retrieve(region_id)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
name: Validator.string().required(),
|
||||
currency_code: Validator.string().required(),
|
||||
tax_rate: Validator.number().required(),
|
||||
payment_providers: Validator.array().items(Validator.string()),
|
||||
fulfillment_providers: Validator.array().items(Validator.string()),
|
||||
countries: Validator.array().items(Validator.string()),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const data = await regionService.create(value)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.delete(region_id)
|
||||
|
||||
res.status(200).json({
|
||||
id: region_id,
|
||||
object: "region",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
13
packages/medusa/src/api/routes/admin/regions/get-region.js
Normal file
13
packages/medusa/src/api/routes/admin/regions/get-region.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const data = await regionService.retrieve(region_id)
|
||||
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
51
packages/medusa/src/api/routes/admin/regions/index.js
Normal file
51
packages/medusa/src/api/routes/admin/regions/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/regions", route)
|
||||
|
||||
route.get("/", middlewares.wrap(require("./list-regions").default))
|
||||
route.get("/:region_id", middlewares.wrap(require("./get-region").default))
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-region").default))
|
||||
route.post(
|
||||
"/:region_id",
|
||||
middlewares.wrap(require("./update-region").default)
|
||||
)
|
||||
|
||||
route.delete(
|
||||
"/:region_id",
|
||||
middlewares.wrap(require("./delete-region").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:region_id/countries",
|
||||
middlewares.wrap(require("./add-country").default)
|
||||
)
|
||||
route.delete(
|
||||
"/:region_id/countries/:country_code",
|
||||
middlewares.wrap(require("./remove-country").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:region_id/payment-providers",
|
||||
middlewares.wrap(require("./add-payment-provider").default)
|
||||
)
|
||||
route.delete(
|
||||
"/:region_id/payment-providers/:provider_id",
|
||||
middlewares.wrap(require("./remove-payment-provider").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:region_id/fulfillment-providers",
|
||||
middlewares.wrap(require("./add-fulfillment-provider").default)
|
||||
)
|
||||
route.delete(
|
||||
"/:region_id/fulfillment-providers/:provider_id",
|
||||
middlewares.wrap(require("./remove-fulfillment-provider").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
12
packages/medusa/src/api/routes/admin/regions/list-regions.js
Normal file
12
packages/medusa/src/api/routes/admin/regions/list-regions.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const data = await regionService.list({})
|
||||
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id, country_code } = req.params
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.removeCountry(region_id, country_code)
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id, provider_id } = req.params
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.removeFulfillmentProvider(region_id, provider_id)
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id, provider_id } = req.params
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
await regionService.removePaymentProvider(region_id, provider_id)
|
||||
|
||||
res.sendStatus(200)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { region_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
name: Validator.string(),
|
||||
currency_code: Validator.string(),
|
||||
tax_rate: Validator.number(),
|
||||
payment_providers: Validator.array().items(Validator.string()),
|
||||
fulfillment_providers: Validator.array().items(Validator.string()),
|
||||
countries: Validator.array().items(Validator.string()),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const data = await regionService.update(region_id, value)
|
||||
res.status(200).json(data)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
66
packages/medusa/src/models/__mocks__/region.js
Normal file
66
packages/medusa/src/models/__mocks__/region.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const regions = {
|
||||
testRegion: {
|
||||
_id: IdMap.getId("testRegion"),
|
||||
name: "Test Region",
|
||||
countries: ["DK", "US", "DE"],
|
||||
tax_rate: 0.25,
|
||||
payment_providers: ["default_provider", "unregistered"],
|
||||
fulfillment_providers: ["test_shipper"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
regionFrance: {
|
||||
_id: IdMap.getId("region-france"),
|
||||
name: "France",
|
||||
countries: ["FR"],
|
||||
payment_providers: ["default_provider", "france-provider"],
|
||||
currency_code: "eur",
|
||||
},
|
||||
regionUs: {
|
||||
_id: IdMap.getId("region-us"),
|
||||
name: "USA",
|
||||
countries: ["US"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
regionGermany: {
|
||||
_id: IdMap.getId("region-de"),
|
||||
name: "Germany",
|
||||
countries: ["DE"],
|
||||
currency_code: "eur",
|
||||
},
|
||||
regionSweden: {
|
||||
_id: IdMap.getId("region-se"),
|
||||
name: "Sweden",
|
||||
countries: ["SE"],
|
||||
payment_providers: ["sweden_provider"],
|
||||
fulfillment_providers: ["sweden_provider"],
|
||||
currency_code: "SEK",
|
||||
},
|
||||
}
|
||||
|
||||
export const RegionModelMock = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
updateOne: jest.fn().mockImplementation((query, update) => {}),
|
||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
findOne: jest.fn().mockImplementation(query => {
|
||||
if (query.countries === "SE") {
|
||||
return Promise.resolve(regions.regionSweden)
|
||||
}
|
||||
|
||||
switch (query._id) {
|
||||
case IdMap.getId("testRegion"):
|
||||
return Promise.resolve(regions.testRegion)
|
||||
case IdMap.getId("region-france"):
|
||||
return Promise.resolve(regions.regionFrance)
|
||||
case IdMap.getId("region-us"):
|
||||
return Promise.resolve(regions.regionUs)
|
||||
case IdMap.getId("region-de"):
|
||||
return Promise.resolve(regions.regionGermany)
|
||||
case IdMap.getId("region-se"):
|
||||
return Promise.resolve(regions.regionSweden)
|
||||
default:
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
}),
|
||||
}
|
||||
17
packages/medusa/src/models/region.js
Normal file
17
packages/medusa/src/models/region.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import mongoose from "mongoose"
|
||||
import { BaseModel } from "medusa-interfaces"
|
||||
|
||||
class RegionModel extends BaseModel {
|
||||
static modelName = "Region"
|
||||
static schema = {
|
||||
name: { type: String, required: true },
|
||||
currency_code: { type: String, required: true },
|
||||
tax_rate: { type: Number, required: true, default: 0 },
|
||||
countries: { type: [String], default: [] },
|
||||
payment_providers: { type: [String], default: [] },
|
||||
fulfillment_providers: { type: [String], default: [] },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
}
|
||||
}
|
||||
|
||||
export default RegionModel
|
||||
@@ -57,6 +57,21 @@ export const RegionServiceMock = {
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
delete: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
create: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
addCountry: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
addFulfillmentProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(data => Promise.resolve()),
|
||||
addPaymentProvider: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
removeCountry: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
removeFulfillmentProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(data => Promise.resolve()),
|
||||
removePaymentProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(data => Promise.resolve()),
|
||||
update: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve([
|
||||
regions.testRegion,
|
||||
|
||||
516
packages/medusa/src/services/__tests__/region.js
Normal file
516
packages/medusa/src/services/__tests__/region.js
Normal file
@@ -0,0 +1,516 @@
|
||||
import mongoose from "mongoose"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import RegionService from "../region"
|
||||
import { RegionModelMock } from "../../models/__mocks__/region"
|
||||
import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"
|
||||
import { FulfillmentProviderServiceMock } from "../__mocks__/fulfillment-provider"
|
||||
|
||||
describe("RegionService", () => {
|
||||
describe("create", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a new region", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.create({
|
||||
name: "Denmark",
|
||||
currency_code: "dkk",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK"],
|
||||
})
|
||||
|
||||
expect(RegionModelMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.create).toHaveBeenCalledWith({
|
||||
name: "Denmark",
|
||||
currency_code: "DKK",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK"],
|
||||
})
|
||||
})
|
||||
|
||||
it("create with payment/fulfillment providers", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.create({
|
||||
name: "Denmark",
|
||||
currency_code: "dkk",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK"],
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
})
|
||||
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledWith(
|
||||
"default_provider"
|
||||
)
|
||||
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledWith("default_provider")
|
||||
|
||||
expect(RegionModelMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.create).toHaveBeenCalledWith({
|
||||
name: "Denmark",
|
||||
currency_code: "DKK",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK"],
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully retrieves a region", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.retrieve(IdMap.getId("region-se"))
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("validateFields_", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("throws on invalid currency code", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ currency_code: "1cw" })
|
||||
).rejects.toThrow("Invalid currency code")
|
||||
})
|
||||
|
||||
it("throws on invalid country code", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ countries: ["ddd"] })
|
||||
).rejects.toThrow("Invalid country code")
|
||||
})
|
||||
|
||||
it("throws on in use country code", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ countries: ["se"] })
|
||||
).rejects.toThrow(
|
||||
"Sweden already exists in Sweden, delete it in that region before adding it"
|
||||
)
|
||||
})
|
||||
|
||||
it("throws on invalid tax_rate", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ tax_rate: 12 })
|
||||
).rejects.toThrow("The tax_rate must be between 0 and 1")
|
||||
})
|
||||
|
||||
it("throws on metadata", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ metadata: { key: "Valie" } })
|
||||
).rejects.toThrow("Please use setMetadata")
|
||||
})
|
||||
|
||||
it("throws on unknown payment providers", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ payment_providers: ["hi"] })
|
||||
).rejects.toThrow("Provider Not Found")
|
||||
})
|
||||
|
||||
it("throws on unknown fulfillment providers", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await expect(
|
||||
regionService.validateFields_({ fulfillment_providers: ["hi"] })
|
||||
).rejects.toThrow("Provider Not Found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully updates a region", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.update(IdMap.getId("region-se"), {
|
||||
name: "New Name",
|
||||
currency_code: "gbp",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK", "se"],
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
})
|
||||
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledWith(
|
||||
"default_provider"
|
||||
)
|
||||
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledWith("default_provider")
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
name: "New Name",
|
||||
currency_code: "GBP",
|
||||
tax_rate: 0.25,
|
||||
countries: ["DK", "SE"],
|
||||
payment_providers: ["default_provider"],
|
||||
fulfillment_providers: ["default_provider"],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
beforeAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully deletes", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.delete(IdMap.getId("region-se"))
|
||||
|
||||
expect(RegionModelMock.deleteOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.deleteOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("addCountry", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully adds to the countries array", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.addCountry(IdMap.getId("region-se"), "dk")
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(2)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
countries: "DK",
|
||||
})
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$push: { countries: "DK" },
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("resolves if exists", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.addCountry(IdMap.getId("region-se"), "SE")
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(2)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
countries: "SE",
|
||||
})
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("removeCountry", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully removes country", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.removeCountry(IdMap.getId("region-se"), "dk")
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$pull: { countries: "DK" },
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("addPaymentProvider", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully adds to the countries array", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.addPaymentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"default_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledWith(
|
||||
"default_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$push: { payment_providers: "default_provider" },
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("resolves if exists", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
paymentProviderService: PaymentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.addPaymentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"sweden_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("addFulfillmentProvider", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully adds to the fulfillment_provider array", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.addFulfillmentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"default_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledTimes(1)
|
||||
expect(
|
||||
FulfillmentProviderServiceMock.retrieveProvider
|
||||
).toHaveBeenCalledWith("default_provider")
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$push: { fulfillment_providers: "default_provider" },
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("resolves if exists", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
fulfillmentProviderService: FulfillmentProviderServiceMock,
|
||||
})
|
||||
|
||||
await regionService.addFulfillmentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"sweden_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("removePaymentProvider", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("removes payment provider", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.removePaymentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"sweden_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$pull: { payment_providers: "sweden_provider" },
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("removeFulfillmentProvider", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("removes fulfillment provider", async () => {
|
||||
const regionService = new RegionService({
|
||||
regionModel: RegionModelMock,
|
||||
})
|
||||
|
||||
await regionService.removeFulfillmentProvider(
|
||||
IdMap.getId("region-se"),
|
||||
"sweden_provider"
|
||||
)
|
||||
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("region-se"),
|
||||
})
|
||||
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(RegionModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("region-se"),
|
||||
},
|
||||
{
|
||||
$pull: { fulfillment_providers: "sweden_provider" },
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,346 @@
|
||||
// paymentProviders
|
||||
// fulfillmentProviders
|
||||
// setCurrency
|
||||
// setTaxRate
|
||||
// putShippingProvider
|
||||
// putPaymentProvider
|
||||
// removeShippingProvider
|
||||
// removePaymentMethod
|
||||
// listShippingMethods
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { countries } from "../utils/countries"
|
||||
import { currencies } from "../utils/currencies"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate regions.
|
||||
* @implements BaseService
|
||||
*/
|
||||
class RegionService extends BaseService {
|
||||
constructor({
|
||||
regionModel,
|
||||
paymentProviderService,
|
||||
fulfillmentProviderService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
/** @private @const {RegionModel} */
|
||||
this.regionModel_ = regionModel
|
||||
|
||||
/** @private @const {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
|
||||
/** @private @const {FulfillmentProviderService} */
|
||||
this.fulfillmentProviderService_ = fulfillmentProviderService
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a region.
|
||||
* @param {Region} rawRegion - the unvalidated region
|
||||
* @return {Region} the newly created region
|
||||
*/
|
||||
async create(rawRegion) {
|
||||
const region = await this.validateFields_(rawRegion)
|
||||
return this.regionModel_.create(region)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a region. Note metadata cannot be set with the update function, use
|
||||
* setMetadata instead.
|
||||
* @param {string} regionId - the region to update
|
||||
* @param {object} update - the data to update the region with
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async update(regionId, update) {
|
||||
const region = await this.validateFields_(update, regionId)
|
||||
return this.regionModel_.updateOne(
|
||||
{
|
||||
_id: regionId,
|
||||
},
|
||||
{
|
||||
$set: region,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates fields for creation and updates. If the region already exisits
|
||||
* the id can be passed to check that country updates are allowed.
|
||||
* @param {object} region - the region data to validate
|
||||
* @param {string?} id - optional id of the region to check against
|
||||
* @return {object} the validated region data
|
||||
*/
|
||||
async validateFields_(region, id = undefined) {
|
||||
if (region.tax_rate) {
|
||||
this.validateTaxRate_(region.tax_rate)
|
||||
}
|
||||
|
||||
if (region.currency_code) {
|
||||
region.currency_code = region.currency_code.toUpperCase()
|
||||
this.validateCurrency_(region.currency_code)
|
||||
}
|
||||
|
||||
if (region.countries) {
|
||||
region.countries = await Promise.all(
|
||||
region.countries.map(countryCode =>
|
||||
this.validateCountry_(countryCode, id)
|
||||
)
|
||||
).catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
if (region.metadata) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Please use setMetadata"
|
||||
)
|
||||
}
|
||||
|
||||
if (region.fulfillment_providers) {
|
||||
// Will throw if we do not find the provider
|
||||
region.fulfillment_providers.forEach(pId => {
|
||||
this.fulfillmentProviderService_.retrieveProvider(pId)
|
||||
})
|
||||
}
|
||||
|
||||
if (region.payment_providers) {
|
||||
// Will throw if we do not find the provider
|
||||
region.payment_providers.forEach(pId => {
|
||||
this.paymentProviderService_.retrieveProvider(pId)
|
||||
})
|
||||
}
|
||||
|
||||
return region
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a tax rate. Will throw if the tax rate is not between 0 and 1.
|
||||
* @param {number} taxRate - a number representing the tax rate of the region
|
||||
*/
|
||||
validateTaxRate_(taxRate) {
|
||||
if (taxRate > 1 || taxRate < 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The tax_rate must be between 0 and 1"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a currency code. Will throw if the currency code doesn't exist.
|
||||
* @param {string} currencyCode - an ISO currency code
|
||||
*/
|
||||
validateCurrency_(currencyCode) {
|
||||
if (!currencies[currencyCode]) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Invalid currency code"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a country code. Will normalize the code before checking for
|
||||
* existence.
|
||||
* @param {string} code - a 2 digit alphanumeric ISO country code
|
||||
* @param {string} id - the id of the current region to check against
|
||||
*/
|
||||
async validateCountry_(code, id) {
|
||||
const countryCode = code.toUpperCase()
|
||||
const country = countries.find(c => c.alpha2 === countryCode)
|
||||
if (!country) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Invalid country code"
|
||||
)
|
||||
}
|
||||
|
||||
const existing = await this.regionModel_.findOne({ countries: countryCode })
|
||||
if (existing && existing._id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`${country.name} already exists in ${existing.name}, delete it in that region before adding it`
|
||||
)
|
||||
}
|
||||
|
||||
return countryCode
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate region ids. Throws an error if the cast fails
|
||||
* @param {string} rawId - the raw region id to validate.
|
||||
* @return {string} the validated id
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"The regionId could not be casted to an ObjectId"
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a region by its id.
|
||||
* @param {string} regionId - the id of the region to retrieve
|
||||
* @return {Region} the region
|
||||
*/
|
||||
async retrieve(regionId) {
|
||||
const validatedId = this.validateId_(regionId)
|
||||
const region = await this.regionModel_.findOne({ _id: validatedId })
|
||||
|
||||
if (!region) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Region with ${regionId} was not found`
|
||||
)
|
||||
}
|
||||
return region
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a region.
|
||||
* @param {string} regionId - the region to delete
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
delete(regionId) {
|
||||
return this.regionModel_.deleteOne({
|
||||
_id: regionId,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a country to the region.
|
||||
* @param {string} regionId - the region to add a country to
|
||||
* @param {string} code - a 2 digit alphanumeric ISO country code.
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addCountry(regionId, code) {
|
||||
const region = await this.retrieve(regionId)
|
||||
const countryCode = await this.validateCountry_(code, regionId)
|
||||
|
||||
if (region.countries.includes(countryCode)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{
|
||||
_id: region._id,
|
||||
},
|
||||
{
|
||||
$push: { countries: countryCode },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a country from a Region
|
||||
* @param {string} regionId - the region to remove from
|
||||
* @param {string} code - a 2 digit alphanumeric ISO country code to remove
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeCountry(regionId, code) {
|
||||
const countryCode = code.toUpperCase()
|
||||
const region = await this.retrieve(regionId)
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{ _id: region._id },
|
||||
{
|
||||
$pull: {
|
||||
countries: countryCode,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a payment provider that is available in the region. Fails if the
|
||||
* provider doesn't exist.
|
||||
* @param {string} regionId - the region to add the provider to
|
||||
* @param {string} providerId - the provider to add to the region
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addPaymentProvider(regionId, providerId) {
|
||||
const region = await this.retrieve(regionId)
|
||||
|
||||
if (region.payment_providers.includes(providerId)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
// Will throw if we do not find the provider
|
||||
this.paymentProviderService_.retrieveProvider(providerId)
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{
|
||||
_id: region._id,
|
||||
},
|
||||
{
|
||||
$push: { payment_providers: providerId },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a fulfillment provider that is available in the region. Fails if the
|
||||
* provider doesn't exist.
|
||||
* @param {string} regionId - the region to add the provider to
|
||||
* @param {string} providerId - the provider to add to the region
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addFulfillmentProvider(regionId, providerId) {
|
||||
const region = await this.retrieve(regionId)
|
||||
|
||||
if (region.fulfillment_providers.includes(providerId)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
// Will throw if we do not find the provider
|
||||
this.fulfillmentProviderService_.retrieveProvider(providerId)
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{
|
||||
_id: region._id,
|
||||
},
|
||||
{
|
||||
$push: { fulfillment_providers: providerId },
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a payment provider from a region. Is idempotent.
|
||||
* @param {string} regionId - the region to remove the provider from
|
||||
* @param {string} providerId - the provider to remove from the region
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removePaymentProvider(regionId, providerId) {
|
||||
const region = await this.retrieve(regionId)
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{ _id: region._id },
|
||||
{
|
||||
$pull: {
|
||||
payment_providers: providerId,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a fulfillment provider from a region. Is idempotent.
|
||||
* @param {string} regionId - the region to remove the provider from
|
||||
* @param {string} providerId - the provider to remove from the region
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeFulfillmentProvider(regionId, providerId) {
|
||||
const region = await this.retrieve(regionId)
|
||||
|
||||
return this.regionModel_.updateOne(
|
||||
{ _id: region._id },
|
||||
{
|
||||
$pull: {
|
||||
fulfillment_providers: providerId,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RegionService
|
||||
|
||||
1073
packages/medusa/src/utils/currencies.js
Normal file
1073
packages/medusa/src/utils/currencies.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user