adds endpoints for draft orders
This commit is contained in:
1 parent
75bc1f67f4
commit
362c5c6055
31 files changed
+1858
-238
No files matched your search
@@ -0,0 +1,344 @@
|
||||
const { dropDatabase } = require("pg-god");
|
||||
const path = require("path");
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server");
|
||||
const { useApi } = require("../../../helpers/use-api");
|
||||
const { initDb } = require("../../../helpers/use-db");
|
||||
|
||||
const draftOrderSeeder = require("../../helpers/draft-order-seeder");
|
||||
const adminSeeder = require("../../helpers/admin-seeder");
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
describe("/admin/draft-orders", () => {
|
||||
let medusaProcess;
|
||||
let dbConnection;
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."));
|
||||
dbConnection = await initDb({ cwd });
|
||||
medusaProcess = await setupServer({ cwd });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dbConnection.close();
|
||||
await dropDatabase({ databaseName: "medusa-integration" });
|
||||
|
||||
medusaProcess.kill();
|
||||
});
|
||||
|
||||
describe("POST /admin/draft-orders", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await adminSeeder(dbConnection);
|
||||
await draftOrderSeeder(dbConnection);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "line_item"`);
|
||||
await manager.query(`DELETE FROM "money_amount"`);
|
||||
await manager.query(`DELETE FROM "product_variant"`);
|
||||
await manager.query(`DELETE FROM "product"`);
|
||||
await manager.query(`DELETE FROM "shipping_method"`);
|
||||
await manager.query(`DELETE FROM "shipping_option"`);
|
||||
await manager.query(`DELETE FROM "discount"`);
|
||||
await manager.query(`DELETE FROM "payment_provider"`);
|
||||
await manager.query(`DELETE FROM "payment_session"`);
|
||||
await manager.query(`UPDATE "payment" SET order_id=NULL`);
|
||||
await manager.query(`DELETE FROM "order"`);
|
||||
await manager.query(`UPDATE "draft_order" SET order_id=NULL`);
|
||||
await manager.query(`DELETE FROM "draft_order"`);
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(`DELETE FROM "payment"`);
|
||||
await manager.query(`DELETE FROM "customer"`);
|
||||
await manager.query(`DELETE FROM "address"`);
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'de'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
await manager.query(`DELETE FROM "user"`);
|
||||
});
|
||||
|
||||
it("creates a draft order cart", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const payload = {
|
||||
email: "oli@test.dk",
|
||||
shipping_address_id: "oli-shipping",
|
||||
items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
},
|
||||
],
|
||||
region_id: "test-region",
|
||||
customer_id: "oli-test",
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await api
|
||||
.post("/admin/draft-orders", payload, {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
it("creates a draft order with custom item", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const payload = {
|
||||
email: "oli@test.dk",
|
||||
shipping_address_id: "oli-shipping",
|
||||
items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
quantity: 1,
|
||||
metadata: {},
|
||||
unit_price: 10000,
|
||||
},
|
||||
],
|
||||
region_id: "test-region",
|
||||
customer_id: "oli-test",
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await api
|
||||
.post("/admin/draft-orders", payload, {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
it("creates a draft order with created shipping address", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const payload = {
|
||||
email: "oli@test.dk",
|
||||
shipping_address: {
|
||||
first_name: "new",
|
||||
last_name: "one",
|
||||
address_1: "New place 1",
|
||||
city: "Copenhagen",
|
||||
country_code: "us",
|
||||
postal_code: "2100",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
quantity: 1,
|
||||
metadata: {},
|
||||
unit_price: 10000,
|
||||
},
|
||||
],
|
||||
region_id: "test-region",
|
||||
customer_id: "oli-test",
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const response = await api
|
||||
.post("/admin/draft-orders", payload, {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
it("creates a draft order and registers manual payment", async () => {
|
||||
const api = useApi();
|
||||
|
||||
// register system payment for draft order
|
||||
const orderResponse = await api.post(
|
||||
`/admin/draft-orders/test-draft-order/register-payment`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const createdOrder = await api.get(
|
||||
`/admin/orders/${orderResponse.data.order.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const updatedDraftOrder = await api.get(
|
||||
`/admin/draft-orders/test-draft-order`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(orderResponse.status).toEqual(200);
|
||||
// expect newly created order to have id of draft order and system payment
|
||||
expect(createdOrder.data.order.draft_order_id).toEqual(
|
||||
"test-draft-order"
|
||||
);
|
||||
expect(createdOrder.data.order.payments).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ provider_id: "system" }),
|
||||
])
|
||||
);
|
||||
// expect draft order to be complete
|
||||
expect(updatedDraftOrder.data.draft_order.status).toEqual("completed");
|
||||
});
|
||||
});
|
||||
describe("GET /admin/draft-orders", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await adminSeeder(dbConnection);
|
||||
await draftOrderSeeder(dbConnection);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "line_item"`);
|
||||
await manager.query(`DELETE FROM "money_amount"`);
|
||||
await manager.query(`DELETE FROM "product_variant"`);
|
||||
await manager.query(`DELETE FROM "product"`);
|
||||
await manager.query(`DELETE FROM "shipping_method"`);
|
||||
await manager.query(`DELETE FROM "shipping_option"`);
|
||||
await manager.query(`DELETE FROM "discount"`);
|
||||
await manager.query(`DELETE FROM "payment_provider"`);
|
||||
await manager.query(`DELETE FROM "payment_session"`);
|
||||
await manager.query(`UPDATE "payment" SET order_id=NULL`);
|
||||
await manager.query(`DELETE FROM "order"`);
|
||||
await manager.query(`UPDATE "draft_order" SET order_id=NULL`);
|
||||
await manager.query(`DELETE FROM "draft_order"`);
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(`DELETE FROM "payment"`);
|
||||
await manager.query(`DELETE FROM "customer"`);
|
||||
await manager.query(`DELETE FROM "address"`);
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'de'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
await manager.query(`DELETE FROM "user"`);
|
||||
});
|
||||
|
||||
it("lists draft orders", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.get("/admin/draft-orders", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
expect(response.data.draft_orders).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: "test-draft-order" }),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("lists draft orders with query", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.get("/admin/draft-orders?q=oli@test", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
expect(response.data.draft_orders).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
cart: expect.objectContaining({ email: "oli@test.dk" }),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("lists no draft orders on query for non-existing email", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.get("/admin/draft-orders?q=heyo@heyo.dk", {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
console.log(response.data.draft_orders);
|
||||
|
||||
expect(response.data.draft_orders).toEqual([]);
|
||||
expect(response.data.count).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
const { dropDatabase } = require("pg-god");
|
||||
const path = require("path");
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server");
|
||||
const { useApi } = require("../../../helpers/use-api");
|
||||
const { initDb } = require("../../../helpers/use-db");
|
||||
|
||||
const draftOrderSeeder = require("../../helpers/draft-order-seeder");
|
||||
const { create } = require("domain");
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
describe("/store/carts (draft-orders)", () => {
|
||||
let medusaProcess;
|
||||
let dbConnection;
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."));
|
||||
dbConnection = await initDb({ cwd });
|
||||
medusaProcess = await setupServer({ cwd });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dbConnection.close();
|
||||
await dropDatabase({ databaseName: "medusa-integration" });
|
||||
|
||||
medusaProcess.kill();
|
||||
});
|
||||
|
||||
describe("POST /admin/draft-order", () => {
|
||||
beforeEach(async () => {
|
||||
try {
|
||||
await draftOrderSeeder(dbConnection);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
await manager.query(`DELETE FROM "line_item"`);
|
||||
await manager.query(`DELETE FROM "money_amount"`);
|
||||
await manager.query(`DELETE FROM "product_variant"`);
|
||||
await manager.query(`DELETE FROM "product"`);
|
||||
await manager.query(`DELETE FROM "shipping_method"`);
|
||||
await manager.query(`DELETE FROM "shipping_option"`);
|
||||
await manager.query(`DELETE FROM "discount"`);
|
||||
await manager.query(`DELETE FROM "draft_order"`);
|
||||
await manager.query(`DELETE FROM "payment_provider"`);
|
||||
await manager.query(`DELETE FROM "payment_session"`);
|
||||
await manager.query(`UPDATE "payment" SET order_id=NULL`);
|
||||
await manager.query(`DELETE FROM "order"`);
|
||||
await manager.query(`DELETE FROM "cart"`);
|
||||
await manager.query(`DELETE FROM "payment"`);
|
||||
await manager.query(`DELETE FROM "customer"`);
|
||||
await manager.query(`DELETE FROM "address"`);
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
|
||||
);
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'de'`
|
||||
);
|
||||
await manager.query(`DELETE FROM "region"`);
|
||||
});
|
||||
|
||||
it("completes a cart for a draft order thereby creating an order for the draft order", async () => {
|
||||
const api = useApi();
|
||||
|
||||
const response = await api
|
||||
.post("/store/carts/test-cart/complete-cart", {})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
|
||||
const createdOrder = await api
|
||||
.get(`/store/orders/${response.data.data.id}`, {})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
expect(createdOrder.data.order.cart_id).toEqual("test-cart");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
const {
|
||||
ShippingProfile,
|
||||
Customer,
|
||||
MoneyAmount,
|
||||
ShippingOption,
|
||||
Product,
|
||||
ProductVariant,
|
||||
Region,
|
||||
Address,
|
||||
Cart,
|
||||
PaymentSession,
|
||||
DraftOrder,
|
||||
} = require("@medusajs/medusa");
|
||||
|
||||
module.exports = async (connection, data = {}) => {
|
||||
const manager = connection.manager;
|
||||
|
||||
const defaultProfile = await manager.findOne(ShippingProfile, {
|
||||
type: "default",
|
||||
});
|
||||
|
||||
await manager.insert(Product, {
|
||||
id: "test-product",
|
||||
title: "test product",
|
||||
profile_id: defaultProfile.id,
|
||||
options: [{ id: "test-option", title: "Size" }],
|
||||
});
|
||||
|
||||
await manager.insert(Address, {
|
||||
id: "oli-shipping",
|
||||
first_name: "oli",
|
||||
last_name: "test",
|
||||
country_code: "us",
|
||||
});
|
||||
|
||||
await manager.insert(Product, {
|
||||
id: "test-product-2",
|
||||
title: "test product 2",
|
||||
profile_id: defaultProfile.id,
|
||||
options: [{ id: "test-option-color", title: "Color" }],
|
||||
});
|
||||
|
||||
await manager.insert(ProductVariant, {
|
||||
id: "test-variant",
|
||||
title: "test variant",
|
||||
product_id: "test-product",
|
||||
inventory_quantity: 1,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
value: "Size",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.insert(ProductVariant, {
|
||||
id: "test-variant-2",
|
||||
title: "test variant-2",
|
||||
product_id: "test-product-2",
|
||||
inventory_quantity: 4,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-option-color",
|
||||
value: "Color",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const ma = manager.create(MoneyAmount, {
|
||||
variant_id: "test-variant",
|
||||
currency_code: "usd",
|
||||
amount: 8000,
|
||||
});
|
||||
await manager.save(ma);
|
||||
|
||||
const ma2 = manager.create(MoneyAmount, {
|
||||
variant_id: "test-variant-2",
|
||||
currency_code: "usd",
|
||||
amount: 10000,
|
||||
});
|
||||
|
||||
await manager.save(ma2);
|
||||
|
||||
await manager.insert(Region, {
|
||||
id: "test-region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
payment_providers: [
|
||||
{
|
||||
id: "test-pay",
|
||||
is_installed: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.insert(Region, {
|
||||
id: "test-region-2",
|
||||
name: "Test Region 2",
|
||||
currency_code: "eur",
|
||||
tax_rate: 0,
|
||||
payment_providers: [
|
||||
{
|
||||
id: "test-pay",
|
||||
is_installed: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
|
||||
);
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='test-region-2' WHERE iso_2 = 'de'`
|
||||
);
|
||||
|
||||
await manager.insert(Customer, {
|
||||
id: "oli-test",
|
||||
email: "oli@test.dk",
|
||||
});
|
||||
|
||||
await manager.insert(Customer, {
|
||||
id: "lebron-james",
|
||||
email: "lebron@james.com",
|
||||
});
|
||||
|
||||
await manager.insert(ShippingOption, {
|
||||
id: "test-option",
|
||||
name: "test-option",
|
||||
provider_id: "test-ful",
|
||||
region_id: "test-region",
|
||||
profile_id: defaultProfile.id,
|
||||
price_type: "flat_rate",
|
||||
amount: 1000,
|
||||
data: {},
|
||||
});
|
||||
|
||||
const c = manager.create(Cart, {
|
||||
id: "test-cart",
|
||||
customer_id: "oli-test",
|
||||
email: "oli@test.dk",
|
||||
shipping_address_id: "oli-shipping",
|
||||
region_id: "test-region",
|
||||
currency_code: "usd",
|
||||
payment_sessions: [],
|
||||
items: [
|
||||
{
|
||||
id: "test-item",
|
||||
fulfilled_quantity: 1,
|
||||
title: "Line Item",
|
||||
description: "Line Item Desc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 8000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
],
|
||||
type: "draft_order",
|
||||
metadata: { draft_order_id: "test-draft-order" },
|
||||
});
|
||||
|
||||
await manager.save(c);
|
||||
|
||||
await manager.insert(PaymentSession, {
|
||||
id: "test-session",
|
||||
cart_id: "test-cart",
|
||||
provider_id: "test-pay",
|
||||
is_selected: true,
|
||||
data: {},
|
||||
status: "pending",
|
||||
});
|
||||
|
||||
// await manager.save(cart);
|
||||
|
||||
const draftOrder = manager.create(DraftOrder, {
|
||||
id: "test-draft-order",
|
||||
status: "awaiting",
|
||||
display_id: 4,
|
||||
cart_id: "test-cart",
|
||||
customer_id: "oli-test",
|
||||
items: [
|
||||
{
|
||||
id: "test-item",
|
||||
fulfilled_quantity: 1,
|
||||
title: "Line Item",
|
||||
description: "Line Item Desc",
|
||||
thumbnail: "https://test.js/1234",
|
||||
unit_price: 8000,
|
||||
quantity: 1,
|
||||
variant_id: "test-variant",
|
||||
},
|
||||
],
|
||||
email: "oli@test.dk",
|
||||
region_id: "test-region",
|
||||
discounts: [],
|
||||
});
|
||||
|
||||
await manager.save(draftOrder);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ class TestPayService extends PaymentService {
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
return {};
|
||||
return { data: {}, status: "authorized" };
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
|
||||
@@ -3,7 +3,7 @@ export default async (req, res) => {
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieve(id, {
|
||||
relations: ["orders"],
|
||||
relations: ["orders", "shipping_addresses"],
|
||||
})
|
||||
|
||||
res.json({ customer })
|
||||
|
||||
@@ -5,13 +5,19 @@ export default async (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 10
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
const selector = {}
|
||||
|
||||
if ("q" in req.query) {
|
||||
selector.q = req.query.q
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
relations: [],
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}
|
||||
|
||||
const customers = await customerService.list({}, listConfig)
|
||||
const customers = await customerService.list(selector, listConfig)
|
||||
|
||||
res.json({ customers, count: customers.length, offset, limit })
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
status: Validator.string()
|
||||
.valid("open", "awaiting", "completed")
|
||||
.optional(),
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
billing_address: Validator.address().optional(),
|
||||
shipping_address: Validator.address().optional(),
|
||||
billing_address_id: Validator.string().optional(),
|
||||
shipping_address_id: Validator.string().optional(),
|
||||
items: Validator.array()
|
||||
.items({
|
||||
variant_id: Validator.string().optional(),
|
||||
unit_price: Validator.number().optional(),
|
||||
title: Validator.string().optional(),
|
||||
quantity: Validator.number().required(),
|
||||
metadata: Validator.object().default({}),
|
||||
})
|
||||
.required(),
|
||||
region_id: Validator.string().required(),
|
||||
discounts: Validator.array().optional(),
|
||||
customer_id: Validator.string().optional(),
|
||||
customer: Validator.string().optional(),
|
||||
shipping_methods: Validator.array()
|
||||
.items({
|
||||
option_id: Validator.string().required(),
|
||||
data: Validator.object().optional(),
|
||||
price: Validator.number()
|
||||
.integer()
|
||||
.integer()
|
||||
.allow(0)
|
||||
.optional(),
|
||||
})
|
||||
.required(),
|
||||
metadata: Validator.object().optional(),
|
||||
requires_shipping: Validator.boolean().default(true),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async manager => {
|
||||
const requiresShipping = value.requires_shipping
|
||||
delete value.requires_shipping
|
||||
|
||||
let draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.create(value, requiresShipping)
|
||||
|
||||
draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.id, {
|
||||
relations: defaultRelations,
|
||||
select: defaultFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
unit_price: Validator.number().optional(),
|
||||
variant_id: Validator.string().optional(),
|
||||
quantity: Validator.number().required(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const lineItemService = req.scope.resolve("lineItemService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async manager => {
|
||||
const draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, { select: defaultFields, relations: ["cart"] })
|
||||
|
||||
if (
|
||||
draftOrder.status === "completed" ||
|
||||
draftOrder.status === "awaiting"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You are only allowed to update open draft orders"
|
||||
)
|
||||
}
|
||||
|
||||
if (value.variant_id) {
|
||||
const line = await lineItemService.generate(
|
||||
value.variant_id,
|
||||
draftOrder.cart.region_id,
|
||||
value.quantity,
|
||||
value.metadata
|
||||
)
|
||||
|
||||
await cartService
|
||||
.withTransaction(manager)
|
||||
.addLineItem(draftOrder.cart_id, line)
|
||||
} else {
|
||||
// custom line items can be added to a draft order
|
||||
await lineItemService.withTransaction(manager).create({
|
||||
cart_id: draftOrder.cart_id,
|
||||
has_shipping: true,
|
||||
title: value.title || "Custom item",
|
||||
allow_discounts: false,
|
||||
unit_price: value.unit_price || 0,
|
||||
quantity: value.quantity,
|
||||
})
|
||||
}
|
||||
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, line_id } = req.params
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async manager => {
|
||||
const draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, { select: defaultFields })
|
||||
|
||||
if (
|
||||
draftOrder.status === "completed" ||
|
||||
draftOrder.status === "awaiting"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You are only allowed to update open draft orders"
|
||||
)
|
||||
}
|
||||
|
||||
await cartService
|
||||
.withTransaction(manager)
|
||||
.removeLineItem(draftOrder.cart_id, line_id)
|
||||
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
|
||||
const draftOrder = await draftOrderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
draftOrder.cart = await cartService.retrieve(draftOrder.cart_id, {
|
||||
relations: [
|
||||
"gift_cards",
|
||||
"region",
|
||||
"items",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region.countries",
|
||||
"region.payment_providers",
|
||||
"shipping_methods",
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
],
|
||||
select: [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"gift_card_total",
|
||||
"total",
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ draft_order: draftOrder })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const shippingProfileService = req.scope.resolve("shippingProfileService")
|
||||
|
||||
const draftOrder = await cartService.retrieve(value.cart_id, {
|
||||
select: ["subtotal"],
|
||||
relations: ["region", "items", "items.variant", "items.variant.product"],
|
||||
})
|
||||
|
||||
const options = await shippingProfileService.fetchCartOptions(cart)
|
||||
|
||||
res.status(200).json({ shipping_options: options })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/draft-orders", route)
|
||||
|
||||
route.get("/", middlewares.wrap(require("./list-draft-orders").default))
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-draft-order").default))
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-draft-order").default))
|
||||
|
||||
route.delete(
|
||||
"/:id/line-items/:line_id",
|
||||
middlewares.wrap(require("./delete-line-item").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/line-items",
|
||||
middlewares.wrap(require("./create-line-item").default)
|
||||
)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-draft-order").default))
|
||||
|
||||
route.post(
|
||||
"/:id/register-payment",
|
||||
middlewares.wrap(require("./register-payment").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultRelations = []
|
||||
|
||||
export const defaultCartRelations = [
|
||||
"region",
|
||||
"items",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region.payment_providers",
|
||||
"shipping_methods",
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
]
|
||||
|
||||
export const defaultCartFields = [
|
||||
"region",
|
||||
"items",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"region.payment_providers",
|
||||
"shipping_methods",
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
]
|
||||
|
||||
export const defaultFields = [
|
||||
"id",
|
||||
"status",
|
||||
"display_id",
|
||||
"cart_id",
|
||||
"canceled_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
]
|
||||
|
||||
export const allowedFields = [
|
||||
"id",
|
||||
"status",
|
||||
"display_id",
|
||||
"cart_id",
|
||||
"canceled_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"metadata",
|
||||
]
|
||||
|
||||
export const allowedRelations = ["cart"]
|
||||
@@ -0,0 +1,34 @@
|
||||
import _ from "lodash"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
|
||||
const limit = parseInt(req.query.limit) || 50
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
let selector = {}
|
||||
|
||||
if ("q" in req.query) {
|
||||
selector.q = req.query.q
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
const [draftOrders, count] = await draftOrderService.listAndCount(
|
||||
selector,
|
||||
listConfig
|
||||
)
|
||||
|
||||
res.json({ draft_orders: draftOrders, count, offset, limit })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
defaultFields as defaultOrderFields,
|
||||
defaultRelations as defaultOrderRelations,
|
||||
} from "../orders/index"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const createdOrder = await draftOrderService.registerSystemPayment(id)
|
||||
|
||||
const order = await orderService.retrieve(createdOrder.id, {
|
||||
relations: defaultOrderRelations,
|
||||
select: defaultOrderFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ order })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultCartFields, defaultCartRelations, defaultFields } from "."
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
region_id: Validator.string().optional(),
|
||||
country_code: Validator.string().optional(),
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.optional(),
|
||||
billing_address: Validator.object().optional(),
|
||||
shipping_address: Validator.object().optional(),
|
||||
|
||||
discounts: Validator.array()
|
||||
.items({
|
||||
code: Validator.string(),
|
||||
})
|
||||
.optional(),
|
||||
customer_id: Validator.string().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const draftOrderService = req.scope.resolve("draftOrderService")
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
const entityManager = req.scope.resolve("manager")
|
||||
|
||||
await entityManager.transaction(async manager => {
|
||||
const draftOrder = await draftOrderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, { select: defaultFields })
|
||||
|
||||
if (
|
||||
draftOrder.status === "completed" ||
|
||||
draftOrder.status === "awaiting"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You are only allowed to update open draft orders"
|
||||
)
|
||||
}
|
||||
|
||||
await cartService
|
||||
.withTransaction(manager)
|
||||
.update(draftOrder.cart_id, value)
|
||||
|
||||
draftOrder.cart = await cartService
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
relations: defaultCartRelations,
|
||||
select: defaultCartFields,
|
||||
})
|
||||
|
||||
res.status(200).json({ draft_order: draftOrder })
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import appRoutes from "./apps"
|
||||
import swapRoutes from "./swaps"
|
||||
import returnRoutes from "./returns"
|
||||
import variantRoutes from "./variants"
|
||||
import draftOrderRoutes from "./draft-orders"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -60,6 +61,7 @@ export default (app, container, config) => {
|
||||
swapRoutes(route)
|
||||
returnRoutes(route)
|
||||
variantRoutes(route)
|
||||
draftOrderRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -21,7 +21,6 @@ const defaultRelations = [
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.images",
|
||||
"claims.claim_items.tags",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
"swaps.payment",
|
||||
@@ -38,6 +37,7 @@ const defaultFields = [
|
||||
"payment_status",
|
||||
"display_id",
|
||||
"cart_id",
|
||||
"draft_order_id",
|
||||
"customer_id",
|
||||
"email",
|
||||
"region_id",
|
||||
|
||||
@@ -208,6 +208,7 @@ export const defaultFields = [
|
||||
"payment_status",
|
||||
"display_id",
|
||||
"cart_id",
|
||||
"draft_order_id",
|
||||
"customer_id",
|
||||
"email",
|
||||
"region_id",
|
||||
@@ -235,6 +236,7 @@ export const allowedFields = [
|
||||
"payment_status",
|
||||
"display_id",
|
||||
"cart_id",
|
||||
"draft_order_id",
|
||||
"customer_id",
|
||||
"email",
|
||||
"region_id",
|
||||
|
||||
@@ -37,3 +37,4 @@ export { StagedJob } from "./models/staged-job"
|
||||
export { Store } from "./models/store"
|
||||
export { Swap } from "./models/swap"
|
||||
export { User } from "./models/user"
|
||||
export { DraftOrder } from "./models/draft-order"
|
||||
@@ -0,0 +1,46 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class draftOrders1613384784316 implements MigrationInterface {
|
||||
name = 'draftOrders1613384784316'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "draft_order_status_enum" AS ENUM('open', 'awaiting', 'completed')`);
|
||||
await queryRunner.query(`CREATE TABLE "draft_order" ("id" character varying NOT NULL, "status" "draft_order_status_enum" NOT NULL DEFAULT 'open', "display_id" SERIAL NOT NULL, "cart_id" character varying, "order_id" character varying, "canceled_at" TIMESTAMP WITH TIME ZONE, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "metadata" jsonb, "idempotency_key" character varying, CONSTRAINT "REL_5bd11d0e2a9628128e2c26fd0a" UNIQUE ("cart_id"), CONSTRAINT "REL_8f6dd6c49202f1466ebf21e77d" UNIQUE ("order_id"), CONSTRAINT "PK_f478946c183d98f8d88a94cfcd7" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e87cc617a22ef4edce5601edab" ON "draft_order" ("display_id") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5bd11d0e2a9628128e2c26fd0a" ON "draft_order" ("cart_id") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8f6dd6c49202f1466ebf21e77d" ON "draft_order" ("order_id") `);
|
||||
await queryRunner.query(`ALTER TABLE "order" ADD "draft_order_id" character varying`);
|
||||
await queryRunner.query(`ALTER TABLE "order" ADD CONSTRAINT "UQ_727b872f86c7378474a8fa46147" UNIQUE ("draft_order_id")`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."cart_type_enum" RENAME TO "cart_type_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "cart_type_enum" AS ENUM('default', 'swap', 'draft_order', 'payment_link')`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum" USING "type"::"text"::"cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "cart_type_enum_old"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "cart"."type" IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "draft_order" ADD CONSTRAINT "FK_5bd11d0e2a9628128e2c26fd0a6" FOREIGN KEY ("cart_id") REFERENCES "cart"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "draft_order" ADD CONSTRAINT "FK_8f6dd6c49202f1466ebf21e77da" FOREIGN KEY ("order_id") REFERENCES "order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "order" ADD CONSTRAINT "FK_727b872f86c7378474a8fa46147" FOREIGN KEY ("draft_order_id") REFERENCES "draft_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "order" DROP CONSTRAINT "FK_727b872f86c7378474a8fa46147"`);
|
||||
await queryRunner.query(`ALTER TABLE "draft_order" DROP CONSTRAINT "FK_8f6dd6c49202f1466ebf21e77da"`);
|
||||
await queryRunner.query(`ALTER TABLE "draft_order" DROP CONSTRAINT "FK_5bd11d0e2a9628128e2c26fd0a6"`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "cart"."type" IS NULL`);
|
||||
await queryRunner.query(`CREATE TYPE "cart_type_enum_old" AS ENUM('default', 'swap', 'payment_link')`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum_old" USING "type"::"text"::"cart_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "cart_type_enum_old" RENAME TO "cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "order" DROP CONSTRAINT "UQ_727b872f86c7378474a8fa46147"`);
|
||||
await queryRunner.query(`ALTER TABLE "order" DROP COLUMN "draft_order_id"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_8f6dd6c49202f1466ebf21e77d"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_5bd11d0e2a9628128e2c26fd0a"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_e87cc617a22ef4edce5601edab"`);
|
||||
await queryRunner.query(`DROP TABLE "draft_order"`);
|
||||
await queryRunner.query(`DROP TYPE "draft_order_status_enum"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import { ShippingMethod } from "./shipping-method"
|
||||
export enum CartType {
|
||||
DEFAULT = "default",
|
||||
SWAP = "swap",
|
||||
DRAFT_ORDER = "draft_order",
|
||||
PAYMENT_LINK = "payment_link",
|
||||
}
|
||||
|
||||
|
||||
@@ -61,76 +61,6 @@ export class DraftOrder {
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
customer_id: string
|
||||
|
||||
@ManyToOne(() => Customer, { cascade: ["insert"] })
|
||||
@JoinColumn({ name: "customer_id" })
|
||||
customer: Customer
|
||||
|
||||
@OneToMany(
|
||||
() => LineItem,
|
||||
lineItem => lineItem.draft_order,
|
||||
{ cascade: ["insert", "remove"] }
|
||||
)
|
||||
items: LineItem[]
|
||||
|
||||
@Column()
|
||||
email: string
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
billing_address_id: string
|
||||
|
||||
@ManyToOne(() => Address, { cascade: ["insert"] })
|
||||
@JoinColumn({ name: "billing_address_id" })
|
||||
billing_address: Address
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
shipping_address_id: string
|
||||
|
||||
@ManyToOne(() => Address, { cascade: ["insert"] })
|
||||
@JoinColumn({ name: "shipping_address_id" })
|
||||
shipping_address: Address
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
region_id: string
|
||||
|
||||
@ManyToOne(() => Region)
|
||||
@JoinColumn({ name: "region_id" })
|
||||
region: Region
|
||||
|
||||
@ManyToMany(() => Discount, { cascade: ["insert"] })
|
||||
@JoinTable({
|
||||
name: "draft_order_discounts",
|
||||
joinColumn: {
|
||||
name: "draft_order_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: "discount_id",
|
||||
referencedColumnName: "id",
|
||||
},
|
||||
})
|
||||
discounts: Discount[]
|
||||
|
||||
@OneToMany(
|
||||
() => ShippingMethod,
|
||||
m => m.draft_order,
|
||||
{ cascade: ["soft-remove", "remove"] }
|
||||
)
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@OneToMany(
|
||||
() => Payment,
|
||||
p => p.draft_order,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
payments: Payment[]
|
||||
|
||||
@Column({ nullable: true, type: "timestamptz" })
|
||||
canceled_at: Date
|
||||
|
||||
|
||||
@@ -50,17 +50,6 @@ export class LineItem {
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
draft_order_id: string
|
||||
|
||||
@ManyToOne(
|
||||
() => DraftOrder,
|
||||
dorder => dorder.items
|
||||
)
|
||||
@JoinColumn({ name: "draft_order_id" })
|
||||
draft_order: DraftOrder
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
swap_id: string
|
||||
|
||||
@@ -50,14 +50,6 @@ export class Payment {
|
||||
@JoinColumn({ name: "order_id" })
|
||||
order: Order
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
draft_order_id: string
|
||||
|
||||
@OneToOne(() => DraftOrder)
|
||||
@JoinColumn({ name: "draft_order_id" })
|
||||
draft_order: DraftOrder
|
||||
|
||||
@Column({ type: "int" })
|
||||
amount: number
|
||||
|
||||
|
||||
@@ -68,14 +68,6 @@ export class ShippingMethod {
|
||||
@Column({ nullable: true })
|
||||
return_id: string
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
draft_order_id: string
|
||||
|
||||
@ManyToOne(() => DraftOrder)
|
||||
@JoinColumn({ name: "draft_order_id" })
|
||||
draft_order: DraftOrder
|
||||
|
||||
@OneToOne(
|
||||
() => Return,
|
||||
ret => ret.shipping_method
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
import _ from "lodash"
|
||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||
import DraftOrderService from "../draft-order"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
describe("DraftOrderService", () => {
|
||||
const totalsService = {
|
||||
getTotal: o => {
|
||||
return o.total || 0
|
||||
},
|
||||
getSubtotal: o => {
|
||||
return o.subtotal || 0
|
||||
},
|
||||
getTaxTotal: o => {
|
||||
return o.tax_total || 0
|
||||
},
|
||||
getDiscountTotal: o => {
|
||||
return o.discount_total || 0
|
||||
},
|
||||
getShippingTotal: o => {
|
||||
return o.shipping_total || 0
|
||||
},
|
||||
getGiftCardTotal: o => {
|
||||
return o.gift_card_total || 0
|
||||
},
|
||||
}
|
||||
|
||||
describe("create", () => {
|
||||
let result
|
||||
|
||||
const regionService = {
|
||||
retrieve: () =>
|
||||
Promise.resolve({ id: "test-region", countries: [{ iso_2: "dk" }] }),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const shippingOptionService = {
|
||||
createShippingMethod: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
shipping_option: {
|
||||
profile_id: "test-profile",
|
||||
},
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const lineItemService = {
|
||||
generate: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
title: "test-item",
|
||||
variant_id: "test-variant",
|
||||
})
|
||||
),
|
||||
create: jest.fn(),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const productVariantService = {
|
||||
retrieve: () =>
|
||||
Promise.resolve({
|
||||
id: "test-variant",
|
||||
product: {
|
||||
profile_id: "test-profile",
|
||||
},
|
||||
}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const cartService = {
|
||||
create: jest.fn().mockImplementation(data =>
|
||||
Promise.resolve({
|
||||
id: "test-cart",
|
||||
...data,
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const testOrder = {
|
||||
region_id: "test-region",
|
||||
shipping_address_id: "test-shipping",
|
||||
billing_address_id: "test-billing",
|
||||
customer_id: "test-customer",
|
||||
items: [{ variant_id: "test-variant", quantity: 2, metadata: {} }],
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const addressRepository = MockRepository({
|
||||
create: addr => ({
|
||||
...addr,
|
||||
}),
|
||||
})
|
||||
|
||||
const draftOrderRepository = MockRepository({
|
||||
create: d => ({
|
||||
...d,
|
||||
}),
|
||||
save: d => ({
|
||||
id: "test-draft-order",
|
||||
...d,
|
||||
}),
|
||||
})
|
||||
|
||||
const draftOrderService = new DraftOrderService({
|
||||
manager: MockManager,
|
||||
regionService,
|
||||
cartService,
|
||||
shippingOptionService,
|
||||
lineItemService,
|
||||
productVariantService,
|
||||
draftOrderRepository,
|
||||
addressRepository,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("creates a draft order", async () => {
|
||||
await draftOrderService.create(testOrder)
|
||||
|
||||
expect(draftOrderRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(draftOrderRepository.create).toHaveBeenCalledWith({
|
||||
cart_id: "test-cart",
|
||||
})
|
||||
|
||||
expect(cartService.create).toHaveBeenCalledTimes(1)
|
||||
expect(cartService.create).toHaveBeenCalledWith({
|
||||
region_id: "test-region",
|
||||
shipping_address_id: "test-shipping",
|
||||
billing_address_id: "test-billing",
|
||||
customer_id: "test-customer",
|
||||
})
|
||||
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
"test-option",
|
||||
{},
|
||||
{
|
||||
cart: {
|
||||
id: "test-cart",
|
||||
region_id: "test-region",
|
||||
shipping_address_id: "test-shipping",
|
||||
billing_address_id: "test-billing",
|
||||
customer_id: "test-customer",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(lineItemService.generate).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.generate).toHaveBeenCalledWith(
|
||||
"test-variant",
|
||||
"test-region",
|
||||
2,
|
||||
{}
|
||||
)
|
||||
|
||||
expect(lineItemService.create).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.create).toHaveBeenCalledWith({
|
||||
cart_id: "test-cart",
|
||||
has_shipping: true,
|
||||
title: "test-item",
|
||||
variant_id: "test-variant",
|
||||
})
|
||||
})
|
||||
|
||||
it("fails on missing region", async () => {
|
||||
try {
|
||||
await draftOrderService.create({
|
||||
items: [],
|
||||
})
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual(
|
||||
`region_id is required to create a draft order`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("fails on missing items", async () => {
|
||||
try {
|
||||
await draftOrderService.create({
|
||||
region_id: "test-region",
|
||||
items: [],
|
||||
})
|
||||
} catch (error) {
|
||||
expect(error.message).toEqual(
|
||||
`Items are required to create a draft order`
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("registerSystemPayment", () => {
|
||||
let result
|
||||
|
||||
const shippingOptionService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const cartService = {
|
||||
retrieve: jest.fn().mockImplementation(data =>
|
||||
Promise.resolve({
|
||||
id: "test-cart",
|
||||
total: 1000,
|
||||
region_id: "test-region",
|
||||
region: {
|
||||
id: "test-region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
},
|
||||
items: [{ id: "test-item" }],
|
||||
discounts: [],
|
||||
email: "oli@test.dk",
|
||||
customer_id: "test-customer",
|
||||
draft_order_id: "test-draft-order",
|
||||
metadata: {},
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const draftOrderRepository = MockRepository({
|
||||
findOne: () =>
|
||||
Promise.resolve({ id: "test-draft-order", cart_id: "test-cart" }),
|
||||
})
|
||||
const orderRepository = MockRepository({
|
||||
create: d => ({ id: "test-order", ...d }),
|
||||
})
|
||||
const paymentRepository = MockRepository({
|
||||
create: d => ({
|
||||
...d,
|
||||
}),
|
||||
})
|
||||
|
||||
const draftOrderService = new DraftOrderService({
|
||||
manager: MockManager,
|
||||
cartService,
|
||||
shippingOptionService,
|
||||
lineItemService,
|
||||
paymentRepository,
|
||||
draftOrderRepository,
|
||||
orderRepository,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("registers system payment", async () => {
|
||||
await draftOrderService.registerSystemPayment("test-draft-order")
|
||||
|
||||
expect(cartService.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(cartService.retrieve).toHaveBeenCalledWith("test-cart", {
|
||||
relations: ["discounts", "shipping_methods", "region", "items"],
|
||||
select: ["total"],
|
||||
})
|
||||
|
||||
expect(paymentRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(paymentRepository.create).toHaveBeenCalledWith({
|
||||
provider_id: "system",
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
})
|
||||
|
||||
expect(orderRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(orderRepository.create).toHaveBeenCalledWith({
|
||||
cart_id: "test-cart",
|
||||
discounts: [],
|
||||
region_id: "test-region",
|
||||
discounts: [],
|
||||
email: "oli@test.dk",
|
||||
customer_id: "test-customer",
|
||||
draft_order_id: "test-draft-order",
|
||||
tax_rate: 0,
|
||||
payment_status: "awaiting",
|
||||
currency_code: "usd",
|
||||
metadata: {},
|
||||
payments: [
|
||||
{
|
||||
provider_id: "system",
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
data: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(draftOrderRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(draftOrderRepository.save).toHaveBeenCalledWith({
|
||||
id: "test-draft-order",
|
||||
cart_id: "test-cart",
|
||||
status: "completed",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -958,6 +958,8 @@ class CartService extends BaseService {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
|
||||
console.log("YELLO: ", session.status)
|
||||
|
||||
if (session.status === "authorized") {
|
||||
const payment = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
|
||||
@@ -3,6 +3,7 @@ import Scrypt from "scrypt-kdf"
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate customers.
|
||||
@@ -127,7 +128,34 @@ class CustomerService extends BaseService {
|
||||
this.customerRepository_
|
||||
)
|
||||
|
||||
let q
|
||||
if ("q" in selector) {
|
||||
q = selector.q
|
||||
delete selector.q
|
||||
}
|
||||
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
if (q) {
|
||||
const where = query.where
|
||||
|
||||
delete where.email
|
||||
delete where.first_name
|
||||
delete where.last_name
|
||||
|
||||
query.where = qb => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
qb.where(`email ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`first_name ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`last_name ILIKE :q`, { q: `%${q}%` })
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return customerRepo.find(query)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from "lodash"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
/**
|
||||
* Handles swaps
|
||||
@@ -14,11 +15,15 @@ class DraftOrderService extends BaseService {
|
||||
constructor({
|
||||
manager,
|
||||
draftOrderRepository,
|
||||
paymentRepository,
|
||||
orderRepository,
|
||||
eventBusService,
|
||||
addressRepository,
|
||||
cartService,
|
||||
customerService,
|
||||
totalsService,
|
||||
lineItemService,
|
||||
paymentProviderService,
|
||||
productVariantService,
|
||||
shippingOptionService,
|
||||
regionService,
|
||||
@@ -28,9 +33,15 @@ class DraftOrderService extends BaseService {
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
|
||||
/** @private @const {SwapModel} */
|
||||
/** @private @const {DraftOrderRepository} */
|
||||
this.draftOrderRepository_ = draftOrderRepository
|
||||
|
||||
/** @private @const {PaymentRepository} */
|
||||
this.paymentRepository_ = paymentRepository
|
||||
|
||||
/** @private @const {OrderRepository} */
|
||||
this.orderRepository_ = orderRepository
|
||||
|
||||
/** @private @const {TotalsService} */
|
||||
this.totalsService_ = totalsService
|
||||
|
||||
@@ -40,15 +51,18 @@ class DraftOrderService extends BaseService {
|
||||
/** @private @const {LineItemService} */
|
||||
this.lineItemService_ = lineItemService
|
||||
|
||||
/** @private @const {ReturnService} */
|
||||
this.returnService_ = returnService
|
||||
|
||||
/** @private @const {CartService} */
|
||||
this.cartService_ = cartService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
|
||||
/** @private @const {ProductVariantService} */
|
||||
this.productVariantService_ = productVariantService
|
||||
|
||||
@@ -67,10 +81,16 @@ class DraftOrderService extends BaseService {
|
||||
const cloned = new DraftOrderService({
|
||||
manager: transactionManager,
|
||||
draftOrderRepository: this.draftOrderRepository_,
|
||||
orderRepository: this.orderRepository_,
|
||||
paymentRepository: this.paymentRepository_,
|
||||
paymentProviderService: this.paymentProviderService_,
|
||||
regionService: this.regionService_,
|
||||
eventBusService: this.eventBus_,
|
||||
cartService: this.cartService_,
|
||||
totalsService: this.totalsService_,
|
||||
productVariantService: this.productVariantService_,
|
||||
addressRepository: this.addressRepository_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
lineItemService: this.lineItemService_,
|
||||
})
|
||||
|
||||
@@ -79,6 +99,68 @@ class DraftOrderService extends BaseService {
|
||||
return cloned
|
||||
}
|
||||
|
||||
transformQueryForTotals_(config) {
|
||||
let { select, relations } = config
|
||||
|
||||
if (!select) {
|
||||
return {
|
||||
select,
|
||||
relations,
|
||||
totalsToSelect: [],
|
||||
}
|
||||
}
|
||||
|
||||
const totalFields = [
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"total",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter(v => totalFields.includes(v))
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
relationSet.add("discounts")
|
||||
relationSet.add("shipping_methods")
|
||||
relationSet.add("region")
|
||||
relations = [...relationSet]
|
||||
|
||||
select = select.filter(v => !totalFields.includes(v))
|
||||
}
|
||||
|
||||
return {
|
||||
relations,
|
||||
select,
|
||||
totalsToSelect,
|
||||
}
|
||||
}
|
||||
|
||||
decorateTotals_(draftOrder, totalsFields = []) {
|
||||
if (totalsFields.includes("shipping_total")) {
|
||||
draftOrder.shipping_total = this.totalsService_.getShippingTotal(
|
||||
draftOrder
|
||||
)
|
||||
}
|
||||
if (totalsFields.includes("discount_total")) {
|
||||
draftOrder.discount_total = this.totalsService_.getDiscountTotal(
|
||||
draftOrder
|
||||
)
|
||||
}
|
||||
if (totalsFields.includes("tax_total")) {
|
||||
draftOrder.tax_total = this.totalsService_.getTaxTotal(draftOrder)
|
||||
}
|
||||
if (totalsFields.includes("subtotal")) {
|
||||
draftOrder.subtotal = this.totalsService_.getSubtotal(draftOrder)
|
||||
}
|
||||
if (totalsFields.includes("total")) {
|
||||
draftOrder.total = this.totalsService_.getTotal(draftOrder)
|
||||
}
|
||||
|
||||
return draftOrder
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a draft order with the given id.
|
||||
* @param {string} id - id of the draft order to retrieve
|
||||
@@ -91,17 +173,32 @@ class DraftOrderService extends BaseService {
|
||||
|
||||
const validatedId = this.validateId_(id)
|
||||
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
|
||||
const draftOrder = await draftOrderRepo.findOne(query)
|
||||
const query = {
|
||||
where: { id: validatedId },
|
||||
}
|
||||
|
||||
if (!draftOrder) {
|
||||
if (relations && relations.length > 0) {
|
||||
query.relations = relations
|
||||
}
|
||||
|
||||
if (select && select.length > 0) {
|
||||
query.select = select
|
||||
}
|
||||
|
||||
const raw = await draftOrderRepo.findOne(query)
|
||||
|
||||
if (!raw) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Draft order with id: ${id} was not found`
|
||||
`Draft order with ${id} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const draftOrder = this.decorateTotals_(raw, totalsToSelect)
|
||||
return draftOrder
|
||||
}
|
||||
|
||||
@@ -132,6 +229,65 @@ class DraftOrderService extends BaseService {
|
||||
return draftOrder
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
selector,
|
||||
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
|
||||
) {
|
||||
const draftOrderRepository = this.manager_.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
|
||||
let q
|
||||
if ("q" in selector) {
|
||||
q = selector.q
|
||||
delete selector.q
|
||||
}
|
||||
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
if (q) {
|
||||
const where = query.where
|
||||
|
||||
delete where.display_id
|
||||
|
||||
query.join = {
|
||||
alias: "draftOrder",
|
||||
innerJoin: {
|
||||
cart: "draftOrder.cart",
|
||||
},
|
||||
}
|
||||
|
||||
query.where = qb => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
qb.where(`cart.email ILIKE :q`, {
|
||||
q: `%${q}%`,
|
||||
}).orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
|
||||
if (select && select.length) {
|
||||
query.select = select
|
||||
}
|
||||
|
||||
if (relations && relations.length) {
|
||||
query.relations = relations
|
||||
}
|
||||
|
||||
const [raw, count] = await draftOrderRepository.findAndCount(query)
|
||||
const draftOrders = raw.map(r => this.decorateTotals_(r, totalsToSelect))
|
||||
|
||||
return [draftOrders, count]
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists draft orders
|
||||
* @param {Object} selector - query object for find
|
||||
@@ -151,31 +307,6 @@ class DraftOrderService extends BaseService {
|
||||
return draftOrderRepo.find(query)
|
||||
}
|
||||
|
||||
async setAddress_(region, address) {
|
||||
const addressRepo = this.manager_.getCustomRepository(
|
||||
this.addressRepository_
|
||||
)
|
||||
|
||||
const regCountries = region.countries.map(({ iso_2 }) => iso_2)
|
||||
|
||||
if (!address.country_code) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Address is missing country code`
|
||||
)
|
||||
}
|
||||
|
||||
if (!regCountries.includes(address.country_code)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Country ${address.country_code} is not in region ${region.name}`
|
||||
)
|
||||
}
|
||||
|
||||
const created = addressRepo.create(adress)
|
||||
return created
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the contents of a line item is covered by the inventory.
|
||||
* To be covered a variant must either not have its inventory managed or it
|
||||
@@ -197,86 +328,13 @@ class DraftOrderService extends BaseService {
|
||||
return this.productVariantService_.canCoverQuantity(variantId, quantity)
|
||||
}
|
||||
|
||||
async addLineItem(doId, lineItem) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const draftOrder = await this.retrieve(doId, {
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
"items",
|
||||
"payment_sessions",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
if (draftOrder.status !== "open") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You are not allowed to add items to a draft order with status awaiting or completed"
|
||||
)
|
||||
}
|
||||
|
||||
let currentItem
|
||||
if (lineItem.should_merge) {
|
||||
currentItem = draftOrder.items.find(line => {
|
||||
if (line.should_merge && line.variant_id === lineItem.variant_id) {
|
||||
return _.isEqual(line.metadata, lineItem.metadata)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// If content matches one of the line items currently in the cart we can
|
||||
// simply update the quantity of the existing line item
|
||||
if (currentItem) {
|
||||
const newQuantity = currentItem.quantity + lineItem.quantity
|
||||
|
||||
// Confirm inventory
|
||||
const hasInventory = await this.confirmInventory_(
|
||||
lineItem.variant_id,
|
||||
newQuantity
|
||||
)
|
||||
|
||||
if (!hasInventory) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Inventory doesn't cover the desired quantity"
|
||||
)
|
||||
}
|
||||
|
||||
await this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.update(currentItem.id, {
|
||||
quantity: newQuantity,
|
||||
})
|
||||
} else {
|
||||
// Confirm inventory
|
||||
const hasInventory = await this.confirmInventory_(
|
||||
lineItem.variant_id,
|
||||
lineItem.quantity
|
||||
)
|
||||
|
||||
if (!hasInventory) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Inventory doesn't cover the desired quantity"
|
||||
)
|
||||
}
|
||||
|
||||
await this.lineItemService_.withTransaction(manager).create({
|
||||
...lineItem,
|
||||
has_shipping: false,
|
||||
cart_id: cartId,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a draft order.
|
||||
* @param {Object} data - data to create draft order from
|
||||
* @param {object} data - data to create draft order from
|
||||
* @param {boolean} shippingRequired - needs shipping flag
|
||||
* @return {Promise<DraftOrder>} the created draft order
|
||||
*/
|
||||
async create(data) {
|
||||
async create(data, shippingRequired = true) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
@@ -289,36 +347,20 @@ class DraftOrderService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const region = await this.regionService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(data.region_id, {
|
||||
relations: ["countries"],
|
||||
})
|
||||
|
||||
if (!data.shipping_address && !data.shipping_address_id) {
|
||||
if (!data.items || !data.items.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Shipping addresss is required to create a draft order`
|
||||
)
|
||||
}
|
||||
|
||||
if (data.shipping_address && !data.shipping_address_id) {
|
||||
data.shipping_address = await this.setAddress_(
|
||||
region,
|
||||
data.shipping_address
|
||||
)
|
||||
}
|
||||
|
||||
if (data.billing_address && !data.billing_address_id) {
|
||||
data.billing_address = await this.setAddress_(
|
||||
region,
|
||||
data.billing_address
|
||||
`Items are required to create a draft order`
|
||||
)
|
||||
}
|
||||
|
||||
const { items, shipping_methods, ...rest } = data
|
||||
|
||||
const draftOrder = draftOrderRepo.create(rest)
|
||||
const createdCart = await this.cartService_
|
||||
.withTransaction(manager)
|
||||
.create({ type: "draft_order", ...rest })
|
||||
|
||||
const draftOrder = draftOrderRepo.create({ cart_id: createdCart.id })
|
||||
const result = await draftOrderRepo.save(draftOrder)
|
||||
|
||||
await this.eventBus_
|
||||
@@ -328,31 +370,140 @@ class DraftOrderService extends BaseService {
|
||||
})
|
||||
|
||||
let shippingMethods = []
|
||||
let profiles = []
|
||||
if (shippingRequired) {
|
||||
for (const method of shipping_methods) {
|
||||
const m = await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(method.option_id, method.data, {
|
||||
draft_order_id: draftOrder.id,
|
||||
cart: createdCart,
|
||||
})
|
||||
|
||||
shippingMethods.push(m)
|
||||
}
|
||||
|
||||
profiles = shippingMethods.map(
|
||||
({ shipping_option }) => shipping_option.profile_id
|
||||
)
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (item.variant_id) {
|
||||
const line = await this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.generate(
|
||||
item.variant_id,
|
||||
cart.region_id,
|
||||
data.region_id,
|
||||
item.quantity,
|
||||
item.metadata
|
||||
)
|
||||
|
||||
const variant = await this.productVariantService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(item.variant_id)
|
||||
const itemProfile = variant.product.profile_id
|
||||
|
||||
let hasShipping = true
|
||||
|
||||
// if shipping is required, ensure items can be shipped
|
||||
if (shippingRequired) {
|
||||
hasShipping = profiles.includes(itemProfile)
|
||||
}
|
||||
|
||||
await this.lineItemService_.withTransaction(manager).create({
|
||||
cart_id: createdCart.id,
|
||||
has_shipping: hasShipping,
|
||||
...line,
|
||||
})
|
||||
} else {
|
||||
// custom line items can be added to a draft order
|
||||
await this.lineItemService_.withTransaction(manager).create({
|
||||
cart_id: createdCart.id,
|
||||
has_shipping: true,
|
||||
title: item.title || "Custom item",
|
||||
allow_discounts: false,
|
||||
unit_price: item.unit_price || 0,
|
||||
quantity: item.quantity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
async registerSystemPayment(doId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const draftOrder = await this.retrieve(doId)
|
||||
|
||||
const draftOrderCart = await this.cartService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
select: ["total"],
|
||||
relations: ["discounts", "shipping_methods", "region", "items"],
|
||||
})
|
||||
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
|
||||
const paymentRepo = manager.getCustomRepository(this.paymentRepository_)
|
||||
|
||||
const created = paymentRepo.create({
|
||||
provider_id: "system",
|
||||
amount: draftOrderCart.total,
|
||||
currency_code: draftOrderCart.region.currency_code,
|
||||
data: {},
|
||||
})
|
||||
|
||||
const toCreate = {
|
||||
payment_status: "awaiting",
|
||||
discounts: draftOrderCart.discounts,
|
||||
region_id: draftOrderCart.region_id,
|
||||
email: draftOrderCart.email,
|
||||
customer_id: draftOrderCart.customer_id,
|
||||
draft_order_id: draftOrder.id,
|
||||
cart_id: draftOrderCart.id,
|
||||
tax_rate: draftOrderCart.region.tax_rate,
|
||||
currency_code: draftOrderCart.region.currency_code,
|
||||
metadata: draftOrderCart.metadata || {},
|
||||
payments: [created],
|
||||
}
|
||||
|
||||
if (draftOrderCart.shipping_address_id) {
|
||||
toCreate.shipping_address_id = draftOrderCart.shipping_address_id
|
||||
}
|
||||
|
||||
if (draftOrderCart.billing_address_id) {
|
||||
toCreate.billing_address_id = draftOrderCart.billing_address_id
|
||||
}
|
||||
|
||||
if (draftOrderCart.shipping_methods) {
|
||||
toCreate.shipping_methods = draftOrderCart.shipping_methods
|
||||
}
|
||||
|
||||
const o = orderRepo.create(toCreate)
|
||||
|
||||
const result = await orderRepo.save(o)
|
||||
|
||||
if (draftOrderCart.shipping_methods) {
|
||||
for (const method of draftOrderCart.shipping_methods) {
|
||||
await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.updateShippingMethod(method.id, { order_id: result.id })
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of draftOrderCart.items) {
|
||||
await this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.create({ draft_order_id: draftOrder.id, ...line })
|
||||
.update(item.id, { order_id: result.id })
|
||||
}
|
||||
|
||||
draftOrder.status = "completed"
|
||||
await draftOrderRepo.save(draftOrder)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
@@ -484,7 +484,6 @@ class OrderService extends BaseService {
|
||||
payment_status: "awaiting",
|
||||
discounts: cart.discounts,
|
||||
gift_cards: cart.gift_cards,
|
||||
payment_status: "awaiting",
|
||||
shipping_methods: cart.shipping_methods,
|
||||
shipping_address_id: cart.shipping_address_id,
|
||||
billing_address_id: cart.billing_address_id,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
class SystemProviderService extends BaseService {
|
||||
static identifier = "system"
|
||||
|
||||
constructor({}) {
|
||||
super()
|
||||
}
|
||||
|
||||
async createPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPaymentData(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(_) {
|
||||
return { data: {}, status: "authorized" }
|
||||
}
|
||||
|
||||
async updatePaymentData(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async deletePayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(_) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(_) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default SystemProviderService
|
||||
Reference in new issue
Block a user