feat: Cart Customer link + create cart with customer (#6426)
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import {
|
||||
createCartWorkflow,
|
||||
findOrCreateCustomerStepId,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
ICartModuleService,
|
||||
ICustomerModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
} from "@medusajs/types"
|
||||
@@ -10,18 +15,22 @@ import { useApi } from "../../../../environment-helpers/use-api"
|
||||
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
const env = { MEDUSA_FF_MEDUSA_V2: true }
|
||||
|
||||
describe("POST /store/carts", () => {
|
||||
describe("Store Carts API", () => {
|
||||
let dbConnection
|
||||
let appContainer
|
||||
let shutdownServer
|
||||
let cartModuleService: ICartModuleService
|
||||
let regionModuleService: IRegionModuleService
|
||||
let scModuleService: ISalesChannelModuleService
|
||||
let customerModule: ICustomerModuleService
|
||||
|
||||
let defaultRegion
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
@@ -31,6 +40,7 @@ describe("POST /store/carts", () => {
|
||||
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||
regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -41,9 +51,14 @@ describe("POST /store/carts", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
// @ts-ignore
|
||||
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 () => {
|
||||
@@ -51,157 +66,333 @@ describe("POST /store/carts", () => {
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should create and update 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,
|
||||
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",
|
||||
region: expect.objectContaining({
|
||||
id: region.id,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
currency_code: "usd",
|
||||
region_id: region.id,
|
||||
sales_channel_id: salesChannel.id,
|
||||
})
|
||||
)
|
||||
|
||||
const updated = await api.post(`/store/carts/${created.data.cart.id}`, {
|
||||
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.com",
|
||||
region: expect.objectContaining({
|
||||
id: region.id,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
sales_channel_id: salesChannel.id,
|
||||
customer: expect.objectContaining({
|
||||
email: "tony@stark.com",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: updated.data.cart.id,
|
||||
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",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create cart with any region", async () => {
|
||||
await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
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",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
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,
|
||||
it("should create cart with any region", async () => {
|
||||
await regionModuleService.create({
|
||||
name: "US",
|
||||
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`, {
|
||||
const api = useApi() as any
|
||||
const response = await api.post(`/store/carts`, {
|
||||
email: "tony@stark.com",
|
||||
currency_code: "usd",
|
||||
})
|
||||
).rejects.toThrow()
|
||||
})
|
||||
|
||||
it("should get cart", async () => {
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
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),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const salesChannel = await scModuleService.create({
|
||||
name: "Webshop",
|
||||
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,
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
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`,
|
||||
{},
|
||||
{
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
title: "Test item",
|
||||
},
|
||||
],
|
||||
region_id: region.id,
|
||||
sales_channel_id: salesChannel.id,
|
||||
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,
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const api = useApi() as any
|
||||
const response = await api.get(`/store/carts/${cart.id}`)
|
||||
it("should throw when no regions exist", async () => {
|
||||
const api = useApi() as any
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
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 () => {
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
)
|
||||
|
||||
const updated = await api.post(`/store/carts/${created.data.cart.id}`, {
|
||||
email: "tony@stark-industries.com",
|
||||
})
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: updated.data.cart.id,
|
||||
currency_code: "usd",
|
||||
email: "tony@stark-industries.com",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /store/carts", () => {
|
||||
it("should get cart", async () => {
|
||||
const region = await regionModuleService.create({
|
||||
name: "US",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const salesChannel = await scModuleService.create({
|
||||
name: "Webshop",
|
||||
})
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
{
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
title: "Test item",
|
||||
}),
|
||||
]),
|
||||
region: expect.objectContaining({
|
||||
id: region.id,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
},
|
||||
],
|
||||
region_id: region.id,
|
||||
sales_channel_id: salesChannel.id,
|
||||
})
|
||||
)
|
||||
|
||||
const api = useApi() as any
|
||||
const response = await api.get(`/store/carts/${cart.id}`)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart).toEqual(
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
currency_code: "usd",
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
title: "Test item",
|
||||
}),
|
||||
]),
|
||||
region: expect.objectContaining({
|
||||
id: region.id,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
sales_channel_id: salesChannel.id,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
ICartModuleService,
|
||||
ICustomerModuleService,
|
||||
IRegionModuleService,
|
||||
ISalesChannelModuleService,
|
||||
} from "@medusajs/types"
|
||||
@@ -18,9 +19,10 @@ describe("Cart links", () => {
|
||||
let appContainer
|
||||
let shutdownServer
|
||||
let cartModuleService: ICartModuleService
|
||||
let regionModule: IRegionModuleService
|
||||
let customerModule: ICustomerModuleService
|
||||
let scModuleService: ISalesChannelModuleService
|
||||
let remoteQuery
|
||||
let regionModule: IRegionModuleService
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
@@ -28,6 +30,8 @@ describe("Cart links", () => {
|
||||
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||
appContainer = getContainer()
|
||||
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
|
||||
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
regionModule = appContainer.resolve(ModuleRegistrationName.REGION)
|
||||
remoteQuery = appContainer.resolve("remoteQuery")
|
||||
@@ -49,12 +53,16 @@ describe("Cart links", () => {
|
||||
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({
|
||||
name: "Region",
|
||||
currency_code: "usd",
|
||||
})
|
||||
|
||||
const customer = await customerModule.create({
|
||||
email: "tony@stark.com",
|
||||
})
|
||||
|
||||
const salesChannel = await scModuleService.create({
|
||||
name: "Webshop",
|
||||
})
|
||||
@@ -64,15 +72,19 @@ describe("Cart links", () => {
|
||||
currency_code: "usd",
|
||||
region_id: region.id,
|
||||
sales_channel_id: salesChannel.id,
|
||||
customer_id: customer.id,
|
||||
})
|
||||
|
||||
const carts = await remoteQuery({
|
||||
cart: {
|
||||
fields: ["id"],
|
||||
sales_channel: {
|
||||
region: {
|
||||
fields: ["id"],
|
||||
},
|
||||
region: {
|
||||
customer: {
|
||||
fields: ["id"],
|
||||
},
|
||||
sales_channel: {
|
||||
fields: ["id"],
|
||||
},
|
||||
},
|
||||
@@ -87,6 +99,15 @@ describe("Cart links", () => {
|
||||
},
|
||||
})
|
||||
|
||||
const customers = await remoteQuery({
|
||||
customer: {
|
||||
fields: ["id"],
|
||||
carts: {
|
||||
fields: ["id"],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const regions = await remoteQuery({
|
||||
region: {
|
||||
fields: ["id"],
|
||||
@@ -100,6 +121,7 @@ describe("Cart links", () => {
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: cart.id,
|
||||
customer: expect.objectContaining({ id: customer.id }),
|
||||
sales_channel: expect.objectContaining({ id: salesChannel.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.arrayContaining([
|
||||
expect.objectContaining({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
import {
|
||||
CreateInventoryItemActions,
|
||||
createInventoryItems,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
|
||||
import { pipe } from "@medusajs/workflows-sdk"
|
||||
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 "./find-one-or-any-region"
|
||||
export * from "./find-or-create-customer"
|
||||
export * from "./update-carts"
|
||||
|
||||
|
||||
@@ -4,28 +4,44 @@ import {
|
||||
createWorkflow,
|
||||
transform,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
import { createCartsStep, findOneOrAnyRegionStep } from "../steps"
|
||||
import {
|
||||
createCartsStep,
|
||||
findOneOrAnyRegionStep,
|
||||
findOrCreateCustomerStep,
|
||||
} from "../steps"
|
||||
|
||||
export const createCartWorkflowId = "create-cart"
|
||||
export const createCartWorkflow = createWorkflow(
|
||||
createCartWorkflowId,
|
||||
(
|
||||
input: WorkflowData<CreateCartWorkflowInputDTO>
|
||||
): WorkflowData<CartDTO[]> => {
|
||||
(input: WorkflowData<CreateCartWorkflowInputDTO>): WorkflowData<CartDTO> => {
|
||||
const region = findOneOrAnyRegionStep({
|
||||
regionId: input.region_id,
|
||||
})
|
||||
|
||||
const cartInput = transform({ input, region }, (data) => {
|
||||
return {
|
||||
const customerData = findOrCreateCustomerStep({
|
||||
customerId: input.customer_id,
|
||||
email: input.email,
|
||||
})
|
||||
|
||||
const cartInput = transform({ input, region, customerData }, (data) => {
|
||||
const data_ = {
|
||||
...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,
|
||||
}
|
||||
|
||||
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])
|
||||
|
||||
return cart
|
||||
return cart[0]
|
||||
}
|
||||
)
|
||||
|
||||
28
packages/link-modules/src/definitions/cart-customer.ts
Normal file
28
packages/link-modules/src/definitions/cart-customer.ts
Normal file
@@ -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-sales-channel"
|
||||
export * from "./inventory-level-stock-location"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
StoreGetCartsCartParams,
|
||||
@@ -8,6 +9,15 @@ import {
|
||||
} from "./validators"
|
||||
|
||||
export const storeCartRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: "ALL",
|
||||
matcher: "/store/carts*",
|
||||
middlewares: [
|
||||
authenticate("store", ["session", "bearer"], {
|
||||
allowUnauthenticated: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/carts/:id",
|
||||
|
||||
@@ -10,6 +10,8 @@ export const defaultStoreCartFields = [
|
||||
"items.title",
|
||||
"items.quantity",
|
||||
"items.unit_price",
|
||||
"customer.id",
|
||||
"customer.email",
|
||||
"shipping_address.id",
|
||||
"shipping_address.first_name",
|
||||
"shipping_address.last_name",
|
||||
@@ -39,6 +41,7 @@ export const defaultStoreCartFields = [
|
||||
export const defaultStoreCartRelations = [
|
||||
"items",
|
||||
"region",
|
||||
"customer",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"shipping_methods",
|
||||
@@ -47,6 +50,7 @@ export const defaultStoreCartRelations = [
|
||||
export const allowedRelations = [
|
||||
"items",
|
||||
"region",
|
||||
"customer",
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"shipping_methods",
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { createCartWorkflow } from "@medusajs/core-flows"
|
||||
import { CreateCartDTO } from "@medusajs/types"
|
||||
import { CreateCartWorkflowInputDTO } from "@medusajs/types"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||
import { defaultStoreCartFields } from "../carts/query-config"
|
||||
import { StorePostCartReq } from "./validators"
|
||||
|
||||
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({
|
||||
input: req.validatedBody as CreateCartDTO,
|
||||
// If the customer is logged in, we auto-assign them to the cart
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -18,7 +27,7 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
|
||||
const variables = { id: result[0].id }
|
||||
const variables = { id: result.id }
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "cart",
|
||||
|
||||
@@ -29,15 +29,10 @@ export class StorePostCartReq {
|
||||
@IsString()
|
||||
region_id?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
customer_id?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
email?: string
|
||||
|
||||
// TODO: Remove in favor of using region currencies, as in the core
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
currency_code?: string
|
||||
@@ -74,10 +69,6 @@ export class StorePostCartsCartReq {
|
||||
@IsType([AddressPayload, String])
|
||||
shipping_address?: AddressPayload | string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
customer_id?: string
|
||||
|
||||
@IsEmail()
|
||||
@IsOptional()
|
||||
sales_channel_id?: string
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import "reflect-metadata"
|
||||
|
||||
import { Transform, Type } from "class-transformer"
|
||||
import {
|
||||
IsDate,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
} from "class-validator"
|
||||
import {
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
@@ -8,20 +16,12 @@ import {
|
||||
FindOptionsWhere,
|
||||
OrderByCondition,
|
||||
} 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 { FindOptionsRelations } from "typeorm/find-options/FindOptionsRelations"
|
||||
import { BaseEntity } from "../interfaces"
|
||||
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
|
||||
|
||||
@@ -33,8 +33,8 @@ export interface CreateCartDTO {
|
||||
currency_code: string
|
||||
shipping_address_id?: string
|
||||
billing_address_id?: string
|
||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
||||
shipping_address?: CreateAddressDTO | string
|
||||
billing_address?: CreateAddressDTO | string
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
items?: CreateLineItemDTO[]
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
import {
|
||||
CreateAddressDTO,
|
||||
CreateLineItemDTO,
|
||||
UpdateAddressDTO,
|
||||
} from "./mutations"
|
||||
export interface CreateCartLineItemDTO {
|
||||
variant_id: string
|
||||
quantity: number
|
||||
}
|
||||
|
||||
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 {
|
||||
region_id?: string
|
||||
customer_id?: string
|
||||
sales_channel_id?: string
|
||||
email?: string
|
||||
currency_code: string
|
||||
currency_code?: string
|
||||
shipping_address_id?: string
|
||||
billing_address_id?: string
|
||||
shipping_address?: CreateAddressDTO | UpdateAddressDTO
|
||||
billing_address?: CreateAddressDTO | UpdateAddressDTO
|
||||
shipping_address?: CreateCartAddressDTO | string
|
||||
billing_address?: CreateCartAddressDTO | string
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
items?: CreateLineItemDTO[]
|
||||
items?: CreateCartLineItemDTO[]
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ export interface FilterableCustomerProps
|
||||
company_name?: string | string[] | OperatorMap<string> | null
|
||||
first_name?: string | string[] | OperatorMap<string> | null
|
||||
last_name?: string | string[] | OperatorMap<string> | null
|
||||
has_account?: boolean | OperatorMap<boolean>
|
||||
created_by?: string | string[] | null
|
||||
created_at?: OperatorMap<string>
|
||||
updated_at?: OperatorMap<string>
|
||||
|
||||
Reference in New Issue
Block a user