docs: improve commerce modules [1/n] (#9498)
Improve and add docs for API Key, Auth, and Cart Modules [1/n]
This commit is contained in:
1 parent
dd162d69be
commit
69b9e73be7
29 files changed
+1024
-322
No files matched your search
@@ -4,7 +4,8 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
In this document, you’ll learn how about the different types of API keys, and their expiration and verification.
|
In this document, you’ll learn about the different types of API keys, their expiration and verification.
|
||||||
|
|
||||||
## API Key Types
|
## API Key Types
|
||||||
|
|
||||||
There are two types of API keys:
|
There are two types of API keys:
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,13 +67,12 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
export async function GET(request: MedusaRequest, res: MedusaResponse) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,14 +111,13 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
request: AuthenticatedMedusaRequest,
|
request: AuthenticatedMedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) {
|
) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -172,13 +169,12 @@ export async function POST(request: Request, { params }: ContextType) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
export async function POST(request: MedusaRequest, res: MedusaResponse) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,19 +225,18 @@ export async function POST(request: Request, { params }: ContextType) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "@medusajs/framework/http"
|
} from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
request: AuthenticatedMedusaRequest,
|
request: AuthenticatedMedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
) {
|
) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: `Relations between API Key Module and Other Modules`,
|
title: `Links between API Key Module and Other Modules`,
|
||||||
}
|
}
|
||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
This document showcases the link modules defined between the API Key Module and other commerce modules.
|
This document showcases the module links defined between the API Key Module and other commerce modules.
|
||||||
|
|
||||||
## Sales Channel Module
|
## Sales Channel Module
|
||||||
|
|
||||||
You can create a publishable API key and associate it with a sales channel. Medusa defines a link module that builds a relation between the `ApiKey` and the `SalesChannel` data models.
|
You can create a publishable API key and associate it with a sales channel. Medusa defines a link between the `ApiKey` and the `SalesChannel` data models.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -6,27 +6,42 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
The API Key Module is the `@medusajs/medusa/api-key` NPM package that provides API-key-related features in your Medusa and Node.js applications.
|
The API Key Module provides API-key-related features in your Medusa and Node.js applications.
|
||||||
|
|
||||||
## How to Use API Key Module's Service
|
## How to Use API Key Module's Service
|
||||||
|
|
||||||
You can use the API Key Module's main service by resolving from the Medusa container the resource `Modules.API_KEY` imported from `@medusajs/framework/utils`.
|
Use the API Key Module's main service by resolving from the Medusa container the resource `Modules.API_KEY` imported from `@medusajs/framework/utils`.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
<CodeTabs groupId="resource-type">
|
<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 apiKeyModuleService = container.resolve(
|
||||||
|
Modules.API_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTab>
|
||||||
<CodeTab label="API Route" value="api-route">
|
<CodeTab label="API Route" value="api-route">
|
||||||
|
|
||||||
```ts title="src/api/store/custom/route.ts"
|
```ts title="src/api/store/custom/route.ts"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: MedusaRequest,
|
request: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = request.scope.resolve(
|
const apiKeyModuleService = request.scope.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,35 +54,17 @@ export async function GET(
|
|||||||
</CodeTab>
|
</CodeTab>
|
||||||
<CodeTab label="Subscriber" value="subscribers">
|
<CodeTab label="Subscriber" value="subscribers">
|
||||||
|
|
||||||
```ts title="src/subscribers/custom-handler.ts"
|
```ts title="src/subscribers/custom-handler.ts"
|
||||||
import { SubscriberArgs } from "@medusajs/framework"
|
import { SubscriberArgs } from "@medusajs/framework"
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||||
const apiKeyModuleService: IApiKeyModuleService = container.resolve(
|
const apiKeyModuleService = container.resolve(
|
||||||
Modules.API_KEY
|
Modules.API_KEY
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
</CodeTab>
|
|
||||||
<CodeTab label="Workflow Step" value="workflow-step">
|
|
||||||
|
|
||||||
```ts title="src/workflows/hello-world/step1.ts"
|
|
||||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
|
||||||
import { IApiKeyModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
const step1 = createStep("step-1", async (_, { container }) => {
|
|
||||||
const apiKeyModuleService: IApiKeyModuleService = container.resolve(
|
|
||||||
Modules.API_KEY
|
|
||||||
)
|
|
||||||
|
|
||||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeTab>
|
</CodeTab>
|
||||||
@@ -79,7 +76,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
|
|||||||
|
|
||||||
### API Key Types and Management
|
### API Key Types and Management
|
||||||
|
|
||||||
Store and manage API keys in your store. You can create both publishable and secret API keys for different use cases, such as:
|
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.
|
- Publishable API Key associated with resources like sales channels.
|
||||||
- Authentication token for admin users to access Admin API Routes.
|
- Authentication token for admin users to access Admin API Routes.
|
||||||
|
|||||||
+26
-6
@@ -8,19 +8,39 @@ In this document, you’ll learn about concepts related to identity and actors i
|
|||||||
|
|
||||||
## What is an Auth Identity?
|
## What is an Auth Identity?
|
||||||
|
|
||||||
The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a registered user.
|
The [AuthIdentity data model](/references/auth/models/AuthIdentity) represents a user registered by an [authentication provider](../auth-providers/page.mdx).
|
||||||
|
|
||||||
When a user is registered, a record of `AuthIdentity` is created. This record is used to validate the user’s authentication in future requests.
|
When a user is registered using an authentication provider, it creates a record of `AuthIdentity`.
|
||||||
|
|
||||||
|
Then, when the user logs-in in the future with the same authentication provider, the associated auth identity is used to validate their credentials.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Actor Types
|
## Actor Types
|
||||||
|
|
||||||
An actor type is a type of user that can be authenticated. This user is a record of a data model defined by a module.
|
An actor type is a type of user that can be authenticated.
|
||||||
|
|
||||||
For example, the `customer` actor type belongs to the Customer Module’s `Customer` data model. Similarly, the `user` actor type belongs to the User Module’s `User` data model.
|
The Auth Module doesn't store or manage any user-like models, such as for customers or users.
|
||||||
|
|
||||||
### Protect Routes by Actor Type
|
Instead, the user types are created and managed by other modules. For example, a customer is managed by the Customer Module.
|
||||||
|
|
||||||
|
Then, when an auth identity is created for the actor type, the ID of the user is stored in the `app_metadata` property of the auth identity.
|
||||||
|
|
||||||
|
For example, an auth identity of a customer has the following `app_metadata` property:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_metadata": {
|
||||||
|
"customer_id": "cus_123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ID of the user is stored in the key `{actor_type}_id` of the `app_metadata` property.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Protect Routes by Actor Type
|
||||||
|
|
||||||
When you protect routes with the `authenticate` middleware, you specify in its first parameter the actor type that must be authenticated to access the specified API routes.
|
When you protect routes with the `authenticate` middleware, you specify in its first parameter the actor type that must be authenticated to access the specified API routes.
|
||||||
|
|
||||||
@@ -54,7 +74,7 @@ By specifying `user` as the first parameter of `authenticate`, only authenticate
|
|||||||
|
|
||||||
## Custom Actor Types
|
## Custom Actor Types
|
||||||
|
|
||||||
You can define custom actor types that point to the data model of your module.
|
You can define custom actor types that allows a custom user, managed by your custom module, to authenticate into Medusa.
|
||||||
|
|
||||||
For example, if you have a custom module with a `Manager` data model, you can authenticate managers with the `manager` actor type.
|
For example, if you have a custom module with a `Manager` data model, you can authenticate managers with the `manager` actor type.
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ const modules = {
|
|||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Related Guides
|
## Related Guides
|
||||||
|
|
||||||
- [How to register a customer using email and password](../../../../storefront-development/customers/register/page.mdx)
|
- [How to register a customer using email and password](../../../../storefront-development/customers/register/page.mdx)
|
||||||
@@ -8,7 +8,7 @@ export const metadata = {
|
|||||||
|
|
||||||
In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
|
In this document, you’ll learn about the GitHub Auth Module Provider and how to install and use it in the Auth Module.
|
||||||
|
|
||||||
The Github Auth Module Provider handles authenticating users with their GitHub account.
|
The Github Auth Module Provider authenticates users with their GitHub account.
|
||||||
|
|
||||||
<Note title="Tip">
|
<Note title="Tip">
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ export const metadata = {
|
|||||||
|
|
||||||
In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
|
In this document, you’ll learn about the Google Auth Module Provider and how to install and use it in the Auth Module.
|
||||||
|
|
||||||
The Google Auth Module Provider handles authenticating users with their Google account.
|
The Google Auth Module Provider authenticates users with their Google account.
|
||||||
|
|
||||||
<Note title="Tip">
|
<Note title="Tip">
|
||||||
|
|
||||||
Learn about the authentication flow in [this guide](../../authentication-route/page.mdx).
|
Learn about the authentication flow for third-party providers in [this guide](../../authentication-route/page.mdx#2-third-party-service-authenticate-flow).
|
||||||
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
@@ -143,4 +143,4 @@ GOOGLE_CALLBACK_URL=<YOUR_GOOGLE_CALLBACK_URL>
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
- [How to implement Google social login in the storefront.](../../../../storefront-development/customers/third-party-login/page.mdx).
|
- [How to implement Google social login in the storefront](../../../../storefront-development/customers/third-party-login/page.mdx).
|
||||||
@@ -37,7 +37,7 @@ For example, the EmailPass Auth Module Provider authenticates a user using their
|
|||||||
|
|
||||||
## Configure Allowed Auth Providers of Actor Types
|
## Configure Allowed Auth Providers of Actor Types
|
||||||
|
|
||||||
By default, users of all actor types can authenticate with all installed auth module providerss.
|
By default, users of all actor types can authenticate with all installed auth module providers.
|
||||||
|
|
||||||
To restrict the auth providers used for actor types, use the [authMethodsPerActor option](/references/medusa-config#http-authMethodsPerActor-1-3) in Medusa's configurations:
|
To restrict the auth providers used for actor types, use the [authMethodsPerActor option](/references/medusa-config#http-authMethodsPerActor-1-3) in Medusa's configurations:
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,13 @@ In this document, learn how to create an actor type and authenticate its associa
|
|||||||
|
|
||||||
## 0. Create Module with Data Model
|
## 0. Create Module with Data Model
|
||||||
|
|
||||||
Before creating an actor type, you must define a data model the actor type belongs to. The data model is defined in a custom module.
|
Before creating an actor type, you must have a module with a data model representing the actor type.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn how to create a module in [this guide](!docs!/basics/modules).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
The rest of this guide uses this `Manager` data model as an example:
|
The rest of this guide uses this `Manager` data model as an example:
|
||||||
|
|
||||||
@@ -31,7 +37,7 @@ export default Manager
|
|||||||
|
|
||||||
Start by creating a workflow that does two things:
|
Start by creating a workflow that does two things:
|
||||||
|
|
||||||
- Create a record of the `Manager` data model.
|
- Creates a record of the `Manager` data model.
|
||||||
- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type.
|
- Sets the `app_metadata` property of the associated `AuthIdentity` record based on the new actor type.
|
||||||
|
|
||||||
For example, create the file `src/workflows/create-manager.ts`. with the following content:
|
For example, create the file `src/workflows/create-manager.ts`. with the following content:
|
||||||
|
|||||||
@@ -19,21 +19,21 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import {
|
import {
|
||||||
IAuthModuleService,
|
IAuthModuleService,
|
||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { MedusaError } from "@medusajs/framework/utils"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,21 +122,21 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import {
|
import {
|
||||||
IAuthModuleService,
|
IAuthModuleService,
|
||||||
AuthenticationInput,
|
AuthenticationInput,
|
||||||
} from "@medusajs/framework/types"
|
} from "@medusajs/framework/types"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { MedusaError } from "@medusajs/framework/utils"
|
import { MedusaError } from "@medusajs/framework/utils"
|
||||||
import jwt from "jsonwebtoken"
|
import jwt from "jsonwebtoken"
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -224,16 +224,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -280,16 +279,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -327,15 +325,14 @@ export async function GET(request: Request) {
|
|||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -393,15 +390,14 @@ export async function POST(request: Request, { params }: ContextType) {
|
|||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ The `providers` option is an array of auth module providers.
|
|||||||
|
|
||||||
When the Medusa application starts, these providers are registered and can be used to handle authentication.
|
When the Medusa application starts, these providers are registered and can be used to handle authentication.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
By default, the `emailpass` provider is registered to authenticate customers and admin users.
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```js title="medusa-config.js"
|
```js title="medusa-config.js"
|
||||||
@@ -46,7 +52,7 @@ module.exports = defineConfig({
|
|||||||
|
|
||||||
The `providers` option is an array of objects that accept the following properties:
|
The `providers` option is an array of objects that accept the following properties:
|
||||||
|
|
||||||
- `resolve`: A string indicating the package name of the module provider or the path to it.
|
- `resolve`: A string indicating the package name of the module provider or the path to it relative to the `src` directory.
|
||||||
- `id`: A string indicating the provider's unique name or ID.
|
- `id`: A string indicating the provider's unique name or ID.
|
||||||
- `options`: An optional object of the module provider's options.
|
- `options`: An optional object of the module provider's options.
|
||||||
|
|
||||||
@@ -54,9 +60,11 @@ The `providers` option is an array of objects that accept the following properti
|
|||||||
|
|
||||||
## Auth CORS
|
## Auth CORS
|
||||||
|
|
||||||
The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration. So, before using these routes, make sure to set that configuration.
|
The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `authCors` property of the `http` configuration.
|
||||||
|
|
||||||
Refer to [Medusa's configuration guide](/references/medusa-config#authCors) for more details.
|
By default, the Medusa application you created will have an `AUTH_CORS` environment variable, which is used as the value of `authCors`.
|
||||||
|
|
||||||
|
Refer to [Medusa's configuration guide](/references/medusa-config#authCors) to learn more about the `authCors` configuration.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -6,27 +6,41 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
The Auth Module is the `@medusajs/medusa/auth` NPM package that provides authentication-related features in your Medusa and Node.js applications.
|
The Auth Module provides authentication-related features in your Medusa and Node.js applications.
|
||||||
|
|
||||||
## How to Use Auth Module's Service
|
## How to Use Auth Module's Service
|
||||||
|
|
||||||
You can use the Auth Module's main service by resolving from the Medusa container the resource `Modules.AUTH` imported from `@medusajs/framework/utils`.
|
Use the Auth Module's main service by resolving from the Medusa container the resource `Modules.AUTH` imported from `@medusajs/framework/utils`.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
<CodeTabs groupId="resource-type">
|
<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 authModuleService = container.resolve(
|
||||||
|
Modules.AUTH
|
||||||
|
)
|
||||||
|
const authIdentitys = await authModuleService.listAuthIdentities()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTab>
|
||||||
<CodeTab label="API Route" value="api-route">
|
<CodeTab label="API Route" value="api-route">
|
||||||
|
|
||||||
```ts title="src/api/store/custom/route.ts"
|
```ts title="src/api/store/custom/route.ts"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const authModuleService: IAuthModuleService = req.scope.resolve(
|
const authModuleService = req.scope.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,32 +55,15 @@ export async function GET(
|
|||||||
|
|
||||||
```ts title="src/subscribers/custom-handler.ts"
|
```ts title="src/subscribers/custom-handler.ts"
|
||||||
import { SubscriberArgs } from "@medusajs/framework"
|
import { SubscriberArgs } from "@medusajs/framework"
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||||
const authModuleService: IAuthModuleService = container.resolve(
|
const authModuleService = container.resolve(
|
||||||
Modules.AUTH
|
Modules.AUTH
|
||||||
)
|
)
|
||||||
|
|
||||||
const authIdentitys = await authModuleService.listAuthIdentities()
|
const authIdentitys = await authModuleService.listAuthIdentities()
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
</CodeTab>
|
|
||||||
<CodeTab label="Workflow Step" value="workflow-step">
|
|
||||||
|
|
||||||
```ts title="src/workflows/hello-world/step1.ts"
|
|
||||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
|
||||||
import { IAuthModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
const step1 = createStep("step-1", async (_, { container }) => {
|
|
||||||
const authModuleService: IAuthModuleService = container.resolve(
|
|
||||||
Modules.AUTH
|
|
||||||
)
|
|
||||||
const authIdentitys = await authModuleService.listAuthIdentities()
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeTab>
|
</CodeTab>
|
||||||
@@ -78,7 +75,7 @@ const step1 = createStep("step-1", async (_, { container }) => {
|
|||||||
|
|
||||||
### Basic User Authentication
|
### Basic User Authentication
|
||||||
|
|
||||||
With the Auth Module, authenticate users using their email and password credentials.
|
Authenticate users using their email and password credentials.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { success, authIdentity, error } = await authModuleService.authenticate(
|
const { success, authIdentity, error } = await authModuleService.authenticate(
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](../authentication-route/page.mdx#generate-reset-password-token-route), to send reset instructions to the user.
|
In this guide, you'll learn how to handle the `auth.password_reset` event, which is emitted when a request is sent to the [Generate Reset Password Token API route](../authentication-route/page.mdx#generate-reset-password-token-route).
|
||||||
|
|
||||||
|
You'll create a subscriber that listens to the event. When the event is emitted, the subscriber sends an email notification to the user.
|
||||||
|
|
||||||
<Prerequisites
|
<Prerequisites
|
||||||
items={[
|
items={[
|
||||||
@@ -29,14 +31,14 @@ export const highlights=[
|
|||||||
["10", "token", "The password reset token."],
|
["10", "token", "The password reset token."],
|
||||||
["11", "actor_type", "The user's actor type."],
|
["11", "actor_type", "The user's actor type."],
|
||||||
["19", "urlPrefix", "Set the page's URL based on the user's actor type."],
|
["19", "urlPrefix", "Set the page's URL based on the user's actor type."],
|
||||||
["21", "createNotifications", "Send a notification to the user."],
|
["23", "createNotifications", "Send a notification to the user."],
|
||||||
["23", `"email"`, "The channel to send the notification through."],
|
["25", `"email"`, "The channel to send the notification through."],
|
||||||
["24", "template", "The template defined in the third-party provider."],
|
["26", "template", "The template defined in the third-party provider."],
|
||||||
["25", "data", "The data to pass to the template in the third-party provider."],
|
["27", "data", "The data to pass to the template in the third-party provider."],
|
||||||
["27", "url", "The frontend URL to redirect the user to reset their password."]
|
["29", "url", "The frontend URL to redirect the user to reset their password."]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/subscribers/handle-reset.ts" collapsibleLines="1-6" expandMoreLabel="Show Imports"
|
```ts title="src/subscribers/handle-reset.ts" highlights={highlights} collapsibleLines="1-6" expandMoreLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
SubscriberArgs,
|
SubscriberArgs,
|
||||||
type SubscriberConfig,
|
type SubscriberConfig,
|
||||||
@@ -55,7 +57,9 @@ export default async function resetPasswordTokenHandler({
|
|||||||
Modules.NOTIFICATION
|
Modules.NOTIFICATION
|
||||||
)
|
)
|
||||||
|
|
||||||
const urlPrefix = actor_type === "customer" ? "https://storefront.com" : "https://admin.com"
|
const urlPrefix = actor_type === "customer" ?
|
||||||
|
"https://storefront.com" :
|
||||||
|
"https://admin.com"
|
||||||
|
|
||||||
await notificationModuleService.createNotifications({
|
await notificationModuleService.createNotifications({
|
||||||
to: email,
|
to: email,
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ A cart has a shipping and billing address. Both of these addresses are represent
|
|||||||
|
|
||||||
## Line Items
|
## Line Items
|
||||||
|
|
||||||
A line item, represented by the `LineItem` data model, is a product variant added to the cart. A cart has multiple line items.
|
A line item, represented by the [LineItem](/references/cart/models/LineItem) data model, is a quantity of a product variant added to the cart. A cart has multiple line items.
|
||||||
|
|
||||||
A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
|
A line item stores some of the product variant’s properties, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|
||||||
A product variant can be from the [Product Module](../../product/page.mdx) but can also be a custom item used only in this cart.
|
In the Medusa application, a product variant is implemented in the [Product Module](../../product/page.mdx).
|
||||||
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
@@ -32,12 +32,12 @@ A product variant can be from the [Product Module](../../product/page.mdx) but c
|
|||||||
|
|
||||||
A shipping method, represented by the [ShippingMethod data model](/references/cart/models/ShippingMethod), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
|
A shipping method, represented by the [ShippingMethod data model](/references/cart/models/ShippingMethod), is used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
|
||||||
|
|
||||||
If the shipping method is created from a shipping option, available through the [Fulfillment Module](../../fulfillment/page.mdx), its ID is stored in the `shipping_option_id`.
|
In the Medusa application, the shipping method is created from a shipping option, available through the [Fulfillment Module](../../fulfillment/page.mdx). Its ID is stored in the `shipping_option_id` property of the method.
|
||||||
|
|
||||||
A shipping method can also be a custom method associated with this cart only.
|
|
||||||
|
|
||||||
### data Property
|
### data Property
|
||||||
|
|
||||||
After an order is placed, you may use a third-party fulfillment provider to fulfill its shipments. If the fulfillment provider requires additional custom data to be passed along from the checkout process, you can add this data in the `ShippingMethod`'s `data` property.
|
After an order is placed, you can use a third-party fulfillment provider to fulfill its shipments.
|
||||||
|
|
||||||
|
If the fulfillment provider requires additional custom data to be passed along from the checkout process, set this data in the `ShippingMethod`'s `data` property.
|
||||||
|
|
||||||
The `data` property is an object used to store custom data relevant later for fulfillment.
|
The `data` property is an object used to store custom data relevant later for fulfillment.
|
||||||
@@ -13,16 +13,15 @@ In this guide, you’ll find common examples of how you can use the Cart Module
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,16 +86,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -135,16 +133,15 @@ export async function GET(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,16 +192,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -253,16 +249,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -311,16 +306,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function POST(
|
export async function POST(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -371,16 +365,15 @@ export async function POST(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -415,16 +408,15 @@ export async function DELETE(request: Request) {
|
|||||||
<CodeTabs groupId="app-type">
|
<CodeTabs groupId="app-type">
|
||||||
<CodeTab label="Medusa API Router" value="medusa">
|
<CodeTab label="Medusa API Router" value="medusa">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,16 +429,15 @@ export async function DELETE(
|
|||||||
</CodeTab>
|
</CodeTab>
|
||||||
<CodeTab label="Next.js App Router" value="nextjs">
|
<CodeTab label="Next.js App Router" value="nextjs">
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,666 @@
|
|||||||
|
import { Prerequisites } from "docs-ui"
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: `Extend Cart Data Model`,
|
||||||
|
}
|
||||||
|
|
||||||
|
# {metadata.title}
|
||||||
|
|
||||||
|
In this documentation, you'll learn how to extend a data model of the Cart Module to add a custom property.
|
||||||
|
|
||||||
|
You'll create a `Custom` data model in a module. This data model will have a `custom_name` property, which is the property you want to add to the [Cart data model](/references/cart/models/Cart) defined in the Cart Module.
|
||||||
|
|
||||||
|
You'll then learn how to:
|
||||||
|
|
||||||
|
- Link the `Custom` data model to the `Cart` data model.
|
||||||
|
- Set the `custom_name` property when a cart is created or updated using Medusa's API routes.
|
||||||
|
- Retrieve the `custom_name` property with the cart's details, in custom or existing API routes.
|
||||||
|
|
||||||
|
## Step 1: Define Custom Data Model
|
||||||
|
|
||||||
|
Consider you have a Hello Module defined in the `/src/modules/hello` directory.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
If you don't have a module, follow [this guide](!docs!/basics/modules) to create one.
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
To add the `custom_name` property to the `Cart` data model, you'll create in the Hello Module a data model that has the `custom_name` property.
|
||||||
|
|
||||||
|
Create the file `src/modules/hello/models/custom.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/modules/hello/models/custom.ts"
|
||||||
|
import { model } from "@medusajs/framework/utils"
|
||||||
|
|
||||||
|
export const Custom = model.define("custom", {
|
||||||
|
id: model.id().primaryKey(),
|
||||||
|
custom_name: model.text(),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `Custom` data model that has the `id` and `custom_name` properties.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn more about data models in [this guide](!docs!/data-models).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Define Link to Cart Data Model
|
||||||
|
|
||||||
|
Next, you'll define a module link between the `Custom` and `Cart` data model. A module link allows you to form a relation between two data models of separate modules while maintaining module isolation.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn more about module links in [this guide](!docs!/module-links).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Create the file `src/links/cart-custom.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/links/cart-custom.ts"
|
||||||
|
import { defineLink } from "@medusajs/framework/utils"
|
||||||
|
import HelloModule from "../modules/hello"
|
||||||
|
import CartModule from "@medusajs/medusa/cart"
|
||||||
|
|
||||||
|
export default defineLink(
|
||||||
|
CartModule.linkable.cart,
|
||||||
|
HelloModule.linkable.custom
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This defines a link between the `Cart` and `Custom` data models. Using this link, you'll later query data across the modules, and link records of each data model.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Generate and Run Migrations
|
||||||
|
|
||||||
|
<Prerequisites
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
text: "Module must be registered in medusa-config.js",
|
||||||
|
link: "!docs!/basics/modules#4-add-module-to-configurations"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
To reflect the `Custom` data model in the database, generate a migration that defines the table to be created for it.
|
||||||
|
|
||||||
|
Run the following command in your Medusa project's root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx medusa db:generate helloModuleService
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `helloModuleService` is your module's name.
|
||||||
|
|
||||||
|
Then, run the `db:migrate` command to run the migrations and create a table in the database for the link between the `Cart` and `Custom` data models:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx medusa db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
A table for the link is now created in the database. You can now retrieve and manage the link between records of the data models.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Consume cartCreated Workflow Hook
|
||||||
|
|
||||||
|
When a cart is created, you also want to create a `Custom` record and set the `custom_name` property, then create a link between the `Cart` and `Custom` records.
|
||||||
|
|
||||||
|
To do that, you'll consume the [cartCreated](/references/medusa-workflows/createCartWorkflow#cartcreated) hook of the [createCartWorkflow](/references/medusa-workflows/createCartWorkflow). This workflow is executed in the [Create Cart API Route](!api!/store#carts_postcarts).
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn more about workflow hooks in [this guide](!docs!/advanced-development/workflows/workflow-hooks).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
The API route accepts in its request body an `additional_data` parameter. You can pass in it custom data, which is passed to the workflow hook handler.
|
||||||
|
|
||||||
|
### Add custom_name to Additional Data Validation
|
||||||
|
|
||||||
|
To pass the `custom_name` in the `additional_data` parameter, you must add a validation rule that tells the Medusa application about this custom property.
|
||||||
|
|
||||||
|
Create the file `src/api/middlewares.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/api/middlewares.ts"
|
||||||
|
import { defineMiddlewares } from "@medusajs/medusa"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export default defineMiddlewares({
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
matcher: "/store/carts",
|
||||||
|
additionalDataValidator: {
|
||||||
|
custom_name: z.string().optional(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The `additional_data` parameter validation is customized using the `defineMiddlewares` utility function. In the routes middleware configuration object, the `additionalDataValidator` property accepts [Zod](https://zod.dev/) validaiton rules.
|
||||||
|
|
||||||
|
In the snippet above, you add a validation rule indicating that `custom_name` is a string that can be passed in the `additional_data` object.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn more about additional data validation in [this guide](!docs!/advanced-development/api-routes/additional-data).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
### Create Workflow to Create Custom Record
|
||||||
|
|
||||||
|
You'll now create a workflow that will be used in the hook handler.
|
||||||
|
|
||||||
|
This workflow will create a `Custom` record, then link it to the cart.
|
||||||
|
|
||||||
|
Start by creating the step that creates the `Custom` record. Create the file `src/workflows/create-custom-from-cart/steps/create-custom.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/create-custom-from-cart/steps/create-custom.ts"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import HelloModuleService from "../../../modules/hello/service"
|
||||||
|
import { HELLO_MODULE } from "../../../modules/hello"
|
||||||
|
|
||||||
|
type CreateCustomStepInput = {
|
||||||
|
custom_name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCustomStep = createStep(
|
||||||
|
"create-custom",
|
||||||
|
async (data: CreateCustomStepInput, { container }) => {
|
||||||
|
if (!data.custom_name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
const custom = await helloModuleService.createCustoms(data)
|
||||||
|
|
||||||
|
return new StepResponse(custom, custom)
|
||||||
|
},
|
||||||
|
async (custom, { container }) => {
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
await helloModuleService.deleteCustoms(custom.id)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In the step, you resolve the Hello Module's main service and create a `Custom` record.
|
||||||
|
|
||||||
|
In the compensation function that undoes the step's actions in case of an error, you delete the created record.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
Learn more about compensation functions in [this guide](!docs!/advanced-development/workflows/compensation-function).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Then, create the workflow at `src/workflows/create-custom-from-cart/index.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/create-custom-from-cart/index.ts" collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||||
|
import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { CartDTO } from "@medusajs/framework/types"
|
||||||
|
import { createCustomStep } from "./steps/create-custom"
|
||||||
|
import { createRemoteLinkStep } from "@medusajs/medusa/core-flows"
|
||||||
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
import { HELLO_MODULE } from "../../modules/hello"
|
||||||
|
|
||||||
|
export type CreateCustomFromCartWorkflowInput = {
|
||||||
|
cart: CartDTO
|
||||||
|
additional_data?: {
|
||||||
|
custom_name?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCustomFromCartWorkflow = createWorkflow(
|
||||||
|
"create-custom-from-cart",
|
||||||
|
(input: CreateCustomFromCartWorkflowInput) => {
|
||||||
|
const customName = transform(
|
||||||
|
{
|
||||||
|
input,
|
||||||
|
},
|
||||||
|
(data) => data.input.additional_data?.custom_name || ""
|
||||||
|
)
|
||||||
|
|
||||||
|
const custom = createCustomStep({
|
||||||
|
custom_name: customName,
|
||||||
|
})
|
||||||
|
|
||||||
|
when(({ custom }), ({ custom }) => custom !== undefined)
|
||||||
|
.then(() => {
|
||||||
|
createRemoteLinkStep([{
|
||||||
|
[Modules.CART]: {
|
||||||
|
cart_id: input.cart.id,
|
||||||
|
},
|
||||||
|
[HELLO_MODULE]: {
|
||||||
|
custom_id: custom.id,
|
||||||
|
},
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse({
|
||||||
|
custom,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The workflow accepts as an input the created cart and the `additional_data` parameter passed in the request. This is the same input that the `cartCreated` hook accepts.
|
||||||
|
|
||||||
|
In the workflow, you:
|
||||||
|
|
||||||
|
1. Use the `transform` utility to get the value of `custom_name` based on whether it's set in `additional_data`. Learn more about why you can't use conditional operators in a workflow without using `transform` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
|
||||||
|
2. Create the `Custom` record using the `createCustomStep`.
|
||||||
|
3. Use the `when-then` utility to link the cart to the `Custom` record if it was created. Learn more about why you can't use if-then conditions in a workflow without using `when-then` in [this guide](!docs!/advanced-development/workflows/conditions#why-if-conditions-arent-allowed-in-workflows).
|
||||||
|
|
||||||
|
You'll next call the workflow in the hook handler.
|
||||||
|
|
||||||
|
### Consume Workflow Hook
|
||||||
|
|
||||||
|
You can now consume the `cartCreated` hook, which is executed in the `createCartWorkflow` after the cart is created.
|
||||||
|
|
||||||
|
To consume the hook, create the file `src/workflow/hooks/cart-created.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflow/hooks/cart-created.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||||
|
import { createCartWorkflow } from "@medusajs/medusa/core-flows"
|
||||||
|
import {
|
||||||
|
createCustomFromCartWorkflow,
|
||||||
|
CreateCustomFromCartWorkflowInput,
|
||||||
|
} from "../create-custom-from-cart"
|
||||||
|
|
||||||
|
createCartWorkflow.hooks.cartCreated(
|
||||||
|
async (hookData, { container }) => {
|
||||||
|
await createCustomFromCartWorkflow(container)
|
||||||
|
.run({
|
||||||
|
input: hookData as CreateCustomFromCartWorkflowInput,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The hook handler executes the `createCustomFromCartWorkflow`, passing it its input.
|
||||||
|
|
||||||
|
### Test it Out
|
||||||
|
|
||||||
|
To test it out, send a `POST` request to `/store/carts` to create a cart, passing `custom_name` in `additional_data`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST 'localhost:9000/store/carts' \
|
||||||
|
-H 'x-publishable-api-key: {publishable_api_key}' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"region_id": "reg_01J9NNNWVV0T71PT44EAMTJCMP",
|
||||||
|
"additional_data": {
|
||||||
|
"custom_name": "test"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to replace `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin. Also, replace the value of `region_id` with an ID of a region in your application.
|
||||||
|
|
||||||
|
The request will return the cart's details. You'll learn how to retrive the `custom_name` property with the cart's details in the next section.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Retrieve custom_name with Cart Details
|
||||||
|
|
||||||
|
When you extend an existing data model through links, you also want to retrieve the custom properties with the data model.
|
||||||
|
|
||||||
|
### Retrieve in API Routes
|
||||||
|
|
||||||
|
To retrieve the `custom_name` property when you're retrieving the cart through API routes, such as the [Get Cart API Route](!api!/store#carts_getcartsid), pass in the `fields` query parameter `+custom.*`, which retrieves the linked `Custom` record's details.
|
||||||
|
|
||||||
|
<Note title="Tip">
|
||||||
|
|
||||||
|
The `+` prefix in `+custom.*` indicates that the relation should be retrieved with the default cart fields. Learn more about selecting fields and relations in the [API reference](!api!/store#select-fields-and-relations).
|
||||||
|
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST 'localhost:9000/store/carts/{cart_id}?fields=+custom.*' \
|
||||||
|
-H 'x-publishable-api-key: {publishable_api_key}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to replace `{cart_id}` with the cart's ID, and `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin.
|
||||||
|
|
||||||
|
Among the returned `cart` object, you'll find a `custom` property which holds the details of the linked `Custom` record:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cart": {
|
||||||
|
// ...
|
||||||
|
"custom": {
|
||||||
|
"id": "01J9NP7ANXDZ0EAYF0956ZE1ZA",
|
||||||
|
"custom_name": "test",
|
||||||
|
"created_at": "2024-10-08T09:09:06.877Z",
|
||||||
|
"updated_at": "2024-10-08T09:09:06.877Z",
|
||||||
|
"deleted_at": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Retrieve using Query
|
||||||
|
|
||||||
|
You can also retrieve the `Custom` record linked to a cart in your code using [Query](!docs!/advanced-development/module-links/query).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data: [cart] } = await query.graph({
|
||||||
|
entity: "cart",
|
||||||
|
fields: ["*", "custom.*"],
|
||||||
|
filters: {
|
||||||
|
id: cart_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Learn more about how to use Query in [this guide](!docs!/advanced-development/module-links/query).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Consume cartUpdated Workflow Hook
|
||||||
|
|
||||||
|
Similar to the `cartCreated` hook, you'll consume the [cartUpdated](/references/medusa-workflows/updateCartWorkflow#cartupdated) hook of the [updateCartWorkflow](/references/medusa-workflows/updateCartWorkflow) to update `custom_name` when the cart is updated.
|
||||||
|
|
||||||
|
The `updateCartWorkflow` is executed by the [Update Cart API route](!api!/store#carts_postcartsid), which accepts the `additional_data` parameter to pass custom data to the hook.
|
||||||
|
|
||||||
|
### Add custom_name to Additional Data Validation
|
||||||
|
|
||||||
|
To allow passing `custom_name` in the `additional_data` parameter of the update cart route, add in `src/api/middlewares.ts` a new route middleware configuration object:
|
||||||
|
|
||||||
|
```ts title="src/api/middlewares.ts"
|
||||||
|
import { defineMiddlewares } from "@medusajs/medusa"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export default defineMiddlewares({
|
||||||
|
routes: [
|
||||||
|
// ...
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
matcher: "/store/carts/:id",
|
||||||
|
additionalDataValidator: {
|
||||||
|
custom_name: z.string().nullish(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
The validation schema is the similar to that of the Create Cart API route, except you can pass a `null` value for `custom_name` to remove or unset the `custom_name`'s value.
|
||||||
|
|
||||||
|
### Create Workflow to Update Custom Record
|
||||||
|
|
||||||
|
Next, you'll create a workflow that creates, updates, or deletes `Custom` records based on the provided `additional_data` parameter:
|
||||||
|
|
||||||
|
1. If `additional_data.custom_name` is set and it's `null`, the `Custom` record linked to the cart is deleted.
|
||||||
|
2. If `additional_data.custom_name` is set and the cart doesn't have a linked `Custom` record, a new record is created and linked to the cart.
|
||||||
|
3. If `additional_data.custom_name` is set and the cart has a linked `Custom` record, the `custom_name` property of the `Custom` record is updated.
|
||||||
|
|
||||||
|
Start by creating the step that updates a `Custom` record. Create the file `src/workflows/update-custom-from-cart/steps/update-custom.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/steps/update-custom.ts"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { HELLO_MODULE } from "../../../modules/hello"
|
||||||
|
import HelloModuleService from "../../../modules/hello/service"
|
||||||
|
|
||||||
|
type UpdateCustomStepInput = {
|
||||||
|
id: string
|
||||||
|
custom_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateCustomStep = createStep(
|
||||||
|
"update-custom",
|
||||||
|
async ({ id, custom_name }: UpdateCustomStepInput, { container }) => {
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
const prevData = await helloModuleService.retrieveCustom(id)
|
||||||
|
|
||||||
|
const custom = await helloModuleService.updateCustoms({
|
||||||
|
id,
|
||||||
|
custom_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new StepResponse(custom, prevData)
|
||||||
|
},
|
||||||
|
async (prevData, { container }) => {
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
await helloModuleService.updateCustoms(prevData)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this step, you update a `Custom` record. In the compensation function, you revert the update.
|
||||||
|
|
||||||
|
Next, you'll create the step that deletes a `Custom` record. Create the file `src/workflows/update-custom-from-cart/steps/delete-custom.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/steps/delete-custom.ts" collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||||
|
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { Custom } from "../../../modules/hello/models/custom"
|
||||||
|
import { InferTypeOf } from "@medusajs/framework/types"
|
||||||
|
import HelloModuleService from "../../../modules/hello/service"
|
||||||
|
import { HELLO_MODULE } from "../../../modules/hello"
|
||||||
|
|
||||||
|
type DeleteCustomStepInput = {
|
||||||
|
custom: InferTypeOf<typeof Custom>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteCustomStep = createStep(
|
||||||
|
"delete-custom",
|
||||||
|
async ({ custom }: DeleteCustomStepInput, { container }) => {
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
await helloModuleService.deleteCustoms(custom.id)
|
||||||
|
|
||||||
|
return new StepResponse(custom, custom)
|
||||||
|
},
|
||||||
|
async (custom, { container }) => {
|
||||||
|
const helloModuleService: HelloModuleService = container.resolve(
|
||||||
|
HELLO_MODULE
|
||||||
|
)
|
||||||
|
|
||||||
|
await helloModuleService.createCustoms(custom)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this step, you delete a `Custom` record. In the compensation function, you create it again.
|
||||||
|
|
||||||
|
Finally, you'll create the workflow. Create the file `src/workflows/update-custom-from-cart/index.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/index.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||||
|
import { CartDTO } from "@medusajs/framework/types"
|
||||||
|
import { createWorkflow, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
|
||||||
|
import { createRemoteLinkStep, dismissRemoteLinkStep, useRemoteQueryStep } from "@medusajs/medusa/core-flows"
|
||||||
|
import { createCustomStep } from "../create-custom-from-cart/steps/create-custom"
|
||||||
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
|
import { HELLO_MODULE } from "../../modules/hello"
|
||||||
|
import { deleteCustomStep } from "./steps/delete-custom"
|
||||||
|
import { updateCustomStep } from "./steps/update-custom"
|
||||||
|
|
||||||
|
export type UpdateCustomFromCartStepInput = {
|
||||||
|
cart: CartDTO
|
||||||
|
additional_data?: {
|
||||||
|
custom_name?: string | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateCustomFromCartWorkflow = createWorkflow(
|
||||||
|
"update-custom-from-cart",
|
||||||
|
(input: UpdateCustomFromCartStepInput) => {
|
||||||
|
const cartData = useRemoteQueryStep({
|
||||||
|
entry_point: "cart",
|
||||||
|
fields: ["custom.*"],
|
||||||
|
variables: {
|
||||||
|
filters: {
|
||||||
|
id: input.cart.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
list: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO create, update, or delete Custom record
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The workflow accepts the same input as the `cartUpdated` workflow hook handler would.
|
||||||
|
|
||||||
|
In the workflow, you retrieve the cart's linked `Custom` record using Query.
|
||||||
|
|
||||||
|
Next, replace the `TODO` with the following:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/index.ts"
|
||||||
|
const created = when({
|
||||||
|
input,
|
||||||
|
cartData,
|
||||||
|
}, (data) =>
|
||||||
|
!data.cartData.custom &&
|
||||||
|
data.input.additional_data?.custom_name?.length > 0
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
const custom = createCustomStep({
|
||||||
|
custom_name: input.additional_data.custom_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
createRemoteLinkStep([{
|
||||||
|
[Modules.CART]: {
|
||||||
|
cart_id: input.cart.id,
|
||||||
|
},
|
||||||
|
[HELLO_MODULE]: {
|
||||||
|
custom_id: custom.id,
|
||||||
|
},
|
||||||
|
}])
|
||||||
|
|
||||||
|
return custom
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO update, or delete Custom record
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the `when-then` utility, you check if the cart doesn't have a linked `Custom` record and the `custom_name` property is set. If so, you create a `Custom` record and link it to the cart.
|
||||||
|
|
||||||
|
To create the `Custom` record, you use the `createCustomStep` you created in an earlier section.
|
||||||
|
|
||||||
|
Next, replace the new `TODO` with the following:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/index.ts"
|
||||||
|
const deleted = when({
|
||||||
|
input,
|
||||||
|
cartData,
|
||||||
|
}, (data) =>
|
||||||
|
data.cartData.custom && (
|
||||||
|
data.input.additional_data?.custom_name === null ||
|
||||||
|
data.input.additional_data?.custom_name.length === 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
deleteCustomStep({
|
||||||
|
custom: cartData.custom,
|
||||||
|
})
|
||||||
|
|
||||||
|
dismissRemoteLinkStep({
|
||||||
|
[HELLO_MODULE]: {
|
||||||
|
custom_id: cartData.custom.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return cartData.custom.id
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO delete Custom record
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the `when-then` utility, you check if the cart has a linked `Custom` record and `custom_name` is `null` or an empty string. If so, you delete the linked `Custom` record and dismiss its links.
|
||||||
|
|
||||||
|
Finally, replace the new `TODO` with the following:
|
||||||
|
|
||||||
|
```ts title="src/workflows/update-custom-from-cart/index.ts"
|
||||||
|
const updated = when({
|
||||||
|
input,
|
||||||
|
cartData,
|
||||||
|
}, (data) => data.cartData.custom && data.input.additional_data?.custom_name?.length > 0)
|
||||||
|
.then(() => {
|
||||||
|
const custom = updateCustomStep({
|
||||||
|
id: cartData.custom.id,
|
||||||
|
custom_name: input.additional_data.custom_name,
|
||||||
|
})
|
||||||
|
|
||||||
|
return custom
|
||||||
|
})
|
||||||
|
|
||||||
|
return new WorkflowResponse({
|
||||||
|
created,
|
||||||
|
updated,
|
||||||
|
deleted,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the `when-then` utility, you check if the cart has a linked `Custom` record and `custom_name` is passed in the `additional_data`. If so, you update the linked `Custom` recod.
|
||||||
|
|
||||||
|
You return in the workflow response the created, updated, and deleted `Custom` record.
|
||||||
|
|
||||||
|
### Consume cartUpdated Workflow Hook
|
||||||
|
|
||||||
|
You can now consume the `cartUpdated` and execute the workflow you created.
|
||||||
|
|
||||||
|
Create the file `src/workflows/hooks/cart-updated.ts` with the following content:
|
||||||
|
|
||||||
|
```ts title="src/workflows/hooks/cart-updated.ts"
|
||||||
|
import { updateCartWorkflow } from "@medusajs/medusa/core-flows"
|
||||||
|
import {
|
||||||
|
UpdateCustomFromCartStepInput,
|
||||||
|
updateCustomFromCartWorkflow,
|
||||||
|
} from "../update-custom-from-cart"
|
||||||
|
|
||||||
|
updateCartWorkflow.hooks.cartUpdated(
|
||||||
|
async (hookData, { container }) => {
|
||||||
|
await updateCustomFromCartWorkflow(container)
|
||||||
|
.run({
|
||||||
|
input: hookData as UpdateCustomFromCartStepInput,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
In the workflow hook handler, you execute the workflow, passing it the hook's input.
|
||||||
|
|
||||||
|
### Test it Out
|
||||||
|
|
||||||
|
To test it out, send a `POST` request to `/store/carts/:id` to update a cart, passing `custom_name` in `additional_data`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST 'localhost:9000/store/carts/{cart_id}?fields=+custom.*' \
|
||||||
|
-H 'x-publishable-api-key: {publishable_api_key}' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"additional_data": {
|
||||||
|
"custom_name": "test 2"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to replace `{cart_id}` with the cart's ID, and `{publishable_api_key}` with your publishable API key, which you can retrieve from the Medusa Admin.
|
||||||
|
|
||||||
|
The request will return the cart's details with the updated `custom` linked record.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
export const metadata = {
|
||||||
|
title: `Links between Cart Module and Other Modules`,
|
||||||
|
}
|
||||||
|
|
||||||
|
# {metadata.title}
|
||||||
|
|
||||||
|
This document showcases the module links defined between the Cart Module and other commerce modules.
|
||||||
|
|
||||||
|
## Payment Module
|
||||||
|
|
||||||
|
The Payment Module handles payment processing and management.
|
||||||
|
|
||||||
|
Medusa defines a link between the `Cart` and `PaymentCollection` data models. A cart has a payment collection which holds all the authorized payment sessions and payments made related to the cart.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Promotion Module
|
||||||
|
|
||||||
|
The Promotion Module provides discount features.
|
||||||
|
|
||||||
|
Medusa defines a link between the `Cart` and `Promotion` data models. This indicates the promotions applied on a cart.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Order Module
|
||||||
|
|
||||||
|
The Order Module provides order-management features.
|
||||||
|
|
||||||
|
Medusa defines a link between the `Cart` and `Order` data models. The cart is linked to the order created once the cart is completed.
|
||||||
|
|
||||||
|

|
||||||
@@ -6,7 +6,7 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
The Cart Module is the `@medusajs/medusa/cart` NPM package that provides cart-related features in your Medusa and Node.js applications.
|
The Cart Module provides cart-related features in your Medusa and Node.js applications.
|
||||||
|
|
||||||
## How to Use Cart Module's Service
|
## How to Use Cart Module's Service
|
||||||
|
|
||||||
@@ -15,18 +15,33 @@ You can use the Cart Module's main service by resolving from the Medusa containe
|
|||||||
For example:
|
For example:
|
||||||
|
|
||||||
<CodeTabs groupId="resource-type">
|
<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 cartModuleService = container.resolve(
|
||||||
|
Modules.CART
|
||||||
|
)
|
||||||
|
|
||||||
|
const carts = await cartModuleService.listCarts()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeTab>
|
||||||
<CodeTab label="API Route" value="api-route">
|
<CodeTab label="API Route" value="api-route">
|
||||||
|
|
||||||
```ts title="src/api/store/custom/route.ts"
|
```ts title="src/api/store/custom/route.ts"
|
||||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
req: MedusaRequest,
|
req: MedusaRequest,
|
||||||
res: MedusaResponse
|
res: MedusaResponse
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const cartModuleService: ICartModuleService = req.scope.resolve(
|
const cartModuleService = req.scope.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,35 +54,17 @@ export async function GET(
|
|||||||
</CodeTab>
|
</CodeTab>
|
||||||
<CodeTab label="Subscriber" value="subscribers">
|
<CodeTab label="Subscriber" value="subscribers">
|
||||||
|
|
||||||
```ts title="src/subscribers/custom-handler.ts"
|
```ts title="src/subscribers/custom-handler.ts"
|
||||||
import { SubscriberArgs } from "@medusajs/framework"
|
import { SubscriberArgs } from "@medusajs/framework"
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
import { Modules } from "@medusajs/framework/utils"
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
export default async function subscriberHandler({ container }: SubscriberArgs) {
|
||||||
const cartModuleService: ICartModuleService = container.resolve(
|
const cartModuleService = container.resolve(
|
||||||
Modules.CART
|
Modules.CART
|
||||||
)
|
)
|
||||||
|
|
||||||
const carts = await cartModuleService.listCarts()
|
const carts = await cartModuleService.listCarts()
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
</CodeTab>
|
|
||||||
<CodeTab label="Workflow Step" value="workflow-step">
|
|
||||||
|
|
||||||
```ts title="src/workflows/hello-world/step1.ts"
|
|
||||||
import { createStep } from "@medusajs/framework/workflows-sdk"
|
|
||||||
import { ICartModuleService } from "@medusajs/framework/types"
|
|
||||||
import { Modules } from "@medusajs/framework/utils"
|
|
||||||
|
|
||||||
const step1 = createStep("step-1", async (_, { container }) => {
|
|
||||||
const cartModuleService: ICartModuleService = container.resolve(
|
|
||||||
Modules.CART
|
|
||||||
)
|
|
||||||
|
|
||||||
const carts = await cartModuleService.listCarts()
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeTab>
|
</CodeTab>
|
||||||
@@ -121,7 +118,7 @@ const shippingAdjustments =
|
|||||||
|
|
||||||
A cart is scoped to a sales channel, region, and a customer.
|
A cart is scoped to a sales channel, region, and a customer.
|
||||||
|
|
||||||
When used with their respective modules and other commerce modules, you benefit from features like:
|
The Medusa application links the Cart Module to each of their respective modules, providing with features like:
|
||||||
|
|
||||||
- Checking product availability in a sales channel.
|
- Checking product availability in a sales channel.
|
||||||
- Retrieving pricing per region.
|
- Retrieving pricing per region.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ In this document, you’ll learn how a promotion is applied to a cart’s line i
|
|||||||
|
|
||||||
An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
|
An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
|
||||||
|
|
||||||
The `LineItemAdjustment` data model represents changes on a line item, and the `ShippingMethodAdjustment` data model represents changes on a shipping method.
|
The [LineItemAdjustment](/references/cart/models/LineItemAdjustment) data model represents changes on a line item, and the [ShippingMethodAdjustment](/references/cart/models/ShippingMethodAdjustment) data model represents changes on a shipping method.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ The `amount` property of the adjustment line indicates the amount to be discount
|
|||||||
|
|
||||||
## Discountable Option
|
## Discountable Option
|
||||||
|
|
||||||
The `LineItem` data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
The [LineItem](/references/cart/models/LineItem) data model has an `is_discountable` property that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
||||||
|
|
||||||
When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
|
When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ When disabled, a promotion can’t be applied to a line item. In the context of
|
|||||||
|
|
||||||
## Promotion Actions
|
## Promotion Actions
|
||||||
|
|
||||||
When using the Cart and Promotion modules together, use the [computeActions method of the Promotion Module’s main service](/references/promotion/computeActions). It retrieves the actions of line items and shipping methods.
|
When using the Cart and Promotion modules together, such as in the Medusa application, use the [computeActions method of the Promotion Module’s main service](/references/promotion/computeActions). It retrieves the actions of line items and shipping methods.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|
||||||
@@ -41,6 +41,8 @@ Learn more about actions in the [Promotion Module’s documentation](../../promo
|
|||||||
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
ComputeActionAdjustmentLine,
|
ComputeActionAdjustmentLine,
|
||||||
@@ -101,7 +103,7 @@ The `computeActions` method accepts the existing adjustments of line items and s
|
|||||||
|
|
||||||
Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
|
Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
|
||||||
|
|
||||||
```ts collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
```ts collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||||
import {
|
import {
|
||||||
AddItemAdjustmentAction,
|
AddItemAdjustmentAction,
|
||||||
AddShippingMethodAdjustment,
|
AddShippingMethodAdjustment,
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
export const metadata = {
|
|
||||||
title: `Relations between Cart Module and Other Modules`,
|
|
||||||
}
|
|
||||||
|
|
||||||
# {metadata.title}
|
|
||||||
|
|
||||||
This document showcases the link modules defined between the Cart Module and other commerce modules.
|
|
||||||
|
|
||||||
## Customer Module
|
|
||||||
|
|
||||||
A cart is scoped to the customer using it. Medusa defines a link module that builds a relationship between the `Cart` and `Customer` data models.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Payment Module
|
|
||||||
|
|
||||||
The Payment Module allows you to associate payments with a cart. Medusa defines a link module that builds a relationship between the `Cart` and `PaymentCollection` data models.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Product Module
|
|
||||||
|
|
||||||
A cart's line item is associated with a product and its variant. Medusa defines a link module that builds a relationship between the `Cart`, `Product`, and `ProductVariant` data models.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Promotion Module
|
|
||||||
|
|
||||||
A promotion can be applied on line items and shipping methods of a cart. Medusa defines a link module that builds a relationship between the `Cart`, `LineItemAdjustment`, and `Promotion` data models.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Region Module
|
|
||||||
|
|
||||||
A cart is scoped to a region. Medusa defines a link module that builds a relationship between the `Cart` and `Region` data models.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sales Channel Module
|
|
||||||
|
|
||||||
A cart is scoped to a sales channel. Medusa defines a link module that builds a relationship between the `Cart` and `SalesChannel` data models.
|
|
||||||
|
|
||||||

|
|
||||||
@@ -16,7 +16,7 @@ A tax line indicates the tax rate of a line item or a shipping method. The [Line
|
|||||||
|
|
||||||
## Tax Inclusivity
|
## Tax Inclusivity
|
||||||
|
|
||||||
By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then added to the item/method’s subtotal.
|
By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then adding them to the item/method’s subtotal.
|
||||||
|
|
||||||
However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
|
However, line items and shipping methods have an `is_tax_inclusive` property that, when enabled, indicates that the item or method’s price already includes taxes.
|
||||||
|
|
||||||
@@ -30,9 +30,11 @@ The following diagram is a simplified showcase of how a subtotal is calculated f
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
For example, if a line item's amount is `5000`, the tax rate is `10`, and tax inclusivity is enabled, the tax amount is 10% of `5000`, which is `500`, making the unit price of the line item `4500`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Retrieving Tax Lines
|
## Retrieve Tax Lines
|
||||||
|
|
||||||
When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
|
When using the Cart and Tax modules together, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ export const metadata = {
|
|||||||
|
|
||||||
# {metadata.title}
|
# {metadata.title}
|
||||||
|
|
||||||
In this section of the documentation, you'll find guides and references related to Medusa's Commerce Modules.
|
In this section of the documentation, you'll find guides and references related to Medusa's commerce modules.
|
||||||
|
|
||||||
|
A commerce module provides features for a commerce domain within its service. The Medusa application exposes these features in its API routes to clients.
|
||||||
|
|
||||||
|
A commerce module also defines data models, representing tables in the database. Medusa's framework and tools allow you to extend these data models to add custom fields.
|
||||||
|
|
||||||
|
## Commerce Modules List
|
||||||
|
|
||||||
<ChildDocs onlyTopLevel={true} />
|
<ChildDocs onlyTopLevel={true} />
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ For example, this is not allowed:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {
|
import {
|
||||||
AdminGetApiKeysParams
|
AdminGetApiKeysParams,
|
||||||
} from "@medusajs/medusa/dist/api/admin/api-keys" // <-- Not allowed
|
} from "@medusajs/medusa/dist/api/admin/api-keys" // <-- Not allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,6 +31,6 @@ So, this is allowed:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {
|
import {
|
||||||
AdminGetApiKeysParams
|
AdminGetApiKeysParams,
|
||||||
} from "@medusajs/medusa/api/admin/api-keys" // <-- Allowed
|
} from "@medusajs/medusa/api/admin/api-keys" // <-- Allowed
|
||||||
```
|
```
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
export const generatedEditDates = {
|
export const generatedEditDates = {
|
||||||
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2024-09-30T08:43:53.153Z",
|
"app/commerce-modules/auth/auth-providers/emailpass/page.mdx": "2024-10-08T07:35:59.167Z",
|
||||||
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-09-05T12:15:19.491Z",
|
"app/commerce-modules/auth/auth-providers/page.mdx": "2024-10-08T07:27:21.859Z",
|
||||||
"app/commerce-modules/auth/authentication-route/page.mdx": "2024-09-05T12:06:38.155Z",
|
"app/commerce-modules/auth/authentication-route/page.mdx": "2024-09-05T12:06:38.155Z",
|
||||||
"app/commerce-modules/auth/examples/page.mdx": "2024-09-30T08:43:53.154Z",
|
"app/commerce-modules/auth/examples/page.mdx": "2024-10-07T15:36:17.212Z",
|
||||||
"app/commerce-modules/auth/module-options/page.mdx": "2024-09-30T08:43:53.156Z",
|
"app/commerce-modules/auth/module-options/page.mdx": "2024-10-07T15:33:05.989Z",
|
||||||
"app/commerce-modules/auth/page.mdx": "2024-09-30T08:43:53.156Z",
|
"app/commerce-modules/auth/page.mdx": "2024-10-08T07:38:27.522Z",
|
||||||
"app/commerce-modules/cart/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/cart/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/cart/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/cart/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/cart/concepts/page.mdx": "2024-06-26T07:55:59+00:00",
|
"app/commerce-modules/cart/concepts/page.mdx": "2024-10-08T07:49:03.737Z",
|
||||||
"app/commerce-modules/cart/examples/page.mdx": "2024-09-30T08:43:53.157Z",
|
"app/commerce-modules/cart/examples/page.mdx": "2024-10-08T07:42:13.336Z",
|
||||||
"app/commerce-modules/cart/promotions/page.mdx": "2024-09-30T08:43:53.158Z",
|
"app/commerce-modules/cart/promotions/page.mdx": "2024-10-08T07:54:31.120Z",
|
||||||
"app/commerce-modules/cart/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
|
"app/commerce-modules/cart/tax-lines/page.mdx": "2024-10-08T07:57:19.168Z",
|
||||||
"app/commerce-modules/cart/tax-lines/page.mdx": "2024-06-26T07:55:59+00:00",
|
"app/commerce-modules/cart/page.mdx": "2024-10-08T07:41:22.711Z",
|
||||||
"app/commerce-modules/cart/page.mdx": "2024-09-30T08:43:53.157Z",
|
|
||||||
"app/commerce-modules/currency/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/currency/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/currency/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/currency/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/currency/examples/page.mdx": "2024-10-08T15:06:26.492Z",
|
"app/commerce-modules/currency/examples/page.mdx": "2024-10-08T15:06:26.492Z",
|
||||||
@@ -122,7 +121,7 @@ export const generatedEditDates = {
|
|||||||
"app/commerce-modules/user/module-options/page.mdx": "2024-09-30T08:43:53.171Z",
|
"app/commerce-modules/user/module-options/page.mdx": "2024-09-30T08:43:53.171Z",
|
||||||
"app/commerce-modules/user/user-creation-flows/page.mdx": "2024-06-26T07:55:59+00:00",
|
"app/commerce-modules/user/user-creation-flows/page.mdx": "2024-06-26T07:55:59+00:00",
|
||||||
"app/commerce-modules/user/page.mdx": "2024-09-30T08:43:53.172Z",
|
"app/commerce-modules/user/page.mdx": "2024-09-30T08:43:53.172Z",
|
||||||
"app/commerce-modules/page.mdx": "2024-05-03T17:36:38+03:00",
|
"app/commerce-modules/page.mdx": "2024-10-07T13:55:08.014Z",
|
||||||
"app/contribution-guidelines/_admin-translations/page.mdx": "2024-05-13T18:55:11+03:00",
|
"app/contribution-guidelines/_admin-translations/page.mdx": "2024-05-13T18:55:11+03:00",
|
||||||
"app/contribution-guidelines/docs/page.mdx": "2024-05-13T18:55:11+03:00",
|
"app/contribution-guidelines/docs/page.mdx": "2024-05-13T18:55:11+03:00",
|
||||||
"app/create-medusa-app/page.mdx": "2024-08-05T11:10:55+03:00",
|
"app/create-medusa-app/page.mdx": "2024-08-05T11:10:55+03:00",
|
||||||
@@ -220,15 +219,14 @@ export const generatedEditDates = {
|
|||||||
"app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/auth/_events/_events-table/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/auth/auth-flows/page.mdx": "2024-09-05T08:50:11.671Z",
|
"app/commerce-modules/auth/auth-flows/page.mdx": "2024-09-05T08:50:11.671Z",
|
||||||
"app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/commerce-modules/auth/_events/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2024-09-05T08:11:28.936Z",
|
"app/commerce-modules/auth/auth-identity-and-actor-types/page.mdx": "2024-10-08T07:08:43.428Z",
|
||||||
"app/commerce-modules/api-key/page.mdx": "2024-09-30T08:43:53.152Z",
|
"app/commerce-modules/api-key/page.mdx": "2024-10-07T13:57:33.042Z",
|
||||||
"app/commerce-modules/auth/create-actor-type/page.mdx": "2024-09-30T08:43:53.154Z",
|
"app/commerce-modules/auth/create-actor-type/page.mdx": "2024-10-08T07:31:11.256Z",
|
||||||
"app/architectural-modules/page.mdx": "2024-05-28T13:25:03+03:00",
|
"app/architectural-modules/page.mdx": "2024-05-28T13:25:03+03:00",
|
||||||
"app/commerce-modules/api-key/relations-to-other-modules/page.mdx": "2024-05-29T11:08:06+00:00",
|
|
||||||
"app/architectural-modules/workflow-engine/redis/page.mdx": "2024-09-30T08:43:53.152Z",
|
"app/architectural-modules/workflow-engine/redis/page.mdx": "2024-09-30T08:43:53.152Z",
|
||||||
"app/commerce-modules/api-key/examples/page.mdx": "2024-09-30T08:43:53.152Z",
|
"app/commerce-modules/api-key/examples/page.mdx": "2024-10-07T13:58:49.457Z",
|
||||||
"app/architectural-modules/notification/sendgrid/page.mdx": "2024-09-30T08:43:53.151Z",
|
"app/architectural-modules/notification/sendgrid/page.mdx": "2024-09-30T08:43:53.151Z",
|
||||||
"app/commerce-modules/api-key/concepts/page.mdx": "2024-06-26T07:55:59+00:00",
|
"app/commerce-modules/api-key/concepts/page.mdx": "2024-10-07T13:59:37.529Z",
|
||||||
"app/architectural-modules/workflow-engine/page.mdx": "2024-05-28T13:25:03+03:00",
|
"app/architectural-modules/workflow-engine/page.mdx": "2024-05-28T13:25:03+03:00",
|
||||||
"app/_events-reference/page.mdx": "2024-07-03T19:27:13+03:00",
|
"app/_events-reference/page.mdx": "2024-07-03T19:27:13+03:00",
|
||||||
"app/architectural-modules/cache/page.mdx": "2024-05-28T13:25:03+03:00",
|
"app/architectural-modules/cache/page.mdx": "2024-05-28T13:25:03+03:00",
|
||||||
@@ -906,8 +904,8 @@ export const generatedEditDates = {
|
|||||||
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminClaimPreviewResponse/page.mdx": "2024-10-03T00:11:52.828Z",
|
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminClaimPreviewResponse/page.mdx": "2024-10-03T00:11:52.828Z",
|
||||||
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminOrderEditPreviewResponse/page.mdx": "2024-10-03T00:11:53.030Z",
|
"references/types/HttpTypes/interfaces/types.HttpTypes.AdminOrderEditPreviewResponse/page.mdx": "2024-10-03T00:11:53.030Z",
|
||||||
"references/types/interfaces/types.BaseClaim/page.mdx": "2024-10-03T00:11:52.699Z",
|
"references/types/interfaces/types.BaseClaim/page.mdx": "2024-10-03T00:11:52.699Z",
|
||||||
"app/commerce-modules/auth/auth-providers/github/page.mdx": "2024-09-30T08:43:53.153Z",
|
"app/commerce-modules/auth/auth-providers/github/page.mdx": "2024-10-08T07:37:27.882Z",
|
||||||
"app/commerce-modules/auth/auth-providers/google/page.mdx": "2024-09-30T08:43:53.153Z",
|
"app/commerce-modules/auth/auth-providers/google/page.mdx": "2024-10-08T07:37:06.517Z",
|
||||||
"app/storefront-development/customers/third-party-login/page.mdx": "2024-09-11T09:58:51.801Z",
|
"app/storefront-development/customers/third-party-login/page.mdx": "2024-09-11T09:58:51.801Z",
|
||||||
"references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-09-17T00:10:58.803Z",
|
"references/types/HttpTypes/types/types.HttpTypes.AdminWorkflowRunResponse/page.mdx": "2024-09-17T00:10:58.803Z",
|
||||||
"references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2024-09-05T00:11:17.182Z",
|
"references/types/HttpTypes/types/types.HttpTypes.BatchResponse/page.mdx": "2024-09-05T00:11:17.182Z",
|
||||||
@@ -2220,6 +2218,11 @@ export const generatedEditDates = {
|
|||||||
"references/user/interfaces/user.IModuleService/page.mdx": "2024-10-03T00:12:20.657Z",
|
"references/user/interfaces/user.IModuleService/page.mdx": "2024-10-03T00:12:20.657Z",
|
||||||
"references/user/interfaces/user.MessageAggregatorFormat/page.mdx": "2024-10-03T00:12:20.662Z",
|
"references/user/interfaces/user.MessageAggregatorFormat/page.mdx": "2024-10-03T00:12:20.662Z",
|
||||||
"app/troubleshooting/dist-imports/page.mdx": "2024-10-03T09:19:37.639Z",
|
"app/troubleshooting/dist-imports/page.mdx": "2024-10-03T09:19:37.639Z",
|
||||||
|
"app/commerce-modules/auth/reset-password/page.mdx": "2024-10-08T07:34:08.488Z",
|
||||||
|
"app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
|
||||||
|
"app/commerce-modules/api-key/links-to-other-modules/page.mdx": "2024-10-08T08:05:36.596Z",
|
||||||
|
"app/commerce-modules/cart/extend/page.mdx": "2024-10-08T11:22:22.523Z",
|
||||||
|
"app/commerce-modules/cart/links-to-other-modules/page.mdx": "2024-10-08T08:22:35.190Z",
|
||||||
"app/commerce-modules/auth/reset-password/page.mdx": "2024-09-25T09:36:26.592Z",
|
"app/commerce-modules/auth/reset-password/page.mdx": "2024-09-25T09:36:26.592Z",
|
||||||
"app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
|
"app/storefront-development/customers/reset-password/page.mdx": "2024-09-25T10:21:46.647Z",
|
||||||
"app/commerce-modules/customer/extend/page.mdx": "2024-10-08T14:18:55.407Z",
|
"app/commerce-modules/customer/extend/page.mdx": "2024-10-08T14:18:55.407Z",
|
||||||
|
|||||||
@@ -88,12 +88,12 @@ export const filesMap = [
|
|||||||
"pathname": "/commerce-modules/api-key/examples"
|
"pathname": "/commerce-modules/api-key/examples"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/api-key/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/api-key/links-to-other-modules/page.mdx",
|
||||||
"pathname": "/commerce-modules/api-key"
|
"pathname": "/commerce-modules/api-key/links-to-other-modules"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/api-key/relations-to-other-modules/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/api-key/page.mdx",
|
||||||
"pathname": "/commerce-modules/api-key/relations-to-other-modules"
|
"pathname": "/commerce-modules/api-key"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/auth/auth-flows/page.mdx",
|
||||||
@@ -151,6 +151,14 @@ export const filesMap = [
|
|||||||
"filePath": "/www/apps/resources/app/commerce-modules/cart/examples/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/examples/page.mdx",
|
||||||
"pathname": "/commerce-modules/cart/examples"
|
"pathname": "/commerce-modules/cart/examples"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/extend/page.mdx",
|
||||||
|
"pathname": "/commerce-modules/cart/extend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/links-to-other-modules/page.mdx",
|
||||||
|
"pathname": "/commerce-modules/cart/links-to-other-modules"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/cart/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/page.mdx",
|
||||||
"pathname": "/commerce-modules/cart"
|
"pathname": "/commerce-modules/cart"
|
||||||
@@ -159,10 +167,6 @@ export const filesMap = [
|
|||||||
"filePath": "/www/apps/resources/app/commerce-modules/cart/promotions/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/promotions/page.mdx",
|
||||||
"pathname": "/commerce-modules/cart/promotions"
|
"pathname": "/commerce-modules/cart/promotions"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/cart/relations-to-other-modules/page.mdx",
|
|
||||||
"pathname": "/commerce-modules/cart/relations-to-other-modules"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"filePath": "/www/apps/resources/app/commerce-modules/cart/tax-lines/page.mdx",
|
"filePath": "/www/apps/resources/app/commerce-modules/cart/tax-lines/page.mdx",
|
||||||
"pathname": "/commerce-modules/cart/tax-lines"
|
"pathname": "/commerce-modules/cart/tax-lines"
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export const generatedSidebar = [
|
|||||||
"loaded": true,
|
"loaded": true,
|
||||||
"isPathHref": true,
|
"isPathHref": true,
|
||||||
"type": "link",
|
"type": "link",
|
||||||
"path": "/commerce-modules/api-key/relations-to-other-modules",
|
"path": "/commerce-modules/api-key/links-to-other-modules",
|
||||||
"title": "Relation to Modules",
|
"title": "Link to Modules",
|
||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -538,6 +538,14 @@ export const generatedSidebar = [
|
|||||||
"title": "Examples",
|
"title": "Examples",
|
||||||
"children": []
|
"children": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"loaded": true,
|
||||||
|
"isPathHref": true,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/commerce-modules/cart/extend",
|
||||||
|
"title": "Extend Module",
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"loaded": true,
|
"loaded": true,
|
||||||
"isPathHref": true,
|
"isPathHref": true,
|
||||||
@@ -572,8 +580,8 @@ export const generatedSidebar = [
|
|||||||
"loaded": true,
|
"loaded": true,
|
||||||
"isPathHref": true,
|
"isPathHref": true,
|
||||||
"type": "link",
|
"type": "link",
|
||||||
"path": "/commerce-modules/cart/relations-to-other-modules",
|
"path": "/commerce-modules/cart/links-to-other-modules",
|
||||||
"title": "Relations to Other Modules",
|
"title": "Links to Other Modules",
|
||||||
"children": []
|
"children": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ const nextConfig = {
|
|||||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/v2/resources",
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/v2/resources",
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
source: "/commerce-modules/api-key/relations-to-other-modules",
|
||||||
|
destination: "/commerce-modules/api-key/links-to-other-modules",
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "/commerce-modules/cart/relations-to-other-modules",
|
||||||
|
destination: "/commerce-modules/cart/links-to-other-modules",
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: "/commerce-modules/fulfillment/relations-to-other-modules",
|
source: "/commerce-modules/fulfillment/relations-to-other-modules",
|
||||||
destination: "/commerce-modules/fulfillment/links-to-other-modules",
|
destination: "/commerce-modules/fulfillment/links-to-other-modules",
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "link",
|
||||||
path: "/commerce-modules/api-key/relations-to-other-modules",
|
path: "/commerce-modules/api-key/links-to-other-modules",
|
||||||
title: "Relation to Modules",
|
title: "Link to Modules",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -223,6 +223,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
|||||||
path: "/commerce-modules/cart/examples",
|
path: "/commerce-modules/cart/examples",
|
||||||
title: "Examples",
|
title: "Examples",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "link",
|
||||||
|
path: "/commerce-modules/cart/extend",
|
||||||
|
title: "Extend Module",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "sub-category",
|
type: "sub-category",
|
||||||
title: "Concepts",
|
title: "Concepts",
|
||||||
@@ -244,8 +249,8 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "link",
|
type: "link",
|
||||||
path: "/commerce-modules/cart/relations-to-other-modules",
|
path: "/commerce-modules/cart/links-to-other-modules",
|
||||||
title: "Relations to Other Modules",
|
title: "Links to Other Modules",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user