feat(medusa, types, utils, core-flows, order) request & accept order transfer (#10106)

**What**
- add request order transfer workflow
- add admin endpoint for transferring an order to a customer
- accept order transfer storefront endpoint
- accept transfer workflow
- changes in the order module to introduce new change and action types

---

**Note**
- we return 400 instead 409 currently if there is already an active order edit, I will revisit this in a followup
- endpoint for requesting order transfer from the storefront will be added in a separate PR

---

RESOLVES CMRC-701
RESOLVES CMRC-703
RESOLVES CMRC-704
RESOLVES CMRC-705
This commit is contained in:
Frane Polić
2024-11-19 09:53:22 +01:00
committed by GitHub
parent b1b7a4abf1
commit 36460a3a07
21 changed files with 660 additions and 4 deletions

View File

@@ -0,0 +1,233 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import {
adminHeaders,
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { createOrderSeeder } from "../../fixtures/order"
jest.setTimeout(300000)
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let order
let customer
let user
let storeHeaders
beforeEach(async () => {
const container = getContainer()
user = (await createAdminUser(dbConnection, adminHeaders, container)).user
const publishableKey = await generatePublishableKey(container)
storeHeaders = generateStoreHeaders({ publishableKey })
const seeders = await createOrderSeeder({ api, container })
const registeredCustomerToken = (
await api.post("/auth/customer/emailpass/register", {
email: "test@email.com",
password: "password",
})
).data.token
customer = (
await api.post(
"/store/customers",
{
email: "test@email.com",
},
{
headers: {
Authorization: `Bearer ${registeredCustomerToken}`,
...storeHeaders.headers,
},
}
)
).data.customer
order = seeders.order
})
describe("Transfer Order flow", () => {
it("should pass order transfer flow from admin successfully", async () => {
// 1. Admin requests order transfer for a customer with an account
await api.post(
`/admin/orders/${order.id}/transfer`,
{
customer_id: customer.id,
},
adminHeaders
)
const orderResult = (
await api.get(
`/admin/orders/${order.id}?fields=+customer_id,+email`,
adminHeaders
)
).data.order
// 2. Order still belongs to the guest customer since the transfer hasn't been accepted yet
expect(orderResult.email).toEqual("tony@stark-industries.com")
expect(orderResult.customer_id).not.toEqual(customer.id)
const orderPreviewResult = (
await api.get(`/admin/orders/${order.id}/preview`, adminHeaders)
).data.order
expect(orderPreviewResult).toEqual(
expect.objectContaining({
customer_id: customer.id,
order_change: expect.objectContaining({
change_type: "transfer",
status: "requested",
requested_by: user.id,
}),
})
)
const orderChangesResult = (
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
).data.order_changes
expect(orderChangesResult.length).toEqual(1)
expect(orderChangesResult[0]).toEqual(
expect.objectContaining({
change_type: "transfer",
status: "requested",
requested_by: user.id,
created_by: user.id,
confirmed_by: null,
confirmed_at: null,
declined_by: null,
actions: expect.arrayContaining([
expect.objectContaining({
version: 2,
action: "TRANSFER_CUSTOMER",
reference: "customer",
reference_id: customer.id,
details: expect.objectContaining({
token: expect.any(String),
original_email: "tony@stark-industries.com",
}),
}),
]),
})
)
// 3. Guest customer who received the token accepts the transfer
await api.post(
`/store/orders/${order.id}/transfer/accept`,
{ token: orderChangesResult[0].actions[0].details.token },
{
headers: {
...storeHeaders.headers,
},
}
)
const finalOrderResult = (
await api.get(
`/admin/orders/${order.id}?fields=+customer_id,+email`,
adminHeaders
)
).data.order
expect(finalOrderResult.email).toEqual("tony@stark-industries.com")
// 4. Customer account is now associated with the order (email on the order is still as original, guest email)
expect(finalOrderResult.customer_id).toEqual(customer.id)
})
it("should fail to request order transfer to a guest customer", async () => {
const customer = (
await api.post(
"/admin/customers",
{
first_name: "guest",
email: "guest@medusajs.com",
},
adminHeaders
)
).data.customer
const err = await api
.post(
`/admin/orders/${order.id}/transfer`,
{
customer_id: customer.id,
},
adminHeaders
)
.catch((e) => e)
expect(err.response.status).toBe(400)
expect(err.response.data).toEqual(
expect.objectContaining({
type: "invalid_data",
message: `Cannot transfer order: ${order.id} to a guest customer account: guest@medusajs.com`,
})
)
})
it("should fail to accept order transfer with invalid token", async () => {
await api.post(
`/admin/orders/${order.id}/transfer`,
{
customer_id: customer.id,
},
adminHeaders
)
const orderChangesResult = (
await api.get(`/admin/orders/${order.id}/changes`, adminHeaders)
).data.order_changes
expect(orderChangesResult.length).toEqual(1)
expect(orderChangesResult[0]).toEqual(
expect.objectContaining({
change_type: "transfer",
status: "requested",
requested_by: user.id,
created_by: user.id,
confirmed_by: null,
confirmed_at: null,
declined_by: null,
actions: expect.arrayContaining([
expect.objectContaining({
version: 2,
action: "TRANSFER_CUSTOMER",
reference: "customer",
reference_id: customer.id,
details: expect.objectContaining({
token: expect.any(String),
original_email: "tony@stark-industries.com",
}),
}),
]),
})
)
const err = await api
.post(
`/store/orders/${order.id}/transfer/accept`,
{ token: "fake-token" },
{
headers: {
...storeHeaders.headers,
},
}
)
.catch((e) => e)
expect(err.response.status).toBe(400)
expect(err.response.data).toEqual(
expect.objectContaining({
type: "not_allowed",
message: `Invalid token.`,
})
)
})
})
},
})