Skip to content

Commit

Permalink
feat: request order transfer storefront API
Browse files Browse the repository at this point in the history
  • Loading branch information
fPolic committed Nov 19, 2024
1 parent 36460a3 commit a714d11
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 32 deletions.
180 changes: 148 additions & 32 deletions integration-tests/http/__tests__/order/admin/transfer-flow.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { Modules } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
Expand All @@ -11,46 +12,47 @@ jest.setTimeout(300000)

medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
let order
let customer
let user
let storeHeaders
describe("Transfer Order flow (Admin)", () => {
let order
let customer
let user
let storeHeaders

beforeEach(async () => {
const container = getContainer()
beforeEach(async () => {
const container = getContainer()

user = (await createAdminUser(dbConnection, adminHeaders, container)).user
const publishableKey = await generatePublishableKey(container)
storeHeaders = generateStoreHeaders({ publishableKey })
user = (await createAdminUser(dbConnection, adminHeaders, container))
.user
const publishableKey = await generatePublishableKey(container)
storeHeaders = generateStoreHeaders({ publishableKey })

const seeders = await createOrderSeeder({ api, container })
const seeders = await createOrderSeeder({ api, container })

const registeredCustomerToken = (
await api.post("/auth/customer/emailpass/register", {
email: "[email protected]",
password: "password",
})
).data.token

customer = (
await api.post(
"/store/customers",
{
const registeredCustomerToken = (
await api.post("/auth/customer/emailpass/register", {
email: "[email protected]",
},
{
headers: {
Authorization: `Bearer ${registeredCustomerToken}`,
...storeHeaders.headers,
password: "password",
})
).data.token

customer = (
await api.post(
"/store/customers",
{
email: "[email protected]",
},
}
)
).data.customer
{
headers: {
Authorization: `Bearer ${registeredCustomerToken}`,
...storeHeaders.headers,
},
}
)
).data.customer

order = seeders.order
})
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(
Expand Down Expand Up @@ -229,5 +231,119 @@ medusaIntegrationTestRunner({
)
})
})

describe("Transfer Order flow (Admin)", () => {
let order
let customer
let storeHeaders
let signInToken

let orderModule

beforeEach(async () => {
const container = getContainer()

orderModule = await container.resolve(Modules.ORDER)

const publishableKey = await generatePublishableKey(container)
storeHeaders = generateStoreHeaders({ publishableKey })

const seeders = await createOrderSeeder({ api, container })

const registeredCustomerToken = (
await api.post("/auth/customer/emailpass/register", {
email: "[email protected]",
password: "password",
})
).data.token

customer = (
await api.post(
"/store/customers",
{
email: "[email protected]",
},
{
headers: {
Authorization: `Bearer ${registeredCustomerToken}`,
...storeHeaders.headers,
},
}
)
).data.customer

signInToken = (
await api.post("/auth/customer/emailpass", {
email: "[email protected]",
password: "password",
})
).data.token

order = seeders.order
})

it("should pass order transfer flow from storefront successfully", async () => {
// 1. Customer requests order transfer
const storeOrder = (
await api.post(
`/store/orders/${order.id}/transfer/request?fields=+email,+customer_id`,
{},
{
headers: {
authorization: `Bearer ${signInToken}`,
...storeHeaders.headers,
},
}
)
).data.order

// 2. Order still belongs to the guest customer since the transfer hasn't been accepted yet
expect(storeOrder.email).toEqual("[email protected]")
expect(storeOrder.customer_id).not.toEqual(customer.id)

const orderChanges = await orderModule.listOrderChanges(
{ order_id: order.id },
{ relations: ["actions"] }
)

expect(orderChanges.length).toEqual(1)
expect(orderChanges[0]).toEqual(
expect.objectContaining({
change_type: "transfer",
status: "requested",
requested_by: customer.id,
created_by: customer.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: "[email protected]",
}),
}),
]),
})
)

// 3. Guest customer who received the token accepts the transfer
const finalOrder = (
await api.post(
`/store/orders/${order.id}/transfer/accept?fields=+email,+customer_id`,
{ token: orderChanges[0].actions[0].details.token },
storeHeaders
)
).data.order

expect(finalOrder.email).toEqual("[email protected]")
// 4. Customer account is now associated with the order (email on the order is still as original, guest email)
expect(finalOrder.customer_id).toEqual(customer.id)
})
})
},
})
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const requestOrderTransferValidationStep = createStep(
export const requestOrderTransferWorkflowId = "request-order-transfer-workflow"
/**
* This workflow requests an order transfer.
*
* Can be initiated by a store admin or the customer.
* If customer requested the transfer `input.logged_in_user === input.customer_id`.
*/
export const requestOrderTransferWorkflow = createWorkflow(
requestOrderTransferWorkflowId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
getOrderDetailWorkflow,
requestOrderTransferWorkflow,
} from "@medusajs/core-flows"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import { HttpTypes } from "@medusajs/framework/types"
import { StoreRequestOrderTransferType } from "../../../validators"

export const POST = async (
req: AuthenticatedMedusaRequest<StoreRequestOrderTransferType>,
res: MedusaResponse<HttpTypes.StoreOrderResponse>
) => {
const orderId = req.params.id
const customerId = req.auth_context.actor_id

await requestOrderTransferWorkflow(req.scope).run({
input: {
order_id: orderId,
customer_id: customerId,
logged_in_user: customerId,
description: req.validatedBody.description,
},
})

const { result } = await getOrderDetailWorkflow(req.scope).run({
input: {
fields: req.remoteQueryConfig.fields,
order_id: orderId,
},
})

res.status(200).json({ order: result as HttpTypes.StoreOrder })
}
13 changes: 13 additions & 0 deletions packages/medusa/src/api/store/orders/middlewares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
StoreGetOrderParams,
StoreGetOrdersParams,
StoreAcceptOrderTransfer,
StoreRequestOrderTransfer,
} from "./validators"

export const storeOrderRoutesMiddlewares: MiddlewareRoute[] = [
Expand All @@ -33,6 +34,18 @@ export const storeOrderRoutesMiddlewares: MiddlewareRoute[] = [
),
],
},
{
method: ["POST"],
matcher: "/store/orders/:id/transfer/request",
middlewares: [
authenticate("customer", ["session", "bearer"]),
validateAndTransformBody(StoreRequestOrderTransfer),
validateAndTransformQuery(
StoreGetOrderParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/store/orders/:id/transfer/accept",
Expand Down
7 changes: 7 additions & 0 deletions packages/medusa/src/api/store/orders/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ export const StoreAcceptOrderTransfer = z.object({
export type StoreAcceptOrderTransferType = z.infer<
typeof StoreAcceptOrderTransfer
>

export type StoreRequestOrderTransferType = z.infer<
typeof StoreRequestOrderTransfer
>
export const StoreRequestOrderTransfer = z.object({
description: z.string().optional(),
})

0 comments on commit a714d11

Please sign in to comment.