fix(core-flows): conditionally create customer on order email update if unset (#14264)

* Find or create customer on order email update when order email is unset

* Tests

* Add changeset
This commit is contained in:
Nicolas Gorga
2026-01-05 12:12:42 -03:00
committed by GitHub
parent a464e9d907
commit 8055a79c52
4 changed files with 260 additions and 25 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/core-flows": patch
---
fix(core-flows): conditionally create customer on order email update if unset

View File

@@ -6,7 +6,11 @@ import {
AdminStockLocation,
MedusaContainer,
} from "@medusajs/types"
import { ContainerRegistrationKeys, Modules, ProductStatus } from "@medusajs/utils"
import {
ContainerRegistrationKeys,
Modules,
ProductStatus,
} from "@medusajs/utils"
import {
adminHeaders,
generatePublishableKey,

View File

@@ -165,7 +165,10 @@ medusaIntegrationTestRunner({
order = seeder.order
order = (
await api.get(`/admin/orders/${order.id}?fields=+email`, adminHeaders)
await api.get(
`/admin/orders/${order.id}?fields=+email,+customer_id`,
adminHeaders
)
).data.order
})
@@ -477,6 +480,215 @@ medusaIntegrationTestRunner({
expect(orderChangesResult.length).toEqual(0)
})
describe("conditional customer creation", () => {
it("should create or find a customer when order email is unset and input email is provided", async () => {
const container = getContainer()
const orderService = container.resolve(ModuleRegistrationName.ORDER)
const customerService = container.resolve(
ModuleRegistrationName.CUSTOMER
)
const orderWithoutEmail = await orderService.createOrders({
region_id: seeder.region.id,
currency_code: "usd",
items: [
{
title: "Test Item",
quantity: 1,
unit_price: 100,
},
],
shipping_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
billing_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
})
expect(orderWithoutEmail.email).toBeNull()
const newEmail = "newcustomer@example.com"
const customersBefore = await customerService.listCustomers()
const customerCountBefore = customersBefore.length
const response = await api.post(
`/admin/orders/${orderWithoutEmail.id}?fields=+email,+customer_id`,
{
email: newEmail,
},
adminHeaders
)
expect(response.data.order.email).toBe(newEmail)
expect(response.data.order.customer_id).toBeDefined()
const customersAfter = await customerService.listCustomers()
expect(customersAfter.length).toBe(customerCountBefore + 1)
const customer = await customerService.retrieveCustomer(
response.data.order.customer_id
)
expect(customer.email).toBe(newEmail)
})
it("should NOT create or find a customer when order email is already set and input email is provided", async () => {
const container = getContainer()
const customerService = container.resolve(
ModuleRegistrationName.CUSTOMER
)
const existingEmail = order.email
expect(existingEmail).toBeDefined()
const originalCustomerId = order.customer_id
const newEmail = "updated@example.com"
const customersBefore = await customerService.listCustomers()
const customerCountBefore = customersBefore.length
const response = await api.post(
`/admin/orders/${order.id}?fields=+email,+customer_id`,
{
email: newEmail,
},
adminHeaders
)
expect(response.data.order.email).toBe(newEmail)
expect(response.data.order.customer_id).toBe(originalCustomerId)
const customersAfter = await customerService.listCustomers()
expect(customersAfter.length).toBe(customerCountBefore)
})
it("should NOT create or find a customer when order email is unset and input email is not provided", async () => {
const container = getContainer()
const orderService = container.resolve(ModuleRegistrationName.ORDER)
const customerService = container.resolve(
ModuleRegistrationName.CUSTOMER
)
const orderWithoutEmail = await orderService.createOrders({
region_id: seeder.region.id,
currency_code: "usd",
email: undefined,
items: [
{
title: "Test Item",
quantity: 1,
unit_price: 100,
},
],
shipping_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
billing_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
})
const customersBefore = await customerService.listCustomers()
const customerCountBefore = customersBefore.length
const response = await api.post(
`/admin/orders/${orderWithoutEmail.id}?fields=+customer_id`,
{
metadata: {
test: "value",
},
},
adminHeaders
)
expect(response.data.order.customer_id).toBeNull()
const customersAfter = await customerService.listCustomers()
expect(customersAfter.length).toBe(customerCountBefore)
})
it("should find existing customer when order email is unset and input email matches existing customer", async () => {
const container = getContainer()
const orderService = container.resolve(ModuleRegistrationName.ORDER)
const customerService = container.resolve(
ModuleRegistrationName.CUSTOMER
)
const existingEmail = "existingcustomer@example.com"
const existingCustomer = await customerService.createCustomers({
email: existingEmail,
})
const orderWithoutEmail = await orderService.createOrders({
region_id: seeder.region.id,
currency_code: "usd",
items: [
{
title: "Test Item",
quantity: 1,
unit_price: 100,
},
],
shipping_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
billing_address: {
first_name: "Test",
last_name: "Test",
address_1: "Test",
city: "Test",
country_code: "US",
postal_code: "12345",
},
})
const customersBefore = await customerService.listCustomers()
const customerCountBefore = customersBefore.length
const response = await api.post(
`/admin/orders/${orderWithoutEmail.id}?fields=+email,+customer_id`,
{
email: existingEmail,
},
adminHeaders
)
expect(response.data.order.email).toBe(existingEmail)
expect(response.data.order.customer_id).toBe(existingCustomer.id)
const customersAfter = await customerService.listCustomers()
expect(customersAfter.length).toBe(customerCountBefore)
})
})
})
describe("POST /orders/:id/cancel", () => {

View File

@@ -28,6 +28,7 @@ import {
updateOrdersStep,
} from "../steps"
import { throwIfOrderIsCancelled } from "../utils/order-validation"
import { findOrCreateCustomerStep } from "../../cart"
/**
* The data to validate the order update.
@@ -151,31 +152,44 @@ export const updateOrderWorkflow = createWorkflow(
updateOrderValidationStep({ order, input })
const updateInput = transform({ input, order }, ({ input, order }) => {
const update: UpdateOrderDTO = {}
if (input.shipping_address) {
const address = {
// we want to create a new address
...order.shipping_address,
...input.shipping_address,
}
delete address.id
update.shipping_address = address
}
if (input.billing_address) {
const address = {
...order.billing_address,
...input.billing_address,
}
delete address.id
update.billing_address = address
}
return { ...input, ...update }
const customerData = when({ input, order }, ({ input, order }) => {
return !order.email && !!input.email
}).then(() => {
return findOrCreateCustomerStep({ email: input.email })
})
const updateInput = transform(
{ input, order, customerData },
({ input, order, customerData }) => {
const update: UpdateOrderDTO = {}
if (input.shipping_address) {
const address = {
// we want to create a new address
...order.shipping_address,
...input.shipping_address,
}
delete address.id
update.shipping_address = address
}
if (input.billing_address) {
const address = {
...order.billing_address,
...input.billing_address,
}
delete address.id
update.billing_address = address
}
if (!!customerData?.customer) {
update.customer_id = customerData.customer.id
}
return { ...input, ...update }
}
)
const updatedOrders = updateOrdersStep({
selector: { id: input.id },
update: updateInput,