feat: Cart Customer link + create cart with customer (#6426)
This commit is contained in:
1 parent
f6d38163bb
commit
5db3ec09e2
16 files changed
+485
-110
No files matched your search
@@ -1,6 +1,11 @@
|
|||||||
|
import {
|
||||||
|
createCartWorkflow,
|
||||||
|
findOrCreateCustomerStepId,
|
||||||
|
} from "@medusajs/core-flows"
|
||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
|
ICustomerModuleService,
|
||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
ISalesChannelModuleService,
|
ISalesChannelModuleService,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
@@ -10,18 +15,22 @@ import { useApi } from "../../../../environment-helpers/use-api"
|
|||||||
import { getContainer } from "../../../../environment-helpers/use-container"
|
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
|
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
|
||||||
|
|
||||||
jest.setTimeout(50000)
|
jest.setTimeout(50000)
|
||||||
|
|
||||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||||
|
|
||||||
describe("POST /store/carts", () => {
|
describe("Store Carts API", () => {
|
||||||
let dbConnection
|
let dbConnection
|
||||||
let appContainer
|
let appContainer
|
||||||
let shutdownServer
|
let shutdownServer
|
||||||
let cartModuleService: ICartModuleService
|
let cartModuleService: ICartModuleService
|
||||||
let regionModuleService: IRegionModuleService
|
let regionModuleService: IRegionModuleService
|
||||||
let scModuleService: ISalesChannelModuleService
|
let scModuleService: ISalesChannelModuleService
|
||||||
|
let customerModule: ICustomerModuleService
|
||||||
|
|
||||||
|
let defaultRegion
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||||
@@ -31,6 +40,7 @@ describe("POST /store/carts", () => {
|
|||||||
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||||
regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION)
|
regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||||
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||||
|
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
@@ -41,9 +51,14 @@ describe("POST /store/carts", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await regionModuleService.createDefaultCountriesAndCurrencies()
|
await regionModuleService.createDefaultCountriesAndCurrencies()
|
||||||
|
|
||||||
|
// Here, so we don't have to create a region for each test
|
||||||
|
defaultRegion = await regionModuleService.create({
|
||||||
|
name: "Default Region",
|
||||||
|
currency_code: "dkk",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -51,6 +66,239 @@ describe("POST /store/carts", () => {
|
|||||||
await db.teardown()
|
await db.teardown()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("POST /store/carts", () => {
|
||||||
|
it("should create a cart", async () => {
|
||||||
|
const region = await regionModuleService.create({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
const salesChannel = await scModuleService.create({
|
||||||
|
name: "Webshop",
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const created = await api.post(`/store/carts`, {
|
||||||
|
email: "tony@stark.com",
|
||||||
|
currency_code: "usd",
|
||||||
|
region_id: region.id,
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(created.status).toEqual(200)
|
||||||
|
expect(created.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: created.data.cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark.com",
|
||||||
|
region: expect.objectContaining({
|
||||||
|
id: region.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
}),
|
||||||
|
sales_channel_id: salesChannel.id,
|
||||||
|
customer: expect.objectContaining({
|
||||||
|
email: "tony@stark.com",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create cart with customer from email", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
const created = await api.post(`/store/carts`, {
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(created.status).toEqual(200)
|
||||||
|
expect(created.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: created.data.cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
customer: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create cart with any region", async () => {
|
||||||
|
await regionModuleService.create({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
const response = await api.post(`/store/carts`, {
|
||||||
|
email: "tony@stark.com",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: response.data.cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark.com",
|
||||||
|
region: expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create cart with region currency code", async () => {
|
||||||
|
const region = await regionModuleService.create({
|
||||||
|
name: "US",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
const response = await api.post(`/store/carts`, {
|
||||||
|
email: "tony@stark.com",
|
||||||
|
region_id: region.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: response.data.cart.id,
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark.com",
|
||||||
|
region: expect.objectContaining({
|
||||||
|
id: region.id,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create cart with logged-in customer", async () => {
|
||||||
|
const { customer, jwt } = await createAuthenticatedCustomer(appContainer)
|
||||||
|
|
||||||
|
const api = useApi() as any
|
||||||
|
const response = await api.post(
|
||||||
|
`/store/carts`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${jwt}` },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toEqual(200)
|
||||||
|
expect(response.data.cart).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: response.data.cart.id,
|
||||||
|
currency_code: "dkk",
|
||||||
|
email: customer.email,
|
||||||
|
customer: expect.objectContaining({
|
||||||
|
id: customer.id,
|
||||||
|
email: customer.email,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should throw when no regions exist", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
await regionModuleService.delete(defaultRegion.id)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
api.post(`/store/carts`, {
|
||||||
|
email: "tony@stark.com",
|
||||||
|
currency_code: "usd",
|
||||||
|
})
|
||||||
|
).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should respond 400 bad request on unknown props", async () => {
|
||||||
|
const api = useApi() as any
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
api.post(`/store/carts`, {
|
||||||
|
foo: "bar",
|
||||||
|
})
|
||||||
|
).rejects.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("compensation", () => {
|
||||||
|
it("should delete created customer if cart-creation fails", async () => {
|
||||||
|
expect.assertions(2)
|
||||||
|
const workflow = createCartWorkflow(appContainer)
|
||||||
|
|
||||||
|
workflow.appendAction("throw", findOrCreateCustomerStepId, {
|
||||||
|
invoke: async function failStep() {
|
||||||
|
throw new Error(`Failed to create cart`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { errors } = await workflow.run({
|
||||||
|
input: {
|
||||||
|
currency_code: "usd",
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
action: "throw",
|
||||||
|
handlerType: "invoke",
|
||||||
|
error: new Error(`Failed to create cart`),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const customers = await customerModule.list({
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(customers).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not delete existing customer if cart-creation fails", async () => {
|
||||||
|
expect.assertions(2)
|
||||||
|
const workflow = createCartWorkflow(appContainer)
|
||||||
|
|
||||||
|
workflow.appendAction("throw", findOrCreateCustomerStepId, {
|
||||||
|
invoke: async function failStep() {
|
||||||
|
throw new Error(`Failed to create cart`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const customer = await customerModule.create({
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
const { errors } = await workflow.run({
|
||||||
|
input: {
|
||||||
|
currency_code: "usd",
|
||||||
|
customer_id: customer.id,
|
||||||
|
},
|
||||||
|
throwOnError: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
action: "throw",
|
||||||
|
handlerType: "invoke",
|
||||||
|
error: new Error(`Failed to create cart`),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const customers = await customerModule.list({
|
||||||
|
email: "tony@stark-industries.com",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(customers).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("GET /store/carts/:id", () => {
|
||||||
it("should create and update a cart", async () => {
|
it("should create and update a cart", async () => {
|
||||||
const region = await regionModuleService.create({
|
const region = await regionModuleService.create({
|
||||||
name: "US",
|
name: "US",
|
||||||
@@ -97,67 +345,9 @@ describe("POST /store/carts", () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should create cart with any region", async () => {
|
|
||||||
await regionModuleService.create({
|
|
||||||
name: "US",
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
|
|
||||||
const api = useApi() as any
|
|
||||||
const response = await api.post(`/store/carts`, {
|
|
||||||
email: "tony@stark.com",
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.cart).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: response.data.cart.id,
|
|
||||||
currency_code: "usd",
|
|
||||||
email: "tony@stark.com",
|
|
||||||
region: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should create cart with region currency code", async () => {
|
|
||||||
await regionModuleService.create({
|
|
||||||
name: "US",
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
|
|
||||||
const api = useApi() as any
|
|
||||||
const response = await api.post(`/store/carts`, {
|
|
||||||
email: "tony@stark.com",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
|
||||||
expect(response.data.cart).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: response.data.cart.id,
|
|
||||||
currency_code: "usd",
|
|
||||||
email: "tony@stark.com",
|
|
||||||
region: expect.objectContaining({
|
|
||||||
id: expect.any(String),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should throw when no regions exist", async () => {
|
|
||||||
const api = useApi() as any
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
api.post(`/store/carts`, {
|
|
||||||
email: "tony@stark.com",
|
|
||||||
currency_code: "usd",
|
|
||||||
})
|
|
||||||
).rejects.toThrow()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("GET /store/carts", () => {
|
||||||
it("should get cart", async () => {
|
it("should get cart", async () => {
|
||||||
const region = await regionModuleService.create({
|
const region = await regionModuleService.create({
|
||||||
name: "US",
|
name: "US",
|
||||||
@@ -205,3 +395,4 @@ describe("POST /store/carts", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import {
|
import {
|
||||||
ICartModuleService,
|
ICartModuleService,
|
||||||
|
ICustomerModuleService,
|
||||||
IRegionModuleService,
|
IRegionModuleService,
|
||||||
ISalesChannelModuleService,
|
ISalesChannelModuleService,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
@@ -18,9 +19,10 @@ describe("Cart links", () => {
|
|||||||
let appContainer
|
let appContainer
|
||||||
let shutdownServer
|
let shutdownServer
|
||||||
let cartModuleService: ICartModuleService
|
let cartModuleService: ICartModuleService
|
||||||
|
let regionModule: IRegionModuleService
|
||||||
|
let customerModule: ICustomerModuleService
|
||||||
let scModuleService: ISalesChannelModuleService
|
let scModuleService: ISalesChannelModuleService
|
||||||
let remoteQuery
|
let remoteQuery
|
||||||
let regionModule: IRegionModuleService
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||||
@@ -28,6 +30,8 @@ describe("Cart links", () => {
|
|||||||
shutdownServer = await startBootstrapApp({ cwd, env })
|
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||||
appContainer = getContainer()
|
appContainer = getContainer()
|
||||||
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||||
|
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||||
|
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||||
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||||
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||||
remoteQuery = appContainer.resolve("remoteQuery")
|
remoteQuery = appContainer.resolve("remoteQuery")
|
||||||
@@ -49,12 +53,16 @@ describe("Cart links", () => {
|
|||||||
await db.teardown()
|
await db.teardown()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should query carts, sales channels, regions with remote query", async () => {
|
it("should query carts, sales channels, customers, regions with remote query", async () => {
|
||||||
const region = await regionModule.create({
|
const region = await regionModule.create({
|
||||||
name: "Region",
|
name: "Region",
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const customer = await customerModule.create({
|
||||||
|
email: "tony@stark.com",
|
||||||
|
})
|
||||||
|
|
||||||
const salesChannel = await scModuleService.create({
|
const salesChannel = await scModuleService.create({
|
||||||
name: "Webshop",
|
name: "Webshop",
|
||||||
})
|
})
|
||||||
@@ -64,15 +72,19 @@ describe("Cart links", () => {
|
|||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
region_id: region.id,
|
region_id: region.id,
|
||||||
sales_channel_id: salesChannel.id,
|
sales_channel_id: salesChannel.id,
|
||||||
|
customer_id: customer.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const carts = await remoteQuery({
|
const carts = await remoteQuery({
|
||||||
cart: {
|
cart: {
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
sales_channel: {
|
region: {
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
},
|
},
|
||||||
region: {
|
customer: {
|
||||||
|
fields: ["id"],
|
||||||
|
},
|
||||||
|
sales_channel: {
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -87,6 +99,15 @@ describe("Cart links", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const customers = await remoteQuery({
|
||||||
|
customer: {
|
||||||
|
fields: ["id"],
|
||||||
|
carts: {
|
||||||
|
fields: ["id"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const regions = await remoteQuery({
|
const regions = await remoteQuery({
|
||||||
region: {
|
region: {
|
||||||
fields: ["id"],
|
fields: ["id"],
|
||||||
@@ -100,6 +121,7 @@ describe("Cart links", () => {
|
|||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: cart.id,
|
id: cart.id,
|
||||||
|
customer: expect.objectContaining({ id: customer.id }),
|
||||||
sales_channel: expect.objectContaining({ id: salesChannel.id }),
|
sales_channel: expect.objectContaining({ id: salesChannel.id }),
|
||||||
region: expect.objectContaining({ id: region.id }),
|
region: expect.objectContaining({ id: region.id }),
|
||||||
}),
|
}),
|
||||||
@@ -117,6 +139,17 @@ describe("Cart links", () => {
|
|||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(customers).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: customer.id,
|
||||||
|
carts: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ id: cart.id }),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
expect(regions).toEqual(
|
expect(regions).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
|
||||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
|
||||||
import {
|
import {
|
||||||
CreateInventoryItemActions,
|
CreateInventoryItemActions,
|
||||||
createInventoryItems,
|
createInventoryItems,
|
||||||
} from "@medusajs/core-flows"
|
} from "@medusajs/core-flows"
|
||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||||
|
|
||||||
import { pipe } from "@medusajs/workflows-sdk"
|
import { pipe } from "@medusajs/workflows-sdk"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import { CustomerDTO, ICustomerModuleService } from "@medusajs/types"
|
||||||
|
import { validateEmail } from "@medusajs/utils"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
interface StepInput {
|
||||||
|
customerId?: string
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StepOutput {
|
||||||
|
customer?: CustomerDTO
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StepCompensateInput {
|
||||||
|
customer?: CustomerDTO
|
||||||
|
customerWasCreated: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findOrCreateCustomerStepId = "find-or-create-customer"
|
||||||
|
export const findOrCreateCustomerStep = createStep(
|
||||||
|
findOrCreateCustomerStepId,
|
||||||
|
async (data: StepInput, { container }) => {
|
||||||
|
const service = container.resolve<ICustomerModuleService>(
|
||||||
|
ModuleRegistrationName.CUSTOMER
|
||||||
|
)
|
||||||
|
|
||||||
|
const customerData: StepOutput = {}
|
||||||
|
let customerWasCreated = false
|
||||||
|
|
||||||
|
if (data.customerId) {
|
||||||
|
const customer = await service.retrieve(data.customerId)
|
||||||
|
customerData.customer = customer
|
||||||
|
customerData.email = customer.email
|
||||||
|
|
||||||
|
return new StepResponse(customerData, {
|
||||||
|
customerWasCreated,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.email) {
|
||||||
|
const validatedEmail = validateEmail(data.email)
|
||||||
|
|
||||||
|
let [customer] = await service.list({
|
||||||
|
email: validatedEmail,
|
||||||
|
has_account: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
customer = await service.create({ email: validatedEmail })
|
||||||
|
customerWasCreated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
customerData.customer = customer
|
||||||
|
customerData.email = customer.email
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepResponse(customerData, {
|
||||||
|
customer: customerData.customer,
|
||||||
|
customerWasCreated,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async (compData, { container }) => {
|
||||||
|
const { customer, customerWasCreated } = compData as StepCompensateInput
|
||||||
|
|
||||||
|
if (!customerWasCreated || !customer?.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<ICustomerModuleService>(
|
||||||
|
ModuleRegistrationName.CUSTOMER
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.delete(customer.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./create-carts"
|
export * from "./create-carts"
|
||||||
export * from "./find-one-or-any-region"
|
export * from "./find-one-or-any-region"
|
||||||
|
export * from "./find-or-create-customer"
|
||||||
export * from "./update-carts"
|
export * from "./update-carts"
|
||||||
|
|
||||||
@@ -4,28 +4,44 @@ import {
|
|||||||
createWorkflow,
|
createWorkflow,
|
||||||
transform,
|
transform,
|
||||||
} from "@medusajs/workflows-sdk"
|
} from "@medusajs/workflows-sdk"
|
||||||
import { createCartsStep, findOneOrAnyRegionStep } from "../steps"
|
import {
|
||||||
|
createCartsStep,
|
||||||
|
findOneOrAnyRegionStep,
|
||||||
|
findOrCreateCustomerStep,
|
||||||
|
} from "../steps"
|
||||||
|
|
||||||
export const createCartWorkflowId = "create-cart"
|
export const createCartWorkflowId = "create-cart"
|
||||||
export const createCartWorkflow = createWorkflow(
|
export const createCartWorkflow = createWorkflow(
|
||||||
createCartWorkflowId,
|
createCartWorkflowId,
|
||||||
(
|
(input: WorkflowData<CreateCartWorkflowInputDTO>): WorkflowData<CartDTO> => {
|
||||||
input: WorkflowData<CreateCartWorkflowInputDTO>
|
|
||||||
): WorkflowData<CartDTO[]> => {
|
|
||||||
const region = findOneOrAnyRegionStep({
|
const region = findOneOrAnyRegionStep({
|
||||||
regionId: input.region_id,
|
regionId: input.region_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const cartInput = transform({ input, region }, (data) => {
|
const customerData = findOrCreateCustomerStep({
|
||||||
return {
|
customerId: input.customer_id,
|
||||||
|
email: input.email,
|
||||||
|
})
|
||||||
|
|
||||||
|
const cartInput = transform({ input, region, customerData }, (data) => {
|
||||||
|
const data_ = {
|
||||||
...data.input,
|
...data.input,
|
||||||
currency_code: data?.input.currency_code || data.region.currency_code,
|
currency_code: data.input.currency_code ?? data.region.currency_code,
|
||||||
region_id: data.region.id,
|
region_id: data.region.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.customerData.customer?.id) {
|
||||||
|
data_.customer_id = data.customerData.customer.id
|
||||||
|
data_.email = data.input?.email ?? data.customerData.customer.email
|
||||||
|
}
|
||||||
|
|
||||||
|
return data_
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: Add line items
|
||||||
|
|
||||||
const cart = createCartsStep([cartInput])
|
const cart = createCartsStep([cartInput])
|
||||||
|
|
||||||
return cart
|
return cart[0]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
|
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||||
|
|
||||||
|
export const CartCustomer: ModuleJoinerConfig = {
|
||||||
|
isLink: true,
|
||||||
|
isReadOnlyLink: true,
|
||||||
|
extends: [
|
||||||
|
{
|
||||||
|
serviceName: Modules.CART,
|
||||||
|
relationship: {
|
||||||
|
serviceName: Modules.CUSTOMER,
|
||||||
|
primaryKey: "id",
|
||||||
|
foreignKey: "customer_id",
|
||||||
|
alias: "customer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: Modules.CUSTOMER,
|
||||||
|
relationship: {
|
||||||
|
serviceName: Modules.CART,
|
||||||
|
primaryKey: "customer_id",
|
||||||
|
foreignKey: "id",
|
||||||
|
alias: "carts",
|
||||||
|
isList: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export * from "./cart-customer"
|
||||||
export * from "./cart-region"
|
export * from "./cart-region"
|
||||||
export * from "./cart-sales-channel"
|
export * from "./cart-sales-channel"
|
||||||
export * from "./inventory-level-stock-location"
|
export * from "./inventory-level-stock-location"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
StoreGetCartsCartParams,
|
StoreGetCartsCartParams,
|
||||||
@@ -8,6 +9,15 @@ import {
|
|||||||
} from "./validators"
|
} from "./validators"
|
||||||
|
|
||||||
export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [
|
export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [
|
||||||
|
{
|
||||||
|
method: "ALL",
|
||||||
|
matcher: "/store/carts*",
|
||||||
|
middlewares: [
|
||||||
|
authenticate("store", ["session", "bearer"], {
|
||||||
|
allowUnauthenticated: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: ["GET"],
|
method: ["GET"],
|
||||||
matcher: "/store/carts/:id",
|
matcher: "/store/carts/:id",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export const defaultStoreCartFields = [
|
|||||||
"items.title",
|
"items.title",
|
||||||
"items.quantity",
|
"items.quantity",
|
||||||
"items.unit_price",
|
"items.unit_price",
|
||||||
|
"customer.id",
|
||||||
|
"customer.email",
|
||||||
"shipping_address.id",
|
"shipping_address.id",
|
||||||
"shipping_address.first_name",
|
"shipping_address.first_name",
|
||||||
"shipping_address.last_name",
|
"shipping_address.last_name",
|
||||||
@@ -39,6 +41,7 @@ export const defaultStoreCartFields = [
|
|||||||
export const defaultStoreCartRelations = [
|
export const defaultStoreCartRelations = [
|
||||||
"items",
|
"items",
|
||||||
"region",
|
"region",
|
||||||
|
"customer",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
@@ -47,6 +50,7 @@ export const defaultStoreCartRelations = [
|
|||||||
export const allowedRelations = [
|
export const allowedRelations = [
|
||||||
"items",
|
"items",
|
||||||
"region",
|
"region",
|
||||||
|
"customer",
|
||||||
"shipping_address",
|
"shipping_address",
|
||||||
"billing_address",
|
"billing_address",
|
||||||
"shipping_methods",
|
"shipping_methods",
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import { createCartWorkflow } from "@medusajs/core-flows"
|
import { createCartWorkflow } from "@medusajs/core-flows"
|
||||||
import { CreateCartDTO } from "@medusajs/types"
|
import { CreateCartWorkflowInputDTO } from "@medusajs/types"
|
||||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||||
import { defaultStoreCartFields } from "../carts/query-config"
|
import { defaultStoreCartFields } from "../carts/query-config"
|
||||||
|
import { StorePostCartReq } from "./validators"
|
||||||
|
|
||||||
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||||
const workflow = createCartWorkflow(req.scope)
|
const input = req.validatedBody as StorePostCartReq
|
||||||
|
const workflowInput: CreateCartWorkflowInputDTO = {
|
||||||
|
...input,
|
||||||
|
}
|
||||||
|
|
||||||
const { result, errors } = await workflow.run({
|
// If the customer is logged in, we auto-assign them to the cart
|
||||||
input: req.validatedBody as CreateCartDTO,
|
if (req.auth_user?.app_metadata?.customer_id) {
|
||||||
|
workflowInput.customer_id = req.auth_user!.app_metadata?.customer_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const { result, errors } = await createCartWorkflow(req.scope).run({
|
||||||
|
input: workflowInput,
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -18,7 +27,7 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
|||||||
|
|
||||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||||
|
|
||||||
const variables = { id: result[0].id }
|
const variables = { id: result.id }
|
||||||
|
|
||||||
const query = remoteQueryObjectFromString({
|
const query = remoteQueryObjectFromString({
|
||||||
entryPoint: "cart",
|
entryPoint: "cart",
|
||||||
|
|||||||
@@ -29,15 +29,10 @@ export class StorePostCartReq {
|
|||||||
@IsString()
|
@IsString()
|
||||||
region_id?: string
|
region_id?: string
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
customer_id?: string
|
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
email?: string
|
email?: string
|
||||||
|
|
||||||
// TODO: Remove in favor of using region currencies, as in the core
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
currency_code?: string
|
currency_code?: string
|
||||||
@@ -74,10 +69,6 @@ export class StorePostCartsCartReq {
|
|||||||
@IsType([AddressPayload, String])
|
@IsType([AddressPayload, String])
|
||||||
shipping_address?: AddressPayload | string
|
shipping_address?: AddressPayload | string
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
customer_id?: string
|
|
||||||
|
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
sales_channel_id?: string
|
sales_channel_id?: string
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
|
|
||||||
|
import { Transform, Type } from "class-transformer"
|
||||||
|
import {
|
||||||
|
IsDate,
|
||||||
|
IsNumber,
|
||||||
|
IsObject,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
} from "class-validator"
|
||||||
import {
|
import {
|
||||||
FindManyOptions,
|
FindManyOptions,
|
||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
@@ -8,20 +16,12 @@ import {
|
|||||||
FindOptionsWhere,
|
FindOptionsWhere,
|
||||||
OrderByCondition,
|
OrderByCondition,
|
||||||
} from "typeorm"
|
} from "typeorm"
|
||||||
import {
|
|
||||||
IsDate,
|
|
||||||
IsNumber,
|
|
||||||
IsObject,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
} from "class-validator"
|
|
||||||
import { Transform, Type } from "class-transformer"
|
|
||||||
|
|
||||||
import { BaseEntity } from "../interfaces"
|
|
||||||
import { ClassConstructor } from "./global"
|
|
||||||
import { FindOptionsOrder } from "typeorm/find-options/FindOptionsOrder"
|
import { FindOptionsOrder } from "typeorm/find-options/FindOptionsOrder"
|
||||||
import { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations"
|
import { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations"
|
||||||
|
import { BaseEntity } from "../interfaces"
|
||||||
import { transformDate } from "../utils/validators/date-transform"
|
import { transformDate } from "../utils/validators/date-transform"
|
||||||
|
import { ClassConstructor } from "./global"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility type used to remove some optional attributes (coming from K) from a type T
|
* Utility type used to remove some optional attributes (coming from K) from a type T
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export interface CreateCartDTO {
|
|||||||
currency_code: string
|
currency_code: string
|
||||||
shipping_address_id?: string
|
shipping_address_id?: string
|
||||||
billing_address_id?: string
|
billing_address_id?: string
|
||||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
shipping_address?: CreateAddressDTO | string
|
||||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
billing_address?: CreateAddressDTO | string
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
items?: CreateLineItemDTO[]
|
items?: CreateLineItemDTO[]
|
||||||
|
|||||||
@@ -1,20 +1,33 @@
|
|||||||
import {
|
export interface CreateCartLineItemDTO {
|
||||||
CreateAddressDTO,
|
variant_id: string
|
||||||
CreateLineItemDTO,
|
quantity: number
|
||||||
UpdateAddressDTO,
|
}
|
||||||
} from "./mutations"
|
|
||||||
|
export interface CreateCartAddressDTO {
|
||||||
|
first_name?: string
|
||||||
|
last_name?: string
|
||||||
|
phone?: string
|
||||||
|
company?: string
|
||||||
|
address_1?: string
|
||||||
|
address_2?: string
|
||||||
|
city?: string
|
||||||
|
country_code?: string
|
||||||
|
province?: string
|
||||||
|
postal_code?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateCartWorkflowInputDTO {
|
export interface CreateCartWorkflowInputDTO {
|
||||||
region_id?: string
|
region_id?: string
|
||||||
customer_id?: string
|
customer_id?: string
|
||||||
sales_channel_id?: string
|
sales_channel_id?: string
|
||||||
email?: string
|
email?: string
|
||||||
currency_code: string
|
currency_code?: string
|
||||||
shipping_address_id?: string
|
shipping_address_id?: string
|
||||||
billing_address_id?: string
|
billing_address_id?: string
|
||||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
shipping_address?: CreateCartAddressDTO | string
|
||||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
billing_address?: CreateCartAddressDTO | string
|
||||||
metadata?: Record<string, unknown>
|
metadata?: Record<string, unknown>
|
||||||
|
|
||||||
items?: CreateLineItemDTO[]
|
items?: CreateCartLineItemDTO[]
|
||||||
}
|
}
|
||||||
@@ -78,6 +78,7 @@ export interface FilterableCustomerProps
|
|||||||
company_name?: string | string[] | OperatorMap<string> | null
|
company_name?: string | string[] | OperatorMap<string> | null
|
||||||
first_name?: string | string[] | OperatorMap<string> | null
|
first_name?: string | string[] | OperatorMap<string> | null
|
||||||
last_name?: string | string[] | OperatorMap<string> | null
|
last_name?: string | string[] | OperatorMap<string> | null
|
||||||
|
has_account?: boolean | OperatorMap<boolean>
|
||||||
created_by?: string | string[] | null
|
created_by?: string | string[] | null
|
||||||
created_at?: OperatorMap<string>
|
created_at?: OperatorMap<string>
|
||||||
updated_at?: OperatorMap<string>
|
updated_at?: OperatorMap<string>
|
||||||
|
|||||||
Reference in new issue
Block a user