docs: revise commerce modules overview pages (#10738)
* revise API Key Module overview * revise auth module * support ref sidebar items * remove examples * revise cart module * revise currency * revise customer module * revise fulfillment module * revise inventory module * revise order module * revise payment * revise pricing module * revise product module * revise promotion module * revise region module * revise sales channel module * revise stock location module * revise store module * revise tax module * revise user module * lint content + fix snippets
This commit is contained in:
1 parent
c8f9938865
commit
ebca8fed28
112 files changed
+9465
-7731
No files matched your search
@@ -1,306 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the API Key Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the API Key Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the API Key Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create an API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
|
||||
const apiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
api_key: apiKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeApiKeyModule } from "@medusajs/medusa/api-key"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const apiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: apiKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List API Keys
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeApiKeyModule } from "@medusajs/medusa/api-key"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Revoke an API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(request.params.id, {
|
||||
revoked_by: request.auth_context.actor_id,
|
||||
})
|
||||
|
||||
res.json({
|
||||
api_key: revokedKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeApiKeyModule } from "@medusajs/medusa/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
user_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(params.id, {
|
||||
revoked_by: params.user_id,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: revokedKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Verify or Authenticate Token
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
|
||||
const authenticatedToken = await apiKeyModuleService.authenticate(
|
||||
request.params.token
|
||||
)
|
||||
|
||||
res.json({
|
||||
is_authenticated: !!authenticatedToken,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeApiKeyModule } from "@medusajs/medusa/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
token: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const authenticatedToken = await apiKeyModuleService.authenticate(
|
||||
request.params.token
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
is_authenticated: !!authenticatedToken,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Roll API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(request.params.id, {
|
||||
revoked_by: request.auth_context.actor_id,
|
||||
})
|
||||
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
|
||||
res.json({
|
||||
api_key: newKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeApiKeyModule } from "@medusajs/medusa/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
user_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(params.id, {
|
||||
revoked_by: params.user_id,
|
||||
})
|
||||
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: newKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [API Key Module's main service reference](/references/api-key) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -37,8 +37,8 @@ To retrieve the sales channels of an API key with [Query](!docs!/learn/fundament
|
||||
const { data: apiKeys } = await query.graph({
|
||||
entity: "api_key",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// apiKeys.sales_channels
|
||||
@@ -55,8 +55,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: apiKeys } = useQueryGraphStep({
|
||||
entity: "api_key",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// apiKeys.sales_channels
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
import { CodeTabs, CodeTab, ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `API Key Module`,
|
||||
@@ -6,132 +6,153 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The API Key Module provides API-key-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the API Key Module and how to use it in your application.
|
||||
|
||||
## How to Use API Key Module's Service
|
||||
Medusa has API-key related features available out-of-the-box through the API Key Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this API Key Module.
|
||||
|
||||
Use the API Key Module's main service by resolving from the Medusa container the resource `Modules.API_KEY`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## API Key Features
|
||||
|
||||
- [API Key Types and Management](./concepts/page.mdx): Manage API keys in your store. You can create both publishable and secret API keys for different use cases.
|
||||
- [Token Verification](./concepts/page.mdx#token-verification): Verify tokens of secret API keys to authenticate users or actions.
|
||||
- [Revoke Keys](./concepts/page.mdx#api-key-expiration): Revoke keys to disable their use permanently.
|
||||
- Roll API Keys: Roll API keys by [revoking](/references/api-key/revoke) a key then [re-creating it](/references/api-key/createApiKeys).
|
||||
|
||||
---
|
||||
|
||||
## How to Use the API Key Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.API_KEY", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-api-key.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const apiKeyModuleService = container.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
const createApiKeyStep = createStep(
|
||||
"create-api-key",
|
||||
async ({}, { container }) => {
|
||||
const apiKeyModuleService = container.resolve(Modules.API_KEY)
|
||||
|
||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||
})
|
||||
const apiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
return new StepResponse({ apiKey }, apiKey.id)
|
||||
},
|
||||
async (apiKeyId, { container }) => {
|
||||
const apiKeyModuleService = container.resolve(Modules.API_KEY)
|
||||
|
||||
await apiKeyModuleService.deleteApiKeys([apiKeyId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createApiKeyWorkflow = createWorkflow(
|
||||
"create-api-key",
|
||||
() => {
|
||||
const { apiKey } = createApiKeyStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
apiKey,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createApiKeyWorkflow } from "../../workflows/create-api-key"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const apiKeyModuleService = request.scope.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
) {
|
||||
const { result } = await createApiKeyWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createApiKeyWorkflow } from "../workflows/create-api-key"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createApiKeyWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const apiKeyModuleService = container.resolve(
|
||||
Modules.API_KEY
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createApiKeyWorkflow } from "../workflows/create-api-key"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createApiKeyWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### API Key Types and Management
|
||||
|
||||
Manage API keys in your store. You can create both publishable and secret API keys for different use cases, such as:
|
||||
|
||||
- Publishable API Key associated with resources like sales channels.
|
||||
- Authentication token for admin users to access Admin API Routes.
|
||||
- Password reset tokens when a user or customer requests to reset their password.
|
||||
|
||||
```ts
|
||||
const pubApiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
const secretApiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Authentication Key",
|
||||
type: "secret",
|
||||
created_by: "user_123",
|
||||
})
|
||||
```
|
||||
|
||||
### Token Verification
|
||||
|
||||
Verify tokens of secret API keys to authenticate users or actions, such as verifying a password reset token.
|
||||
|
||||
```ts
|
||||
const authenticatedToken = await apiKeyModuleService.authenticate("sk_123")
|
||||
|
||||
if (!authenticatedToken) {
|
||||
console.error("Couldn't verify token")
|
||||
} else {
|
||||
console.log("Token verified successfully!")
|
||||
}
|
||||
```
|
||||
|
||||
### Revoke Keys
|
||||
|
||||
Revoke keys to disable their use permenantly.
|
||||
|
||||
```ts
|
||||
const revokedKey = await apiKeyModuleService.revoke("apk_1", {
|
||||
revoked_by: "user_123",
|
||||
})
|
||||
```
|
||||
|
||||
### Roll API Keys
|
||||
|
||||
Roll API keys by revoking a key then re-creating it.
|
||||
|
||||
```ts
|
||||
const revokedKey = await apiKeyModuleService.revoke("apk_1", {
|
||||
revoked_by: "user_123",
|
||||
})
|
||||
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
```
|
||||
<CommerceModuleSections name="API Key" />
|
||||
@@ -1,444 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Auth Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Auth Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Auth Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Authenticate User
|
||||
|
||||
<Note>
|
||||
|
||||
This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/jsonwebtoken) to create the authentication token.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import {
|
||||
IAuthModuleService,
|
||||
AuthenticationInput,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
const { success, authIdentity, location, error } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new MedusaError(MedusaError.Types.UNAUTHORIZED, error)
|
||||
}
|
||||
|
||||
if (location) {
|
||||
res.json({ location })
|
||||
return
|
||||
}
|
||||
|
||||
const { jwtSecret } = req.scope.resolve("configModule").projectConfig.http
|
||||
const token = jwt.sign(authIdentity, jwtSecret)
|
||||
|
||||
res.status(200).json({ token })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
const url = new URL(request.url)
|
||||
|
||||
const { success, authIdentity, location, error } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers),
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
body: await request.json(),
|
||||
authScope: "admin",
|
||||
protocol: url.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
if (location) {
|
||||
return NextResponse.json({ location })
|
||||
}
|
||||
|
||||
const token = jwt.sign(authIdentity, "supersecret")
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
token,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Validate Callback
|
||||
|
||||
<Note>
|
||||
|
||||
This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/jsonwebtoken) to create the authentication token.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import {
|
||||
IAuthModuleService,
|
||||
AuthenticationInput,
|
||||
} from "@medusajs/framework/types"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
import { MedusaError } from "@medusajs/framework/utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
const { success, authIdentity, error, successRedirectUrl } =
|
||||
await authModuleService.validateCallback("google", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new MedusaError(MedusaError.Types.UNAUTHORIZED, error)
|
||||
}
|
||||
|
||||
const { jwtSecret } = req.scope.resolve("configModule").projectConfig.http
|
||||
const token = jwt.sign(authIdentity, jwtSecret)
|
||||
|
||||
if (successRedirectUrl) {
|
||||
const url = new URL(successRedirectUrl!)
|
||||
url.searchParams.append("auth_token", token)
|
||||
|
||||
return res.redirect(url.toString())
|
||||
}
|
||||
|
||||
res.status(200).json({ token })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
const url = new URL(request.url)
|
||||
|
||||
const { success, authIdentity, location, error } =
|
||||
await authModuleService.authenticate("google", {
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers),
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
body: await request.json(),
|
||||
authScope: "admin",
|
||||
protocol: url.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
const token = jwt.sign(authIdentity, "supersecret")
|
||||
|
||||
if (successRedirectUrl) {
|
||||
const url = new URL(successRedirectUrl!)
|
||||
url.searchParams.append("auth_token", token)
|
||||
|
||||
return NextResponse.redirect(url.toString())
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
token,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Auth Identity
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
const authIdentity = await authModuleService.createAuthIdentities({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
res.json({ auth_identity: authIdentity })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authIdentity = await authModuleService.createAuthIdentities({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identity: authIdentity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Auth Identities
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
res.json({
|
||||
auth_identitys: await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identities: await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update an Auth Identity
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
const authIdentity = await authModuleService.updateAuthIdentites({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
auth_identity: authIdentity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authIdentity = await authModuleService.updateAuthIdentites({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identity: authIdentity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete an Auth Identity
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
await authModuleService.deleteAuthIdentities(["authusr_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeAuthModule } from "@medusajs/medusa/auth"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request, { params }: ContextType) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
await authModuleService.deleteAuthIdentities(["authusr_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Auth Module's main service reference](/references/auth) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
import { CodeTabs, CodeTab, ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Auth Module`,
|
||||
@@ -6,132 +6,141 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Auth Module provides authentication-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Auth Module and how to use it in your application.
|
||||
|
||||
## How to Use Auth Module's Service
|
||||
Medusa has auth related features available out-of-the-box through the Auth Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Auth Module.
|
||||
|
||||
Use the Auth Module's main service by resolving from the Medusa container the resource `Modules.AUTH`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Auth Features
|
||||
|
||||
- [Basic User Authentication](./authentication-route/page.mdx#1-basic-authentication-flow): Authenticate users using their email and password credentials.
|
||||
- [Third-Party and Social Authentication](./authentication-route/page.mdx#2-third-party-service-authenticate-flow): Authenticate users using third-party services and social platforms, such as [Google](./auth-providers/google/page.mdx) and [GitHub](./auth-providers/github/page.mdx).
|
||||
- [Authenticate Custom Actor Types](./create-actor-type/page.mdx): Create custom user or actor types, such as managers, authenticate them in your application, and guard routes based on the custom user types.
|
||||
- [Custom Authentication Providers](/references/auth/provider): Integrate third-party services with custom authentication providors.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Auth Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["18", "Modules.AUTH", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="src/workflows/authenticate-user.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules, MedusaError } from "@medusajs/framework/utils"
|
||||
import { MedusaRequest } from "@medusajs/framework/http"
|
||||
import { AuthenticationInput } from "@medusajs/framework/types"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const authModuleService = container.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
const authIdentitys = await authModuleService.listAuthIdentities()
|
||||
})
|
||||
type Input = {
|
||||
req: MedusaRequest
|
||||
}
|
||||
|
||||
const authenticateUserStep = createStep(
|
||||
"authenticate-user",
|
||||
async ({ req }: Input, { container }) => {
|
||||
const authModuleService = container.resolve(Modules.AUTH)
|
||||
|
||||
const { success, authIdentity, error } = await authModuleService
|
||||
.authenticate(
|
||||
"emailpass",
|
||||
{
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin", // or custom actor type
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput
|
||||
)
|
||||
|
||||
if (!success) {
|
||||
// incorrect authentication details
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.UNAUTHORIZED,
|
||||
error || "Incorrect authentication details"
|
||||
)
|
||||
}
|
||||
|
||||
return new StepResponse({ authIdentity }, authIdentity?.id)
|
||||
},
|
||||
async (authIdentityId, { container }) => {
|
||||
if (!authIdentityId) {
|
||||
return
|
||||
}
|
||||
|
||||
const authModuleService = container.resolve(Modules.AUTH)
|
||||
|
||||
await authModuleService.deleteAuthIdentities([authIdentityId])
|
||||
}
|
||||
)
|
||||
|
||||
export const authenticateUserWorkflow = createWorkflow(
|
||||
"authenticate-user",
|
||||
(input: Input) => {
|
||||
const { authIdentity } = authenticateUserStep(input)
|
||||
|
||||
return new WorkflowResponse({
|
||||
authIdentity,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
```ts title="API Route" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { authenticateUserWorkflow } from "../../workflows/authenticate-user"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService = req.scope.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
) {
|
||||
const { result } = await authenticateUserWorkflow(req.scope)
|
||||
.run({
|
||||
req,
|
||||
})
|
||||
|
||||
res.json({
|
||||
authIdentitys: await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const authModuleService = container.resolve(
|
||||
Modules.AUTH
|
||||
)
|
||||
|
||||
const authIdentitys = await authModuleService.listAuthIdentities()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Basic User Authentication
|
||||
|
||||
Authenticate users using their email and password credentials.
|
||||
|
||||
```ts
|
||||
const { success, authIdentity, error } = await authModuleService.authenticate(
|
||||
"emailpass",
|
||||
{
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput
|
||||
)
|
||||
|
||||
if (!success) {
|
||||
// incorrect authentication details
|
||||
throw new Error(error)
|
||||
}
|
||||
```
|
||||
|
||||
### Third-Party and Social Authentication
|
||||
|
||||
The Auth Module supports a variety of authentication methods, such as authenticating with third-party services and social platforms.
|
||||
|
||||
```ts
|
||||
// in authentication API route
|
||||
const { success, authIdentity, location } =
|
||||
await authModuleService.authenticate("google", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (location) {
|
||||
return res.json({ location })
|
||||
}
|
||||
|
||||
// in callback API route
|
||||
const { success, authIdentity } = await authModuleService.validateCallback(
|
||||
"google",
|
||||
{
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput
|
||||
)
|
||||
```
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Configure Auth Module
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
The Auth Module accepts options for further configurations. Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## Providers
|
||||
|
||||
Medusa provides the following authentication providers out-of-the-box. You can use them to authenticate admin users, customers, or custom actor types.
|
||||
|
||||
<ChildDocs showItems={["Providers"]} hideTitle />
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="Auth" />
|
||||
@@ -1,463 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Cart Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Cart Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Cart Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ cart })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
cart,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Cart by ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const cart = await cartModuleService.retrieveCart("cart_123")
|
||||
|
||||
res.json({ cart })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.retrieveCart("cart_123")
|
||||
|
||||
return NextResponse.json({
|
||||
cart,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Item to Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const lineItem = await cartModuleService.addLineItems({
|
||||
cart_id: "cart_123",
|
||||
title: "Shirt",
|
||||
quantity: 2,
|
||||
unit_price: 5000,
|
||||
})
|
||||
|
||||
res.json({
|
||||
line_item: lineItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const lineItem = await cartModuleService.addLineItems({
|
||||
cart_id: "cart_123",
|
||||
title: "Shirt",
|
||||
quantity: 2,
|
||||
unit_price: 5000,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
line_item: lineItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Shipping Method to Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const shippingMethod = await cartModuleService.addShippingMethods({
|
||||
cart_id: "cart_123",
|
||||
name: "Custom shipping",
|
||||
amount: 1000,
|
||||
})
|
||||
|
||||
res.json({
|
||||
shipping_method: shippingMethod,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const shippingMethod = await cartModuleService.addShippingMethods({
|
||||
cart_id: "cart_123",
|
||||
name: "Custom shipping",
|
||||
amount: 1000,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
shipping_method: shippingMethod,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Item Adjustment Line
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const itemAdjustment = await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
amount: 500,
|
||||
code: "50%OFF",
|
||||
})
|
||||
|
||||
res.json({
|
||||
adjustment: itemAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const itemAdjustment = await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
amount: 500,
|
||||
code: "50%OFF",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
adjustment: itemAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Shipping Method Adjustment Line
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
const shippingMethodAdjustment =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
amount: 500,
|
||||
code: "FREESHIPPING",
|
||||
})
|
||||
|
||||
res.json({
|
||||
adjustment: shippingMethodAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const shippingMethodAdjustment =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
amount: 500,
|
||||
code: "FREESHIPPING",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
adjustment: shippingMethodAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove Line Item from Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
await cartModuleService.deleteLineItems(["cali_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCartModule } from "@medusajs/medusa/cart"
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
await cartModuleService.deleteLineItems(["cali_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove Shipping Method from Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
await cartModuleService.deleteShippingMethods(["casm_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
|
||||
await cartModuleService.deleteShippingMethods(["casm_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Cart Module's main service reference](/references/cart) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -44,8 +44,8 @@ To retrieve the customer of a cart with [Query](!docs!/learn/fundamentals/module
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"customer.*"
|
||||
]
|
||||
"customer.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.order
|
||||
@@ -62,8 +62,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"customer.*"
|
||||
]
|
||||
"customer.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.order
|
||||
@@ -93,8 +93,8 @@ To retrieve the order of a cart with [Query](!docs!/learn/fundamentals/module-li
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.order
|
||||
@@ -111,8 +111,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.order
|
||||
@@ -186,8 +186,8 @@ To retrieve the payment collection of a cart with [Query](!docs!/learn/fundament
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"payment_collection.*"
|
||||
]
|
||||
"payment_collection.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.payment_collection
|
||||
@@ -204,8 +204,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"payment_collection.*"
|
||||
]
|
||||
"payment_collection.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.payment_collection
|
||||
@@ -283,8 +283,8 @@ To retrieve the product, pass `product.*` in `fields`.
|
||||
const { data: lineItems } = await query.graph({
|
||||
entity: "line_item",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// lineItems.variant
|
||||
@@ -301,8 +301,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: lineItems } = useQueryGraphStep({
|
||||
entity: "line_item",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// lineItems.variant
|
||||
@@ -340,8 +340,8 @@ To retrieve the promotion of a line item adjustment, pass `promotion.*` in `fiel
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"promotions.*"
|
||||
]
|
||||
"promotions.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.promotions
|
||||
@@ -358,8 +358,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"promotions.*"
|
||||
]
|
||||
"promotions.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.promotions
|
||||
@@ -429,8 +429,8 @@ To retrieve the region of a cart with [Query](!docs!/learn/fundamentals/module-l
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"region.*"
|
||||
]
|
||||
"region.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.region
|
||||
@@ -447,8 +447,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"region.*"
|
||||
]
|
||||
"region.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.region
|
||||
@@ -474,8 +474,8 @@ To retrieve the sales channel of a cart with [Query](!docs!/learn/fundamentals/m
|
||||
const { data: carts } = await query.graph({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"sales_channel.*"
|
||||
]
|
||||
"sales_channel.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.sales_channel
|
||||
@@ -492,8 +492,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"sales_channel.*"
|
||||
]
|
||||
"sales_channel.*",
|
||||
],
|
||||
})
|
||||
|
||||
// carts.sales_channel
|
||||
|
||||
@@ -6,120 +6,165 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Cart Module provides cart-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Cart Module and how to use it in your application.
|
||||
|
||||
## How to Use Cart Module's Service
|
||||
Medusa has cart related features available out-of-the-box through the Cart Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Cart Module.
|
||||
|
||||
You can use the Cart Module's main service by resolving from the Medusa container the resource `Modules.CART`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Cart Features
|
||||
|
||||
- [Cart Management](./concepts/page.mdx): Store and manage carts, including their addresses, line items, shipping methods, and more.
|
||||
- [Apply Promotion Adjustments](./promotions/page.mdx): Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
- [Apply Tax Lines](./tax-lines/page.mdx): Apply tax lines to line items and shipping methods.
|
||||
- [Cart Scoping](./links-to-other-modules/page.mdx): When used in the Medusa application, Medusa creates links to other commerce modules, scoping a cart to a sales channel, region, and a customer.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Cart Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.CART", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-cart.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const cartModuleService = container.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
const createCartStep = createStep(
|
||||
"create-cart",
|
||||
async ({}, { container }) => {
|
||||
const cartModuleService = container.resolve(Modules.CART)
|
||||
|
||||
const carts = await cartModuleService.listCarts()
|
||||
})
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return new StepResponse({ cart }, cart.id)
|
||||
},
|
||||
async (cartId, { container }) => {
|
||||
if (!cartId) {
|
||||
return
|
||||
}
|
||||
const cartModuleService = container.resolve(Modules.CART)
|
||||
|
||||
await cartModuleService.deleteCarts([cartId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createCartWorkflow = createWorkflow(
|
||||
"create-cart",
|
||||
() => {
|
||||
const { cart } = createCartStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
cart,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createCartWorkflow } from "../../workflows/create-cart"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService = req.scope.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
) {
|
||||
const { result } = await createCartWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
carts: await cartModuleService.listCarts(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createCartWorkflow } from "../workflows/create-cart"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createCartWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const cartModuleService = container.resolve(
|
||||
Modules.CART
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const carts = await cartModuleService.listCarts()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createCartWorkflow } from "../workflows/create-cart"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createCartWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Cart Management
|
||||
|
||||
Store and manage carts, including their addresses, line items, shipping methods, and more.
|
||||
|
||||
```ts
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Apply Promotions
|
||||
|
||||
Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
|
||||
```ts
|
||||
const lineAdjustments = await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
code: "50OFF",
|
||||
amount: 500,
|
||||
})
|
||||
|
||||
const shippingAdjustments =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
code: "FREESHIPPING",
|
||||
amount: 1000,
|
||||
})
|
||||
```
|
||||
|
||||
### Cart Context and Scoping
|
||||
|
||||
A cart is scoped to a sales channel, region, and a customer.
|
||||
|
||||
The Medusa application links the Cart Module to each of their respective modules, providing features like:
|
||||
|
||||
- Checking product availability in a sales channel.
|
||||
- Retrieving pricing per region.
|
||||
- Applying promotions based on the customer's group.
|
||||
<CommerceModuleSections name="Cart" />
|
||||
@@ -1,111 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Currency Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Currency Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Currency Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## List Currencies
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCurrencyModule } from "@medusajs/medusa/currency"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Currency by its Code
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
|
||||
const currency = await currencyModuleService.retrieveCurrency("usd")
|
||||
|
||||
res.json({
|
||||
currency,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCurrencyModule } from "@medusajs/medusa/currency"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
|
||||
const currency = await currencyModuleService.retrieveCurrency("usd")
|
||||
|
||||
return NextResponse.json({ currency })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Currency Module's main service reference](/references/currency) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -39,8 +39,8 @@ To retrieve the details of a store's currencies with [Query](!docs!/learn/fundam
|
||||
const { data: stores } = await query.graph({
|
||||
entity: "store",
|
||||
fields: [
|
||||
"supported_currencies.currency.*"
|
||||
]
|
||||
"supported_currencies.currency.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stores.supported_currencies
|
||||
@@ -57,8 +57,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: stores } = useQueryGraphStep({
|
||||
entity: "store",
|
||||
fields: [
|
||||
"supported_currencies.currency.*"
|
||||
]
|
||||
"supported_currencies.currency.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stores.supported_currencies
|
||||
|
||||
@@ -6,91 +6,161 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Currency Module provides currency-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Currency Module and how to use it in your application.
|
||||
|
||||
## How to Use Currency Module's Service
|
||||
Medusa has currency related features available out-of-the-box through the Currency Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Currency Module.
|
||||
|
||||
You can use the Currency Module's main service by resolving from the Medusa container the resource `Modules.CURRENCY`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Currency Features
|
||||
|
||||
- [Currency Management and Retrieval](/references/currency/listAndCountCurrencies): This module adds all common currencies to your application and allows you to retrieve them.
|
||||
- [Support Currencies in Modules](./links-to-other-modules/page.mdx): Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Currency Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["13", "Modules.CURRENCY", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/retrieve-price-with-currency.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
transform,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const currencyModuleService = container.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
const retrieveCurrencyStep = createStep(
|
||||
"retrieve-currency",
|
||||
async ({}, { container }) => {
|
||||
const currencyModuleService = container.resolve(Modules.CURRENCY)
|
||||
|
||||
const currencies = await currencyModuleService.listCurrencies()
|
||||
})
|
||||
const currency = await currencyModuleService
|
||||
.retrieveCurrency("usd")
|
||||
|
||||
return new StepResponse({ currency })
|
||||
}
|
||||
)
|
||||
|
||||
type Input = {
|
||||
price: number
|
||||
}
|
||||
|
||||
export const retrievePriceWithCurrency = createWorkflow(
|
||||
"create-currency",
|
||||
(input: Input) => {
|
||||
const { currency } = retrieveCurrencyStep()
|
||||
|
||||
const formattedPrice = transform({
|
||||
input,
|
||||
currency,
|
||||
}, (data) => {
|
||||
return `${data.currency.symbol}${data.input.price}`
|
||||
})
|
||||
|
||||
return new WorkflowResponse({
|
||||
formattedPrice,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { retrievePriceWithCurrency } from "../../workflows/retrieve-price-with-currency"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService = req.scope.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
) {
|
||||
const { result } = await retrievePriceWithCurrency(req.scope)
|
||||
.run({
|
||||
price: 10,
|
||||
})
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"], ["13"], ["14"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await retrievePriceWithCurrency(container)
|
||||
.run({
|
||||
price: 10,
|
||||
})
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const currencyModuleService = container.resolve(
|
||||
Modules.CURRENCY
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const currencies = await currencyModuleService.listCurrencies()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { retrievePriceWithCurrency } from "../workflows/retrieve-price-with-currency"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await retrievePriceWithCurrency(container)
|
||||
.run({
|
||||
price: 10,
|
||||
})
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Currency Retrieval
|
||||
|
||||
List and retrieve currencies stored in your application.
|
||||
|
||||
```ts
|
||||
const currency = await currencyModuleService.retrieveCurrency("usd")
|
||||
```
|
||||
|
||||
### Support Currencies in Modules
|
||||
|
||||
Other commerce modules use currency codes in their data models or operations. Use the Currency Module to retrieve a currency code and its details.
|
||||
|
||||
An example with the Region Module:
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.retrieveRegion("reg_123")
|
||||
const currency = await currencyModuleService.retrieveCurrency(
|
||||
region.currency_code
|
||||
)
|
||||
```
|
||||
<CommerceModuleSections name="Currency" />
|
||||
@@ -1,220 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Customer Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Customer Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Customer Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Customer
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
|
||||
res.json({
|
||||
customer,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCustomerModule } from "@medusajs/medusa/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
customer,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Customer Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
const customerGroup = await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
res.json({
|
||||
customer_group: customerGroup,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCustomerModule } from "@medusajs/medusa/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customerGroup = await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
customer_group: customerGroup,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add a Customer to a Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeCustomerModule } from "@medusajs/medusa/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({}, { status: 200 })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove a Customer from a Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
await customerModuleService.removeCustomerFromGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
|
||||
import { initialize as initializeCustomerModule } from "@medusajs/medusa/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
await customerModuleService.removeCustomerFromGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({}, { status: 200 })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Customer Module's main service reference](/references/customer) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -276,9 +276,9 @@ You'll next execute the workflow in the hook handler.
|
||||
|
||||
You can now consume the `customersCreated` hook, which is executed in the `createCustomersWorkflow` after the customer is created.
|
||||
|
||||
To consume the hook, create the file `src/workflow/hooks/customer-created.ts` with the following content:
|
||||
To consume the hook, create the file `src/workflow/hooks/user-created.ts` with the following content:
|
||||
|
||||
```ts title="src/workflow/hooks/customer-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
```ts title="src/workflow/hooks/user-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import { createCustomersWorkflow } from "@medusajs/medusa/core-flows"
|
||||
import {
|
||||
createCustomFromCustomerWorkflow,
|
||||
|
||||
@@ -38,8 +38,8 @@ To retrieve a customer's carts with [Query](!docs!/learn/fundamentals/module-lin
|
||||
const { data: customers } = await query.graph({
|
||||
entity: "customer",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// customers.carts
|
||||
@@ -56,8 +56,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: customers } = useQueryGraphStep({
|
||||
entity: "customer",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// customers.carts
|
||||
@@ -83,8 +83,8 @@ To retrieve a customer's orders with [Query](!docs!/learn/fundamentals/module-li
|
||||
const { data: customers } = await query.graph({
|
||||
entity: "customer",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// customers.orders
|
||||
@@ -101,8 +101,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: customers } = useQueryGraphStep({
|
||||
entity: "customer",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// customers.orders
|
||||
|
||||
@@ -6,96 +6,154 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Customer Module provides customer-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Customer Module and how to use it in your application.
|
||||
|
||||
## How to Use Customer Module's Service
|
||||
Medusa has customer related features available out-of-the-box through the Customer Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Customer Module.
|
||||
|
||||
You can use the Customer Module's main service by resolving from the Medusa container the resource `Modules.CUSTOMER`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Customer Features
|
||||
|
||||
- [Customer Management](./customer-accounts/page.mdx): Store and manage guest and registered customers in your store.
|
||||
- [Customer Organization](/references/customer/models): Organize customers into groups. This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the [Promotion Module](../promotion/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Customer Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.CUSTOMER", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-customer.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const customerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
const createCustomerStep = createStep(
|
||||
"create-customer",
|
||||
async ({}, { container }) => {
|
||||
const customerModuleService = container.resolve(Modules.CUSTOMER)
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
})
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
|
||||
return new StepResponse({ customer }, customer.id)
|
||||
},
|
||||
async (customerId, { container }) => {
|
||||
if (!customerId) {
|
||||
return
|
||||
}
|
||||
const customerModuleService = container.resolve(Modules.CUSTOMER)
|
||||
|
||||
await customerModuleService.deleteCustomers([customerId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createCustomerWorkflow = createWorkflow(
|
||||
"create-customer",
|
||||
() => {
|
||||
const { customer } = createCustomerStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
customer,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createCustomerWorkflow } from "../../workflows/create-customer"
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await createCustomerWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const customerModuleService = request.scope.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
|
||||
res.json({
|
||||
customers: await customerModuleService.listCustomers(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createCustomerWorkflow } from "../workflows/create-customer"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createCustomerWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const customerModuleService = container.resolve(
|
||||
Modules.CUSTOMER
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createCustomerWorkflow } from "../workflows/create-customer"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createCustomerWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Customer Management
|
||||
|
||||
With the Customer Module, store and manage customers.
|
||||
|
||||
```ts
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
```
|
||||
|
||||
### Customer Organization
|
||||
|
||||
Organize customers into groups.
|
||||
|
||||
This has a lot of benefits and supports many use cases, such as provide discounts for specific customer groups using the Promotion Module.
|
||||
|
||||
```ts
|
||||
const customerGroup = await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: customerGroup.id,
|
||||
})
|
||||
```
|
||||
<CommerceModuleSections name="Customer" />
|
||||
+12
-12
@@ -49,8 +49,8 @@ To retrieve the return, pass `return.*` in `fields`.
|
||||
const { data: fulfillments } = await query.graph({
|
||||
entity: "fulfillment",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// fulfillments.order
|
||||
@@ -67,8 +67,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: fulfillments } = useQueryGraphStep({
|
||||
entity: "fulfillment",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// fulfillments.order
|
||||
@@ -142,8 +142,8 @@ To retrieve the price set of a shipping option with [Query](!docs!/learn/fundame
|
||||
const { data: shippingOptions } = await query.graph({
|
||||
entity: "shipping_option",
|
||||
fields: [
|
||||
"price_set.*"
|
||||
]
|
||||
"price_set.*",
|
||||
],
|
||||
})
|
||||
|
||||
// shippingOptions.price_set
|
||||
@@ -160,8 +160,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: shippingOptions } = useQueryGraphStep({
|
||||
entity: "shipping_option",
|
||||
fields: [
|
||||
"price_set.*"
|
||||
]
|
||||
"price_set.*",
|
||||
],
|
||||
})
|
||||
|
||||
// shippingOptions.price_set
|
||||
@@ -245,8 +245,8 @@ To retrieve the stock location of a fulfillment provider, pass `locations.*` in
|
||||
const { data: fulfillmentSets } = await query.graph({
|
||||
entity: "fulfillment_set",
|
||||
fields: [
|
||||
"location.*"
|
||||
]
|
||||
"location.*",
|
||||
],
|
||||
})
|
||||
|
||||
// fulfillmentSets.location
|
||||
@@ -263,8 +263,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: fulfillmentSets } = useQueryGraphStep({
|
||||
entity: "fulfillment_set",
|
||||
fields: [
|
||||
"location.*"
|
||||
]
|
||||
"location.*",
|
||||
],
|
||||
})
|
||||
|
||||
// fulfillmentSets.location
|
||||
|
||||
@@ -6,162 +6,176 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Fulfillment Module provides fulfillment-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Fulfillment Module and how to use it in your application.
|
||||
|
||||
## How to Use Fulfillment Module's Service
|
||||
Medusa has fulfillment related features available out-of-the-box through the Fulfillment Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Fulfillment Module.
|
||||
|
||||
You can use the Fulfillment Module's main service by resolving from the Medusa container the resource `Modules.FULFILLMENT`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Fulfillment Features
|
||||
|
||||
- [Fulfillment Management](./item-fulfillment/page.mdx): Create fulfillments and keep track of their status, items, and more.
|
||||
- [Integrate Third-Party Fulfillment Providers](./fulfillment-provider/page.mdx): Create third-party fulfillment providers to provide customers with shipping options and fulfill their orders.
|
||||
- [Restrict By Location and Rules](./shipping-option/page.mdx): Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options.
|
||||
- [Support Different Fulfillment Forms](./concepts/page.mdx): Support various fulfillment forms, such as shipping or pick up.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Fulfillment Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.FULFILLMENT", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-fulfillment.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const fulfillmentModuleService = container.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
const createFulfillmentStep = createStep(
|
||||
"create-fulfillment",
|
||||
async ({}, { container }) => {
|
||||
const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
|
||||
|
||||
const fulfillments = await fulfillmentModuleService.listFulfillments()
|
||||
})
|
||||
const fulfillment = await fulfillmentModuleService.createFulfillment({
|
||||
location_id: "loc_123",
|
||||
provider_id: "webshipper",
|
||||
delivery_address: {
|
||||
country_code: "us",
|
||||
city: "Strongsville",
|
||||
address_1: "18290 Royalton Rd",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
sku: "SHIRT",
|
||||
quantity: 1,
|
||||
barcode: "123456",
|
||||
},
|
||||
],
|
||||
labels: [],
|
||||
order: {},
|
||||
})
|
||||
|
||||
return new StepResponse({ fulfillment }, fulfillment.id)
|
||||
},
|
||||
async (fulfillmentId, { container }) => {
|
||||
if (!fulfillmentId) {
|
||||
return
|
||||
}
|
||||
const fulfillmentModuleService = container.resolve(Modules.FULFILLMENT)
|
||||
|
||||
await fulfillmentModuleService.deleteFulfillment(fulfillmentId)
|
||||
}
|
||||
)
|
||||
|
||||
export const createFulfillmentWorkflow = createWorkflow(
|
||||
"create-fulfillment",
|
||||
() => {
|
||||
const { fulfillment } = createFulfillmentStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
fulfillment,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createFulfillmentWorkflow } from "../../workflows/create-fuilfillment"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const fulfillmentModuleService = req.scope.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
) {
|
||||
const { result } = await createFulfillmentWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
fulfillments: await fulfillmentModuleService.listFulfillments(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createFulfillmentWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const fulfillmentModuleService = container.resolve(
|
||||
Modules.FULFILLMENT
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const fulfillments = await fulfillmentModuleService.listFulfillments()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createFulfillmentWorkflow } from "../workflows/create-fuilfillment"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createFulfillmentWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Fulfillment Management
|
||||
|
||||
Create fulfillments and keep track of their status, items, and more.
|
||||
|
||||
```ts
|
||||
const fulfillment = await fulfillmentModuleService.createFulfillment({
|
||||
location_id: "loc_123",
|
||||
provider_id: "webshipper",
|
||||
delivery_address: {
|
||||
country_code: "us",
|
||||
city: "Strongsville",
|
||||
address_1: "18290 Royalton Rd",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
sku: "SHIRT",
|
||||
quantity: 1,
|
||||
barcode: "123456",
|
||||
},
|
||||
],
|
||||
labels: [],
|
||||
order: {},
|
||||
})
|
||||
```
|
||||
|
||||
### Integrate Third-Party Fulfillment Providers
|
||||
|
||||
Use third-party fulfillment providers to provide customers with shipping options and fulfill their orders.
|
||||
|
||||
```ts
|
||||
const shippingOption = await fulfillmentModuleService.createShippingOptions({
|
||||
// ...
|
||||
provider_id: "webshipper",
|
||||
})
|
||||
```
|
||||
|
||||
### Restrict By Location and Rules
|
||||
|
||||
Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options.
|
||||
|
||||
```ts
|
||||
const serviceZone = await fulfillmentModuleService.createServiceZones({
|
||||
name: "US",
|
||||
fulfillment_set_id: "fset_123",
|
||||
geo_zones: [
|
||||
{
|
||||
type: "country",
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption = await fulfillmentModuleService.createShippingOptions({
|
||||
name: "Express Shipping",
|
||||
// restrict location
|
||||
service_zone_id: serviceZone.id,
|
||||
// restrict by custom rules
|
||||
rules: [
|
||||
{
|
||||
field: "customer_group",
|
||||
operator: "eq",
|
||||
value: "vip",
|
||||
},
|
||||
],
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### Support Different Fulfillment Forms
|
||||
|
||||
Support various fulfillment forms, such as shipping or pick up.
|
||||
|
||||
```ts
|
||||
const fulfillmentSets = await fulfillmentModuleService.createFulfillmentSets([
|
||||
{
|
||||
name: "Shipping",
|
||||
type: "shipping",
|
||||
},
|
||||
{
|
||||
name: "Pick-up",
|
||||
type: "pick-up",
|
||||
},
|
||||
])
|
||||
```
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Configure Fulfillment Module
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
The Fulfillment Module accepts options for further configurations. Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="Fulfillment" />
|
||||
@@ -1,526 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Inventory Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Inventory Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Inventory Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create an Inventory Item
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItem = await inventoryModuleService.createInventoryItems({
|
||||
sku: request.body.sku,
|
||||
title: request.body.title,
|
||||
requires_shipping: request.body.requires_shipping,
|
||||
})
|
||||
|
||||
res.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryItem = await inventoryModuleService.createInventoryItems({
|
||||
sku: body.sku,
|
||||
title: body.title,
|
||||
requires_shipping: body.requires_shipping,
|
||||
})
|
||||
|
||||
return NextResponse.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Inventory Items
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
|
||||
res.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
|
||||
return NextResponse.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve an Inventory Item by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryItem = await inventoryModuleService.retrieveInventoryItem(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: Request, { params }: ContextType) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
|
||||
const inventoryItem = await inventoryModuleService.retrieveInventoryItem(
|
||||
params.id
|
||||
)
|
||||
|
||||
return NextResponse.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create an Inventory Level
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryLevel = await inventoryModuleService.createInventoryLevels({
|
||||
inventory_item_id: request.body.inventory_item_id,
|
||||
location_id: request.body.location_id,
|
||||
stocked_quantity: request.body.quantity,
|
||||
})
|
||||
|
||||
res.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryLevel = await inventoryModuleService.createInventoryLevels({
|
||||
inventory_item_id: body.inventory_item_id,
|
||||
location_id: body.location_id,
|
||||
stocked_quantity: body.quantity,
|
||||
})
|
||||
|
||||
return NextResponse.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Adjust Inventory in Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const inventoryLevel = await inventoryModuleService.adjustInventory(
|
||||
request.body.inventory_item_id,
|
||||
request.body.location_id,
|
||||
request.body.new_quantity
|
||||
)
|
||||
|
||||
res.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryLevel = await inventoryModuleService.adjustInventory(
|
||||
body.inventory_item_id,
|
||||
body.location_id,
|
||||
body.new_quantity
|
||||
)
|
||||
|
||||
return NextResponse.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Confirm Inventory Item Availability
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
res.json({
|
||||
is_available: await inventoryModuleService.confirmInventory(
|
||||
request.body.inventory_item_id,
|
||||
[request.body.location_id],
|
||||
request.body.required_quantity
|
||||
),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
return NextResponse.json({
|
||||
is_available: await inventoryModuleService.confirmInventory(
|
||||
body.inventory_item_id,
|
||||
[body.location_id],
|
||||
body.required_quantity
|
||||
),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Reservation Item
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const reservationItem = await inventoryModuleService.createReservationItems({
|
||||
inventory_item_id: request.body.inventory_item_id,
|
||||
location_id: request.body.location_id,
|
||||
quantity: request.body.reserved_quantity,
|
||||
})
|
||||
|
||||
res.json({ reservation_item: reservationItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const reservationItem = await inventoryModuleService.createReservationItems({
|
||||
inventory_item_id: body.inventory_item_id,
|
||||
location_id: body.location_id,
|
||||
quantity: body.reserved_quantity,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
reservation_item: reservationItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Quantity Details
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
const reservedQuantity =
|
||||
await inventoryModuleService.retrieveReservedQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
const stockedQuantity = await inventoryModuleService.retrieveStockedQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
const availableQuantity =
|
||||
await inventoryModuleService.retrieveAvailableQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
|
||||
res.json({
|
||||
reserved_quantity: reservedQuantity,
|
||||
stocked_quantity: stockedQuantity,
|
||||
available_quantity: availableQuantity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
inventory_item_id: string
|
||||
location_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
|
||||
const reservedQuantity =
|
||||
await inventoryModuleService.retrieveReservedQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
const stockedQuantity = await inventoryModuleService.retrieveStockedQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
const availableQuantity =
|
||||
await inventoryModuleService.retrieveAvailableQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
reserved_quantity: reservedQuantity,
|
||||
stocked_quantity: stockedQuantity,
|
||||
available_quantity: availableQuantity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Reservation
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
|
||||
await inventoryModuleService.deleteReservationItems([
|
||||
request.params.reservation_id,
|
||||
])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { initialize as initializeInventoryModule } from "@medusajs/medusa/inventory"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
reservation_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request, { params }: ContextType) {
|
||||
const inventoryModuleService = await initializeInventoryModule({})
|
||||
|
||||
await inventoryModuleService.deleteReservationItems([params.reservation_id])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Inventory Module's main service reference](/references/inventory-next) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -42,8 +42,8 @@ To retrieve the product variants of an inventory item with [Query](!docs!/learn/
|
||||
const { data: inventoryItems } = await query.graph({
|
||||
entity: "inventory_item",
|
||||
fields: [
|
||||
"variants.*"
|
||||
]
|
||||
"variants.*",
|
||||
],
|
||||
})
|
||||
|
||||
// inventoryItems.variants
|
||||
@@ -60,8 +60,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: inventoryItems } = useQueryGraphStep({
|
||||
entity: "inventory_item",
|
||||
fields: [
|
||||
"variants.*"
|
||||
]
|
||||
"variants.*",
|
||||
],
|
||||
})
|
||||
|
||||
// inventoryItems.variants
|
||||
@@ -131,8 +131,8 @@ To retrieve the stock locations of an inventory level with [Query](!docs!/learn/
|
||||
const { data: inventoryLevels } = await query.graph({
|
||||
entity: "inventory_level",
|
||||
fields: [
|
||||
"stock_locations.*"
|
||||
]
|
||||
"stock_locations.*",
|
||||
],
|
||||
})
|
||||
|
||||
// inventoryLevels.stock_locations
|
||||
@@ -149,8 +149,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: inventoryLevels } = useQueryGraphStep({
|
||||
entity: "inventory_level",
|
||||
fields: [
|
||||
"stock_locations.*"
|
||||
]
|
||||
"stock_locations.*",
|
||||
],
|
||||
})
|
||||
|
||||
// inventoryLevels.stock_locations
|
||||
|
||||
@@ -6,130 +6,156 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Inventory Module provides inventory-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Inventory Module and how to use it in your application.
|
||||
|
||||
## How to Use Inventory Module's Service
|
||||
Medusa has inventory related features available out-of-the-box through the Inventory Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Inventory Module.
|
||||
|
||||
You can use the Inventory Module's main service by resolving from the Medusa container the resource `Modules.INVENTORY`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Inventory Features
|
||||
|
||||
- [Inventory Items Management](./concepts/page.mdx): Store and manage inventory of any stock-kept item, such as product variants.
|
||||
- [Inventory Across Locations](./concepts/page.mdx#inventorylevel): Manage inventory levels across different locations, such as warehouses.
|
||||
- [Reservation Management](./concepts/page.mdx#reservationitem): Reserve quantities of inventory items at specific locations for orders or other purposes.
|
||||
- [Check Inventory Availability](/references/inventory-next/confirmInventory): Check whether an inventory item has the necessary quantity for purchase.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Inventory Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.INVENTORY", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-inventory-item.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const inventoryModuleService = container.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
const createInventoryItemStep = createStep(
|
||||
"create-inventory-item",
|
||||
async ({}, { container }) => {
|
||||
const inventoryModuleService = container.resolve(Modules.INVENTORY)
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
})
|
||||
const inventoryItem = await inventoryModuleService.createInventoryItems({
|
||||
sku: "SHIRT",
|
||||
title: "Green Medusa Shirt",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
return new StepResponse({ inventoryItem }, inventoryItem.id)
|
||||
},
|
||||
async (inventoryItemId, { container }) => {
|
||||
if (!inventoryItemId) {
|
||||
return
|
||||
}
|
||||
const inventoryModuleService = container.resolve(Modules.INVENTORY)
|
||||
|
||||
await inventoryModuleService.deleteInventoryItems([inventoryItemId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createInventoryItemWorkflow = createWorkflow(
|
||||
"create-inventory-item-workflow",
|
||||
() => {
|
||||
const { inventoryItem } = createInventoryItemStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
inventoryItem,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createInventoryItemWorkflow } from "../../workflows/create-inventory-item"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService = request.scope.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
) {
|
||||
const { result } = await createInventoryItemWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
inventory_items: await inventoryModuleService.listInventoryItems({}),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createInventoryItemWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const inventoryModuleService = container.resolve(
|
||||
Modules.INVENTORY
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const inventoryItems = await inventoryModuleService.listInventoryItems({})
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createInventoryItemWorkflow } from "../workflows/create-inventory-item"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createInventoryItemWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Inventory Items Management
|
||||
|
||||
Store and manage inventory of any stock-kept item, such as product variants.
|
||||
|
||||
Inventory items hold details of the underlying stock-kept item, as well as inventory details such as whether the item requires shipping.
|
||||
|
||||
```ts
|
||||
const inventoryItem = await inventoryModuleService.createInventoryItems({
|
||||
sku: "SHIRT",
|
||||
title: "Green Medusa Shirt",
|
||||
requires_shipping: true,
|
||||
})
|
||||
```
|
||||
|
||||
### Inventory Across Locations
|
||||
|
||||
Inventory items' quantities are set per locations through inventory levels.
|
||||
|
||||
This gives you more flexibility in managing the quantity of a stock-kept item across different locations, such as different warehouses.
|
||||
|
||||
```ts
|
||||
const inventoryLevel = await inventoryModuleService.createInventoryLevels([
|
||||
{
|
||||
inventory_item_id: "iitem_123",
|
||||
location_id: "sloc_123",
|
||||
stocked_quantity: 20,
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Reservation Management
|
||||
|
||||
Reserve quantities of inventory items at specific locations for orders or other purposes.
|
||||
|
||||
The reserved quantity isn't considered for purchase, but can be deleted to revert the reservation.
|
||||
|
||||
```ts
|
||||
const reservationItem = await inventoryModuleService.createReservationItems([
|
||||
{
|
||||
inventory_item_id: "iitem_123",
|
||||
location_id: "sloc_123",
|
||||
quantity: 10,
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Check Inventory Availability
|
||||
|
||||
Check whether an inventory item has the necessary quantity for purchase.
|
||||
|
||||
Any reserved quantity is considered unavailable.
|
||||
|
||||
```ts
|
||||
const isAvailable = await inventoryModuleService.confirmInventory(
|
||||
inventoryItemId,
|
||||
[locationId],
|
||||
neededQuantity
|
||||
)
|
||||
```
|
||||
<CommerceModuleSections name="Inventory" />
|
||||
@@ -47,8 +47,8 @@ To retrieve the customer of an order with [Query](!docs!/learn/fundamentals/modu
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"customer.*"
|
||||
]
|
||||
"customer.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.customer
|
||||
@@ -65,8 +65,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"customer.*"
|
||||
]
|
||||
"customer.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.customer
|
||||
@@ -96,8 +96,8 @@ To retrieve the cart of an order with [Query](!docs!/learn/fundamentals/module-l
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"cart.*"
|
||||
]
|
||||
"cart.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.cart
|
||||
@@ -114,8 +114,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"cart.*"
|
||||
]
|
||||
"cart.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.cart
|
||||
@@ -197,8 +197,8 @@ To retrieve the fulfillments of a return, pass `fulfillments.*` in `fields`.
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"fulfillments.*"
|
||||
]
|
||||
"fulfillments.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.fulfillments
|
||||
@@ -215,8 +215,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"fulfillments.*"
|
||||
]
|
||||
"fulfillments.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.fulfillments
|
||||
@@ -290,8 +290,8 @@ To retrieve the payment collections of an order, order exchange, or order claim
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"payment_collections.*"
|
||||
]
|
||||
"payment_collections.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.payment_collections
|
||||
@@ -308,8 +308,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"payment_collections.*"
|
||||
]
|
||||
"payment_collections.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.payment_collections
|
||||
@@ -388,8 +388,8 @@ To retrieve the product, pass `product.*` in `fields`.
|
||||
const { data: lineItems } = await query.graph({
|
||||
entity: "order_line_item",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// lineItems.variant
|
||||
@@ -406,8 +406,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: lineItems } = useQueryGraphStep({
|
||||
entity: "order_line_item",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// lineItems.variant
|
||||
@@ -435,8 +435,8 @@ To retrieve the promotion applied on an order with [Query](!docs!/learn/fundamen
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"promotion.*"
|
||||
]
|
||||
"promotion.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.promotion
|
||||
@@ -453,8 +453,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"promotion.*"
|
||||
]
|
||||
"promotion.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.promotion
|
||||
@@ -524,8 +524,8 @@ To retrieve the region of an order with [Query](!docs!/learn/fundamentals/module
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"region.*"
|
||||
]
|
||||
"region.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.region
|
||||
@@ -542,8 +542,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"region.*"
|
||||
]
|
||||
"region.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.region
|
||||
@@ -569,8 +569,8 @@ To retrieve the sales channel of an order with [Query](!docs!/learn/fundamentals
|
||||
const { data: orders } = await query.graph({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"sales_channel.*"
|
||||
]
|
||||
"sales_channel.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.sales_channel
|
||||
@@ -587,8 +587,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"sales_channel.*"
|
||||
]
|
||||
"sales_channel.*",
|
||||
],
|
||||
})
|
||||
|
||||
// orders.sales_channel
|
||||
|
||||
@@ -6,138 +6,169 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Order Module provides order-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Order Module and how to use it in your application.
|
||||
|
||||
## How to Use Order Module's Service
|
||||
Medusa has order related features available out-of-the-box through the Order Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Order Module.
|
||||
|
||||
You can use the Order Module's main service by resolving from the Medusa container the resource `Modules.ORDER`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Order Features
|
||||
|
||||
- [Order Management](./concepts/page.mdx): Store and manage your orders to retrieve, create, cancel, and perform other operations.
|
||||
- Draft Orders: Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
|
||||
- [Apply Promotion Adjustments](./promotion-adjustments/page.mdx): Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
- [Apply Tax Lines](./tax-lines/page.mdx): Apply tax lines to an order's line items and shipping methods.
|
||||
- [Returns](./return/page.mdx), [Edits](./edit/page.mdx), [Exchanges](./exchange/page.mdx), and [Claims](./claim/page.mdx): Make [changes](./order-change/page.mdx) to an order to edit, return, or exchange its items, with [version-based control](./order-versioning/page.mdx) over the order's timeline.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Order Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.ORDER", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-draft-order.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const orderModuleService = container.resolve(
|
||||
Modules.ORDER
|
||||
)
|
||||
const orders = await orderModuleService.listOrders()
|
||||
})
|
||||
const createDraftOrderStep = createStep(
|
||||
"create-order",
|
||||
async ({}, { container }) => {
|
||||
const orderModuleService = container.resolve(Modules.ORDER)
|
||||
|
||||
const draftOrder = await orderModuleService.createOrders({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
quantity: 1,
|
||||
unit_price: 3000,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "Express shipping",
|
||||
amount: 3000,
|
||||
},
|
||||
],
|
||||
status: "draft",
|
||||
})
|
||||
|
||||
return new StepResponse({ draftOrder }, draftOrder.id)
|
||||
},
|
||||
async (draftOrderId, { container }) => {
|
||||
if (!draftOrderId) {
|
||||
return
|
||||
}
|
||||
const orderModuleService = container.resolve(Modules.ORDER)
|
||||
|
||||
await orderModuleService.deleteOrders([draftOrderId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createDraftOrderWorkflow = createWorkflow(
|
||||
"create-draft-order",
|
||||
() => {
|
||||
const { draftOrder } = createDraftOrderStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
draftOrder,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createDraftOrderWorkflow } from "../../workflows/create-draft-order"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const orderModuleService = req.scope.resolve(
|
||||
Modules.ORDER
|
||||
)
|
||||
) {
|
||||
const { result } = await createDraftOrderWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
orders: await orderModuleService.listOrders(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createDraftOrderWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const orderModuleService = container.resolve(
|
||||
Modules.ORDER
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const orders = await orderModuleService.listOrders()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createDraftOrderWorkflow } from "../workflows/create-draft-order"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createDraftOrderWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Order Management
|
||||
|
||||
Store and manage your orders to retrieve, create, cancel, and perform other operations.
|
||||
|
||||
```ts
|
||||
const order = await orderModuleService.createOrders({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
quantity: 1,
|
||||
unit_price: 3000,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "Express shipping",
|
||||
amount: 3000,
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Draft Orders
|
||||
|
||||
Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
|
||||
|
||||
```ts
|
||||
const draftOrder = await orderModuleService.createOrders({
|
||||
currency_code: "usd",
|
||||
// other details...
|
||||
status: "draft",
|
||||
is_draft_order: true,
|
||||
})
|
||||
```
|
||||
|
||||
### Apply Promotions
|
||||
|
||||
Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
|
||||
```ts
|
||||
const lineAdjustments = await orderModuleService.createOrderLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
code: "50OFF",
|
||||
amount: 500,
|
||||
})
|
||||
|
||||
const shippingAdjustments =
|
||||
await orderModuleService.createOrderShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
code: "FREESHIPPING",
|
||||
amount: 1000,
|
||||
})
|
||||
```
|
||||
|
||||
### Returns, Exchanges, and Claims
|
||||
|
||||
Return or exchange items, with version-based control over the order's timeline.
|
||||
|
||||
```ts
|
||||
const orderReturn = await orderModuleService.createReturn({
|
||||
order_id: "order_123",
|
||||
items: [{
|
||||
id: "orditem_123",
|
||||
quantity: 1,
|
||||
}],
|
||||
})
|
||||
```
|
||||
<CommerceModuleSections name="Order" />
|
||||
@@ -1,359 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Payment Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Payment Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Payment Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Payment Collection
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const paymentCollection = await paymentModuleService.createPaymentCollections(
|
||||
{
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
payment_collection: paymentCollection,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentCollection = await paymentModuleService.createPaymentCollections(
|
||||
{
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
payment_collection: paymentCollection,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const paymentSession = await paymentModuleService.createPaymentSession(
|
||||
"pay_col_123",
|
||||
{
|
||||
currency_code: "usd",
|
||||
provider_id: "system",
|
||||
amount: 4000,
|
||||
data: {},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
payment_session: paymentSession,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentSession = await paymentModuleService.createPaymentSession(
|
||||
"pay_col_123",
|
||||
{
|
||||
currency_code: "usd",
|
||||
provider_id: "system",
|
||||
amount: 4000,
|
||||
data: {},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
payment_session: paymentSession,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Payment Sessions of Payment Collection
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const paymentSessions = await paymentModuleService.listPaymentSessions({
|
||||
payment_collection_id: ["pay_col_123"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
payment_sessions: paymentSessions,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentSessions = await paymentModuleService.listPaymentSessions({
|
||||
payment_collection_id: ["pay_col_123"],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payment_sessions: paymentSessions,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Authorize Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const payment = await paymentModuleService.authorizePaymentSession(
|
||||
"payses_123",
|
||||
{}
|
||||
)
|
||||
|
||||
res.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payment = await paymentModuleService.authorizePaymentSession(
|
||||
"payses_123",
|
||||
{}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Payments of Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const payments = await paymentModuleService.listPayments({
|
||||
session_id: "payses_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
payments,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payments = await paymentModuleService.listPayments({
|
||||
session_id: "payses_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payments,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Capture Payment
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
|
||||
const payment = await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePaymentModule } from "@medusajs/medusa/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payment = await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Payment Module's main service reference](/references/payment) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -39,8 +39,8 @@ To retrieve the cart associated with the payment collection with [Query](!docs!/
|
||||
const { data: paymentCollections } = await query.graph({
|
||||
entity: "payment_collection",
|
||||
fields: [
|
||||
"cart.*"
|
||||
]
|
||||
"cart.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentCollections.cart
|
||||
@@ -57,8 +57,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: paymentCollections } = useQueryGraphStep({
|
||||
entity: "payment_collection",
|
||||
fields: [
|
||||
"cart.*"
|
||||
]
|
||||
"cart.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentCollections.cart
|
||||
@@ -131,8 +131,8 @@ To retrieve the order of a payment collection with [Query](!docs!/learn/fundamen
|
||||
const { data: paymentCollections } = await query.graph({
|
||||
entity: "payment_collection",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentCollections.order
|
||||
@@ -149,8 +149,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: paymentCollections } = useQueryGraphStep({
|
||||
entity: "payment_collection",
|
||||
fields: [
|
||||
"order.*"
|
||||
]
|
||||
"order.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentCollections.order
|
||||
@@ -224,8 +224,8 @@ To retrieve the regions of a payment provider with [Query](!docs!/learn/fundamen
|
||||
const { data: paymentProviders } = await query.graph({
|
||||
entity: "payment_provider",
|
||||
fields: [
|
||||
"regions.*"
|
||||
]
|
||||
"regions.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentProviders.regions
|
||||
@@ -242,8 +242,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: paymentProviders } = useQueryGraphStep({
|
||||
entity: "payment_provider",
|
||||
fields: [
|
||||
"regions.*"
|
||||
]
|
||||
"regions.*",
|
||||
],
|
||||
})
|
||||
|
||||
// paymentProviders.regions
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
import { Table } from "docs-ui"
|
||||
import { CodeTabs, CodeTab, ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Payment Module`,
|
||||
@@ -7,130 +6,170 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Payment Module provides payment-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Payment Module and how to use it in your application.
|
||||
|
||||
## How to Use Payment Module's Service
|
||||
Medusa has payment related features available out-of-the-box through the Payment Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Payment Module.
|
||||
|
||||
You can use the Payment Module's main service by resolving from the Medusa container the resource `Modules.PAYMENT`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Payment Features
|
||||
|
||||
- [Authorize, Capture, and Refund Payments](./payment/page.mdx): Authorize, capture, and refund payments for a single resource.
|
||||
- [Payment Collection Management](./payment-collection/page.mdx): Store and manage all payments of a single resources, such as a cart, in payment collections.
|
||||
- [Integrate Third-Party Payment Providers](./payment-provider/page.mdx): Use payment providers like [Stripe](./payment-provider/stripe/page.mdx) to handle and process payments, or integrate custom payment providers.
|
||||
- [Handle Webhook Events](./webhook-events/page.mdx): Handle webhook events from third-party providers and process the associated payment.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Payment Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.PAYMENT", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-payment-collection.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const paymentModuleService = container.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
const createPaymentCollectionStep = createStep(
|
||||
"create-payment-collection",
|
||||
async ({}, { container }) => {
|
||||
const paymentModuleService = container.resolve(Modules.PAYMENT)
|
||||
|
||||
const payment_collections =
|
||||
await paymentModuleService.listPaymentCollections()
|
||||
})
|
||||
const paymentCollection = await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
})
|
||||
|
||||
return new StepResponse({ paymentCollection }, paymentCollection.id)
|
||||
},
|
||||
async (paymentCollectionId, { container }) => {
|
||||
if (!paymentCollectionId) {
|
||||
return
|
||||
}
|
||||
const paymentModuleService = container.resolve(Modules.PAYMENT)
|
||||
|
||||
await paymentModuleService.deletePaymentCollections([paymentCollectionId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createPaymentCollectionWorkflow = createWorkflow(
|
||||
"create-payment-collection",
|
||||
() => {
|
||||
const { paymentCollection } = createPaymentCollectionStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
paymentCollection,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createPaymentCollectionWorkflow } from "../../workflows/create-payment-collection"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService = req.scope.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
) {
|
||||
const { result } = await createPaymentCollectionWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
payment_collections: await paymentModuleService.listPaymentCollections(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createPaymentCollectionWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const paymentModuleService = container.resolve(
|
||||
Modules.PAYMENT
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const payment_collections =
|
||||
await paymentModuleService.listPaymentCollections()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createPaymentCollectionWorkflow } from "../workflows/create-payment-collection"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createPaymentCollectionWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Add Payment Functionalities to Any Resource
|
||||
|
||||
The Payment Module provides payment functionalities that allow you to process payment of any resource, such as a cart.
|
||||
|
||||
All payment processing starts with creating a payment collection, which carries information for authorized, captured, and refunded payments for a single resource.
|
||||
|
||||
```ts
|
||||
const paymentCollection = await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
})
|
||||
```
|
||||
|
||||
### Authorize, Capture, and Refund Payment
|
||||
|
||||
Receive and handle payments, including authorizing, capturing, and refunding payment.
|
||||
|
||||
```ts
|
||||
await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_1",
|
||||
})
|
||||
```
|
||||
|
||||
### Integrate Third-Party Payment Providers
|
||||
|
||||
Use payment providers like Stripe to handle and process payments.
|
||||
|
||||
```ts
|
||||
const payment = await paymentModuleService.createPaymentSession("pay_col_1", {
|
||||
provider_id: "stripe",
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
data: {
|
||||
// necessary data for the payment provider
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Handle Webhook Events
|
||||
|
||||
The Payment Module allows you to handle webhook events from third-party providers and process the associated payment.
|
||||
|
||||
```ts
|
||||
await paymentModuleService.processEvent({
|
||||
provider: "stripe",
|
||||
payload: {
|
||||
// webhook payload
|
||||
},
|
||||
})
|
||||
```
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Configure Payment Module
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
The Payment Module accepts options for further configurations. Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## Providers
|
||||
|
||||
Medusa provides the following payment providers out-of-the-box. You can use them to process payments for orders, returns, and other resources.
|
||||
|
||||
<ChildDocs showItems={["Providers"]} hideTitle />
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="Payment" />
|
||||
@@ -1,395 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Pricing Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Pricing Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Pricing Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Price Set
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const priceSet = await pricingModuleService.createPriceSets([
|
||||
{
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 10,
|
||||
rules: {
|
||||
region_id: "reg_123",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
const body = await request.json()
|
||||
|
||||
const priceSet = await pricingModuleService.createPriceSets([
|
||||
{
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 10,
|
||||
rules: {
|
||||
region_id: "reg_123",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Price Sets
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
|
||||
res.json({ price_sets: priceSets })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
|
||||
return NextResponse.json({ price_sets: priceSets })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Price Set by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const priceSet = await pricingModuleService.retrievePriceSet("pset_123")
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSet = await pricingModuleService.retrievePriceSet("pset_123")
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Prices with Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId: "pset_123",
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
rules: {
|
||||
region_id: "reg_123",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
const body = await request.json()
|
||||
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId: "pset_123",
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
rules: {
|
||||
region_id: "reg_123",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Price List
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { IPricingModuleService } from "@medusajs/framework/types"
|
||||
import { PriceListType } from "@medusajs/framework/utils"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const priceLists = await pricingModuleService.createPriceLists([
|
||||
{
|
||||
title: "My Sale",
|
||||
description: "This is my sale",
|
||||
type: PriceListType.SALE,
|
||||
starts_at: Date.parse("01/10/2023").toString(),
|
||||
ends_at: Date.parse("31/10/2023").toString(),
|
||||
rules: {
|
||||
region_id: ["reg_123", "reg_321"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: "pset_124",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ price_lists: priceLists })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
import { PriceListType } from "@medusajs/framework/utils"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
import { PriceListType } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceLists = await pricingModuleService.createPriceLists([
|
||||
{
|
||||
title: "My Sale",
|
||||
description: "This is my sale",
|
||||
type: PriceListType.SALE,
|
||||
starts_at: Date.parse("01/10/2023").toString(),
|
||||
ends_at: Date.parse("31/10/2023").toString(),
|
||||
rules: {
|
||||
region_id: ["reg_123", "reg_321"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: "pset_124",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ price_lists: priceLists })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Calculate Prices For a Currency
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{
|
||||
id: ["pset_123"],
|
||||
},
|
||||
{
|
||||
context: {
|
||||
currency_code: "eur",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({ price })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePricingModule } from "@medusajs/medusa/pricing"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{
|
||||
id: ["pset_123"],
|
||||
},
|
||||
{
|
||||
context: {
|
||||
currency_code: "eur",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({ price })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Pricing Module's main service reference](/references/pricing) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -36,8 +36,8 @@ To retrieve the shipping option of a price set with [Query](!docs!/learn/fundame
|
||||
const { data: priceSets } = await query.graph({
|
||||
entity: "price_set",
|
||||
fields: [
|
||||
"shipping_option.*"
|
||||
]
|
||||
"shipping_option.*",
|
||||
],
|
||||
})
|
||||
|
||||
// priceSets.shipping_option
|
||||
@@ -54,8 +54,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: priceSets } = useQueryGraphStep({
|
||||
entity: "price_set",
|
||||
fields: [
|
||||
"shipping_option.*"
|
||||
]
|
||||
"shipping_option.*",
|
||||
],
|
||||
})
|
||||
|
||||
// priceSets.shipping_option
|
||||
@@ -133,8 +133,8 @@ To retrieve the variant of a price set with [Query](!docs!/learn/fundamentals/mo
|
||||
const { data: priceSets } = await query.graph({
|
||||
entity: "price_set",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// priceSets.variant
|
||||
@@ -151,8 +151,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: priceSets } = useQueryGraphStep({
|
||||
entity: "price_set",
|
||||
fields: [
|
||||
"variant.*"
|
||||
]
|
||||
"variant.*",
|
||||
],
|
||||
})
|
||||
|
||||
// priceSets.variant
|
||||
|
||||
@@ -6,157 +6,167 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Pricing Module provides pricing-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Pricing Module and how to use it in your application.
|
||||
|
||||
## How to Use Pricing Module's Service
|
||||
Medusa has pricing related features available out-of-the-box through the Pricing Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Pricing Module.
|
||||
|
||||
You can use the Pricing Module's main service by resolving from the Medusa container the resource `Modules.PRICING`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Pricing Features
|
||||
|
||||
- [Price Management](./concepts/page.mdx): Store and manage prices of a resource, such as a product or a variant.
|
||||
- [Advanced Rule Engine](./price-rules/page.mdx): Create prices with custom rules to condition prices based on different contexts.
|
||||
- [Price Lists](./concepts/page.mdx#price-list): Group prices and apply them only in specific conditions with price lists.
|
||||
- [Price Calculation Strategy](./price-calculation/page.mdx): Retrieve the best price in a given context and for the specified rule values.
|
||||
- [Tax-Inclusive Pricing](./tax-inclusive-pricing/page.mdx): Calculate prices with taxes included in the price, and Medusa will handle calculating the taxes automatically.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Pricing Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.PRICING", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-price-set.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const pricingModuleService = container.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
const createPriceSetStep = createStep(
|
||||
"create-price-set",
|
||||
async ({}, { container }) => {
|
||||
const pricingModuleService = container.resolve(Modules.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
})
|
||||
const priceSet = await pricingModuleService.createPriceSets({
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
},
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
min_quantity: 0,
|
||||
max_quantity: 4,
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return new StepResponse({ priceSet }, priceSet.id)
|
||||
},
|
||||
async (priceSetId, { container }) => {
|
||||
if (!priceSetId) {
|
||||
return
|
||||
}
|
||||
const pricingModuleService = container.resolve(Modules.PRICING)
|
||||
|
||||
await pricingModuleService.deletePriceSets([priceSetId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createPriceSetWorkflow = createWorkflow(
|
||||
"create-price-set",
|
||||
() => {
|
||||
const { priceSet } = createPriceSetStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
priceSet,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createPriceSetWorkflow } from "../../workflows/create-price-set"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService = request.scope.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
) {
|
||||
const { result } = await createPriceSetWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
price_sets: await pricingModuleService.listPriceSets(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createPriceSetWorkflow } from "../workflows/create-price-set"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createPriceSetWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const pricingModuleService = container.resolve(
|
||||
Modules.PRICING
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createPriceSetWorkflow } from "../workflows/create-price-set"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createPriceSetWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Price Management
|
||||
|
||||
Store the prices of a resource and manage them through the main service's methods.
|
||||
|
||||
Prices are grouped in a price set, allowing you to add more than one price for a resource based on different conditions, such as currency code.
|
||||
|
||||
```ts
|
||||
const priceSet = await pricingModuleService.createPriceSets({
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
},
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
min_quantity: 0,
|
||||
max_quantity: 4,
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced Rule Engine
|
||||
|
||||
Create prices with custom rules. This gives you more flexibility in how you condition prices, filter them, and ensure the best prices are retrieved for custom contexts.
|
||||
|
||||
```ts highlights={[["8"]]}
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId: "pset_123",
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "EUR",
|
||||
rules: {
|
||||
region_id: "reg_123",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Price Lists
|
||||
|
||||
Group prices and apply them only in specific conditions with price lists.
|
||||
|
||||
You can also use them to override existing prices for specified conditions, or create a sale.
|
||||
|
||||
```ts
|
||||
const priceList = await pricingModuleService.createPriceLists([
|
||||
{
|
||||
title: "My Sale",
|
||||
description: "Sale on selected items.",
|
||||
type: "sale",
|
||||
starts_at: Date.parse("01/10/2023").toString(),
|
||||
ends_at: Date.parse("31/10/2023").toString(),
|
||||
rules: {
|
||||
region_id: ["reg_123", "reg_321"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: "pset_123",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Price Calculation Strategy
|
||||
|
||||
Retrieve the best price in a given context and for the specified rule values.
|
||||
|
||||
```ts
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: ["pset_123"] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR",
|
||||
region_id: "reg_123",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
<CommerceModuleSections name="Pricing" />
|
||||
@@ -1,325 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Product Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Product Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Product Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create Product
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const products = await productModuleService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: [
|
||||
{
|
||||
value: "Black",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const products = await productModuleService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: [
|
||||
{
|
||||
value: "Black",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Products
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const products = await productModuleService.listProducts()
|
||||
|
||||
res.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const products = await productModuleService.listProducts()
|
||||
|
||||
return NextResponse.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Product by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const product = await productModuleService.retrieveProduct(request.params.id)
|
||||
|
||||
res.json({ product })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }
|
||||
) {
|
||||
const { id } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const product = await productModuleService.retrieveProduct(id)
|
||||
|
||||
return NextResponse.json({ product })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Product by its Handle
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const data = await productModuleService.listProducts({
|
||||
handle: "shirt",
|
||||
})
|
||||
|
||||
res.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.listProducts({
|
||||
handle: "shirt",
|
||||
})
|
||||
|
||||
return NextResponse.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Categories
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const categories = await productModuleService.listProductCategories()
|
||||
|
||||
res.json({ categories })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const categories = await productModuleService.listProductCategories()
|
||||
|
||||
return NextResponse.json({ categories })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Category by Handle
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab value="medusa" label="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
const data = await productModuleService.listProductCategories({
|
||||
handle: request.params.handle,
|
||||
})
|
||||
|
||||
res.json({ category: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab value="nextjs" label="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeProductModule } from "@medusajs/medusa/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }
|
||||
) {
|
||||
const { handle } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.listProductCategories({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ category: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Product Module's main service reference](/references/product) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -5,6 +5,7 @@ tags:
|
||||
- pricing
|
||||
- query
|
||||
- tax
|
||||
- server
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
|
||||
@@ -4,6 +4,7 @@ tags:
|
||||
- product
|
||||
- pricing
|
||||
- query
|
||||
- server
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
|
||||
@@ -50,8 +50,8 @@ To retrieve the line items of a product, pass `line_items.*` in `fields`.
|
||||
const { data: variants } = await query.graph({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"line_items.*"
|
||||
]
|
||||
"line_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.line_items
|
||||
@@ -68,8 +68,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: variants } = useQueryGraphStep({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"line_items.*"
|
||||
]
|
||||
"line_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.line_items
|
||||
@@ -101,8 +101,8 @@ To retrieve the inventory items of a product variant with [Query](!docs!/learn/f
|
||||
const { data: variants } = await query.graph({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"inventory_items.*"
|
||||
]
|
||||
"inventory_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.inventory_items
|
||||
@@ -119,8 +119,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: variants } = useQueryGraphStep({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"inventory_items.*"
|
||||
]
|
||||
"inventory_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.inventory_items
|
||||
@@ -199,8 +199,8 @@ To retrieve a product's order line items, pass `order_items.*` in `fields`.
|
||||
const { data: variants } = await query.graph({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"order_items.*"
|
||||
]
|
||||
"order_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.order_items
|
||||
@@ -217,8 +217,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: variants } = useQueryGraphStep({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"order_items.*"
|
||||
]
|
||||
"order_items.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.order_items
|
||||
@@ -250,8 +250,8 @@ To retrieve the price set of a variant with [Query](!docs!/learn/fundamentals/mo
|
||||
const { data: variants } = await query.graph({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"price_set.*"
|
||||
]
|
||||
"price_set.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.price_set
|
||||
@@ -268,8 +268,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: variants } = useQueryGraphStep({
|
||||
entity: "variant",
|
||||
fields: [
|
||||
"price_set.*"
|
||||
]
|
||||
"price_set.*",
|
||||
],
|
||||
})
|
||||
|
||||
// variants.price_set
|
||||
@@ -343,8 +343,8 @@ To retrieve the sales channels of a product with [Query](!docs!/learn/fundamenta
|
||||
const { data: products } = await query.graph({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// products.sales_channels
|
||||
@@ -361,8 +361,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: products } = useQueryGraphStep({
|
||||
entity: "product",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// products.sales_channels
|
||||
|
||||
@@ -6,114 +6,166 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Product Module provides product-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Product Module and how to use it in your application.
|
||||
|
||||
## How to Use Product Module's Service
|
||||
Medusa has product related features available out-of-the-box through the Product Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Product Module.
|
||||
|
||||
You can use the Product Module's main service by resolving from the Medusa container the resource `Modules.PRODUCT`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Product Features
|
||||
|
||||
- [Products Management](/references/product/models/Product): Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
|
||||
- [Product Organization](/references/product/models): The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Product Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.PRODUCT", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-product.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const productModuleService = container.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
const createProductStep = createStep(
|
||||
"create-product",
|
||||
async ({}, { container }) => {
|
||||
const productService = container.resolve(Modules.PRODUCT)
|
||||
|
||||
const products = await productModuleService.listProducts()
|
||||
})
|
||||
const product = await productService.createProducts({
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
values: ["Black", "White"],
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: {
|
||||
Color: "Black",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return new StepResponse({ product }, product.id)
|
||||
},
|
||||
async (productId, { container }) => {
|
||||
if (!productId) {
|
||||
return
|
||||
}
|
||||
const productService = container.resolve(Modules.PRODUCT)
|
||||
|
||||
await productService.deleteProducts([productId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createProductWorkflow = createWorkflow(
|
||||
"create-product",
|
||||
() => {
|
||||
const { product } = createProductStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
product,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createProductWorkflow } from "../../workflows/create-product"
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await createProductWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||
const productModuleService = request.scope.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
|
||||
res.json({
|
||||
products: await productModuleService.listProducts(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createProductWorkflow } from "../workflows/create-product"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createProductWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const productModuleService = container.resolve(
|
||||
Modules.PRODUCT
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const products = await productModuleService.listProducts()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createProductWorkflow } from "../workflows/create-product"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createProductWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Products Management
|
||||
|
||||
Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
|
||||
|
||||
```ts
|
||||
const products = await productService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
values: ["Black", "White"],
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: {
|
||||
Color: "Black",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Product Organization
|
||||
|
||||
The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
|
||||
|
||||
```ts
|
||||
const category = await productService.createProductCategories({
|
||||
name: "Shirts",
|
||||
})
|
||||
|
||||
const products = await productService.updateProducts([
|
||||
{
|
||||
id: product.id,
|
||||
categories: [
|
||||
{
|
||||
id: category.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
<CommerceModuleSections name="Product" />
|
||||
@@ -1,261 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Promotion Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Promotion Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Promotion Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Promotion
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService = request.scope.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePromotionModule } from "@medusajs/medusa/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService = await initializePromotionModule()
|
||||
const body = await request.json()
|
||||
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Campaign
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService = request.scope.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
|
||||
const campaign = await promotionModuleService.createCampaigns({
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
})
|
||||
|
||||
res.json({ campaign })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePromotionModule } from "@medusajs/medusa/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService = await initializePromotionModule()
|
||||
const body = await request.json()
|
||||
|
||||
const campaign = await promotionModuleService.createCampaigns({
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
})
|
||||
|
||||
return NextResponse.json({ campaign })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Promotion with Flexible Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService = request.scope.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
attribute: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePromotionModule } from "@medusajs/medusa/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService = await initializePromotionModule()
|
||||
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
attribute: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Promotions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService = request.scope.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializePromotionModule } from "@medusajs/medusa/promotion"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const promotionModuleService = await initializePromotionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Promotion Module's main service reference](/references/promotion) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -49,8 +49,8 @@ To retrieve the line item adjustments of a promotion, pass `line_item_adjustment
|
||||
const { data: promotions } = await query.graph({
|
||||
entity: "promotion",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// promotions.carts
|
||||
@@ -67,8 +67,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: promotions } = useQueryGraphStep({
|
||||
entity: "promotion",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// promotions.carts
|
||||
@@ -140,8 +140,8 @@ To retrieve the orders a promotion is applied on with [Query](!docs!/learn/funda
|
||||
const { data: promotions } = await query.graph({
|
||||
entity: "promotion",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// promotions.orders
|
||||
@@ -158,8 +158,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: promotions } = useQueryGraphStep({
|
||||
entity: "promotion",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// promotions.orders
|
||||
|
||||
@@ -6,130 +6,161 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Promotion Module provides promotion-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Promotion Module and how to use it in your application.
|
||||
|
||||
## How to Use the Promotion Module's Service
|
||||
Medusa has promotion related features available out-of-the-box through the Promotion Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Promotion Module.
|
||||
|
||||
You can use the Promotion Module's main service by resolving from the Medusa container the resource `Modules.PROMOTION`.
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Promotion Features
|
||||
|
||||
- [Discount Functionalities](./concepts/page.mdx): A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order.
|
||||
- [Flexible Promotion Rules](./concepts/page.mdx#flexible-rules): A promotion has rules that restricts when the promotion is applied.
|
||||
- [Campaign Management](./campaign/page.mdx): A campaign combines promotions under the same conditions, such as start and end dates, and budget configurations.
|
||||
- [Apply Promotion on Carts and Orders](./actions/page.mdx): Apply promotions on carts and orders to discount items, shipping methods, or the entire order.
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Promotion Module
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.PROMOTION", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-promotion.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const promotionModuleService = container.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
const createPromotionStep = createStep(
|
||||
"create-promotion",
|
||||
async ({}, { container }) => {
|
||||
const promotionModuleService = container.resolve(Modules.PROMOTION)
|
||||
|
||||
const promotions = await promotionModuleService.listPromotions()
|
||||
})
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: 10,
|
||||
currency_code: "usd",
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse({ promotion }, promotion.id)
|
||||
},
|
||||
async (promotionId, { container }) => {
|
||||
if (!promotionId) {
|
||||
return
|
||||
}
|
||||
const promotionModuleService = container.resolve(Modules.PROMOTION)
|
||||
|
||||
await promotionModuleService.deletePromotions(promotionId)
|
||||
}
|
||||
)
|
||||
|
||||
export const createPromotionWorkflow = createWorkflow(
|
||||
"create-promotion",
|
||||
() => {
|
||||
const { promotion } = createPromotionStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
promotion,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createPromotionWorkflow } from "../../workflows/create-cart"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService = request.scope.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
) {
|
||||
const { result } = await createPromotionWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createPromotionWorkflow } from "../workflows/create-cart"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createPromotionWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const promotionModuleService = container.resolve(
|
||||
Modules.PROMOTION
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const promotions = await promotionModuleService.listPromotions()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createPromotionWorkflow } from "../workflows/create-cart"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createPromotionWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Discount Functionalities
|
||||
|
||||
A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order.
|
||||
|
||||
The Promotion Module allows you to store and manage promotions.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Flexible Promotion Rules
|
||||
|
||||
A promotion has rules that restricts when it's applied.
|
||||
|
||||
For example, you can create a promotion that's only applied to VIP customers.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
currency_code: "usd",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
attribute: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Campaign Management
|
||||
|
||||
A campaign combines promotions under the same conditions, such as start and end dates.
|
||||
|
||||
A campaign can also have an identifier for tracking purposes, such as tracking through tools like Google Analytics.
|
||||
|
||||
```ts
|
||||
const campaign = await promotionModuleService.createCampaigns({
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
})
|
||||
```
|
||||
<CommerceModuleSections name="Promotion" />
|
||||
@@ -1,256 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Region Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Region Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Region Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
|
||||
res.json({
|
||||
region,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeRegionModule } from "@medusajs/medusa/region"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
|
||||
res.json({
|
||||
region,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Regions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeRegionModule } from "@medusajs/medusa/region"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Region by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
|
||||
const region = await regionModuleService.retrieveRegion("reg_123")
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeRegionModule } from "@medusajs/medusa/region"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.retrieveRegion("reg_123")
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
|
||||
const region = await regionModuleService.updateRegions("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeRegionModule } from "@medusajs/medusa/region"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.updateRegions("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
|
||||
await regionModuleService.deleteRegions("reg_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeRegionModule } from "@medusajs/medusa/region"
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
await regionModuleService.deleteRegions("reg_123")
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Region Module's main service reference](/references/region) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -39,8 +39,8 @@ To retrieve the carts of a region with [Query](!docs!/learn/fundamentals/module-
|
||||
const { data: regions } = await query.graph({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.carts
|
||||
@@ -57,8 +57,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: regions } = useQueryGraphStep({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.carts
|
||||
@@ -84,8 +84,8 @@ To retrieve the orders of a region with [Query](!docs!/learn/fundamentals/module
|
||||
const { data: regions } = await query.graph({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.orders
|
||||
@@ -102,8 +102,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: regions } = useQueryGraphStep({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.orders
|
||||
@@ -133,8 +133,8 @@ To retrieve the payment providers of a region with [Query](!docs!/learn/fundamen
|
||||
const { data: regions } = await query.graph({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"payment_providers.*"
|
||||
]
|
||||
"payment_providers.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.payment_providers
|
||||
@@ -151,8 +151,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: regions } = useQueryGraphStep({
|
||||
entity: "region",
|
||||
fields: [
|
||||
"payment_providers.*"
|
||||
]
|
||||
"payment_providers.*",
|
||||
],
|
||||
})
|
||||
|
||||
// regions.payment_providers
|
||||
|
||||
@@ -6,125 +6,156 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Region Module provides region-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Region Module and how to use it in your application.
|
||||
|
||||
Medusa has region related features available out-of-the-box through the Region Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Region Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Region Features
|
||||
|
||||
- [Region Management](/references/region/models/Region): Manage regions in your store. You can create regions with different currencies and settings.
|
||||
- [Multi-Currency Support](/references/region/models/Region): Each region has a currency. You can support multiple currencies in your store by creating multiple regions.
|
||||
- [Different Settings Per Region](/references/region/models/Region): Each region has its own settings, such as what countries belong to a region or its tax settings.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Region Module's Service
|
||||
|
||||
You can use the Region Module's main service by resolving from the Medusa container the resource `Modules.REGION`.
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.REGION", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-region.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const regionModuleService = container.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
const createRegionStep = createStep(
|
||||
"create-region",
|
||||
async ({}, { container }) => {
|
||||
const regionModuleService = container.resolve(Modules.REGION)
|
||||
|
||||
const regions = await regionModuleService.listRegions()
|
||||
})
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
|
||||
return new StepResponse({ region }, region.id)
|
||||
},
|
||||
async (regionId, { container }) => {
|
||||
if (!regionId) {
|
||||
return
|
||||
}
|
||||
const regionModuleService = container.resolve(Modules.REGION)
|
||||
|
||||
await regionModuleService.deleteRegions([regionId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createRegionWorkflow = createWorkflow(
|
||||
"create-region",
|
||||
() => {
|
||||
const { region } = createRegionStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
region,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createRegionWorkflow } from "../../workflows/create-region"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService = req.scope.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
) {
|
||||
const { result } = await createRegionWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createRegionWorkflow } from "../workflows/create-region"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createRegionWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const regionModuleService = container.resolve(
|
||||
Modules.REGION
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const regions = await regionModuleService.listRegions()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createRegionWorkflow } from "../workflows/create-region"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createRegionWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## What is a Region?
|
||||
|
||||
A region represents the area you sell products in. Each region can cover multiple countries, but uses a single currency.
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Region Management
|
||||
|
||||
You can manage your regions to create, update, retrieve, or delete them.
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Currency Support
|
||||
|
||||
As each region has a currency, you can support multiple currencies in your store by creating multiple regions.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.createRegions([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
},
|
||||
{
|
||||
name: "United States of America",
|
||||
currency_code: "usd",
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Different Settings Per Region
|
||||
|
||||
Each region has its own settings, such as what countries belong to a region or its tax settings. Each region has different tax rates, payment providers, and more provided by other commerce modules.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.createRegions([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
countries: ["dk", "de", "fr", "it", "pt"],
|
||||
automatic_taxes: true,
|
||||
},
|
||||
{
|
||||
name: "United States of America",
|
||||
currency_code: "usd",
|
||||
countries: ["us"],
|
||||
payment_providers: ["stripe"],
|
||||
},
|
||||
])
|
||||
```
|
||||
<CommerceModuleSections name="Region" />
|
||||
@@ -1,257 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Sales Channel Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Sales Channel Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Sales Channel Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.createSalesChannels({
|
||||
name: "B2B",
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeSalesChannelModule } from "@medusajs/medusa/sales-channel"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const salesChannelModuleService = await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.createSalesChannels({
|
||||
name: "B2B",
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Sales Channels
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: salesChannelModuleService.listSalesChannels(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeSalesChannelModule } from "@medusajs/medusa/sales-channel"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const salesChannelModuleService = await initializeSalesChannelModule()
|
||||
|
||||
const salesChannels = await salesChannelModuleService.listSalesChannels()
|
||||
|
||||
return NextResponse.json({ sales_channels: salesChannels })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Sales Channel by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieveSalesChannel(
|
||||
"sc_123"
|
||||
)
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeSalesChannelModule } from "@medusajs/medusa/sales-channel"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const salesChannelModuleService = await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieveSalesChannel(
|
||||
"sc_123"
|
||||
)
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.updateSalesChannels({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeSalesChannelModule } from "@medusajs/medusa/sales-channel"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const salesChannelModuleService = await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.updateSalesChannels({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
await salesChannelModuleService.deleteSalesChannels("sc_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeSalesChannelModule } from "@medusajs/medusa/sales-channel"
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const salesChannelModuleService = await initializeSalesChannelModule()
|
||||
|
||||
await salesChannelModuleService.deleteSalesChannels("sc_123")
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Sales Channel Module's main service reference](/references/sales-channel) provides a reference to all the methods available for use with examples for each.
|
||||
+20
-20
@@ -45,8 +45,8 @@ To retrieve the API keys associated with a sales channel with [Query](!docs!/lea
|
||||
const { data: salesChannels } = await query.graph({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"publishable_api_keys.*"
|
||||
]
|
||||
"publishable_api_keys.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.publishable_api_keys
|
||||
@@ -63,8 +63,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: salesChannels } = useQueryGraphStep({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"publishable_api_keys.*"
|
||||
]
|
||||
"publishable_api_keys.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.publishable_api_keys
|
||||
@@ -134,8 +134,8 @@ To retrieve the carts of a sales channel with [Query](!docs!/learn/fundamentals/
|
||||
const { data: salesChannels } = await query.graph({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.carts
|
||||
@@ -152,8 +152,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: salesChannels } = useQueryGraphStep({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"carts.*"
|
||||
]
|
||||
"carts.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.carts
|
||||
@@ -179,8 +179,8 @@ To retrieve the orders of a sales channel with [Query](!docs!/learn/fundamentals
|
||||
const { data: salesChannels } = await query.graph({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.orders
|
||||
@@ -197,8 +197,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: salesChannels } = useQueryGraphStep({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"orders.*"
|
||||
]
|
||||
"orders.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.orders
|
||||
@@ -228,8 +228,8 @@ To retrieve the products of a sales channel with [Query](!docs!/learn/fundamenta
|
||||
const { data: salesChannels } = await query.graph({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"products.*"
|
||||
]
|
||||
"products.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.products
|
||||
@@ -246,8 +246,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: salesChannels } = useQueryGraphStep({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"products.*"
|
||||
]
|
||||
"products.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.products
|
||||
@@ -321,8 +321,8 @@ To retrieve the stock locations of a sales channel with [Query](!docs!/learn/fun
|
||||
const { data: salesChannels } = await query.graph({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"stock_locations.*"
|
||||
]
|
||||
"stock_locations.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.stock_locations
|
||||
@@ -339,8 +339,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: salesChannels } = useQueryGraphStep({
|
||||
entity: "sales_channel",
|
||||
fields: [
|
||||
"stock_locations.*"
|
||||
]
|
||||
"stock_locations.*",
|
||||
],
|
||||
})
|
||||
|
||||
// salesChannels.stock_locations
|
||||
|
||||
@@ -6,68 +6,15 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Sales Channel Module provides sales-channel-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Sales Channel Module and how to use it in your application.
|
||||
|
||||
## How to Use Sales Channel Module's Service
|
||||
Medusa has sales channel related features available out-of-the-box through the Sales Channel Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Sales Channel Module.
|
||||
|
||||
You can use the Sales Channel Module's main service by resolving from the Medusa container the resource `Modules.SALES_CHANNEL`.
|
||||
<Note>
|
||||
|
||||
For example:
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const salesChannelModuleService =
|
||||
container.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.listSalesChannels()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService =
|
||||
request.scope.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: await salesChannelModuleService.listSalesChannels(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const salesChannelModuleService =
|
||||
container.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.listSalesChannels()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
</Note>
|
||||
|
||||
## What's a Sales Channel?
|
||||
|
||||
@@ -81,31 +28,151 @@ Some use case examples for using a sales channel:
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## Sales Channel Features
|
||||
|
||||
### Sales Channel Management
|
||||
- [Sales Channel Management](/references/sales-channel/models/SalesChannel): Manage sales channels in your store. Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
|
||||
- [Product Availability](./links-to-other-modules/page.mdx): Medusa uses the Product and Sales Channel modules to allow merchants to specify a product's availability per sales channel.
|
||||
- [Cart and Order Scoping](./links-to-other-modules/page.mdx): Carts, available through the Cart Module, are scoped to a sales channel. Paired with the product availability feature, you benefit from more features like allowing only products available in sales channel in a cart.
|
||||
- [Inventory Availability Per Sales Channel](./links-to-other-modules/page.mdx): Medusa links sales channels to stock locations, allowing you to retrieve available inventory of products based on the specified sales channel.
|
||||
|
||||
Manage sales channels in your store. Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
|
||||
---
|
||||
|
||||
```ts
|
||||
const salesChannels = await salesChannelModuleService.createSalesChannels([
|
||||
{
|
||||
name: "B2B",
|
||||
## How to Use Sales Channel Module's Service
|
||||
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["12", "Modules.SALES_CHANNEL", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/create-sales-channel.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const createSalesChannelStep = createStep(
|
||||
"create-sales-channel",
|
||||
async ({}, { container }) => {
|
||||
const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.createSalesChannels([
|
||||
{
|
||||
name: "B2B",
|
||||
},
|
||||
{
|
||||
name: "Mobile App",
|
||||
},
|
||||
])
|
||||
|
||||
return new StepResponse({ salesChannels }, salesChannels.map((sc) => sc.id))
|
||||
},
|
||||
{
|
||||
name: "Mobile App",
|
||||
},
|
||||
])
|
||||
async (salesChannelIds, { container }) => {
|
||||
if (!salesChannelIds) {
|
||||
return
|
||||
}
|
||||
const salesChannelModuleService = container.resolve(Modules.SALES_CHANNEL)
|
||||
|
||||
await salesChannelModuleService.deleteSalesChannels(
|
||||
salesChannelIds
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export const createSalesChannelWorkflow = createWorkflow(
|
||||
"create-sales-channel",
|
||||
() => {
|
||||
const { salesChannels } = createSalesChannelStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
salesChannels,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Product Availability
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
Medusa uses the Product and Sales Channel modules to allow merchants to specify a product's availability per sales channel.
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createSalesChannelWorkflow } from "../../workflows/create-sales-channel"
|
||||
|
||||
For example, B2B customers viewing products only see products in the B2B sales channel.
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { result } = await createSalesChannelWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
### Cart and Order Scoping
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
Carts, available through the Cart Module, are scoped to a sales channel. Paired with the product availability feature, you benefit from more features like allowing only products available in sales channel in a cart.
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
|
||||
|
||||
Orders are also scoped to a sales channel due to the relation between the Sales Channel and Order modules.
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createSalesChannelWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createSalesChannelWorkflow } from "../workflows/create-sales-channel"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createSalesChannelWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="Sales Channel" />
|
||||
@@ -1,219 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Stock Location Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Stock Location Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Stock Location Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService =
|
||||
request.scope.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.createStockLocations({
|
||||
name: "Warehouse 1",
|
||||
})
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStockLocationModule } from "@medusajs/medusa/stock-location"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const stockLocationModuleService = await initializeStockLocationModule({})
|
||||
|
||||
const stockLocation = await stockLocationModuleService.createStockLocations({
|
||||
name: "Warehouse 1",
|
||||
})
|
||||
|
||||
return NextResponse.json({ stock_location: stockLocation })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Stock Locations
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService =
|
||||
request.scope.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.listStockLocations({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStockLocationModule } from "@medusajs/medusa/stock-location"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const stockLocationModuleService = await initializeStockLocationModule({})
|
||||
|
||||
return NextResponse.json({
|
||||
stock_locations: await stockLocationModuleService.listStockLocations({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Address to Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService =
|
||||
request.scope.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.updateStockLocations({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "US",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStockLocationModule } from "@medusajs/medusa/stock-location"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const stockLocationModuleService = await initializeStockLocationModule({})
|
||||
|
||||
const stockLocation = await stockLocationModuleService.updateStockLocations({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService =
|
||||
request.scope.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
await stockLocationModuleService.deleteStockLocations("sloc_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStockLocationModule } from "@medusajs/medusa/stock-location"
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const stockLocationModuleService = await initializeStockLocationModule({})
|
||||
|
||||
await stockLocationModuleService.deleteStockLocations("sloc_123")
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Stock Location Module's main service reference](/references/stock-location) provides a reference to all the methods available for use with examples for each.
|
||||
+12
-12
@@ -54,8 +54,8 @@ To retrieve the fulfillment providers, pass `fulfillment_providers.*` in `fields
|
||||
const { data: stockLocations } = await query.graph({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"fulfillment_sets.*"
|
||||
]
|
||||
"fulfillment_sets.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.fulfillment_sets
|
||||
@@ -72,8 +72,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: stockLocations } = useQueryGraphStep({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"fulfillment_sets.*"
|
||||
]
|
||||
"fulfillment_sets.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.fulfillment_sets
|
||||
@@ -143,8 +143,8 @@ To retrieve the inventory levels of a stock location with [Query](!docs!/learn/f
|
||||
const { data: stockLocations } = await query.graph({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"inventory_levels.*"
|
||||
]
|
||||
"inventory_levels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.inventory_levels
|
||||
@@ -161,8 +161,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: stockLocations } = useQueryGraphStep({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"inventory_levels.*"
|
||||
]
|
||||
"inventory_levels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.inventory_levels
|
||||
@@ -192,8 +192,8 @@ To retrieve the sales channels of a stock location with [Query](!docs!/learn/fun
|
||||
const { data: stockLocations } = await query.graph({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.sales_channels
|
||||
@@ -210,8 +210,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: stockLocations } = useQueryGraphStep({
|
||||
entity: "stock_location",
|
||||
fields: [
|
||||
"sales_channels.*"
|
||||
]
|
||||
"sales_channels.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stockLocations.sales_channels
|
||||
|
||||
@@ -6,95 +6,150 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Stock Location Module provides stock-location-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Stock Location Module and how to use it in your application.
|
||||
|
||||
Medusa has stock location related features available out-of-the-box through the Stock Location Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Stock Location Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Stock Location Features
|
||||
|
||||
- [Stock Location Management](/references/stock-location-next/models): Store and manage stock locations. Medusa links stock locations with data models of other modules that require a location, such as the [Inventory Module's InventoryLevel](./links-to-other-modules/page.mdx).
|
||||
- [Address Management](/references/stock-location-next/models/StockLocationAddress): Manage the address of each stock location.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Stock Location Module's Service
|
||||
|
||||
You can use the Stock Location Module's main service by resolving from the Medusa container the resource `Modules.STOCK_LOCATION`.
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.STOCK_LOCATION", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-stock-location.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const stockLocationModuleService = container.resolve(
|
||||
Modules.STOCK_LOCATION
|
||||
)
|
||||
const createStockLocationStep = createStep(
|
||||
"create-stock-location",
|
||||
async ({}, { container }) => {
|
||||
const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
const stockLocations = await stockLocationModuleService.listStockLocations({})
|
||||
})
|
||||
const stockLocation = await stockLocationModuleService.createStockLocations({
|
||||
name: "Warehouse 1",
|
||||
})
|
||||
|
||||
return new StepResponse({ stockLocation }, stockLocation.id)
|
||||
},
|
||||
async (stockLocationId, { container }) => {
|
||||
if (!stockLocationId) {
|
||||
return
|
||||
}
|
||||
const stockLocationModuleService = container.resolve(Modules.STOCK_LOCATION)
|
||||
|
||||
await stockLocationModuleService.deleteStockLocations([stockLocationId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createStockLocationWorkflow = createWorkflow(
|
||||
"create-stock-location",
|
||||
() => {
|
||||
const { stockLocation } = createStockLocationStep()
|
||||
|
||||
return new WorkflowResponse({ stockLocation })
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createStockLocationWorkflow } from "../../workflows/create-stock-location"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService =
|
||||
request.scope.resolve(Modules.STOCK_LOCATION)
|
||||
) {
|
||||
const { result } = await createStockLocationWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.listStockLocations({}),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createStockLocationWorkflow } from "../workflows/create-stock-location"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createStockLocationWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const stockLocationModuleService = container.resolve(
|
||||
Modules.STOCK_LOCATION
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const stockLocations = await stockLocationModuleService.listStockLocations({})
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createStockLocationWorkflow } from "../workflows/create-stock-location"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createStockLocationWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Stock Location Management
|
||||
|
||||
Store and manage stock locations. Stock locations are associated with data models of other modules that require a location, such as the [Inventory Module's InventoryLevel](../inventory/concepts/page.mdx#inventory-level).
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.createStockLocations({
|
||||
name: "Warehouse 1",
|
||||
})
|
||||
```
|
||||
|
||||
### Address Management
|
||||
|
||||
Manage the address of each stock location.
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.updateStockLocations({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
```
|
||||
<CommerceModuleSections name="Stock Location" />
|
||||
@@ -1,272 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Store Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Store Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Store Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currencies: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
is_default: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStoreModule } from "@medusajs/medusa/store"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currencies: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
is_default: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Stores
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.listStores(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStoreModule } from "@medusajs/medusa/store"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const salesChannels = await storeModuleService.listStores()
|
||||
|
||||
return NextResponse.json({
|
||||
stores: await storeModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Store by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
|
||||
const store = await storeModuleService.retrieveStore("store_123")
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStoreModule } from "@medusajs/medusa/store"
|
||||
|
||||
export async function GET(request: Request, { params }: ContextType) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.retrieveStore("store_123")
|
||||
|
||||
return NextResponse.json({ store })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
|
||||
const store = await storeModuleService.updateStores("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStoreModule } from "@medusajs/medusa/store"
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.updateStores("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
return NextResponse.json({ store })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
|
||||
await storeModuleService.deleteStores("store_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeStoreModule } from "@medusajs/medusa/store"
|
||||
|
||||
export async function DELETE(request: Request, { params }: ContextType) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
await storeModuleService.deleteStores("store_123")
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Store Module's main service reference](/references/store) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -39,8 +39,8 @@ To retrieve the details of a store's currencies with [Query](!docs!/learn/fundam
|
||||
const { data: stores } = await query.graph({
|
||||
entity: "store",
|
||||
fields: [
|
||||
"supported_currencies.currency.*"
|
||||
]
|
||||
"supported_currencies.currency.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stores.supported_currencies
|
||||
@@ -57,8 +57,8 @@ import { useQueryGraphStep } from "@medusajs/medusa/core-flows"
|
||||
const { data: stores } = useQueryGraphStep({
|
||||
entity: "store",
|
||||
fields: [
|
||||
"supported_currencies.currency.*"
|
||||
]
|
||||
"supported_currencies.currency.*",
|
||||
],
|
||||
})
|
||||
|
||||
// stores.supported_currencies
|
||||
|
||||
@@ -6,98 +6,154 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Store Module provides store-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Store Module and how to use it in your application.
|
||||
|
||||
Medusa has store related features available out-of-the-box through the Store Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Store Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Store Features
|
||||
|
||||
- [Store Management](/references/store/models/Store): Create and manage stores in your application.
|
||||
- [Multi-Tenancy Support](/references/store/models/Store): Create multiple stores, each having its own configurations.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Store Module's Service
|
||||
|
||||
You can use the Store Module's main service by resolving from the Medusa container the resource `Modules.STORE`.
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.STORE", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-store.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const storeModuleService: IStoreModuleService = container.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
const createStoreStep = createStep(
|
||||
"create-store",
|
||||
async ({}, { container }) => {
|
||||
const storeModuleService = container.resolve(Modules.STORE)
|
||||
|
||||
const stores = await storeModuleService.listStores()
|
||||
})
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currencies: [{
|
||||
currency_code: "usd",
|
||||
is_default: true,
|
||||
}],
|
||||
})
|
||||
|
||||
return new StepResponse({ store }, store.id)
|
||||
},
|
||||
async (storeId, { container }) => {
|
||||
if(!storeId) {
|
||||
return
|
||||
}
|
||||
const storeModuleService = container.resolve(Modules.STORE)
|
||||
|
||||
await storeModuleService.deleteStores([storeId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createStoreWorkflow = createWorkflow(
|
||||
"create-store",
|
||||
() => {
|
||||
const { store } = createStoreStep()
|
||||
|
||||
return new WorkflowResponse({ store })
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createStoreWorkflow } from "../../workflows/create-store"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService = request.scope.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
) {
|
||||
const { result } = await createStoreWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.listStores(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createStoreWorkflow } from "../workflows/create-store"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createStoreWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const storeModuleService: IStoreModuleService = container.resolve(
|
||||
Modules.STORE
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const stores = await storeModuleService.listStores()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createStoreWorkflow } from "../workflows/create-store"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createStoreWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Store Management
|
||||
|
||||
A store holds the main configurations of your commerce store, such as supported currencies, default region and sales channel, and more.
|
||||
|
||||
```ts
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Tenancy Support
|
||||
|
||||
You can create multiple stores, each having its own configurations.
|
||||
|
||||
```ts
|
||||
const stores = await storeModuleService.createStores([
|
||||
{
|
||||
name: "USA Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
},
|
||||
{
|
||||
name: "Europe Store",
|
||||
supported_currency_codes: ["eur"],
|
||||
},
|
||||
])
|
||||
```
|
||||
<CommerceModuleSections name="Store" />
|
||||
@@ -1,344 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Tax Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Tax Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the Tax Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a Tax Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "us",
|
||||
default_tax_rate: {
|
||||
rate: 10,
|
||||
name: "Default rate",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
tax_region: taxRegion,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeTaxModule } from "@medusajs/medusa/tax"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "us",
|
||||
default_tax_rate: {
|
||||
rate: 10,
|
||||
name: "Default rate",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
tax_region: taxRegion,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Tax Regions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeTaxModule } from "@medusajs/medusa/tax"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Tax Rate with Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
|
||||
const taxRate = await taxModuleService.createTaxRates({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
{
|
||||
reference: "product",
|
||||
reference_id: "prod_123",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
tax_rate: taxRate,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeTaxModule } from "@medusajs/medusa/tax"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxRate = await taxModuleService.createTaxRates({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
{
|
||||
reference: "product",
|
||||
reference_id: "prod_123",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
tax_rate: taxRate,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Tax Rates
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_rates: await taxModuleService.listTaxRates(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeTaxModule } from "@medusajs/medusa/tax"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_rates: await taxModuleService.listTaxRates(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Get Tax Lines
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
postal_code: "123456",
|
||||
},
|
||||
customer: {
|
||||
id: "cus_123",
|
||||
email: "user@example.com",
|
||||
customer_groups: ["VIP"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_lines: taxLines,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeTaxModule } from "@medusajs/medusa/tax"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
postal_code: "123456",
|
||||
},
|
||||
customer: {
|
||||
id: "cus_123",
|
||||
email: "user@example.com",
|
||||
customer_groups: ["VIP"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
tax_lines: taxLines,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Tax Module's main service reference](/references/tax) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -6,142 +6,157 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Tax Module provides tax-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the Tax Module and how to use it in your application.
|
||||
|
||||
Medusa has tax related features available out-of-the-box through the Tax Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this Tax Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## Tax Features
|
||||
|
||||
- [Tax Settings Per Region](./tax-region/page.mdx): Set different tax settings for each tax region.
|
||||
- [Tax Rates and Rules](./tax-rates-and-rules/page.mdx): Manage each region's default tax rates and override them with conditioned tax rates.
|
||||
- [Retrieve Tax Lines for carts and orders](./tax-calculation-with-provider/page.mdx): Calculate and retrieve the tax lines of a cart or order's line items and shipping methods with tax providers.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Tax Module's Service
|
||||
|
||||
You can use the Tax Module's main service by resolving from the Medusa container the resource `Modules.TAX`.
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.TAX", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-tax-region.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const taxModuleService = container.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
const createTaxRegionStep = createStep(
|
||||
"create-tax-region",
|
||||
async ({}, { container }) => {
|
||||
const taxModuleService = container.resolve(Modules.TAX)
|
||||
|
||||
const taxRegions = await taxModuleService.listTaxRegions()
|
||||
})
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "us",
|
||||
})
|
||||
|
||||
return new StepResponse({ taxRegion }, taxRegion.id)
|
||||
},
|
||||
async (taxRegionId, { container }) => {
|
||||
if (!taxRegionId) {
|
||||
return
|
||||
}
|
||||
const taxModuleService = container.resolve(Modules.TAX)
|
||||
|
||||
await taxModuleService.deleteTaxRegions([taxRegionId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createTaxRegionWorkflow = createWorkflow(
|
||||
"create-tax-region",
|
||||
() => {
|
||||
const { taxRegion } = createTaxRegionStep()
|
||||
|
||||
return new WorkflowResponse({ taxRegion })
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createTaxRegionWorkflow } from "../../workflows/create-tax-region"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService = req.scope.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
) {
|
||||
const { result } = await createTaxRegionWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createTaxRegionWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const taxModuleService = container.resolve(
|
||||
Modules.TAX
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const taxRegions = await taxModuleService.listTaxRegions()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createTaxRegionWorkflow } from "../workflows/create-tax-region"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createTaxRegionWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Tax Settings Per Region
|
||||
|
||||
Set different tax settings for each tax region.
|
||||
|
||||
```ts
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "us",
|
||||
})
|
||||
```
|
||||
|
||||
### Tax Rates and Rules
|
||||
|
||||
Manage each region's default tax rates and override them with conditioned tax rates.
|
||||
|
||||
```ts
|
||||
const taxRates = await taxModuleService.createTaxRates([
|
||||
{
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Default tax rate",
|
||||
is_default: true,
|
||||
rate: 10,
|
||||
},
|
||||
{
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Shirt product type",
|
||||
is_default: false,
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Retrieve Cart's Tax Lines
|
||||
|
||||
Calculate and retrieve the tax lines of a cart's line items and shipping methods with tax providers.
|
||||
|
||||
```ts
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You can use different tax providers for each region to handle tax-line retrieval differently.
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Configure Tax Module
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
The Tax Module accepts options for further configurations. Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="Tax" />
|
||||
@@ -1,384 +0,0 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the User Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the User Module in your application.
|
||||
|
||||
<Note>
|
||||
|
||||
You should only use the User Module's main service when implementing complex customizations. For common cases, check out [available workflows instead](../../../medusa-workflows-reference/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
## Create a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
res.json({ user })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Users
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
return NextResponse.json({
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const user = await userModuleService.updateUsers({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
res.json({ user })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.updateUsers({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
await userModuleService.deleteUsers(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
await userModuleService.deleteUsers(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Invite User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
res.json({
|
||||
invite,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
invite,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Accept Invite
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const invite = await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.createUsers({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
|
||||
await userModuleService.updateInvites({
|
||||
id: invite.id,
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
res.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invite = await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.createUsers({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
|
||||
await userModuleService.updateInvites({
|
||||
id: invite.id,
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Refresh Invite
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
|
||||
const invites = await userModuleService.refreshInviteTokens(["invite_123"])
|
||||
|
||||
res.json({
|
||||
invite: invites[0],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import { initialize as initializeUserModule } from "@medusajs/medusa/user"
|
||||
|
||||
export async function POST(request: Request, { params }: ContextType) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invites = await userModuleService.refreshInviteTokens(["invite_123"])
|
||||
|
||||
return NextResponse.json({
|
||||
invite: invites[0],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [User Module's main service reference](/references/user) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -6,101 +6,160 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The User Module provides user-related features in your Medusa and Node.js applications.
|
||||
In this section of the documentation, you will find resources to learn more about the User Module and how to use it in your application.
|
||||
|
||||
Medusa has user related features available out-of-the-box through the User Module. A [module](!docs!/learn/fundamentals/modules) is a standalone package that provides features for a single domain. Each of Medusa's commerce features are placed in commerce modules, such as this User Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why modules are isolated in [this documentation](!docs!/learn/fundamentals/modules/isolation).
|
||||
|
||||
</Note>
|
||||
|
||||
## User Features
|
||||
|
||||
- [User Management](./user-creation-flows/page.mdx): Store and manage users in your store.
|
||||
- [Invite Users](./user-creation-flows/page.mdx#invite-users): Invite users to join your store and manage those invites.
|
||||
|
||||
---
|
||||
|
||||
## How to Use User Module's Service
|
||||
|
||||
You can use the User Module's main service by resolving from the Medusa container the resource `Modules.USER`.
|
||||
In your Medusa application, you build flows around commerce modules. A flow is built as a [Workflow](!docs!/learn/fundamentals/workflows), which is a special function composed of a series of steps that guarantees data consistency and reliable roll-back mechanism.
|
||||
|
||||
You can build custom workflows and steps. You can also re-use Medusa's workflows and steps, which are provided by the `@medusajs/medusa/core-flows` package.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
export const highlights = [
|
||||
["12", "Modules.USER", "Resolve the module in a step."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
||||
```ts title="src/workflows/create-user.ts" highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
WorkflowResponse,
|
||||
createStep,
|
||||
StepResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
|
||||
const step1 = createStep("step-1", async (_, { container }) => {
|
||||
const userModuleService = container.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
const createUserStep = createStep(
|
||||
"create-user",
|
||||
async ({}, { container }) => {
|
||||
const userModuleService = container.resolve(Modules.USER)
|
||||
|
||||
const users = await userModuleService.listUsers()
|
||||
})
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
return new StepResponse({ user }, user.id)
|
||||
},
|
||||
async (userId, { container }) => {
|
||||
if (!userId) {
|
||||
return
|
||||
}
|
||||
const userModuleService = container.resolve(Modules.USER)
|
||||
|
||||
await userModuleService.deleteUsers([userId])
|
||||
}
|
||||
)
|
||||
|
||||
export const createUserWorkflow = createWorkflow(
|
||||
"create-user",
|
||||
() => {
|
||||
const { user } = createUserStep()
|
||||
|
||||
return new WorkflowResponse({
|
||||
user,
|
||||
})
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
You can then execute the workflow in your custom API routes, scheduled jobs, or subscribers:
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
<CodeTabs group="resource-types">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { createUserWorkflow } from "../../workflows/create-user"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService = req.scope.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
) {
|
||||
const { result } = await createUserWorkflow(req.scope)
|
||||
.run()
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
res.send(result)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
<CodeTab label="Subscriber" value="subscriber">
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={[["11"], ["12"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
} from "@medusajs/framework"
|
||||
import { createUserWorkflow } from "../workflows/create-user"
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/framework"
|
||||
import { Modules } from "@medusajs/framework/utils"
|
||||
export default async function handleUserCreated({
|
||||
event: { data },
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { result } = await createUserWorkflow(container)
|
||||
.run()
|
||||
|
||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||
const userModuleService = container.resolve(
|
||||
Modules.USER
|
||||
)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
const users = await userModuleService.listUsers()
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Scheduled Job" value="scheduled-job">
|
||||
|
||||
```ts title="src/jobs/run-daily.ts" highlights={[["7"], ["8"]]}
|
||||
import { MedusaContainer } from "@medusajs/framework/types"
|
||||
import { createUserWorkflow } from "../workflows/create-user"
|
||||
|
||||
export default async function myCustomJob(
|
||||
container: MedusaContainer
|
||||
) {
|
||||
const { result } = await createUserWorkflow(container)
|
||||
.run()
|
||||
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
name: "run-once-a-day",
|
||||
schedule: `0 0 * * *`,
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### User Management
|
||||
|
||||
Manage and store your users through Create, Read, Update, and Delete (CRUD) operations:
|
||||
|
||||
```ts
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
```
|
||||
|
||||
### Invite Users
|
||||
|
||||
Invite users to join your store and manage those invites, with expiry and revalidation features.
|
||||
|
||||
```ts
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
// refresh token later
|
||||
await userModuleService.refreshInviteTokens([invite.id])
|
||||
```
|
||||
Learn more about workflows in [this documentation](!docs!/learn/fundamentals/workflows).
|
||||
|
||||
---
|
||||
|
||||
## Configure User Module
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
The User Module accepts options for further configurations. Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
<CommerceModuleSections name="User" />
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
tags:
|
||||
- storefront
|
||||
- payment
|
||||
---
|
||||
|
||||
import { Prerequisites } from "docs-ui"
|
||||
|
||||
export const ogImage = "https://res.cloudinary.com/dza7lstvk/image/upload/v1734007558/Medusa%20Resources/integrations-stripe_qfnwtf.jpg"
|
||||
|
||||
@@ -3,6 +3,7 @@ tags:
|
||||
- publishable api key
|
||||
- api key
|
||||
- storefront
|
||||
- sales channel
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
|
||||
Reference in new issue
Block a user