diff --git a/.changeset/bright-bees-decide.md b/.changeset/bright-bees-decide.md new file mode 100644 index 0000000000..b28724aaf4 --- /dev/null +++ b/.changeset/bright-bees-decide.md @@ -0,0 +1,5 @@ +--- +"@medusajs/core-flows": patch +--- + +Keep customer when updating cart diff --git a/integration-tests/modules/__tests__/cart/store/carts.spec.ts b/integration-tests/modules/__tests__/cart/store/carts.spec.ts index 5777991203..e40873c772 100644 --- a/integration-tests/modules/__tests__/cart/store/carts.spec.ts +++ b/integration-tests/modules/__tests__/cart/store/carts.spec.ts @@ -1882,6 +1882,192 @@ medusaIntegrationTestRunner({ }) }) + it("should create another guest customer and update the cart email when an different email is provided", async () => { + const cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + email: "tony@stark-industries.com", + shipping_address: { + address_1: "test address 1", + address_2: "test address 2", + city: "ny", + country_code: "us", + province: "ny", + postal_code: "94016", + }, + sales_channel_id: salesChannel.id, + }, + storeHeaders + ) + ).data.cart + + const originalCartCustomer = cart.customer + + // update the cart without providing an email + await api.post( + `/store/carts/${cart.id}`, + { + metadata: { + test: "test updated", + }, + }, + storeHeaders + ) + + let currentCart = await api.get( + `/store/carts/${cart.id}`, + storeHeaders + ) + let currentCartCustomer = currentCart.data.cart.customer + + let customerData = ( + await api.get( + `/admin/customers/${currentCart.data.cart.customer_id}`, + adminHeaders + ) + ).data.customer + + expect(currentCartCustomer).toEqual(originalCartCustomer) + expect(customerData.email).toEqual(currentCart.data.cart.email) + expect(currentCart.data.cart.email).toEqual( + "tony@stark-industries.com" + ) + expect(currentCart.data.cart.metadata).toEqual({ + test: "test updated", + }) + + // update the cart providing an email + await api.post( + `/store/carts/${cart.id}`, + { + email: "foo@bar.com", + metadata: { + test: "test updated 2, new customer", + }, + }, + storeHeaders + ) + + currentCart = await api.get(`/store/carts/${cart.id}`, storeHeaders) + + customerData = ( + await api.get( + `/admin/customers/${currentCart.data.cart.customer_id}`, + adminHeaders + ) + ).data.customer + + currentCartCustomer = currentCart.data.cart.customer + + expect(currentCartCustomer).not.toEqual(originalCartCustomer) + expect(currentCart.data.cart.customer_id).not.toEqual( + originalCartCustomer.id + ) + expect(customerData.email).toEqual(currentCart.data.cart.email) + expect(currentCart.data.cart.email).toEqual("foo@bar.com") + expect(currentCart.data.cart.metadata).toEqual({ + test: "test updated 2, new customer", + }) + }) + + it("should keep the same customer when updating the customer cart and update Cart's email if provided", async () => { + // create a customer + const mainEmail = "jhon.doe@acme.com" + await customerModule.createCustomers({ + email: mainEmail, + has_account: true, + }) + + const cart = ( + await api.post( + `/store/carts`, + { + currency_code: "usd", + email: mainEmail, + shipping_address: { + address_1: "test address 1", + address_2: "test address 2", + city: "ny", + country_code: "us", + province: "ny", + postal_code: "94016", + }, + sales_channel_id: salesChannel.id, + }, + storeHeaders + ) + ).data.cart + + const originalCartCustomer = cart.customer + + // update the cart without providing an email + await api.post( + `/store/carts/${cart.id}`, + { + metadata: { + test: "test updated", + }, + }, + storeHeaders + ) + + let currentCart = await api.get( + `/store/carts/${cart.id}`, + storeHeaders + ) + let currentCartCustomer = currentCart.data.cart.customer + + let customerData = ( + await api.get( + `/admin/customers/${currentCart.data.cart.customer_id}`, + adminHeaders + ) + ).data.customer + + expect(currentCartCustomer).toEqual(originalCartCustomer) + expect(customerData.email).toEqual(currentCart.data.cart.email) + expect(currentCart.data.cart.email).toEqual(mainEmail) + expect(currentCart.data.cart.metadata).toEqual({ + test: "test updated", + }) + + // update the cart providing an email + await api.post( + `/store/carts/${cart.id}`, + { + email: "foo@bar.com", + metadata: { + test: "test updated 2, new customer", + }, + }, + storeHeaders + ) + + currentCart = await api.get(`/store/carts/${cart.id}`, storeHeaders) + + customerData = ( + await api.get( + `/admin/customers/${currentCart.data.cart.customer_id}`, + adminHeaders + ) + ).data.customer + + currentCartCustomer = currentCart.data.cart.customer + + expect(currentCartCustomer).toEqual(originalCartCustomer) + expect(currentCart.data.cart.customer_id).toEqual( + originalCartCustomer.id + ) + expect(customerData.email).not.toEqual(currentCart.data.cart.email) + expect(customerData.email).toEqual(mainEmail) + expect(currentCart.data.cart.email).toEqual("foo@bar.com") + expect(currentCart.data.cart.metadata).toEqual({ + test: "test updated 2, new customer", + }) + }) + it("should return order when cart is already completed", async () => { const cart = ( await api.post( diff --git a/packages/core/core-flows/src/cart/steps/find-or-create-customer.ts b/packages/core/core-flows/src/cart/steps/find-or-create-customer.ts index eead9681f9..3a651d9038 100644 --- a/packages/core/core-flows/src/cart/steps/find-or-create-customer.ts +++ b/packages/core/core-flows/src/cart/steps/find-or-create-customer.ts @@ -1,6 +1,6 @@ import { CustomerDTO, ICustomerModuleService } from "@medusajs/framework/types" -import { Modules, validateEmail } from "@medusajs/framework/utils" -import { StepResponse, createStep } from "@medusajs/framework/workflows-sdk" +import { isDefined, Modules, validateEmail } from "@medusajs/framework/utils" +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" export interface FindOrCreateCustomerStepInput { customerId?: string | null @@ -21,20 +21,20 @@ export const findOrCreateCustomerStepId = "find-or-create-customer" /** * This step either finds a customer matching the specified ID, or finds / create a customer * matching the specified email. If both ID and email are provided, ID takes precedence. + * If the customer is a guest, the email is updated to the provided email. */ export const findOrCreateCustomerStep = createStep( findOrCreateCustomerStepId, async (data: FindOrCreateCustomerStepInput, { container }) => { - if ( - typeof data.customerId === undefined && - typeof data.email === undefined - ) { + if (!isDefined(data.customerId) && !isDefined(data.email)) { return new StepResponse( { customer: undefined, email: undefined, }, - { customerWasCreated: false } + { + customerWasCreated: false, + } ) } @@ -44,37 +44,50 @@ export const findOrCreateCustomerStep = createStep( customer: null, email: null, } + let originalCustomer: CustomerDTO | null = null let customerWasCreated = false if (data.customerId) { - const customer = await service.retrieveCustomer(data.customerId) - customerData.customer = customer - customerData.email = customer.email - - return new StepResponse(customerData, { - customerWasCreated, - }) + originalCustomer = await service.retrieveCustomer(data.customerId) + customerData.customer = originalCustomer + customerData.email = originalCustomer.email } if (data.email) { - const validatedEmail = validateEmail(data.email) + const validatedEmail = (data.email && validateEmail(data.email)) as string - let [customer] = await service.listCustomers({ - email: validatedEmail, - has_account: false, - }) + let [customer] = originalCustomer + ? [originalCustomer] + : await service.listCustomers({ + email: validatedEmail, + }) - if (!customer) { + // if NOT a guest customer, return it + if (customer?.has_account) { + customerData.customer = customer + customerData.email = customer.email + + return new StepResponse(customerData, { + customerWasCreated, + }) + } + + if ( + !customer || + (isDefined(data.email) && customer.email !== validatedEmail) + ) { customer = await service.createCustomers({ email: validatedEmail }) customerWasCreated = true } + originalCustomer = customer + customerData.customer = customer customerData.email = customer.email } return new StepResponse(customerData, { - customer: customerData.customer, + customer: originalCustomer, customerWasCreated, }) }, diff --git a/packages/core/core-flows/src/cart/workflows/update-cart.ts b/packages/core/core-flows/src/cart/workflows/update-cart.ts index 5318d40408..9114ed6f57 100644 --- a/packages/core/core-flows/src/cart/workflows/update-cart.ts +++ b/packages/core/core-flows/src/cart/workflows/update-cart.ts @@ -34,18 +34,32 @@ export const updateCartWorkflow = createWorkflow( const cartToUpdate = useRemoteQueryStep({ entry_point: "cart", variables: { id: input.id }, - fields: ["id", "shipping_address.*", "region.*", "region.countries.*"], + fields: [ + "id", + "email", + "customer_id", + "shipping_address.*", + "region.*", + "region.countries.*", + ], list: false, throw_if_key_not_found: true, }).config({ name: "get-cart" }) - const [salesChannel, customerData] = parallelize( + const customerDataInput = transform({ input, cartToUpdate }, (data) => { + return { + customer_id: data.cartToUpdate.customer_id, + email: data.input.email ?? data.cartToUpdate.email, + } + }) + + const [salesChannel, customer] = parallelize( findSalesChannelStep({ salesChannelId: input.sales_channel_id, }), findOrCreateCustomerStep({ - customerId: input.customer_id, - email: input.email, + customerId: customerDataInput.customer_id, + email: customerDataInput.email, }) ) @@ -66,7 +80,13 @@ export const updateCartWorkflow = createWorkflow( }) const cartInput = transform( - { input, region, customerData, salesChannel, cartToUpdate }, + { + input, + region, + customer, + salesChannel, + cartToUpdate, + }, (data) => { const { promo_codes, @@ -116,13 +136,16 @@ export const updateCartWorkflow = createWorkflow( } } - if ( - isDefined(updateCartData.customer_id) || - isDefined(updateCartData.email) - ) { - data_.customer_id = data.customerData.customer?.id || null - data_.email = - data.input?.email ?? (data.customerData.customer?.email || null) + if (isDefined(updateCartData.email) && data.customer?.customer) { + const currentCustomer = data.customer.customer! + data_.customer_id = currentCustomer.id + + // registered customers can update the cart email + if (currentCustomer.has_account) { + data_.email = updateCartData.email + } else { + data_.email = data.customer.email + } } if (isDefined(updateCartData.sales_channel_id)) { @@ -133,6 +156,7 @@ export const updateCartWorkflow = createWorkflow( } ) + /* when({ cartInput }, ({ cartInput }) => { return isDefined(cartInput.customer_id) || isDefined(cartInput.email) }).then(() => { @@ -141,6 +165,7 @@ export const updateCartWorkflow = createWorkflow( data: { id: input.id }, }).config({ name: "emit-customer-updated" }) }) + */ when({ input, cartToUpdate }, ({ input, cartToUpdate }) => { return (