docs: updates to use DML and other changes (#7834)
- Change existing data model guides and add new ones for DML - Change module's docs around service factory + remove guides that are now necessary - Hide/remove all mentions of module relationships, or label them as coming soon. - Change all data model creation snippets to use DML - use `property` instead of `field` when referring to a data model's properties. - Fix all snippets in commerce module guides to use new method suffix (no more main model methods) - Rework recipes, removing/hiding a lot of sections as a lot of recipes are incomplete with the current state of DML. ### Other changes - Highlight fixes in some guides - Remove feature flags guide - Fix code block styles when there are no line numbers. ### Upcoming changes in other PRs - Re-generate commerce module references (for the updates in the method names) - Ensure that the data model references are generated correctly for models using DML. - (probably at a very later point) revisit recipes
This commit is contained in:
@@ -75,9 +75,12 @@ Widgets that are injected into a details page (for example, `product.details.aft
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["5"]]}
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["8"]]}
|
||||
import { defineWidgetConfig } from "@medusajs/admin-shared"
|
||||
import { DetailWidgetProps, AdminProduct } from "@medusajs/types"
|
||||
import {
|
||||
DetailWidgetProps,
|
||||
AdminProduct,
|
||||
} from "@medusajs/types"
|
||||
|
||||
const ProductWidget = ({
|
||||
data,
|
||||
|
||||
@@ -41,8 +41,8 @@ To create an API route that accepts multiple path parameters, create within the
|
||||
For example, create an API route at `src/api/store/hello-world/[id]/name/[name]/route.ts`:
|
||||
|
||||
export const multiplePathHighlights = [
|
||||
["11", "req.params.id", "Access the path parameter `id`"],
|
||||
["11", "req.params.name", "Access the path parameter `name`"]
|
||||
["12", "req.params.id", "Access the path parameter `id`"],
|
||||
["13", "req.params.name", "Access the path parameter `name`"]
|
||||
]
|
||||
|
||||
```ts title="src/api/store/hello-world/[id]/name/[name]/route.ts" highlights={multiplePathHighlights}
|
||||
@@ -56,7 +56,9 @@ export const GET = (
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: `[GET] Hello ${req.params.id} - ${req.params.name}!`,
|
||||
message: `[GET] Hello ${
|
||||
req.params.id
|
||||
} - ${req.params.name}!`,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -125,12 +125,15 @@ To protect custom API Routes that don’t start with `/store/customers/me` or `/
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["8", "authenticate", "Only authenticated admin users can access routes starting with `/custom/admin`"],
|
||||
["14", "authenticate", "Only authenticated customers can access routes starting with `/custom/customers`"]
|
||||
["11", "authenticate", "Only authenticated admin users can access routes starting with `/custom/admin`"],
|
||||
["17", "authenticate", "Only authenticated customers can access routes starting with `/custom/customers`"]
|
||||
]
|
||||
|
||||
```ts title="src/api/middlewares.ts" highlights={highlights}
|
||||
import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
|
||||
import {
|
||||
MiddlewaresConfig,
|
||||
authenticate,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Common Data Model Definitions`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn where to find resources on common data model definitions related to field's column types, relations, and indices.
|
||||
|
||||
## MikroORM Learning Resources
|
||||
|
||||
Refer to the following resources to learn about common definitions:
|
||||
|
||||
1. [Field column types](https://mikro-orm.io/docs/defining-entities).
|
||||
2. [Creating indices](https://mikro-orm.io/docs/defining-entities#indexes).
|
||||
|
||||
Refer to MikroORM's documentation for more details related to your use case.
|
||||
|
||||
---
|
||||
|
||||
## Relations Between Data Models
|
||||
|
||||
You can build relations between data models in the same module using foreign keys.
|
||||
|
||||
Refer to [MikroORM's relations definitions](https://mikro-orm.io/docs/relationships) for more details.
|
||||
|
||||
<Note>
|
||||
|
||||
For building relationships between data models in different modules, refer to the [Module Relationships chapter](../../modules/module-relationships/page.mdx) instead.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Example Data Model
|
||||
|
||||
The following example showcase a data model with common definitions:
|
||||
|
||||
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
Entity,
|
||||
Enum,
|
||||
OneToOne,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
// assuming this is another implemented data model
|
||||
import ProductVariant from "./product-variant"
|
||||
|
||||
export enum MediaType {
|
||||
MAIN = "main",
|
||||
PREVIEW = "preview"
|
||||
}
|
||||
|
||||
@Entity()
|
||||
class ProductMedia extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Enum({ items: ["main", "preview"] })
|
||||
type: MediaType
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
file_key: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
mime_type: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
variant_id: string
|
||||
|
||||
@OneToOne({
|
||||
entity: ProductVariant,
|
||||
onDelete: "cascade",
|
||||
})
|
||||
variant: ProductVariant
|
||||
}
|
||||
|
||||
export default ProductMedia
|
||||
```
|
||||
|
||||
In the example above:
|
||||
|
||||
- The `ProductMedia` data model has the columns `id`, `name`, `type`, `file_key`, `mime_type`, and `variant_id`.
|
||||
- The data model has an index on the `variant_id` column.
|
||||
- The data model has a one-to-one relation to a `ProductVariant` data model.
|
||||
@@ -0,0 +1,84 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Configure Data Model Properties`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to configure data model properties, such as setting their default value.
|
||||
|
||||
## Property’s Default Value
|
||||
|
||||
Use the `default` method of the `model` utility to specify the default value of a property.
|
||||
|
||||
For example:
|
||||
|
||||
export const defaultHighlights = [
|
||||
["6", "default", "Set the default value to `black`."],
|
||||
["9", "default", "Set the default value to `0`."]
|
||||
]
|
||||
|
||||
```ts highlights={defaultHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
color: model
|
||||
.enum(["black", "white"])
|
||||
.default("black"),
|
||||
age: model
|
||||
.number()
|
||||
.default(0),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
|
||||
|
||||
---
|
||||
|
||||
## Nullable Property
|
||||
|
||||
Use the `nullable` method to indicate that a property’s value can be `null`.
|
||||
|
||||
For example:
|
||||
|
||||
export const nullableHighlights = [
|
||||
["4", "nullable", "Configure the `price` property to allow `null` values."]
|
||||
]
|
||||
|
||||
```ts highlights={nullableHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
price: model.bigNumber().nullable(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unique Property
|
||||
|
||||
The `unique` method indicates that a property’s value must be unique in the database.
|
||||
|
||||
For example:
|
||||
|
||||
export const uniqueHighlights = [
|
||||
["5", "unique", "Configure the `email` property to allow unique values only."]
|
||||
]
|
||||
|
||||
```ts highlights={uniqueHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
email: model.text().unique(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default User
|
||||
```
|
||||
|
||||
In this example, multiple users can’t have the same email.
|
||||
@@ -0,0 +1,32 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Indexes`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to define indexes on a data model.
|
||||
|
||||
## Define Index
|
||||
|
||||
The `index` method defines a custom index on a property.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["5", "index", "Define an index on the `name` property."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id(),
|
||||
name: model.text().index(
|
||||
"IDX_MY_CUSTOM_NAME"
|
||||
),
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In this example, you define an index on the `name` property. The `index` method optionally accepts the name of the index as a parameter.
|
||||
@@ -0,0 +1,40 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model’s Primary Key`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to configure the primary key of a data model.
|
||||
|
||||
## id Property
|
||||
|
||||
A property defined with the `id` method is, by default, considered the data model’s primary key.
|
||||
|
||||
---
|
||||
|
||||
## primaryKey Method
|
||||
|
||||
To set any `text` or `number` property as a primary key, use the `primaryKey` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["4", "primaryKey", "Define the `code` property to be the data model's primary key."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
code: model.text().primaryKey(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
A property that’s defined with the `primaryKey` method takes precedence over an `id` property.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,198 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Property Types`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn about the types of properties in a data model’s schema.
|
||||
|
||||
## id
|
||||
|
||||
The `id` method defines an automatically generated string ID property.
|
||||
|
||||
For example:
|
||||
|
||||
export const idHighlights = [["4", ".id()", "Define an `id` property."]]
|
||||
|
||||
```ts highlights={idHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
By default, this property is considered to be the data model’s primary key.
|
||||
|
||||
---
|
||||
|
||||
## text
|
||||
|
||||
The `text` method defines a string property.
|
||||
|
||||
For example:
|
||||
|
||||
export const textHighlights = [["4", "text", "Define a `text` property."]]
|
||||
|
||||
```ts highlights={textHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
name: model.text(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## number
|
||||
|
||||
The `number` method defines a number property.
|
||||
|
||||
For example:
|
||||
|
||||
export const numberHighlights = [["4", "number", "Define a `number` property."]]
|
||||
|
||||
```ts highlights={numberHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
age: model.number(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## bigNumber
|
||||
|
||||
The `bigNumber` method defines a number property that expects large numbers, such as prices.
|
||||
|
||||
For example:
|
||||
|
||||
export const bigNumberHighlights = [["4", "bigNumber", "Define a `bigNumber` property."]]
|
||||
|
||||
```ts highlights={bigNumberHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
price: model.bigNumber(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## boolean
|
||||
|
||||
The `boolean` method defines a boolean property.
|
||||
|
||||
For example:
|
||||
|
||||
export const booleanHighlights = [["4", "boolean", "Define a `boolean` property."]]
|
||||
|
||||
```ts highlights={booleanHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
hasAccount: model.boolean(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### enum
|
||||
|
||||
The `enum` method defines a property whose value can only be one of the specified values.
|
||||
|
||||
For example:
|
||||
|
||||
export const enumHighlights = [["4", "enum", "Define a `enum` property."]]
|
||||
|
||||
```ts highlights={enumHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
color: model.enum(["black", "white"]),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
The `enum` method accepts an array of possible string values.
|
||||
|
||||
---
|
||||
|
||||
## dateTime
|
||||
|
||||
The `dateTime` method defines a timestamp property.
|
||||
|
||||
For example:
|
||||
|
||||
export const dateTimeHighlights = [["4", "dateTime", "Define a `dateTime` property."]]
|
||||
|
||||
```ts highlights={dateTimeHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
date_of_birth: model.dateTime(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## json
|
||||
|
||||
The `json` method defines a property whose value is a stringified JSON object.
|
||||
|
||||
For example:
|
||||
|
||||
export const jsonHighlights = [["4", "json", "Define a `json` property."]]
|
||||
|
||||
```ts highlights={jsonHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
metadata: model.json(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## array
|
||||
|
||||
The `array` method defines an array or strings property.
|
||||
|
||||
For example:
|
||||
|
||||
export const arrHightlights = [["4", "array", "Define an `array` property."]]
|
||||
|
||||
```ts highlights={arrHightlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
names: model.array(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
@@ -0,0 +1,190 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Data Model Relationships`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you’ll learn how to define relationships between data models in your module.
|
||||
|
||||
## What is a Relationship Property?
|
||||
|
||||
A relationship property is defined using relation methods, such as `hasOne` or `belongsTo`. It represents a relationship between two data models in a module.
|
||||
|
||||
---
|
||||
|
||||
## One-to-One Relationship
|
||||
|
||||
To define a one-to-one relationship, create relationship properties in the data models using the following methods:
|
||||
|
||||
1. `hasOne`: indicates that the model has one record of the specified model.
|
||||
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
|
||||
|
||||
For example:
|
||||
|
||||
export const oneToOneHighlights = [
|
||||
["5", "hasOne", "A user has one email."],
|
||||
["10", "belongsTo", "An email belongs to a user."],
|
||||
["11", `"email"`, "The relationship's name in the `User` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={oneToOneHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
id: model.id(),
|
||||
email: model.hasOne(() => Email),
|
||||
})
|
||||
|
||||
const Email = model.define("email", {
|
||||
id: model.id(),
|
||||
user: model.belongsTo(() => User, {
|
||||
mappedBy: "email",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
The `hasOne` and `belongsTo` methods accept a function as a first parameter. The function returns the associated data model.
|
||||
|
||||
The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model.
|
||||
|
||||
In the example above, a user has one email, and an email belongs to one user.
|
||||
|
||||
---
|
||||
|
||||
## One-to-Many Relationship
|
||||
|
||||
To define a one-to-many relationship, create relationship properties in the data models using the following methods:
|
||||
|
||||
1. `hasMany`: indicates that the model has more than one records of the specified model.
|
||||
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
|
||||
|
||||
For example:
|
||||
|
||||
export const oneToManyHighlights = [
|
||||
["5", "hasMany", "A store has many products"],
|
||||
["10", "belongsTo", "A product has one store."],
|
||||
["11", `"products"`, "The relationship's name in the `Store` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={oneToManyHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const Store = model.define("store", {
|
||||
id: model.id(),
|
||||
products: model.hasMany(() => Product),
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id(),
|
||||
store: model.belongsTo(() => Store, {
|
||||
mappedBy: "products",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
In this example, a store has many products, but a product belongs to one store.
|
||||
|
||||
---
|
||||
|
||||
## Many-to-Many Relationship
|
||||
|
||||
To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const manyToManyHighlights = [
|
||||
["5", "manyToMany", "An order is associated with many products."],
|
||||
["10", "manyToMany", "A product is associated with many orders."]
|
||||
]
|
||||
|
||||
```ts highlights={manyToManyHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const Order = model.define("order", {
|
||||
id: model.id(),
|
||||
products: model.manyToMany(() => Product),
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id(),
|
||||
order: model.manyToMany(() => Order),
|
||||
})
|
||||
```
|
||||
|
||||
In this example, an order is associated with many products, and a product is associated with many orders.
|
||||
|
||||
---
|
||||
|
||||
## Configure Relationship Property Name
|
||||
|
||||
The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model.
|
||||
|
||||
As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
|
||||
|
||||
For example:
|
||||
|
||||
export const relationNameHighlights = [
|
||||
["6", `"owner"`, "The relationship's name in the `Email` data model."],
|
||||
["13", `"email"`, "The relationship's name in the `User` data model."]
|
||||
]
|
||||
|
||||
```ts highlights={relationNameHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const User = model.define("user", {
|
||||
id: model.id(),
|
||||
email: model.hasOne(() => Email, {
|
||||
mappedBy: "owner",
|
||||
}),
|
||||
})
|
||||
|
||||
const Email = model.define("email", {
|
||||
id: model.id(),
|
||||
owner: model.belongsTo(() => User, {
|
||||
mappedBy: "email",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
In this example, you specify in the `User` data model’s relationship property that the name of the relationship in the `Email` data model is `owner`.
|
||||
|
||||
This is useful if the relationship property’s name is different than that of the associated data model.
|
||||
|
||||
---
|
||||
|
||||
## Cascades
|
||||
|
||||
When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it.
|
||||
|
||||
For example, if a store is deleted, its products should also be deleted.
|
||||
|
||||
The `cascades` method used on a data model configures which child records an operation is cascaded to.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["8", "", "When a store is deleted, delete its associated products."]
|
||||
]
|
||||
|
||||
```ts highlights={highlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const Store = model.define("store", {
|
||||
id: model.id(),
|
||||
products: model.hasMany(() => Product),
|
||||
})
|
||||
.cascades({
|
||||
delete: ["products"],
|
||||
})
|
||||
|
||||
const Product = model.define("product", {
|
||||
id: model.id(),
|
||||
store: model.belongsTo(() => Store, {
|
||||
mappedBy: "products",
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
The `cascades` method accepts an object. Its key is the operation’s name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to.
|
||||
|
||||
In the example above, when a store is deleted, its associated products are also deleted.
|
||||
@@ -0,0 +1,46 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Searchable Data Model Property`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn what a searchable property is and how to define it.
|
||||
|
||||
## What is a Searchable Property?
|
||||
|
||||
Methods generated by the [service factory](../../modules/service-factory/page.mdx) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
|
||||
|
||||
When the `q` filter is passed, the query is applied on searchable properties in a data model.
|
||||
|
||||
---
|
||||
|
||||
## Define a Searchable Property
|
||||
|
||||
The `searchable` method of the `model` utility indicates that a `text` property is searchable.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const MyCustom = model.define("my_custom", {
|
||||
name: model.text().searchable(),
|
||||
// ...
|
||||
})
|
||||
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
In this example, the `name` property is searchable.
|
||||
|
||||
### Search Example
|
||||
|
||||
If you pass a `q` filter to the `listMyCustoms` method:
|
||||
|
||||
```ts
|
||||
const myCustoms = await helloModuleService.listMyCustoms({
|
||||
q: "John",
|
||||
})
|
||||
```
|
||||
|
||||
The `q` filter is applied on the `name` property of the `MyCustom` records.
|
||||
@@ -1,99 +1,19 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Soft-Deletable Models`,
|
||||
title: `${pageNumber} Soft-Deletable Data Models`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how to create soft-deletable data models.
|
||||
In this chapter, you’ll learn about soft-deletable data models.
|
||||
|
||||
## What is a Soft-Deletable Model?
|
||||
## What is a Soft-Deletable Data Model?
|
||||
|
||||
A soft-deletable data model is a model whose records aren't actually removed from the database when they're deleted.
|
||||
A soft-deletable data model is a model that has a `deleted_at` `dateTime` property.
|
||||
|
||||
Instead, their `deleted_at` field is set to the date the record was deleted.
|
||||
|
||||
When retrieving or listing records of that data model, records having their `deleted_at` field set aren't retrieved unless the `withDeleted` filter is provided.
|
||||
When a record of the data model is deleted, this field is set to the current date, marking it as deleted.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Soft-Deletable Model?
|
||||
## Configure Data Model Soft-Deletion
|
||||
|
||||
To create a soft-deletable model, first, add the following filter decorator to the data model class:
|
||||
|
||||
```ts title="src/module/hello/models/my-soft-deletable.ts" highlights={[["7"]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
// other imports...
|
||||
import { Entity, Filter } from "@mikro-orm/core"
|
||||
import { DALUtils } from "@medusajs/utils"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
|
||||
@Entity()
|
||||
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
|
||||
class MySoftDeletable extends BaseEntity {
|
||||
// ...
|
||||
}
|
||||
|
||||
export default MySoftDeletable
|
||||
```
|
||||
|
||||
Then, add a `deleted_at` field to the data model:
|
||||
|
||||
```ts highlights={[["7"], ["8"]]}
|
||||
// other imports...
|
||||
import { Property } from "@mikro-orm/core"
|
||||
|
||||
class MySoftDeletable extends BaseEntity {
|
||||
// ...
|
||||
|
||||
@Property({ columnType: "timestamptz", nullable: true })
|
||||
deleted_at: Date | null = null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage Soft-Deletable Models
|
||||
|
||||
Services extending the service factory have methods to soft delete and restore records for all models specified during its creation.
|
||||
|
||||
### Soft Delete a Record
|
||||
|
||||
For example, to soft delete a `MySoftDeletable` record:
|
||||
|
||||
```ts
|
||||
await helloModuleService.softDelete([
|
||||
"id_123", "id_321",
|
||||
])
|
||||
```
|
||||
|
||||
The method receives an array of IDs of records to delete.
|
||||
|
||||
### Retrieve Soft-Deleted Records
|
||||
|
||||
The `retrieve`, `list`, and `listAndCount` methods accept as a second parameter a configuration object.
|
||||
|
||||
To retrieve soft-deleted records, set `withDeleted` to `true` in the configuration object passed as a second parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const deletedRecords = await helloModuleService
|
||||
.listMySoftDeletables({
|
||||
// ...
|
||||
}, {
|
||||
withDeleted: true,
|
||||
})
|
||||
```
|
||||
|
||||
### Restore a Soft-Deleted Record
|
||||
|
||||
To restore a `MySoftDeletable` record:
|
||||
|
||||
```ts
|
||||
await helloModuleService.restore([
|
||||
"id_123", "id_321",
|
||||
])
|
||||
```
|
||||
|
||||
The method also receives an array of IDs of records to restore.
|
||||
|
||||
If the data model isn't the main data model, its method names are `softDelete` and `restore` suffixed with the plural name of the model. For example, `softDeleteMySoftDeletable`.
|
||||
By default, all data models have a `deleted_at` property and are considered soft-deletable.
|
||||
|
||||
@@ -4,24 +4,17 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn about the module's container and how to register resources in that container.
|
||||
In this chapter, you'll learn about the module's container and how to resolve resources in that container.
|
||||
|
||||
## Module's Container
|
||||
|
||||
Each module has a local container only used by the resources of that module.
|
||||
|
||||
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
|
||||
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container, such as:
|
||||
|
||||
---
|
||||
- `logger`: A utility to log message in the Medusa application's logs.
|
||||
|
||||
## Resources Registered in the Module's Container
|
||||
|
||||
Some resources registered in the module's container are:
|
||||
|
||||
- The module's main service.
|
||||
- A generated service for each data model in your module. The registration name is the camel-case data model name suffixed by `Service`. For example, `myCustomService`.
|
||||
|
||||

|
||||
{/* TODO add other relevant resources, such as event bus */}
|
||||
|
||||
---
|
||||
|
||||
@@ -33,23 +26,25 @@ A service's constructor accepts as a first parameter an object used to resolve r
|
||||
|
||||
For example:
|
||||
|
||||
```ts highlights={[["5"], ["12"]]}
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
```ts highlights={[["4"], ["10"]]}
|
||||
import { Logger } from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
myCustomService: ModulesSdkTypes.InternalModuleService<any>
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
export default class HelloModuleService {
|
||||
protected myCustomService_:
|
||||
ModulesSdkTypes.InternalModuleService<MyCustom>
|
||||
constructor({ myCustomService }: InjectedDependencies) {
|
||||
this.myCustomService_ = myCustomService
|
||||
protected logger_: Logger
|
||||
|
||||
constructor({ logger }: InjectedDependencies) {
|
||||
this.logger_ = logger
|
||||
|
||||
this.logger_.info("[HelloModuleService]: Hello World!")
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Loader
|
||||
@@ -58,16 +53,18 @@ A loader function in a module accepts as a parameter an object having the proper
|
||||
|
||||
For example:
|
||||
|
||||
```ts highlights={[["8"]]}
|
||||
```ts highlights={[["9"]]}
|
||||
import {
|
||||
LoaderOptions,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import { Logger } from "@medusajs/medusa"
|
||||
|
||||
export default function helloWorldLoader({
|
||||
container,
|
||||
}: LoaderOptions) {
|
||||
const myCustomService = container.resolve("myCustomService")
|
||||
|
||||
// ...
|
||||
const logger: Logger = container.resolve("logger")
|
||||
|
||||
logger.info("[helloWorldLoader]: Hello, World!")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Database Operations in Service Methods`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how to implement database operations, such as creating a record, in the main service.
|
||||
|
||||
## Use the Data Model's Generated Service
|
||||
|
||||
To perform database operations on a data model, use the model's generated service in the module's container.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["13", "", "Inject myCustomService, which is the generated service of the `MyCustom` data model."],
|
||||
["22", "", "Add a new field for the generated service of the MyCustom data model."],
|
||||
["29", "", "Set the class field to the injected dependency."],
|
||||
["35", "create", "Use the `create` method of the generated service."]
|
||||
]
|
||||
|
||||
```ts title="src/modules/hello/service.ts" highlights={highlights}
|
||||
// other imports...
|
||||
import { ModulesSdkTypes } from "@medusajs/types"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
|
||||
// ...
|
||||
|
||||
// recommended to define type in another file
|
||||
type CreateMyCustomDTO = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type InjectedDependencies = {
|
||||
myCustomService: ModulesSdkTypes.InternalModuleService<any>
|
||||
}
|
||||
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
// ...
|
||||
>(
|
||||
// ...
|
||||
) {
|
||||
protected myCustomService_: ModulesSdkTypes.InternalModuleService<MyCustom>
|
||||
|
||||
constructor(
|
||||
{ myCustomService }: InjectedDependencies
|
||||
) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.myCustomService_ = myCustomService
|
||||
}
|
||||
|
||||
async create(
|
||||
data: CreateMyCustomDTO
|
||||
): Promise<MyCustomDTO> {
|
||||
const myCustom = await this.myCustomService_.create(
|
||||
data
|
||||
)
|
||||
|
||||
return myCustom
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, you resolve `myCustomService` in the main service's constructor. The `myCustomService` is the generated service for the `myCustom` data model.
|
||||
|
||||
Then, in the `create` method of the main service, you use `myCustomService`'s `create` method to create the record.
|
||||
@@ -14,7 +14,7 @@ For example, Medusa has a link module that defines a relationship between the Pr
|
||||
|
||||

|
||||
|
||||
Link modules provide more flexibility in managing relationships between modules while maintaining module isolation. The Medusa application only creates the link tables when both modules are available.
|
||||
Link modules create the relationship between modules while maintaining module isolation. The Medusa application only creates the link tables when both modules are available.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
import { TypeList, CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Module Relationships`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about creating relationships between modules.
|
||||
|
||||
## What is a Module Relationship?
|
||||
|
||||
A module can have a relationship to another module in the form of a reference.
|
||||
|
||||
The Medusa application resolves these relationships while maintaining isolation between the modules and allowing you to retrieve data across them.
|
||||
|
||||
<Note title="Use module relationships when" type="success">
|
||||
|
||||
- You want to build relationships between the data models of modules.
|
||||
- You want to assoaciate more fields with the data model of another module.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note title="Don't use module relationships if" type="error">
|
||||
|
||||
- You’re building relationships between data models in the same module. Use foreign keys instead.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Module Relationship?
|
||||
|
||||
<Note title="Steps Summary">
|
||||
|
||||
1. Define a `__joinerConfig` method in the module's main service.
|
||||
2. Configure module to be queryable.
|
||||
|
||||
</Note>
|
||||
|
||||
Consider you’re creating a data model that adds custom fields associated with a product:
|
||||
|
||||
```ts title="src/modules/hello/models/custom-product-data.ts" highlights={[["17"]]} collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
export class CustomProductData extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
custom_field: string
|
||||
|
||||
@Property({ columnType: "text", nullable: true })
|
||||
product_id?: string
|
||||
}
|
||||
```
|
||||
|
||||
The `CustomProductData` data model has a `product_id` field to reference the product it adds custom fields for.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
When you add a new data model, make sure to:
|
||||
|
||||
- [Create a migration for it.](../../../basics/data-models/page.mdx#create-a-migration)
|
||||
- [Add it to the second parameter of the main service's factory function.](../service-factory/page.mdx#abstractModuleServiceFactory-parameters)
|
||||
|
||||
</Note>
|
||||
|
||||
### 1. Define `__joinerConfig` Method
|
||||
|
||||
To create a relationship to the `product` data model of the Product Module, create a public `__joinerConfig` method in your main module's service:
|
||||
|
||||
export const relationshipsHighlight = [
|
||||
["39", "serviceName", "The name of the module that this relationship is referencing."],
|
||||
["40", "alias", "The alias of the data model you’re referencing in the other module."],
|
||||
["41", "primaryKey", "The name of the field you’re referencing in the other module’s data model."],
|
||||
["42", "foreignKey", "The name of the field in your data models referencing the other module’s model."],
|
||||
]
|
||||
|
||||
```ts title="src/modules/hello/service.ts" highlights={relationshipsHighlight} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
// other imports...
|
||||
import { MyCustom } from "./models/custom-product-data"
|
||||
import { CustomProductData } from "./models/custom-product-data"
|
||||
import { ModuleJoinerConfig } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
// ...
|
||||
>(
|
||||
// ...
|
||||
) {
|
||||
|
||||
// ...
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "helloModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: ["my_custom"],
|
||||
args: {
|
||||
entity: MyCustom.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["custom_product_data"],
|
||||
args: {
|
||||
entity: CustomProductData.name,
|
||||
// Only needed if data model isn't main data model
|
||||
// of service
|
||||
methodSuffix: "CustomProductDatas",
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
alias: "product",
|
||||
primaryKey: "id",
|
||||
foreignKey: "product_id",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This creates a relationship to the `Product` data model of the Product Module using the alias `product`. The `product_id` fields in your data models are considered references to the `id` field of the `Product` data model.
|
||||
|
||||
#### `__joinerConfig` Return Type
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
name: "serviceName",
|
||||
type: "`string`",
|
||||
optional: false,
|
||||
description: "The name of your module (as added in `medusa-config.js`)."
|
||||
},
|
||||
{
|
||||
name: "alias",
|
||||
type: "`object[]`",
|
||||
description: "The alias definitions for each data model in your module. This allows other modules to reference your module's data models in relationships.",
|
||||
children: [
|
||||
{
|
||||
name: "name",
|
||||
type: "`string[]`",
|
||||
description: "The alias names of a data model used later when fetching or referencing the data model."
|
||||
},
|
||||
{
|
||||
name: "args",
|
||||
type: "`object`",
|
||||
description: "The alias's arguments.",
|
||||
children: [
|
||||
{
|
||||
name: "entity",
|
||||
type: "string",
|
||||
description: "The name of the data model this alias is defined for."
|
||||
},
|
||||
{
|
||||
name: "methodSuffix",
|
||||
type: "string",
|
||||
description: "The plural name of the data model. This is only required if the data model isn't the main data model of the module's service."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "relationships",
|
||||
type: "`object[]`",
|
||||
description: "Your module's relationships to other modules.",
|
||||
children: [
|
||||
{
|
||||
name: "serviceName",
|
||||
type: "`string`",
|
||||
optional: false,
|
||||
description: "The name of the module (as added in `medusa-config.js`) that this relationship is referencing. When referencing a Medusa commerce module, use the `Modules` enum imported from `@medusajs/modules-sdk`."
|
||||
},
|
||||
{
|
||||
name: "alias",
|
||||
type: "`string`",
|
||||
optional: false,
|
||||
description: "The alias of the data model you’re referencing in the other module. You can find it in the `__joinerConfig` method of the other module's service."
|
||||
},
|
||||
{
|
||||
name: "primaryKey",
|
||||
type: "`string`",
|
||||
optional: false,
|
||||
description: "The name of the field you’re referencing in the other module’s data model."
|
||||
},
|
||||
{
|
||||
name: "foreignKey",
|
||||
type: "`string`",
|
||||
optional: false,
|
||||
description: "The name of the field in your data models that references the other module’s data model."
|
||||
}
|
||||
]
|
||||
}
|
||||
]} sectionTitle="Define __joinerConfig Method" />
|
||||
|
||||
### 2. Adjust Module Configuration
|
||||
|
||||
To use relationships in a module, adjust its configuration object passed to `modules` in `medusa-config.js`:
|
||||
|
||||
export const configHighlights = [
|
||||
["7", "isQueryable", "Enable this property to use relationships in a module."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={configHighlights}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: {
|
||||
helloModuleService: {
|
||||
// ...
|
||||
definition: {
|
||||
isQueryable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Enabling the `isQueryable` property is required to use relationships in a module.
|
||||
|
||||
---
|
||||
|
||||
## Reference Inner Data Models
|
||||
|
||||
If the data model you’re referencing isn’t the main data model of the main module service, pass to the relationship definition the `args` property:
|
||||
|
||||
```ts title="src/modules/hello/service.ts" highlights={[["20", "methodSuffix", "The suffix of the referenced data model's methods."]]}
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
// ...
|
||||
>(
|
||||
// ...
|
||||
) {
|
||||
|
||||
// ...
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
// ...
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
primaryKey: "id",
|
||||
foreignKey: "variant_id",
|
||||
alias: "variant",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `args` property’s value is an object accepting a `methodSuffix` property. The `methodSuffix` property’s value is the plural name of the data model.
|
||||
|
||||
---
|
||||
|
||||
## Querying Module Relationships
|
||||
|
||||
The next chapter explains how to query data across module relationships.
|
||||
@@ -34,7 +34,6 @@ module.exports = defineConfig({
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
The `options` property’s value is an object. You can pass any properties you want.
|
||||
@@ -47,36 +46,30 @@ The module’s main service receives the module options as a second parameter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/hello/service.ts" highlights={[["15"], ["21"], ["25"], ["26"], ["27"]]}
|
||||
// ...
|
||||
```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import MyCustom from "./models/my-custom"
|
||||
|
||||
// recommended to define type in another file
|
||||
type HelloModuleOptions = {
|
||||
type ModuleOptions = {
|
||||
capitalize?: boolean
|
||||
}
|
||||
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
// ...
|
||||
>(
|
||||
// ...
|
||||
) {
|
||||
// ...
|
||||
protected options_: HelloModuleOptions
|
||||
|
||||
constructor(
|
||||
{
|
||||
// ...
|
||||
}: InjectedDependencies,
|
||||
protected readonly moduleOptions: HelloModuleOptions
|
||||
) {
|
||||
//...
|
||||
|
||||
this.options_ = moduleOptions || {
|
||||
capitalize: false,
|
||||
}
|
||||
export default class HelloModuleService extends MedusaService({
|
||||
MyCustom,
|
||||
}){
|
||||
protected options_: ModuleOptions
|
||||
|
||||
constructor({}, options?: ModuleOptions) {
|
||||
super(...arguments)
|
||||
|
||||
this.options_ = options || {
|
||||
capitalize: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -93,13 +86,13 @@ import {
|
||||
} from "@medusajs/modules-sdk"
|
||||
|
||||
// recommended to define type in another file
|
||||
type HelloModuleOptions = {
|
||||
type ModuleOptions = {
|
||||
capitalize?: boolean
|
||||
}
|
||||
|
||||
export default function helloWorldLoader({
|
||||
options,
|
||||
}: LoaderOptions<HelloModuleOptions>) {
|
||||
}: LoaderOptions<ModuleOptions>) {
|
||||
|
||||
console.log(
|
||||
"[HELLO MODULE] Just started the Medusa application!",
|
||||
|
||||
@@ -10,21 +10,43 @@ In this chapter, you’ll learn about the remote query and how to use it to fetc
|
||||
|
||||
## What is the Remote Query?
|
||||
|
||||
The remote query fetches data across modules and their relationships having their `isQueryable` configuration enabled. It’s a function registered in the Medusa container under the `remoteQuery` key.
|
||||
The remote query fetches data across modules. It’s a function registered in the Medusa container under the `remoteQuery` key.
|
||||
|
||||
In your resources, such as API routes or workflows, you can resolve the remote query to fetch data across custom modules and Medusa’s commerce modules.
|
||||
|
||||
---
|
||||
|
||||
## Example: Query Hello Module
|
||||
## isQueryable Configuration
|
||||
|
||||
Before you use remote query on your module, you must enable the `isQueryable` configuration of the module.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: {
|
||||
helloModuleService: {
|
||||
resolve: "./modules/hello",
|
||||
definition: {
|
||||
isQueryable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Remote Query Example
|
||||
|
||||
For example, create the route `src/api/store/query/route.ts` with the following content:
|
||||
|
||||
export const exampleHighlights = [
|
||||
["18", "", "Resolve the remote query from the Medusa container."],
|
||||
["21", "remoteQueryObjectFromString", "Utility function to build the query."],
|
||||
["22", "entryPoint", "The alias name of the model you’re querying."],
|
||||
["23", "fields", "An array of the data model’s field names to retrieve in the result."],
|
||||
["22", "entryPoint", "The name of the data model you're querying."],
|
||||
["23", "fields", "An array of the data model’s properties to retrieve in the result."],
|
||||
["27", "remoteQuery", "Run the query using the remote query."]
|
||||
]
|
||||
|
||||
@@ -50,12 +72,12 @@ export async function GET(
|
||||
)
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "custom_product_data",
|
||||
fields: ["id", "custom_field", "product.title"],
|
||||
entryPoint: "my_custom",
|
||||
fields: ["id", "test"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
custom_product_data: await remoteQuery(query),
|
||||
my_customs: await remoteQuery(query),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -64,8 +86,8 @@ In the above example, you resolve `remoteQuery` from the Medusa container.
|
||||
|
||||
Then, you create a query using the `remoteQueryObjectFromString` utility function imported from `@medusajs/utils`. This function accepts as a parameter an object with the following required properties:
|
||||
|
||||
- `entryPoint`: The alias name of the model you’re querying. You defined the alias name in the `__joinerConfig` method of your main service.
|
||||
- `fields`: An array of the data model’s field names to retrieve in the result. You can also specify fields of a relationship using dot notation.
|
||||
- `entryPoint`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
|
||||
- `fields`: An array of the data model’s properties to retrieve in the result.
|
||||
|
||||
You then pass the query to the `remoteQuery` function to retrieve the results.
|
||||
|
||||
@@ -75,17 +97,17 @@ You then pass the query to the `remoteQuery` function to retrieve the results.
|
||||
|
||||
```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "custom_product_data",
|
||||
fields: ["id", "custom_field", "product.title"],
|
||||
entryPoint: "my_custom",
|
||||
fields: ["id", "name"],
|
||||
variables: {
|
||||
filters: {
|
||||
id: [
|
||||
"cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
|
||||
"cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
|
||||
"mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
|
||||
"mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
```
|
||||
@@ -102,7 +124,7 @@ The `remoteQueryObjectFromString` function accepts a `variables` property. You c
|
||||
{
|
||||
name: "filters",
|
||||
type: "`object`",
|
||||
description: "The filters to apply on any of the data model's fields."
|
||||
description: "The filters to apply on any of the data model's properties."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -116,8 +138,8 @@ The `remoteQueryObjectFromString` function accepts a `variables` property. You c
|
||||
|
||||
```ts highlights={[["5"], ["6"], ["7"]]}
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "custom_product_data",
|
||||
fields: ["id", "custom_field", "product.title"],
|
||||
entryPoint: "my_custom",
|
||||
fields: ["id", "name"],
|
||||
variables: {
|
||||
order: {
|
||||
name: "DESC",
|
||||
@@ -128,12 +150,12 @@ const query = remoteQueryObjectFromString({
|
||||
const result = await remoteQuery(query)
|
||||
```
|
||||
|
||||
To sort returned records, pass an `order` property to the `variables` property's value.
|
||||
To sort returned records, pass an `order` property to `variables`.
|
||||
|
||||
The `order` property is an object whose keys are field names, and values are either:
|
||||
The `order` property is an object whose keys are property names, and values are either:
|
||||
|
||||
- `ASC` to sort records by that field in ascending order.
|
||||
- `DESC` to sort records by that field in descending order.
|
||||
- `ASC` to sort records by that property in ascending order.
|
||||
- `DESC` to sort records by that property in descending order.
|
||||
|
||||
---
|
||||
|
||||
@@ -141,8 +163,8 @@ The `order` property is an object whose keys are field names, and values are eit
|
||||
|
||||
```ts highlights={[["5", "skip", "The number of records to skip before fetching the results."], ["6", "take", "The number of records to fetch."]]}
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "custom_product_data",
|
||||
fields: ["id", "custom_field", "product.title"],
|
||||
entryPoint: "my_custom",
|
||||
fields: ["id", "name"],
|
||||
variables: {
|
||||
skip: 0,
|
||||
take: 10,
|
||||
@@ -155,7 +177,7 @@ const {
|
||||
} = await remoteQuery(query)
|
||||
```
|
||||
|
||||
To paginate the returned records, pass the following properties to the `variables` property's value:
|
||||
To paginate the returned records, pass the following properties to `variables`:
|
||||
|
||||
- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
|
||||
- `take`: The number of records to fetch.
|
||||
@@ -215,7 +237,6 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
import type {
|
||||
RemoteQueryFunction,
|
||||
@@ -231,20 +252,15 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
|
||||
const query = `
|
||||
query {
|
||||
custom_product_data {
|
||||
my_custom {
|
||||
id
|
||||
custom_field
|
||||
product {
|
||||
title
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
res.json({
|
||||
custom_product_data: result,
|
||||
my_customs: result,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -256,15 +272,12 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
|
||||
The `remoteQuery` function accepts as a second parameter an object of variables to reference in the GraphQL query.
|
||||
|
||||
```ts highlights={[["2"], ["3"], ["16"], ["17"], ["18"], ["19"]]}
|
||||
```ts highlights={[["2"], ["3"], ["13"], ["14"], ["15"], ["16"]]}
|
||||
const query = `
|
||||
query($id: ID) {
|
||||
custom_product_data(id: $id) {
|
||||
my_custom(id: $id) {
|
||||
id
|
||||
custom_field
|
||||
product {
|
||||
title
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -273,8 +286,8 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
query,
|
||||
{
|
||||
id: [
|
||||
"cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
|
||||
"cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
|
||||
"mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
|
||||
"mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -285,22 +298,19 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
|
||||
### Sort Records with GraphQL
|
||||
|
||||
To sort the records by a field, pass in the query an `order` argument whose value is an object. The object’s key is the field’s name, and the value is either:
|
||||
To sort the records by a property, pass in the query an `order` argument whose value is an object. The object’s key is the property’s name, and the value is either:
|
||||
|
||||
- `ASC` to sort items by that field in ascending order.
|
||||
- `DESC` to sort items by that field in descending order.
|
||||
- `ASC` to sort items by that property in ascending order.
|
||||
- `DESC` to sort items by that property in descending order.
|
||||
|
||||
For example:
|
||||
|
||||
```ts highlights={[["3"]]}
|
||||
const query = `
|
||||
query {
|
||||
custom_product_data(order: {custom_field: DESC}) {
|
||||
my_custom(order: {name: DESC}) {
|
||||
id
|
||||
custom_field
|
||||
product {
|
||||
title
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -320,12 +330,9 @@ The remote query function alternatively accepts a string with GraphQL syntax as
|
||||
```ts highlights={[["2"], ["3"]]}
|
||||
const query = `
|
||||
query($skip: Int, $take: Int) {
|
||||
custom_product_data(skip: $skip, take: $take) {
|
||||
my_custom(skip: $skip, take: $take) {
|
||||
id
|
||||
custom_field
|
||||
product {
|
||||
title
|
||||
}
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -6,11 +6,13 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about what the service factory is and how to use it to create a service.
|
||||
In this chapter, you’ll learn about what the service factory is and how to use it.
|
||||
|
||||
## What is the Service Factory?
|
||||
|
||||
Medusa provides a service factory that your module’s main service can extend. The service factory implements data management methods for your data models.
|
||||
Medusa provides a service factory that your module’s main service can extend.
|
||||
|
||||
The service factory generates data management methods for your data models, so you don't have to implement them manually.
|
||||
|
||||
<Note title="Use the service factory when" type="success">
|
||||
|
||||
@@ -22,34 +24,41 @@ Medusa provides a service factory that your module’s main service can extend.
|
||||
|
||||
## How to Extend the Service Factory?
|
||||
|
||||
Medusa provides the service factory as a function your service extends. The function creates and returns a service class with generated data-management methods.
|
||||
Medusa provides the service factory as a `MedusaService` function your service extends. The function creates and returns a service class with generated data-management methods.
|
||||
|
||||
For example, create the file `src/modules/hello/service.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/service.ts"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
export const highlights = [
|
||||
["4", "MedusaService", "The service factory function."],
|
||||
["5", "MyCustom", "The data models to generate data-management methods for."]
|
||||
]
|
||||
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory(
|
||||
MyCustom, []
|
||||
) {
|
||||
// TODO implement custom methods
|
||||
}
|
||||
```ts title="src/modules/hello/service.ts" highlights={highlights}
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import MyCustom from "./models/my-custom"
|
||||
|
||||
class HelloModuleService extends MedusaService({
|
||||
MyCustom,
|
||||
}){
|
||||
// TODO implement custom methods
|
||||
}
|
||||
|
||||
export default HelloModuleService
|
||||
```
|
||||
|
||||
### abstractModuleServiceFactory Parameters
|
||||
### MedusaService Parameters
|
||||
|
||||
The `abstractModuleServiceFactory` function accepts two parameters:
|
||||
The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
|
||||
|
||||
1. The first parameter is the main data model this service is creating methods for. For example, `MyCustom`.
|
||||
2. The second parameter is an array of data models to generate methods for. If you have an `AnotherCustom` data model, this is where you add it.
|
||||
In the example above, the `HelloModuleService` now has methods to manage the `MyCustom` data model, such as `createMyCustoms`.
|
||||
|
||||
### Generated Methods
|
||||
|
||||
The service factory generates the following methods for the main data model:
|
||||
The service factory generates data-management methods for each of the data models provided in the first parameter.
|
||||
|
||||
The method's names are the operation's name, suffixed by the data model's name.
|
||||
|
||||
For example, the following methods are generated for the code snippet above:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
@@ -62,7 +71,7 @@ The service factory generates the following methods for the main data model:
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`list`
|
||||
`listMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -74,7 +83,7 @@ The service factory generates the following methods for the main data model:
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`listAndCount`
|
||||
`listAndCountMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -86,7 +95,7 @@ The service factory generates the following methods for the main data model:
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`retrieve`
|
||||
`retrieveMyCustom`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -98,7 +107,31 @@ The service factory generates the following methods for the main data model:
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`delete`
|
||||
`createMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Create and retrieve records of the data model.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`updateMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Update and retrieve records of the data model.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`deleteMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -110,77 +143,32 @@ The service factory generates the following methods for the main data model:
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`softDelete`
|
||||
`softDeleteMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Soft-deletes a record by an ID or filter. This only applies if the data model has a `deleted_at` field.
|
||||
Soft-deletes records using an array of IDs or an object of filters.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`restore`
|
||||
`restoreMyCustoms`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Restores a soft-deleted record by an ID or filter. This only applies if the data model has a `deleted_at` field.
|
||||
Restores soft-deleted records using an array of IDs or an object of filters.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
The same methods are generated for data models passed in the second parameter of the service factory. The methods' names end with the data model's name. For example, `listAnotherCustom`.
|
||||
<Note>
|
||||
|
||||
### Type Arguments
|
||||
Except for the `retrieve` method, the suffixed data model's name is plural.
|
||||
|
||||
For a better development experience and accurate typing of the generated methods, the `abstractModuleServiceFactory` function accepts three type arguments:
|
||||
|
||||
export const typeArgsHighlights = [
|
||||
["25", "InjectedDependencies", "The type of dependencies resolved from the Module's container."],
|
||||
["26", "MyCustomDTO", "The expected input/output type of the main data model's generated methods."],
|
||||
["27", "AllModelsDTO", "The expected input/output type of the generated methods of every data model."],
|
||||
]
|
||||
|
||||
```ts title="src/modules/hello/service.ts" highlights={typeArgsHighlights} collapsibleLines="1-22" expandButtonLabel="Show More"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
|
||||
// recommended to define type in another file
|
||||
type MyCustomDTO = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type InjectedDependencies = {
|
||||
// TODO add dependencies
|
||||
}
|
||||
|
||||
type AllModelsDTO = {
|
||||
MyCustom: {
|
||||
dto: MyCustomDTO
|
||||
}
|
||||
}
|
||||
|
||||
// add other data models in your module here.
|
||||
const generateMethodsFor = []
|
||||
|
||||
class HelloModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
MyCustomDTO,
|
||||
AllModelsDTO
|
||||
>(MyCustom, generateMethodsFor) {
|
||||
// TODO implement custom methods
|
||||
}
|
||||
|
||||
export default HelloModuleService
|
||||
```
|
||||
|
||||
1. The first one is the type of the dependencies to resolve from the module's container.
|
||||
2. The second one is the expected input and output type of the main data model’s methods.
|
||||
3. The third type is the expected input and output type of all data models that the service factory generates methods for.
|
||||
</Note>
|
||||
|
||||
@@ -8,7 +8,9 @@ In this chapter, you'll learn about Medusa's commerce modules.
|
||||
|
||||
## What is a Commerce Module?
|
||||
|
||||
Medusa provides all its commerce features as separate modules, such as the Product Module, Cart Module, or Order Module. These modules and your custom modules are interchangeable in the Medusa application, making Medusa’s architecture more flexible.
|
||||
Medusa provides all its commerce features as separate modules, such as the Product or Order modules.
|
||||
|
||||
These modules and your custom modules are interchangeable in the Medusa application, making Medusa’s architecture more flexible.
|
||||
|
||||
Refer to [this reference](!resources!/commerce-modules) for a full list of commerce modules in Medusa.
|
||||
|
||||
@@ -47,6 +49,6 @@ When you resolve the `ModuleRegistrationName.PRODUCT` (or `productModuleService`
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
To resolve the main service of any commerce module, use the `ModuleRegistrationName` enum imported from `@medusajs/modules-sdk` to refer to its registration name in the Medusa container.
|
||||
To resolve the main service of any commerce module, use the registration name defined in the `ModuleRegistrationName` enum imported from `@medusajs/modules-sdk`.
|
||||
|
||||
</Note>
|
||||
@@ -8,9 +8,7 @@ In this chapter, you’ll learn what data models are and how to create a data mo
|
||||
|
||||
## What is a Data Model?
|
||||
|
||||
A data model is a class that represents a table in the database. A data model is created in a module. You can then create a service that manages that data model.
|
||||
|
||||
Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So, you can use its decorators, types, and utilities when creating a model.
|
||||
A data model is a class that represents a table in the database. It's created in a module.
|
||||
|
||||
---
|
||||
|
||||
@@ -20,67 +18,66 @@ Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So,
|
||||
|
||||
1. Create data model class in a module.
|
||||
2. Generate migration for the data model.
|
||||
3. Add migration scripts to the module's definition.
|
||||
4. Run migration to add table for data model in the database.
|
||||
|
||||
</Note>
|
||||
|
||||
A data model is a class created in a TypeScript or JavaScript file under a module's `models` directory.
|
||||
A data model is created in a TypeScript or JavaScript file under a module's `models` directory. It's defined using the `model` utility imported from `@medjusajs/utils`.
|
||||
|
||||
For example, create the file `src/modules/hello/models/my-custom.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/hello/models/my-custom.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Entity()
|
||||
export class MyCustom extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
const MyCustom = model.define("my_custom", {
|
||||
id: model.id(),
|
||||
name: model.text(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
}
|
||||
export default MyCustom
|
||||
```
|
||||
|
||||
This defines a new data model `MyCustom` with the fields `id` and `name`. Data models extend the `BaseEntity` class imported from `@medusajs/utils`.
|
||||
You define a data model using the `model`'s `define` method. It accepts two parameters:
|
||||
|
||||
1. The first one is the name of the data model's table in the database.
|
||||
2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods.
|
||||
|
||||
The example above defines the data model `MyCustom` with the properties `id` and `name`.
|
||||
|
||||
### Create a Migration
|
||||
|
||||
After creating the data model, you must create a migration that creates a table in your database for this data model.
|
||||
A migration defines changes to be made in the database, such as create or update tables.
|
||||
|
||||
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It implements an `up` and `down` method, where the `up` method reflects changes on the database, and the `down` method reverts the changes from the database.
|
||||
So, you must create a migration that creates a table for your data model in the database.
|
||||
|
||||
<Details summaryContent="Generate with MikroORM">
|
||||
MikroORM provides a CLI tool that helps you generate migrations. To use it:
|
||||
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
|
||||
|
||||
1. Create the file `src/modules/hello/mikro-orm.config.dev.ts` with the following content:
|
||||
- The `up` method reflects changes on the database.
|
||||
- The `down` method reverts the changes made in the `up` method.
|
||||
|
||||
```ts highlights={[["8", "hello", "The module's name."]]}
|
||||
<Details summaryContent="Generate Migration">
|
||||
To generate migrations:
|
||||
|
||||
1. Create the file `src/modules/hello/migrations-config.ts` with the following content:
|
||||
|
||||
```ts highlights={[["7", '"medusa-hello"', "Use any database name relevant for your module."]]}
|
||||
import { defineMikroOrmCliConfig } from "@medusajs/utils"
|
||||
import path from "path"
|
||||
import { TSMigrationGenerator } from "@medusajs/utils"
|
||||
import { MyCustom } from "./models/my-custom"
|
||||
import MyCustom from "./models/my-custom"
|
||||
|
||||
module.exports = {
|
||||
entities: [MyCustom],
|
||||
schema: "public",
|
||||
clientUrl: "postgres://postgres@localhost/medusa-hello",
|
||||
type: "postgresql",
|
||||
export default defineMikroOrmCliConfig({
|
||||
entities: [MyCustom] as any[],
|
||||
databaseName: "medusa-hello",
|
||||
migrations: {
|
||||
path: path.join(__dirname, "migrations"),
|
||||
generator: TSMigrationGenerator,
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
2. Run the following command in the root directory of your Medusa application:
|
||||
|
||||
```bash
|
||||
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/mikro-orm.config.dev.ts mikro-orm migration:create
|
||||
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/migrations-config.ts mikro-orm migration:create
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
@@ -89,7 +86,7 @@ A migration is a class created in a TypeScript or JavaScript file under a module
|
||||
|
||||
</Note>
|
||||
|
||||
After running the command, a new file is created under the `src/modules/hello/migrations` directory. This file holds `up` and `down` methods that define the actions to execute when running and reverting the migration respectively.
|
||||
After running the command, a migration file is generated under the `src/modules/hello/migrations` directory.
|
||||
|
||||
</Details>
|
||||
|
||||
@@ -98,10 +95,10 @@ For example:
|
||||
```ts title="src/modules/migrations/Migration20240429090012.ts"
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240429090012 extends Migration {
|
||||
export class Migration20240624145652 extends Migration {
|
||||
|
||||
async up(): Promise<void> {
|
||||
this.addSql("create table if not exists \"my_custom\" (\"id\" varchar(255) not null, \"name\" text not null, constraint \"my_custom_pkey\" primary key (\"id\"));")
|
||||
this.addSql("create table if not exists \"my_custom\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"my_custom_pkey\" primary key (\"id\"));")
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
|
||||
@@ -81,9 +81,9 @@ For example:
|
||||
{/* TODO change how event names are loaded */}
|
||||
|
||||
export const highlights = [
|
||||
["11"],
|
||||
["14", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
["10", "container", "Recieve the Medusa Container in the object parameter."],
|
||||
["13", "resolve", "Resolve the Product Module's main service."],
|
||||
["13", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={highlights}
|
||||
@@ -111,4 +111,4 @@ export const config: SubscriberConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
You use the container to resolve the `IProductModuleService`, then log the title of the created product.
|
||||
You use the container to resolve the Product Module's main service, then log the title of the created product.
|
||||
|
||||
@@ -15,7 +15,7 @@ You use the Medusa container to resolve resources, such as services.
|
||||
For example, in a custom API route you can resolve any service registered in the Medusa application using the `scope.resolve` method of the `MedusaRequest` parameter:
|
||||
|
||||
export const highlights = [
|
||||
["13", "resolve", "Resolve the `IProductModuleService`"],
|
||||
["13", "resolve", "Resolve the Product Module's main service."],
|
||||
["13", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
@@ -41,5 +41,3 @@ export const GET = async (
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You resolve the `IProductModuleService` and uses it to return the full count of products in the Medusa application.
|
||||
|
||||
@@ -115,7 +115,7 @@ The scheduled job function receives an object parameter that has a `container` p
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["12", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["12", "resolve", "Resolve the Product Module's main service."],
|
||||
["12", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
@@ -147,4 +147,4 @@ export const config: ScheduledJobConfig = {
|
||||
}
|
||||
```
|
||||
|
||||
In the scheduled job function, you resolve the `IProductModuleService` and retrieve the number of products in the store, then log the number in the terminal.
|
||||
In the scheduled job function, you resolve the Product Module's main service and retrieve the number of products in the store, then log the number in the terminal.
|
||||
@@ -228,11 +228,11 @@ Each step in the workflow receives as a second parameter a `context` object. The
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["14", "resolve", "Resolve the `IProductModuleService`."],
|
||||
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
["15", "resolve", "Resolve the Product Module's main service."],
|
||||
["15", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/product-count.ts" highlights={highlights}
|
||||
```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
createStep,
|
||||
StepResponse,
|
||||
@@ -265,4 +265,6 @@ const myWorkflow = createWorkflow<unknown, WorkflowOutput>(
|
||||
)
|
||||
|
||||
export default myWorkflow
|
||||
```
|
||||
```
|
||||
|
||||
In the step, you resolve the Product Module's main service and use it to retrieve the product count.
|
||||
+26
-10
@@ -106,18 +106,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
|
||||
path: "/advanced-development/modules/service-factory",
|
||||
title: "Service Factory",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/modules/database-operations-in-services",
|
||||
title: "Database Operations",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/modules/options",
|
||||
title: "Module Options",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/modules/module-relationships",
|
||||
title: "Module Relationships",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/modules/remote-query",
|
||||
title: "Remote Query",
|
||||
@@ -136,13 +128,37 @@ export const sidebar = sidebarAttachHrefCommonOptions(
|
||||
title: "Data Models",
|
||||
children: [
|
||||
{
|
||||
path: "/advanced-development/data-models/common-definitions",
|
||||
title: "Common Definitions",
|
||||
path: "/advanced-development/data-models/property-types",
|
||||
title: "Property Types",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/configure-properties",
|
||||
title: "Configure Properties",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/primary-key",
|
||||
title: "Primary Key",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/relationships",
|
||||
title: "Relationships",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/relationship-cascades",
|
||||
title: "Relationship Cascades",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/indexes",
|
||||
title: "Data Model Index",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/soft-deletable",
|
||||
title: "Soft-Deletable Models",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/searchable-property",
|
||||
title: "Searchable Property",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Segment Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Segment](https://github.com/medusajs/medusa/tree/master/packages/medusa-plugin-segment) is a powerful Customer Data Platform that allows users to collect, transform, send and archive their customer data.
|
||||
|
||||
Through Segment, you can integrate other third-party services such as:
|
||||
|
||||
- Google Analytics
|
||||
- Mailchimp
|
||||
- Zendesk
|
||||
- Data warehousing for advanced data analytics and segmentation through services like Metabase
|
||||
|
||||

|
||||
|
||||
The Segment plugin in Medusa allows you to track ecommerce events and record them in Segment. Then, you can push these events to other destinations using Segment.
|
||||
|
||||
---
|
||||
|
||||
## Events That the Segment Plugin Tracks
|
||||
|
||||
The Segment plugin tracks the following events:
|
||||
|
||||
1. `order.placed`: Triggered when an order is placed.
|
||||
2. `order.shipment_created`: Triggered when a shipment is created for an order.
|
||||
3. `claim.created`: Triggered when a new claim is created.
|
||||
4. `order.items_returned`: Triggered when an item in an order is returned.
|
||||
5. `order.canceled`: Triggered when an order is canceled.
|
||||
6. `swap.created`: Triggered when a swap is created.
|
||||
7. `swap.shipment_created`: Triggered when a shipment is created for a swap.
|
||||
8. `swap.payment_completed`: Triggered when payment for a swap is completed.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Check out the [Event Reference](../../../events-reference/page.mdx) to learn more about these events and their data payloads.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Preparations
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Segment Account](https://app.segment.com/signup/)
|
||||
|
||||
</Note>
|
||||
|
||||
### Create a Segment Source
|
||||
|
||||
On your Segment dashboard:
|
||||
|
||||
1. Choose Catalog from the sidebar under Connections.
|
||||
2. Search for "Node.js" or find "Node.js" under the Sources directory.
|
||||
3. In the Node.js details page, click on Add Source.
|
||||
4. This opens a new page to create a Node.js source. Enter the name of the source then click Add Source.
|
||||
5. On the new source's dashboard, find a "Write Key". You’ll use this key in the next section after you install the Segment plugin in your Medusa application.
|
||||
|
||||
### Optional: Add Destination
|
||||
|
||||
After you create the Segment source, you can add destinations. This is where the data is sent when you send them to Segment. You can add more than one destination.
|
||||
|
||||
To add a destination:
|
||||
|
||||
1. Choose Destinations from the sidebar under Connections.
|
||||
2. Click on the "Add destination" button.
|
||||
3. Choose the desired destination, such as Google Universal Analytics or Facebook Pixel.
|
||||
|
||||
The process of integrating each destination is different, so you must follow the steps detailed in Segment for each destination you choose.
|
||||
|
||||
---
|
||||
|
||||
## Install the Segment Plugin
|
||||
|
||||
To install the Segment plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-segment
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "write_key", "The Segment source's write key."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-segment`,
|
||||
options: {
|
||||
write_key: process.env.SEGMENT_WRITE_KEY,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Segment Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`write_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The Segment source's write key.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
SEGMENT_WRITE_KEY=<YOUR_SEGMENT_WRITE_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, try triggering one of the [mentioned events earlier in this document](#events-that-the-segment-plugin-tracks). For example, you can place an order either using the [REST APIs](https://docs.medusajs.com/api/store) or using the [Next.js Starter](../../../nextjs-starter/page.mdx).
|
||||
|
||||
After you place an order, on the Segment source that you created, click on the Debugger tab. You should see at least one event triggered for each order you place. If you click on the event, you can see the order details are passed to the event.
|
||||
|
||||
If you added a destination, you can also check your destination to make sure the data is reflected there.
|
||||
|
||||
---
|
||||
|
||||
## Add Custom Tracking
|
||||
|
||||
The `SegmentService` allows you to track other Medusa events or custom events.
|
||||
|
||||
For example, create the file `src/subscribers/customer.ts` with the following content:
|
||||
|
||||
```ts title="src/subscribers/customer.ts"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
CustomerService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function handleCustomerCreated({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const segmentService = container.resolve("segmentService")
|
||||
|
||||
const customerData = data
|
||||
delete customerData["password_hash"]
|
||||
|
||||
segmentService.track({
|
||||
event: "Customer Created",
|
||||
userId: data.id,
|
||||
properties: customerData,
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: CustomerService.Events.CREATED,
|
||||
}
|
||||
```
|
||||
|
||||
This creates a subscriber that listens to the `CustomerService.Events.CREATED` (`customer.created`) event and sends tracking information to Segment for every customer created.
|
||||
|
||||
The `SegmentService` has a `track` method used to send tracking data to Segment. It accepts an object of data, where the keys `event` and `userId` are required. Instead of `userId`, you can use `anonymousId` to pass an anonymous user ID.
|
||||
|
||||
To pass additional data to Segment, pass them under the `properties` object key.
|
||||
|
||||
The `SegmentService` also has the method `identify` to tie a user to their actions or specific traits.
|
||||
@@ -1,686 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Contentful Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Contentful](https://www.contentful.com/) is a headless CMS service that allows developers to integrate rich CMS functionalities into any platform.
|
||||
|
||||
By integrating Contentful to Medusa, you can benefit from powerful features in your ecommerce store such as:
|
||||
|
||||
- Rich CMS details for product.
|
||||
- Easy-to-use interface to manage content for static content and pages.
|
||||
- Localization for product and storefront content.
|
||||
- Two-way sync between Contentful and Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Install the Contentful Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Contentful account with a space](https://www.contentful.com/sign-up/).
|
||||
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Contentful plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-contentful
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "space_id", "The Contentful space's ID."],
|
||||
["7", "access_token", "The personal access token for content management."],
|
||||
["8", "environment", "The Contentful environment."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-contentful`,
|
||||
options: {
|
||||
space_id: process.env.CONTENTFUL_SPACE_ID,
|
||||
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
|
||||
environment: process.env.CONTENTFUL_ENV,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Contentful Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`space_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of your [Contentful space](https://www.contentful.com/help/find-space-id/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_token`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating [the personal access token for content management](https://www.contentful.com/help/personal-access-tokens/#how-to-get-a-personal-access-token-the-web-app).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`environment`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Contentful environment](https://www.contentful.com/developers/docs/concepts/multiple-environments/). Typically, its value should be `master`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ignore_threshold`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds to wait before re-syncing a specific record.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`2`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`custom_<TYPE>_fields`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object that allows you to map fields in Medusa to [custom field names](#custom-field-mapping).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables.
|
||||
|
||||
```bash
|
||||
CONTENTFUL_SPACE_ID=<YOUR_SPACE_ID>
|
||||
CONTENTFUL_ACCESS_TOKEN=<YOUR_ACCESS_TOKEN>
|
||||
CONTENTFUL_ENV=master
|
||||
```
|
||||
|
||||
### Custom Field Mapping
|
||||
|
||||
When the plugin syncs data between Contentful and Medusa, it expects a set of fields to be defined in the respective content models in Contentful. If you use different names to define those fields in Contentful, specify them in the `custom_<TYPE>_fields` option mentioned earlier, where `<TYPE>` is the name of the content model.
|
||||
|
||||
For example, to change the name of the product’s `title` field, pass the following option to the plugin:
|
||||
|
||||
```js title="medusa-config.js" highlights={[["9"], ["10"], ["11"]]}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-contentful`,
|
||||
options: {
|
||||
space_id: process.env.CONTENTFUL_SPACE_ID,
|
||||
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
|
||||
environment: process.env.CONTENTFUL_ENV,
|
||||
custom_product_fields: {
|
||||
title: "name",
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
The rest of this section includes the field names you can customize using this option for each content model type.
|
||||
|
||||
<Details summaryContent="product">
|
||||
|
||||
- `title`
|
||||
- `subtitle`
|
||||
- `description`
|
||||
- `variants`
|
||||
- `options`
|
||||
- `medusaId`
|
||||
- `type`
|
||||
- `collection`
|
||||
- `tags`
|
||||
- `handle`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="variant">
|
||||
|
||||
- `title`
|
||||
- `sku`
|
||||
- `prices`
|
||||
- `options`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="region">
|
||||
|
||||
- `name`
|
||||
- `countries`
|
||||
- `paymentProviders`
|
||||
- `fulfillmentProviders`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="collection">
|
||||
|
||||
- `title`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="type">
|
||||
|
||||
- `name`
|
||||
- `medusaId`
|
||||
|
||||
</Details>
|
||||
|
||||
### Migrate Content Models
|
||||
|
||||
In your Contentful space, you must have content models for Medusa entities such as products and regions.
|
||||
|
||||
You can either create the content models manually, or create a loader in the Medusa application that migrates these content models into Contentful.
|
||||
|
||||
This section includes migration scripts for Medusa’s data models that are relevant for Contentful.
|
||||
|
||||
Before creating the migration scripts, run the following command in the root of your Medusa backend to install Contentful’s migration SDK:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install --save-dev contentful-migration
|
||||
```
|
||||
|
||||
<Details summaryContent="product Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const product = migration
|
||||
.createContentType("product")
|
||||
.name("Product")
|
||||
.displayField("title")
|
||||
|
||||
product
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
product
|
||||
.createField("subtitle")
|
||||
.name("Subtitle")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("handle")
|
||||
.name("Handle")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("thumbnail")
|
||||
.name("Thumbnail")
|
||||
.type("Link")
|
||||
.linkType("Asset")
|
||||
product
|
||||
.createField("description")
|
||||
.name("Description")
|
||||
.type("Text")
|
||||
product
|
||||
.createField("options")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
product
|
||||
.createField("tags")
|
||||
.name("Tags")
|
||||
.type("Object")
|
||||
product
|
||||
.createField("collection")
|
||||
.name("Collection")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("type")
|
||||
.name("Type")
|
||||
.type("Symbol")
|
||||
product
|
||||
.createField("variants")
|
||||
.name("Variants")
|
||||
.type("Array")
|
||||
.items({
|
||||
type: "Link",
|
||||
linkType: "Entry",
|
||||
validations: [
|
||||
{
|
||||
linkContentType: ["productVariant"],
|
||||
},
|
||||
],
|
||||
})
|
||||
product
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="productVariant Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-variant.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-variant.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productVariantMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const productVariant = migration
|
||||
.createContentType("productVariant")
|
||||
.name("Product Variant")
|
||||
.displayField("title")
|
||||
|
||||
productVariant
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
productVariant
|
||||
.createField("sku")
|
||||
.name("SKU")
|
||||
.type("Symbol")
|
||||
productVariant
|
||||
.createField("options")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
productVariant
|
||||
.createField("prices")
|
||||
.name("Prices")
|
||||
.type("Object")
|
||||
productVariant
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="collection Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-collection.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-collection.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productCollectionMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const collection = migration
|
||||
.createContentType("collection")
|
||||
.name("Product Collection")
|
||||
.displayField("title")
|
||||
|
||||
collection
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
collection
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="productType Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/product-type.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/product-type.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function productTypeMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const collection = migration
|
||||
.createContentType("productType")
|
||||
.name("Product Type")
|
||||
.displayField("title")
|
||||
|
||||
collection
|
||||
.createField("title")
|
||||
.name("Title")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
collection
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summaryContent="region Content Model">
|
||||
|
||||
Create the file `src/loaders/contentful-migrations/region.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/contentful-migrations/region.ts"
|
||||
import Migration, {
|
||||
MigrationContext,
|
||||
} from "contentful-migration"
|
||||
|
||||
export function regionMigration(
|
||||
migration: Migration,
|
||||
context?: MigrationContext
|
||||
) {
|
||||
const region = migration
|
||||
.createContentType("region")
|
||||
.name("Region")
|
||||
.displayField("name")
|
||||
|
||||
region
|
||||
.createField("name")
|
||||
.name("Name")
|
||||
.type("Symbol")
|
||||
.required(true)
|
||||
region
|
||||
.createField("countries")
|
||||
.name("Options")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("paymentProviders")
|
||||
.name("Payment Providers")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("fulfillmentProviders")
|
||||
.name("Fulfillment Providers")
|
||||
.type("Object")
|
||||
region
|
||||
.createField("currencyCode")
|
||||
.name("Currency Code")
|
||||
.type("Symbol")
|
||||
region
|
||||
.createField("medusaId")
|
||||
.name("Medusa ID")
|
||||
.type("Symbol")
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
|
||||
Finally, create a loader at `src/loaders/index.ts` with the following content:
|
||||
|
||||
```ts title="src/loaders/index.ts"
|
||||
import {
|
||||
ConfigModule,
|
||||
StoreService,
|
||||
MedusaContainer,
|
||||
} from "@medusajs/medusa"
|
||||
import { runMigration } from "contentful-migration"
|
||||
import {
|
||||
productMigration,
|
||||
} from "./contentful-migrations/product"
|
||||
import {
|
||||
productVariantMigration,
|
||||
} from "./contentful-migrations/product-variant"
|
||||
import {
|
||||
productCollectionMigration,
|
||||
} from "./contentful-migrations/product-collection"
|
||||
import {
|
||||
productTypeMigration,
|
||||
} from "./contentful-migrations/product-type"
|
||||
import {
|
||||
regionMigration,
|
||||
} from "./contentful-migrations/region"
|
||||
|
||||
type ContentfulPluginType = {
|
||||
resolve: string
|
||||
options: {
|
||||
space_id: string
|
||||
access_token: string
|
||||
environment: string
|
||||
}
|
||||
}
|
||||
|
||||
export default async (
|
||||
container: MedusaContainer,
|
||||
config: ConfigModule
|
||||
): Promise<void> => {
|
||||
// ensure that migration only runs once
|
||||
const storeService = container.resolve<StoreService>(
|
||||
"storeService"
|
||||
)
|
||||
const store = await storeService.retrieve()
|
||||
|
||||
if (store.metadata?.ran_contentful_migrations) {
|
||||
return
|
||||
}
|
||||
|
||||
console.info("Running contentful migrations...")
|
||||
|
||||
// load Contentful options
|
||||
const contentfulPlugin = config.plugins
|
||||
.find((plugin) =>
|
||||
typeof plugin === "object" &&
|
||||
plugin.resolve === "medusa-plugin-contentful"
|
||||
) as ContentfulPluginType
|
||||
|
||||
if (!contentfulPlugin) {
|
||||
console.log(
|
||||
"Didn't find Contentful plugin. Aborting migration..."
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const options = {
|
||||
spaceId: contentfulPlugin.options.space_id,
|
||||
accessToken: contentfulPlugin.options.access_token,
|
||||
environment: contentfulPlugin.options.environment,
|
||||
yes: true,
|
||||
}
|
||||
|
||||
const migrationFunctions = [
|
||||
{
|
||||
name: "Product",
|
||||
function: productMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Variant",
|
||||
function: productVariantMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Collection",
|
||||
function: productCollectionMigration,
|
||||
},
|
||||
{
|
||||
name: "Product Type",
|
||||
function: productTypeMigration,
|
||||
},
|
||||
{
|
||||
name: "Region",
|
||||
function: regionMigration,
|
||||
},
|
||||
]
|
||||
|
||||
await Promise.all(
|
||||
migrationFunctions.map(async (migrationFunction) => {
|
||||
console.info(`Migrating ${
|
||||
migrationFunction.name
|
||||
} component...`)
|
||||
try {
|
||||
await runMigration({
|
||||
...options,
|
||||
migrationFunction: migrationFunction.function,
|
||||
})
|
||||
console.info(`Finished migrating ${
|
||||
migrationFunction.name
|
||||
} component`)
|
||||
} catch (e) {
|
||||
if (
|
||||
typeof e === "object" && "errors" in e &&
|
||||
Array.isArray(e.errors) &&
|
||||
e.errors.length > 0 &&
|
||||
e.errors[0].type === "Invalid Action" &&
|
||||
e.errors[0].message.includes("already exists")
|
||||
) {
|
||||
console.info(`${
|
||||
migrationFunction.name
|
||||
} already exists. Skipping its migration.`)
|
||||
} else {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
await storeService.update({
|
||||
metadata: {
|
||||
ran_contentful_migrations: true,
|
||||
},
|
||||
})
|
||||
|
||||
console.info("Finished contentful migrations")
|
||||
}
|
||||
```
|
||||
|
||||
Notice that in the script you store a flag in the default store’s `metadata` attribute to ensure these migrations only run once.
|
||||
|
||||
### Setup Webhooks
|
||||
|
||||
As mentioned in the introduction, this plugin supports two-way sync. A subscriber in the plugin listens to changes in the data, such as adding a new product, and syncs the data with Contentful.
|
||||
|
||||
To update the Medusa application when changes occur in Contentful, you must configure webhooks settings in Contentful.
|
||||
|
||||
<Note>
|
||||
|
||||
For webhooks to work, your Medusa application must be deployed and accessible publicly.
|
||||
|
||||
</Note>
|
||||
|
||||
To do that:
|
||||
|
||||
1. On your Contentful Space Dashboard, click on Settings from the navigation bar, then choose Webhooks.
|
||||
2. Click on the Add Webhook button.
|
||||
3. In the form, enter a name for the webhook.
|
||||
4. In the URL field, choose the method `POST` and in the input next to it enter the URL `<MEDUSA_URL>/hooks/contentful` where `<MEDUSA_URL>` is the URL of your deployed Medusa application.
|
||||
5. Scroll down to find the Content Type select field. Choose `application/json` as its value.
|
||||
6. You can leave the rest of the fields the same and click on the Save button.
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
Run the following command to start your Medusa application and test the plugin:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
If you created migration scripts, they’ll run when the Medusa application starts and migrate your content models to Contentful. You can go to your space’s dashboard to confirm they’ve been created.
|
||||
|
||||
After that, try the sync functionality by creating or updating products in the Medusa application. If you’ve also setup webhooks, you can test out the sync from Contentful to Medusa.
|
||||
@@ -1,229 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Strapi Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note>
|
||||
|
||||
This plugin is a [community plugin](https://github.com/SGFGOV/medusa-strapi-repo) and is not managed by the official Medusa team. It supports v4 of Strapi. If you run into any issues, please refer to the [plugin's repository](https://github.com/SGFGOV/medusa-strapi-repo).
|
||||
|
||||
</Note>
|
||||
|
||||
## Features
|
||||
|
||||
[Strapi](https://strapi.io/) is an open source headless CMS service that allows developers to have complete control over their content models. It can be integrated into many other frameworks, including Medusa.
|
||||
|
||||
By integrating Strapi into Medusa, you can benefit from powerful features in your ecommerce store, such as:
|
||||
|
||||
- Rich CMS details for product.
|
||||
- Easy-to-use interface to manage content for static content and pages.
|
||||
- Localization for product and storefront content.
|
||||
- Two-way sync between Strapi and Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Preparations
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- A [PostgreSQL database](https://www.postgresql.org/docs/current/sql-createdatabase.html) for Strapi.
|
||||
|
||||
</Note>
|
||||
|
||||
In this section, you’ll setup a Strapi project with a Medusa plugin installed. To do that:
|
||||
|
||||
1. Clone the Strapi project repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/SGFGOV/medusa-strapi-repo.git
|
||||
```
|
||||
|
||||
2. Change to the `medusa-strapi-repo/packages/medusa-strapi` directory.
|
||||
3. Copy the `.env.test` file to a new `.env` file.
|
||||
|
||||
### Change Strapi Environment Variables
|
||||
|
||||
In the `.env` file, change the following environment variables:
|
||||
|
||||
```bash
|
||||
# IMPORTANT: Change supersecret with random and unique strings
|
||||
APP_KEYS=supersecret
|
||||
API_TOKEN_SALT=supersecret
|
||||
ADMIN_JWT_SECRET=supersecret
|
||||
JWT_SECRET=supersecret
|
||||
|
||||
MEDUSA_STRAPI_SECRET=supersecret
|
||||
|
||||
MEDUSA_BACKEND_URL=http://localhost:9000
|
||||
MEDUSA_BACKEND_ADMIN=http://localhost:7001
|
||||
|
||||
SUPERUSER_EMAIL=support@medusa-commerce.com
|
||||
SUPERUSER_USERNAME=SuperUser
|
||||
SUPERUSER_PASSWORD=MedusaStrapi1
|
||||
|
||||
DATABASE_HOST=localhost
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_NAME=postgres_strapi
|
||||
DATABASE_USERNAME=postgres
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_SSL=false
|
||||
DATABASE_SCHEMA=public
|
||||
```
|
||||
|
||||
1. Change `APP_KEYS`, `API_TOKEN_SALT`, `JWT_SECRET`, and `ADMIN_JWT_SECRET` to a random and unique string. These keys are used by Strapi to sign session cookies, generate API tokens, and more.
|
||||
2. Change `MEDUSA_STRAPI_SECRET` to a random unique string. The value of this environment variable is used later in your Medusa configurations.
|
||||
3. Change `MEDUSA_BACKEND_URL` to the URL of your Medusa backend. If you’re running it locally, it should be `http://localhost:9000`.
|
||||
4. Change `MEDUSA_BACKEND_ADMIN` to the URL of your Medusa Admin. If you’re running it locally, it should be `http://localhost:7001`.
|
||||
5. Change the following environment variables to define the Strapi super user:
|
||||
1. `SUPERUSER_EMAIL`: the super user’s email. By default, it’s `support@medusa-commerce.com`.
|
||||
2. `SUPERUSER_USERNAME`: the super user’s username. By default, it’s `SuperUser`.
|
||||
3. `SUPERUSER_PASSWORD`: the super user’s password. By default, it’s `MedusaStrapi1`.
|
||||
4. `SUPERUSER_FIRSTNAME`: the super user’s first name. By default, it’s `Medusa`.
|
||||
5. `SUPERUSER_LASTNAME`: the super user’s last name. By default, it’s `Commerce`.
|
||||
6. Change the database environment variables based on your database configurations. All database environment variables start with `DATABASE_`.
|
||||
7. You can optionally configure other services, such as S3 or MeiliSearch, as explained [here](https://github.com/SGFGOV/medusa-strapi-repo/tree/development/packages/medusa-strapi#media-bucket).
|
||||
|
||||
### Build Packages
|
||||
|
||||
Once you’re done, install and build packages in the root `medusa-strapi-repo` directory:
|
||||
|
||||
```bash npm2yarn
|
||||
# Install packages
|
||||
npm install
|
||||
# Build packages
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Install the Strapi Plugin in Medusa
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Strapi plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-strapi-ts
|
||||
```
|
||||
|
||||
Next, add the plugin to the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "medusa-plugin-strapi-ts",
|
||||
options: {
|
||||
strapi_protocol: process.env.STRAPI_PROTOCOL,
|
||||
strapi_host: process.env.STRAPI_SERVER_HOSTNAME,
|
||||
strapi_port: process.env.STRAPI_PORT,
|
||||
strapi_secret: process.env.STRAPI_SECRET,
|
||||
strapi_default_user: {
|
||||
username: process.env.STRAPI_MEDUSA_USER,
|
||||
password: process.env.STRAPI_MEDUSA_PASSWORD,
|
||||
email: process.env.STRAPI_MEDUSA_EMAIL,
|
||||
confirmed: true,
|
||||
blocked: false,
|
||||
provider: "local",
|
||||
},
|
||||
strapi_admin: {
|
||||
username: process.env.STRAPI_SUPER_USERNAME,
|
||||
password: process.env.STRAPI_SUPER_PASSWORD,
|
||||
email: process.env.STRAPI_SUPER_USER_EMAIL,
|
||||
},
|
||||
auto_start: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Strapi Plugin Options
|
||||
|
||||
1. `strapi_protocol`: The protocol of the Strapi server. If running locally, it should be `http`. Otherwise, it should be `https`.
|
||||
2. `strapi_host`: the domain of the Strapi server. If running locally, use `127.0.0.1`.
|
||||
3. `strapi_port`: the port that the Strapi server is running on, if any. If running locally, use `1337`.
|
||||
4. `strapi_secret`: the same secret used for the `MEDUSA_STRAPI_SECRET` environment variable in the Strapi project.
|
||||
5. `strapi_default_user`: The details of an existing user or a user to create in the Strapi backend that is used to update data in Strapi. It’s an object accepting the following properties:
|
||||
1. `username`: The user’s username.
|
||||
2. `password`: The user’s password.
|
||||
3. `email`: The user’s email.
|
||||
4. `confirmed`: Whether the user is confirmed.
|
||||
5. `blocked`: Whether the user is blocked.
|
||||
6. `provider`: The name of the authentication provider.
|
||||
6. `strapi_admin`: The details of the super admin. The super admin is only used to create the default user if it doesn’t exist. It’s an object accepting the following properties:
|
||||
1. `username`: the super admin’s username. Its value is the same as that of the `SUPERUSER_USERNAME` environment variable in the Strapi project.
|
||||
2. `password`: the super admin’s password. Its value is the same as that of the `SUPERUSER_PASSWORD` environment variable in the Strapi project.
|
||||
3. `email`: the super admin’s email. Its value is the same as that of the `SUPERUSER_EMAIL` environment variable in the Strapi project.
|
||||
7. `auto_start`: Whether to initialize the Strapi connection when Medusa starts. Disabling this may cause issues when syncing data from Medusa to Strapi.
|
||||
|
||||
Refer to the [plugin’s README](https://github.com/SGFGOV/medusa-strapi-repo/blob/development/packages/medusa-plugin-strapi-ts/README.md) for more options.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
STRAPI_PROTOCOL=http
|
||||
STRAPI_SERVER_HOSTNAME=127.0.0.1
|
||||
STRAPI_PORT=1337
|
||||
STRAPI_SECRET=supersecret
|
||||
|
||||
STRAPI_MEDUSA_USER=medusa
|
||||
STRAPI_MEDUSA_PASSWORD=supersecret
|
||||
STRAPI_MEDUSA_EMAIL=admin@medusa-test.com
|
||||
|
||||
STRAPI_SUPER_USERNAME=SuperUser
|
||||
STRAPI_SUPER_PASSWORD=MedusaStrapi1
|
||||
STRAPI_SUPER_USER_EMAIL=support@medusa-commerce.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the integration between Medusa and Strapi, first, start the Strapi server by running the following command in the `medusa-strapi-repo/packages/medusa-strapi` directory:
|
||||
|
||||
```bash title="medusa-strapi-repo/packages/medusa-strapi" npm2yarn
|
||||
npm run develop
|
||||
```
|
||||
|
||||
Then, start the Medusa application:
|
||||
|
||||
```bash title="Medusa Backend" npm2yarn
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
If the connection to Strapi is successful, you’ll find the following message logged in your Medusa application with no errors:
|
||||
|
||||
```bash
|
||||
info: Checking Strapi Health ,data:
|
||||
debug: check-url: http://127.0.0.1:1337/_health ,data:
|
||||
info: Strapi Subscriber Initialized
|
||||
```
|
||||
|
||||
### Synced Entities
|
||||
|
||||
The Medusa and Strapi plugins support syncing the following Medusa data models:
|
||||
|
||||
- `Region`
|
||||
- `Product`
|
||||
- `ProductVariant`
|
||||
- `ProductCollection`
|
||||
- `ProductCategory`
|
||||
|
||||
### Two-Way Syncing
|
||||
|
||||
To test syncing data from Medusa to Strapi, try creating or updating a product either using the Medusa Admin or the [REST APIs](https://docs.medusajs.com/api/admin#products_postproducts). This triggers the associated event in Medusa, which makes the updates in Strapi.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Data is only synced to Strapi once you create or update them. So, if you have products in your Medusa application from before integrating Strapi, they won’t be available by default in Strapi. You’ll have to make updates to them, which triggers the update in Strapi.
|
||||
|
||||
</Note>
|
||||
|
||||
To test syncing data from Strapi to Medusa, try updating one of the products in the Strapi dashboard. If you check the product’s details in Medusa, they’re updated as expected.
|
||||
@@ -1,487 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Brightpearl Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Brightpearl](https://www.brightpearl.com/) is a Retail Operations Platform. It can be integrated to a business's different sales channels to provide features related to inventory management, automation, analytics and reporting, and more.
|
||||
|
||||
Medusa provides an official Brightpearl plugin with the following features:
|
||||
|
||||
- Send and sync orders with Brightpearl.
|
||||
- Listen for inventory and stock movements in Brightpearl.
|
||||
- Handle order returns through Brightpearl.
|
||||
|
||||
---
|
||||
|
||||
## Install the Brightpearl Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Brightpearl account](https://www.brightpearl.com/)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Brightpearl plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-brightpearl
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "account", "The Brightpearl account ID."],
|
||||
["7", "backend_url", "The URL of the Medusa application."],
|
||||
["8", "channel_id", "The ID of the channel to map sales and credits to."],
|
||||
["9", "event_owner", "The ID of the contact used when sending the Goods-Out Note Event."],
|
||||
["10", "warehouse", "The ID of the warehouse to allocate order items' inventory from."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-brightpearl`,
|
||||
options: {
|
||||
account: process.env.BRIGHTPEARL_ACCOUNT,
|
||||
backend_url: process.env.BRIGHTPEARL_BACKEND_URL,
|
||||
channel_id: process.env.BRIGHTPEARL_CHANNEL_ID,
|
||||
event_owner: process.env.BRIGHTPEARL_EVENT_OWNER,
|
||||
warehouse: process.env.BRIGHTPEARL_WAREHOUSE,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Brightpearl Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`account`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
a string indicating your [Brightpearl account ID](https://help.brightpearl.com/s/article/360028541892#:~:text=Your%20account%20ID%20can%20be,your%20email%20address%20and%20password).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`backend_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL of your Medusa application. This is useful for webhooks.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`channel_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the channel to map sales and credits to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`event_owner`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the contact used when sending the [Goods-Out Note Event](https://api-docs.brightpearl.com/warehouse/goods-out-note%20event/post.html).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`warehouse`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the warehouse to allocate order items' inventory from.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`default_status_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the status to assign new orders. This value will also be used on
|
||||
swaps or claims if their respective options, `swap_status_id` and `claim_status_id`, are not provided.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`3`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`swap_status_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the status to assign new swaps.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Value of `default_status_id`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`claim_status_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the status to assign new claims.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Value of `default_status_id`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`payment_method_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the payment method code to register payments with.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`1220`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`sales_account_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the nominal code to assign line items to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`4000`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`shipping_account_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the nominal code to assign shipping lines to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`4040`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`discount_account_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the nominal code to use for discount-type refunds.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`gift_card_account_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the nominal code to use for gift card products and redeems.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`4000`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`inventory_sync_cron`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating a cron pattern that should be used to create a scheduled job
|
||||
for syncing inventory. If not provided, the scheduled job will not be created.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`cost_price_list`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the price list to assign to created claims.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`1`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`base_currency`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ISO 3 character code of the currency to assign to created claims.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`EUR`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
BRIGHTPEARL_ACCOUNT=<YOUR_ACCOUNT>
|
||||
BRIGHTPEARL_CHANNEL_ID=<YOUR_CHANNEL_ID>
|
||||
BRIGHTPEARL_BACKEND_URL=<YOUR_BACKEND_URL>
|
||||
BRIGHTPEARL_EVENT_OWNER=<YOUR_EVENT_OWNER>
|
||||
BRIGHTPEARL_WAREHOUSE=<YOUR_WAREHOUSE>
|
||||
BRIGHTPEARL_DEFAULT_STATUS_ID=<YOUR_DEFAULT_STATUS_ID>
|
||||
BRIGHTPEARL_SWAP_STATUS_ID=<YOUR_SWAP_STATUS_ID>
|
||||
BRIGHTPEARL_CLAIM_STATUS_ID=<YOUR_CLAIM_STATUS_ID>
|
||||
BRIGHTPEARL_PAYMENT_METHOD_CODE=<YOUR_PAYMENT_METHOD_CODE>
|
||||
BRIGHTPEARL_SALES_ACCOUNT_CODE=<YOUR_SALES_ACCOUNT_CODE>
|
||||
BRIGHTPEARL_SHIPPING_ACCOUNT_CODE=<YOUR_SHIPPING_ACCOUNT_CODE>
|
||||
BRIGHTPEARL_DISCOUNT_ACCOUNT_CODE=<YOUR_DISCOUNT_ACCOUNT_CODE>
|
||||
BRIGHTPEARL_GIFT_CARD_ACCOUNT_CODE=<YOUR_GIFT_CARD_ACCOUNT_CODE>
|
||||
BRIGHTPEARL_INVENTORY_SYNC_CRON=<YOUR_INVENTORY_SYNC_CRON>
|
||||
BRIGHTPEARL_COST_PRICE_LIST=<YOUR_COST_PRICE_LIST>
|
||||
BRIGHTPEARL_BASE_CURRENCY=<YOUR_BASE_CURRENCY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, place an order either using a [storefront](../../../nextjs-starter/page.mdx) or the [Store REST APIs](https://docs.medusajs.com/api/store). The order should appear on Brightpearl.
|
||||
|
||||
---
|
||||
|
||||
## How the Plugin Works
|
||||
|
||||
### OAuth
|
||||
|
||||
The plugin registers an OAuth app in Medusa allowing installation at `<MEDUSA_URL>/a/settings/apps`, where `<MEDUSA_URL>` is the URL of your Medusa application.
|
||||
|
||||
The OAuth tokens are refreshed every hour to prevent unauthorized requests.
|
||||
|
||||
### Orders and Fulfillments
|
||||
|
||||
When an order is created in the Medusa application, it'll automatically be sent to Brightpearl and allocated there. Once allocated, it is up to Brightpearl to figure out how the order is to be fulfilled.
|
||||
|
||||
The plugin listens for Goods-Out notes and tries to map each of these to a Medusa order. If the matching succeeds, the Medusa application sends the order to the fulfillment provider associated with the shipping method selected by the Customer.
|
||||
|
||||
### Order Returns
|
||||
|
||||
When line items in an order are returned, the plugin will generate a sales credit in Brightpearl.
|
||||
|
||||
### Products
|
||||
|
||||
The plugin doesn't automatically create products in Medusa, but listens for inventory changes in Brightpearl. Then, the plugin updates each product variant to reflect the inventory quantity listed in Brightpearl, thereby ensuring that the inventory levels in Medusa are always in sync with Brightpearl.
|
||||
@@ -1,48 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Manual Fulfillment Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
The manual fulfillment plugin is a minimal plugin that allows merchants to handle fulfillments manually. This plugin is installed by default in your Medusa application.
|
||||
|
||||
The manual fulfillment plugin is similar to a cash-on-delivery (COD) fulfillment plugin. While the merchant can use shipping and fulfillment functionalities, they only change data in the database. The merchant has to handle the actual fulfillment of the order manually.
|
||||
|
||||
---
|
||||
|
||||
## Install the Manual Fulfillment Plugin
|
||||
|
||||
To install the Manual Fulfillment plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-fulfillment-manual
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-fulfillment-manual`,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, you must enable the Manual Fulfillment Provider in at least one region to use it. You can do that using either the [Medusa Admin](!user-guide!/settings/regions/providers), or the [Admin API Routes](https://docs.medusajs.com/api/admin#regions_postregionsregionfulfillmentproviders).
|
||||
|
||||
After enabling the provider, you must add shipping options for that provider. You can also do that using either the [Medusa Admin](!user-guide!/settings/regions/shipping-options) or the [Admin API Routes](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions).
|
||||
|
||||
Finally, try to place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store). You can use the shipping options you created for the fulfillment provider.
|
||||
@@ -1,333 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Webshipper Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Webshipper](https://webshipper.com/) is a service that allows merchants to connect to multiple carriers through a single Webshipper account. Developers can then integrate webshipper with ecommerce store like Medusa to handle shipping and fulfillment.
|
||||
|
||||
Medusa provides an official plugin that allows you to integrate Webshipper in your store. When integrated, you can provide customers with Webshippers' shipping options on checkout, and process and handle fulfillment and shipments of orders through Webshipper.
|
||||
|
||||
---
|
||||
|
||||
## Install the Webshipper Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Webshipper account](https://webshipper.com/)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Webshipper plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-fulfillment-webshipper
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "account", "The Webshipper account name."],
|
||||
["7", "api_token", "The Webshipper API token."],
|
||||
["8", "order_channel_id", "The ID of the order channel to retrieve shipping rates from."],
|
||||
["10", "webhook_secret", "The secret used to sign the HMAC in webhooks."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-fulfillment-webshipper`,
|
||||
options: {
|
||||
account: process.env.WEBSHIPPER_ACCOUNT,
|
||||
api_token: process.env.WEBSHIPPER_API_TOKEN,
|
||||
order_channel_id:
|
||||
process.env.WEBSHIPPER_ORDER_CHANNEL_ID,
|
||||
webhook_secret: process.env.WEBSHIPPER_WEBHOOK_SECRET,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Webshipper Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`account`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating your account name. It's in the first part of the URL you use when accessing the Webshipper UI which has the format `https://<account_name>.webshipper.io`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`api_token`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating your API token. You can create it from the Webshipper UI under Settings > Access and tokens.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`order_channel_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ID of the order channel to retrieve shipping rates from.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`webhook_secret`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the secret used to sign the HMAC in webhooks.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`return_address`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object that indicates the return address to use when fulfilling an order return. Refer to [Webshipper's API reference](https://docs.webshipper.io/#shipping_addresses) for accepted properties in this object.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes for returns.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`coo_countries`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string or an array of strings, each being an ISO 3 character country codes used when attaching a Certificate of Origin. To support all countries you can set the value to `all`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`all`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`delete_on_cancel`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean value that determines whether Webshipper orders should be deleted when its associated Medusa fulfillment is canceled.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`document_size`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the size used when retrieving documents, such as fulfillment documents. The accepted values, are `100X150`, `100X192`, or `A4`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`A4`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`return_portal`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object that includes options related to order returns. It includes the following properties:
|
||||
|
||||
- `id`: is a string indicating the ID of the return portal to use when fulfilling an order return.
|
||||
- `cause_id`: is a string indicating the ID of the return cause to use when fulfilling an order return.
|
||||
- `refund_method_id` is a string indicating the ID of the refund method to use when fulfilling an order return.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
WEBSHIPPER_ACCOUNT=<YOUR_WEBSHIPPER_ACCOUNT>
|
||||
WEBSHIPPER_API_TOKEN=<YOUR_WEBSHIPPER_API_TOKEN>
|
||||
WEBSHIPPER_ORDER_CHANNEL_ID=<YOUR_WEBSHIPPER_ORDER_CHANNEL_ID>
|
||||
WEBSHIPPER_WEBHOOK_SECRET=<YOUR_WEBSHIPPER_WEBHOOK_SECRET>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, enable the Webshipper Fulfillment Provider in at least one region to use it. You can do that using either the [Medusa Admin](!user-guide!/settings/regions/providers) or the [Admin API Routes](https://docs.medusajs.com/api/admin#regions_postregionsregionfulfillmentproviders).
|
||||
|
||||
After enabling the provider, add shipping options for that provider using either the [Medusa Admin](!user-guide!/settings/regions/shipping-options) or the [Admin API Routes](https://docs.medusajs.com/api/admin#shipping-options_postshippingoptions).
|
||||
|
||||
Finally, try to place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store).
|
||||
|
||||
---
|
||||
|
||||
## Personal Customs Numbers
|
||||
|
||||
In countries like South Korea, a personal customs number is required to clear customs. The Webshipper plugin can pass this information to Webshipper given that the number is stored in `order.shipping_address.metadata.personal_customs_no`.
|
||||
|
||||
### Add Field in Checkout Flow
|
||||
|
||||
To allow the customer to pass their personal customs number along with the order, dynamically show an input field to the customer when they are shopping from a region that requires a personal customs number. Then, make sure that the `metadata` field includes the personal customs number when updating the cart's shipping address.
|
||||
|
||||
```ts
|
||||
const onUpdateAddress = async () => {
|
||||
const address = {
|
||||
first_name: "John",
|
||||
last_name: "Johnson",
|
||||
// ...,
|
||||
metadata: {
|
||||
// TODO the value should be replaced with the
|
||||
// value entered by the customer
|
||||
personal_customs_no: "my-customs-number",
|
||||
},
|
||||
}
|
||||
|
||||
await medusaClient.carts
|
||||
.update(cartId, {
|
||||
shipping_address: address,
|
||||
})
|
||||
.then(() => {
|
||||
console.log(
|
||||
"Webshipper will pass along the customs number"
|
||||
)
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -1,238 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Mailchimp Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Mailchimp](https://mailchimp.com) is an email marketing service used to create newsletters and subscriptions.
|
||||
|
||||
By integrating Mailchimp with Medusa, customers can subscribe from Medusa to your Mailchimp newsletter and are automatically added to your Mailchimp subscribers list.
|
||||
|
||||
---
|
||||
|
||||
## Install the Mailchimp Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Mailchimp account](https://mailchimp.com/signup)
|
||||
- [Mailchimp API Key](https://mailchimp.com/help/about-api-keys/#Find_or_generate_your_API_key)
|
||||
- [Mailchimp Audience ID](https://mailchimp.com/help/find-audience-id/)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Mailchimp plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-mailchimp
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "api_key", "The Mailchimp API Key."],
|
||||
["7", "newsletter_list_id", "The Mailchimp Audience ID."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...,
|
||||
{
|
||||
resolve: `medusa-plugin-mailchimp`,
|
||||
options: {
|
||||
api_key: process.env.MAILCHIMP_API_KEY,
|
||||
newsletter_list_id:
|
||||
process.env.MAILCHIMP_NEWSLETTER_LIST_ID,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Mailchimp Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`api_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Mailchimp API Key.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`newsletter_list_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Mailchimp Audience ID.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
MAILCHIMP_API_KEY=<YOUR_API_KEY>
|
||||
MAILCHIMP_NEWSLETTER_LIST_ID=<YOUR_NEWSLETTER_LIST_ID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This plugin adds new `POST` and `PUT` API Routes at `/mailchimp/subscribe`. These API Routes require in the body of the request an `email` field. You can also optionally include a `data` object that holds any additional data you want to send to Mailchimp.
|
||||
|
||||
Check out [Mailchimp’s subscription documentation](https://mailchimp.com/developer/marketing/api/list-merges/) for more details on the data you can send.
|
||||
|
||||
### Without Additional Data
|
||||
|
||||
Try sending a `POST` or `PUT` request to `/mailchimp/subscribe`:
|
||||
|
||||
```bash noReport apiTesting testApiUrl="http://localhost:9000/mailchimp/subscribe" testApiMethod="POST" testBodyParams={{ "email": "example@gmail.com" }}
|
||||
curl -X POST http://localhost:9000/mailchimp/subscribe \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "example@gmail.com"
|
||||
}'
|
||||
```
|
||||
|
||||
When the subscription is successful, a `200` response code is returned with `OK` message.
|
||||
|
||||
When the same email address is used again in the `POST`, a `400` response is returned. If this can occur in your usecase, use the `PUT` API Route instead.
|
||||
|
||||
Check your Mailchimp dashboard, you should find the email added to your Audience list.
|
||||
|
||||
### With Additional Data
|
||||
|
||||
For example, send in the `data` request body parameter a `tags` array:
|
||||
|
||||
```bash noReport apiTesting testApiUrl="http://localhost:9000/mailchimp/subscribe" testApiMethod="POST" testBodyParams={{ "email": "example@gmail.com" }}
|
||||
curl -X POST http://localhost:9000/mailchimp/subscribe \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "example@gmail.com",
|
||||
"data": {
|
||||
"tags": ["customer"]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
All fields inside `data` is sent to Mailchimp along with the email.
|
||||
|
||||
---
|
||||
|
||||
## Use MailchimpService
|
||||
|
||||
Use the `MailchimpService` to subscribe users to the newsletter in other contexts. This service has a method `subscribeNewsletter` that subscribes a customer to the newsletter.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/subscribers/customer-created.ts"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
CustomerService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function handleCustomerCreated({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const mailchimpService = container.resolve("mailchimpService")
|
||||
|
||||
mailchimpService.subscribeNewsletter(
|
||||
data.email,
|
||||
{ tags: ["customer"] } // optional
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: CustomerService.Events.CREATED,
|
||||
}
|
||||
```
|
||||
|
||||
This creates a subscriber that listens to the `CustomerService.Events.CREATED` (`customer.created`) event and subscribes the customer automatically using the `mailchimpService`.
|
||||
|
||||
---
|
||||
|
||||
## Add Subscription Form
|
||||
|
||||
This section provides a simple example of adding a subscription form in your storefront. The code is for React-based frameworks, but you can use the same logic for your storefronts regardless of the framework you are using.
|
||||
|
||||
You need to use [axios](https://github.com/axios/axios) to send API requests, so start by installing it in your storefront project:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install axios
|
||||
```
|
||||
|
||||
Then, create the following component that uses the mailchimp plugin's API route to subscribe customers:
|
||||
|
||||
```tsx
|
||||
import axios from "axios"
|
||||
import { useState } from "react"
|
||||
|
||||
export default function NewsletterForm() {
|
||||
const [email, setEmail] = useState("")
|
||||
|
||||
function subscribe(e) {
|
||||
e.preventDefault()
|
||||
if (!email) {
|
||||
return
|
||||
}
|
||||
|
||||
axios.post("http://localhost:9000/mailchimp/subscribe", {
|
||||
email,
|
||||
})
|
||||
.then((e) => {
|
||||
alert("Subscribed successfully!")
|
||||
setEmail("")
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
alert("An error occurred")
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={subscribe}>
|
||||
<h2>Sign Up for our newsletter</h2>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
placeholder="example@gmail.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<button type="submit">Subscribe</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,160 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Slack Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
Slack is a communication platform used by teams and organizations for collaboration and messaging. This plugin sends merchants a slack message when a new order is placed.
|
||||
|
||||
The notification contains details about the order including:
|
||||
|
||||
- Customer's details and address.
|
||||
- Items ordered, their quantity, and the price.
|
||||
- Order totals including Tax amount.
|
||||
- Promotion details if there are any (this is optional and can be turned off).
|
||||
|
||||
---
|
||||
|
||||
## Install the Slack Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Slack account](https://slack.com)
|
||||
- [A Slack app](https://api.slack.com/start/quickstart#creating)
|
||||
- [Activate incoming webhooks in Slack and create a new webhook](https://api.slack.com/start/quickstart#webhooks)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Slack plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-slack-notification
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "slack_url", "The Slack webhook URL."],
|
||||
["7", "show_discount_code", "Whether to show the discount code after creating the Slack app."],
|
||||
["8", "admin_orders_url", "The prefix of the URL of the order detail pages on your admin panel."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-slack-notification`,
|
||||
options: {
|
||||
slack_url: process.env.SLACK_WEBHOOK_URL,
|
||||
show_discount_code: false,
|
||||
admin_orders_url: `http://localhost:7001/a/orders`,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Twilio SMS Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`slack_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Slack webhook URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`show_discount_code`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean whether to show the discount code after creating the Slack app.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`admin_orders_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the prefix of the URL of the order detail pages on your admin panel.
|
||||
If you’re using Medusa Admin locally, it should be `http://localhost:7001/a/orders`. This results in a URL like `http://localhost:7001/a/orders/order_01FYP7DM7PS43H9VQ1PK59ZR5G`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No, but if not provided the order URL in the messages will be `/order_01FYP7DM7PS43H9VQ1PK59ZR5G`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
SLACK_WEBHOOK_URL=<YOUR_WEBHOOK_URL>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store). A message is sent to the DM or slack channel you configured in the Slack webhook.
|
||||
@@ -1,165 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Twilio SMS Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Twilio’s SMS API](https://www.twilio.com/sms) is used to send users SMS messages instantly. It has a lot of additional features such as Whatsapp messaging and conversations.
|
||||
|
||||
By integrating Twilio SMS into Medusa, you’ll have easy access to Twilio’s SMS API to send SMS messages to your users and customers. You can use it to send order confirmations, verification codes, reset password messages, and more.
|
||||
|
||||
This plugin only gives you access to the Twilio SMS API but doesn't automate sending messages. You’ll have to add this yourself where you need it. There's an [example later in this guide](#example-plugin-usage) on how to send an SMS for a new order.
|
||||
|
||||
---
|
||||
|
||||
## Install the Twilio SMS Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Twilio account](https://www.twilio.com/sms)
|
||||
- [Twilio account SID](https://help.twilio.com/articles/14726256820123-What-is-a-Twilio-Account-SID-and-where-can-I-find-it-)
|
||||
- [Twilio auth token](https://help.twilio.com/articles/223136027-Auth-Tokens-and-How-to-Change-Them?_gl=1*qv22ht*_ga*OTY3NzYwMDAzLjE2OTE0MjA5MDI.*_ga_RRP8K4M4F3*MTcwOTIwMDA2Ny40LjAuMTcwOTIwMDA2Ny4wLjAuMA..)
|
||||
- [Twilio phone number](https://help.twilio.com/articles/223135247)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Twilio SMS plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-twilio-sms
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "account_sid", "The Twilio account SID."],
|
||||
["7", "auth_token", "The Twilio auth token."],
|
||||
["8", "from_number", "The Twilio phone number."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-twilio-sms`,
|
||||
options: {
|
||||
account_sid: process.env.TWILIO_SMS_ACCOUNT_SID,
|
||||
auth_token: process.env.TWILIO_SMS_AUTH_TOKEN,
|
||||
from_number: process.env.TWILIO_SMS_FROM_NUMBER,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Twilio SMS Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`account_sid`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Twilio account SID.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`auth_token`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Twilio auth token.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`from_number`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Twilio phone number.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
TWILIO_SMS_ACCOUNT_SID=<YOUR_ACCOUNT_SID>
|
||||
TWILIO_SMS_AUTH_TOKEN=<YOUR_AUTH_TOKEN>
|
||||
TWILIO_SMS_FROM_NUMBER=<YOUR_TWILIO_NUMBER>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Plugin Usage
|
||||
|
||||
Resolve and use the `TwilioSmsService` to send SMS.
|
||||
|
||||
For example, create the file `src/subscriber/sms.ts` with the following content:
|
||||
|
||||
```ts title="src/subscriber/sms.ts"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function handleOrderPlaced({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const twilioSmsService = container.resolve("twilioSmsService")
|
||||
const orderService: OrderService =
|
||||
container.resolve("orderService")
|
||||
|
||||
const order = await orderService.retrieve(data.id, {
|
||||
relations: ["shipping_address"],
|
||||
})
|
||||
|
||||
if (order.shipping_address.phone) {
|
||||
twilioSmsService.sendSms({
|
||||
to: order.shipping_address.phone,
|
||||
body: "We have received your order #" + data.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: OrderService.Events.PLACED,
|
||||
}
|
||||
```
|
||||
|
||||
This creates a subscriber that listens to the `OrderService.Events.PLACED` (`order.placed`) event and sends an SMS to the customer confirming their order.
|
||||
|
||||
The `sendSms` method of the `TwilioSmsService` accepts an object whose shape is as described in [Twilio's API reference](https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource).
|
||||
|
||||
<Note type="warning">
|
||||
|
||||
If you’re on a Twilio trial make sure that the phone number you entered on checkout is a [verified Twilio number on your console](https://console.twilio.com/us1/develop/phone-numbers/manage/verified).
|
||||
|
||||
</Note>
|
||||
@@ -1,96 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `Discount Generator Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
In Medusa, merchants can create dynamic discounts that act as a template for other discounts. With dynamic discounts, merchants don't have to repeat certain conditions every time they want to create a new discount.
|
||||
|
||||
The discount generator plugin allows merchants and developers to generate new discounts from a dynamic discount either using the `/discount-code` API Route or the `DiscountGeneratorService`.
|
||||
|
||||
---
|
||||
|
||||
## Install the Discount Generator Plugin
|
||||
|
||||
To install the Discount Generator plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-discount-generator
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-discount-generator`,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- Dynamic discount. Create it using either the [Medusa Admin](!user-guide!/discounts/create) or the [Admin API routes](https://docs.medusajs.com/api/admin#discounts_postdiscounts).
|
||||
|
||||
</Note>
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `POST` request to the `/discount-code` API Route:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/discount-code/" testApiMethod="POST" testBodyParams={{"discount_code": "TEST"}}
|
||||
curl -X POST http://localhost:9000/discount-code/ \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"discount_code": "TEST"
|
||||
}'
|
||||
```
|
||||
|
||||
The API Route accepts in the request body the parameter `discount_code` which is a string indicating the code of the dynamic discount to generate a new discount from.
|
||||
|
||||
The API Route then creates the new discount from the dynamic discount and returns it in the response.
|
||||
|
||||
---
|
||||
|
||||
## Use DiscountGeneratorService
|
||||
|
||||
Use the `DiscountGeneratorService` to generate a discount in other resources.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/generate-discount-code/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const POST = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// skipping validation for simplicity
|
||||
const { dynamicCode } = req.body
|
||||
const discountGenerator = req.scope.resolve(
|
||||
"discountGeneratorService"
|
||||
)
|
||||
const code =
|
||||
await discountGenerator.generateDiscount(dynamicCode)
|
||||
|
||||
res.json({
|
||||
code,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The `DiscountGeneratorService` has the method `generateDiscount`. It accepts the code of a dynamic discount as a parameter and creates a new discount having the same attributes as the dynamic discount, but with a different, random code.
|
||||
@@ -1,154 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `IP Lookup (ipstack) Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
Location detection in a commerce store is essential for multi-region support.
|
||||
|
||||
Medusa provides an IP Lookup plugin that integrates the application with [ipstack](https://ipstack.com/) to detect a customer’s location and region.
|
||||
|
||||
---
|
||||
|
||||
## Install the IP Lookup Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [ipstack account](https://ipstack.com/)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the IP Lookup plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-ip-lookup
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "access_token", "The ipstack account’s access key"],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// other plugins...
|
||||
{
|
||||
resolve: `medusa-plugin-ip-lookup`,
|
||||
options: {
|
||||
access_token: process.env.IPSTACK_ACCESS_KEY,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### IP Lookup Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_token`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the ipstack account’s access key.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
IPSTACK_ACCESS_KEY=<YOUR_ACCESS_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
The plugin provides two resources: the `IpLookupService` and the `preCartCreation` middleware.
|
||||
|
||||
<Note>
|
||||
|
||||
Due to how Express resolves the current IP when accessing your website from `localhost`, you won’t be able to test the plugin locally. You can either use tools like ngrok to expose the `9000` port to be accessed publicly, or you have to test it on a deployed Medusa application.
|
||||
|
||||
</Note>
|
||||
|
||||
### IpLookupService
|
||||
|
||||
The `IpLookupService` has a method `lookupIp` that accepts the IP address as a parameter, sends a request to ipstack’s API, and returns the retrieved result.
|
||||
|
||||
For example, you can use it in a custom API route:
|
||||
|
||||
```ts title="src/api/store/customer-region/route.ts"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
RegionService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const ipLookupService = req.scope.resolve("ipLookupService")
|
||||
const regionService =
|
||||
req.scope.resolve<RegionService>("regionService")
|
||||
|
||||
const ip =
|
||||
req.headers["x-forwarded-for"] || req.socket.remoteAddress
|
||||
|
||||
const { data } = await ipLookupService.lookupIp(ip)
|
||||
|
||||
if (!data.country_code) {
|
||||
throw new Error("Couldn't detect country code.")
|
||||
}
|
||||
|
||||
const region = await regionService.retrieveByCountryCode(
|
||||
data.country_code
|
||||
)
|
||||
|
||||
res.json({
|
||||
region,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### preCartCreation
|
||||
|
||||
The `preCartCreation` middleware can be added as a middleware to any route to attach the region ID to that route based on the user’s location.
|
||||
|
||||
For example, you can attach it to all `/store` routes to ensure the customer’s region is always detected:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import type { MiddlewaresConfig } from "@medusajs/medusa"
|
||||
const { preCartCreation } = require(
|
||||
"medusa-plugin-ip-lookup/api/medusa-middleware"
|
||||
).default
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/*",
|
||||
middlewares: [preCartCreation],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
@@ -1,189 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Restock Notifications Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
Customers browsing your products may find something that they need, but it's out of stock. In this scenario, you can keep them interested in your product by notifying them when the product is back in stock.
|
||||
|
||||
The Restock Notifications plugin provides new API Routes to subscribe customers to restock notifications of a specific product variant. It also triggers the `restock-notification.restocked` event whenever a product variant's stock quantity is above a specified threshold.
|
||||
|
||||
However, this plugin doesn't actually implement the sending of the notification, only the required implementation to trigger restock events and allow customers to subscribe to product variants' stock status. To send the notification, use a Notification plugin.
|
||||
|
||||
---
|
||||
|
||||
## Install the Restock Notifications Plugin
|
||||
|
||||
To install the Restock Notifications plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-restock-notification
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// other plugins...
|
||||
{
|
||||
resolve: `medusa-plugin-restock-notification`,
|
||||
options: {
|
||||
// optional options
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Restock Notifications Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`trigger_delay`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the time in milliseconds to delay the triggering of the `restock-notification.restocked` event.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`0`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`inventory_required`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the minimum inventory quantity to consider a product variant as restocked.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`0`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Run Migrations
|
||||
|
||||
The plugin requires changes in the database. So, before using it, run the `migrations` command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- An out-of-stock product variant. You can edit a variant's stock quantity for testing either using the [Medusa Admin](!user-guide!/products/manage) or the [Admin API Routes](https://docs.medusajs.com/api/admin#products_postproductsproductvariantsvariant).
|
||||
|
||||
</Note>
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `POST` request to the API Route `/restock-notifications/variants/{variant_id}` to subscribe to restock notifications of a product variant ID:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/restock-notifications/variants/{variant_id}" testApiMethod="POST" testPathParams={{"variant_id": "variant_01G1G5V2MRX2V3PVSR2WXYPFB6"}} testBodyParams={{"email": "example@gmail.com"}}
|
||||
curl -X POST http://localhost:9000/restock-notifications/variants/variant_01G1G5V2MRX2V3PVSR2WXYPFB6 \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"email": "example@gmail.com"
|
||||
}'
|
||||
```
|
||||
|
||||
The API Route accepts the following request body parameters:
|
||||
|
||||
1. `email`: a string indicating the email that is subscribing to the product variant's restock notification.
|
||||
2. `sales_channel_id`: an optional string indicating the ID of the sales channel to check the stock quantity in when subscribing.
|
||||
|
||||
After subscribing to the out-of-stock variant, change its stock quantity to the minimum inventory required to test the event trigger. The new stock quantity should be any value above `0` if you didn't set the `inventory_required` option.
|
||||
|
||||
{/* [Medusa Admin](../../user-guide/products/manage.mdx#manage-product-variants) */}
|
||||
|
||||
You can use the Medusa Admin or the [Admin API Routes](https://docs.medusajs.com/api/admin#products_postproductsproductvariantsvariant) to update the quantity.
|
||||
|
||||
After you update the quantity, the `restock-notification.restocked` is emitted.
|
||||
|
||||
---
|
||||
|
||||
## Example: Implement Notification Sending with SendGrid
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The SendGrid plugin already listens to and handles the `restock-notification.restocked` event. So, if you install it, you don't need to manually create a subscriber that handles this event as explained here. This example is only provided for reference on how to send a notification to the customer using a Notification plugin.
|
||||
|
||||
</Note>
|
||||
|
||||
Here's an example of a subscriber that listens to the `restock-notification.restocked` event and uses the [SendGrid plugin](../../notifications/sendgrid/page.mdx) to send the subscribed customers an email:
|
||||
|
||||
```ts title="src/subscribers/restock-notification.ts"
|
||||
import {
|
||||
type SubscriberConfig,
|
||||
type SubscriberArgs,
|
||||
ProductVariantService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export default async function handleRestockNotification({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<Record<string, string>>) {
|
||||
const sendgridService = container.resolve("sendgridService")
|
||||
const productVariantService: ProductVariantService =
|
||||
container.resolve("productVariantService")
|
||||
|
||||
// retrieve variant
|
||||
const variant = await productVariantService.retrieve(
|
||||
data.variant_id
|
||||
)
|
||||
|
||||
sendgridService.sendEmail({
|
||||
templateId: "restock-notification",
|
||||
from: "hello@medusajs.com",
|
||||
to: data.emails,
|
||||
dynamic_template_data: {
|
||||
// any data necessary for your template...
|
||||
variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "restock-notification.restocked",
|
||||
}
|
||||
```
|
||||
|
||||
The handler function receives in the `data` property of the first parameter the following properties:
|
||||
|
||||
- `variant_id`: The ID of the variant that has been restocked.
|
||||
- `emails`: An array of strings indicating the email addresses subscribed to the restocked variant.
|
||||
|
||||
In the handler function, you retrieve the variant by its ID using the `ProductVariantService`, then send the email using the `SendGridService`.
|
||||
@@ -1,110 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Wishlist Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
A wishlist allows customers to save items they like so they can browse and purchase them later.
|
||||
|
||||
Medusa's Wishlist plugin provides the following features:
|
||||
|
||||
- Allow a customer to manage their wishlist, including adding or deleting items.
|
||||
- Allow a customer to share their wishlist with others using a token.
|
||||
|
||||
Items in the wishlist are added as line items. This allows you to implement functionalities like moving an item from the wishlist to the cart.
|
||||
|
||||
---
|
||||
|
||||
## Install the Wishlist Plugin
|
||||
|
||||
To install the Wishlist plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-wishlist
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-wishlist`,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The plugin exposes four API Routes.
|
||||
|
||||
### Add Item to Wishlist API Route
|
||||
|
||||
The `POST` API Route at `/store/customers/{customer_id}/wishlist` allows customers to add items to their existing or new wishlist:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/customers/{customer_id}/wishlist" testApiMethod="POST" testPathParams={{"customer_id": "cus_01G2SG30J8C85S4A5CHM2S1NS2"}} testBodyParams={{"variant_id": "variant_01G1G5V2MRX2V3PVSR2WXYPFB6"}}
|
||||
curl -X POST http://localhost:9000/store/customers/cus_01G2SG30J8C85S4A5CHM2S1NS2/wishlist \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"variant_id": "variant_01G1G5V2MRX2V3PVSR2WXYPFB6"
|
||||
}'
|
||||
```
|
||||
|
||||
It accepts the following body parameters:
|
||||
|
||||
- `variant_id`: a string indicating the ID of the product variant to add to the wishlist.
|
||||
- `quantity`: (optional) a number indicating the quantity of the product variant.
|
||||
- `metadata`: (optional) any metadata to attach to the wishlist item.
|
||||
|
||||
The request returns the full customer object. The wishlist is available in the `customer.metadata.wishlist` property, where its value is an array of items.
|
||||
|
||||
### Delete Item from Wishlist API Route
|
||||
|
||||
The `DELETE` API Route at `/store/customers/{customer_id}/wishlist` allows customers to delete items from their wishlist:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/customers/{customer_id}/wishlist" testApiMethod="DELETE" testPathParams={{"customer_id": "cus_01G2SG30J8C85S4A5CHM2S1NS2"}} testBodyParams={{"index": 1}}
|
||||
curl -X DELETE http://localhost:9000/store/customers/cus_01G2SG30J8C85S4A5CHM2S1NS2/wishlist \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"index": 1
|
||||
}'
|
||||
```
|
||||
|
||||
The API Route accepts one request body parameter `index`, which indicates the index of the item in the `customer.metadata.wishlist` array.
|
||||
|
||||
The request returns the full customer object. The wishlist is available in the `customer.metadata.wishlist` property, where its value is an array of items.
|
||||
|
||||
#### Generate Share Token API Route
|
||||
|
||||
The `POST` API Route at `/store/customers/{customer_id}/wishlist/share-token` allows customers to retrieve a token that can be used to share the wishlist:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/customers/{customer_id}/wishlist/share-token" testApiMethod="POST" testPathParams={{"customer_id": "cus_01G2SG30J8C85S4A5CHM2S1NS2"}}
|
||||
curl -X POST http://localhost:9000/store/customers/cus_01G2SG30J8C85S4A5CHM2S1NS2/wishlist/share-token
|
||||
```
|
||||
|
||||
The request returns an object in the response having the property `share_token`, being the token that can be used to access the wishlist.
|
||||
|
||||
#### Access Wishlist with Token API Route
|
||||
|
||||
The `GET` API Route at `/wishlists/{token}` allows anyone to access the wishlist using its token, where `{token}` is the token retrieved from the [Generate Share Token API Route](#generate-share-token-api-token):
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/wishlists/{token}" testApiMethod="GET" testPathParams={{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}}
|
||||
curl http://localhost:9000/wishlists/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
||||
```
|
||||
|
||||
The request returns an object in the response having the following properties:
|
||||
|
||||
- `items`: an array of objects, each being an item in the wishlist.
|
||||
- `first_name`: a string indicating the first name of the customer that this wishlist belongs to.
|
||||
@@ -1,11 +0,0 @@
|
||||
import { ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Plugins`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This section includes documentation for official Medusa plugins. You can find community plugins in the [Plugins Library](https://medusajs.com/plugins/)
|
||||
|
||||
<ChildDocs />
|
||||
@@ -1,271 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Klarna Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Klarna](https://www.klarna.com/) is a payment provider that allows customers to pay in different ways including direct payment, installment payments, payment after delivery, and more.
|
||||
|
||||
---
|
||||
|
||||
## Install the Klarna Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Klarna business account](https://slack.com)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Klarna plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-payment-klarna
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "backend_url", "The Klarna URL."],
|
||||
["7", "url", "The base Klarna URL based on your environment."],
|
||||
["8", "user", "The Klarna Merchant ID (MID)."],
|
||||
["9", "password", "The string associated with the Klarna Merchant ID (MID) used for authorization."],
|
||||
["10", "merchant_urls", "The merchant URLs to use for orders."],
|
||||
["15", "payment_collection_urls", "The merchant URLs to use for payment collections."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-payment-klarna`,
|
||||
options: {
|
||||
backend_url: process.env.KLARNA_BACKEND_URL,
|
||||
url: process.env.KLARNA_URL,
|
||||
user: process.env.KLARNA_USER,
|
||||
password: process.env.KLARNA_PASSWORD,
|
||||
merchant_urls: {
|
||||
terms: process.env.KLARNA_MERCHANT_TERMS_URL,
|
||||
checkout: process.env.KLARNA_MERCHANT_CHECKOUT_URL,
|
||||
confirmation: process.env.KLARNA_MERCHANT_CONFIRMATION_URL,
|
||||
},
|
||||
payment_collection_urls: {
|
||||
terms: process.env.KLARNA_PAYCOL_TERMS_URL,
|
||||
checkout: process.env.KLARNA_PAYCOL_CHECKOUT_URL,
|
||||
confirmation: process.env.KLARNA_PAYCOL_CONFIRMATION_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Klarna Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`backend_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Klarna URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [base Klarna URL based on your environment](https://docs.klarna.com/api/api-urls/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`user`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The [Klarna Merchant ID (MID)](https://www.klarna.com/us/business/merchant-support/what-is-a-merchant-id/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`password`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The string associated with the Klarna Merchant ID (MID) used for [API authorization](https://docs.klarna.com/api/authentication/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`merchant_urls`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of merchant URLs passed to [Klarna's APIs](https://docs.klarna.com/api/payments/#operation/createCreditSession) for orders. It accepts the following keys:
|
||||
|
||||
- `terms`: The terms URL.
|
||||
- `checkout`: The checkout URL.
|
||||
- `confirmation`: The confirmation URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`payment_collection_urls`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of merchant URLs passed to [Klarna's APIs](https://docs.klarna.com/api/payments/#operation/createCreditSession) for payment collections. It accepts the following keys:
|
||||
|
||||
- `terms`: The terms URL.
|
||||
- `checkout`: The checkout URL.
|
||||
- `confirmation`: The confirmation URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`language`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating [Klarna's locale](https://docs.klarna.com/klarna-payments/in-depth-knowledge/puchase-countries-currencies-locales/#data-mapping).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`en-US`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
KLARNA_BACKEND_URL=<YOUR_KLARNA_BACKEND_URL>
|
||||
KLARNA_URL=<YOUR_KLARNA_URL>
|
||||
KLARNA_USER=<YOUR_KLARNA_USER>
|
||||
KLARNA_PASSWORD=<YOUR_KLARNA_PASSWORD>
|
||||
KLARNA_MERCHANT_TERMS_URL=<YOUR_KLARNA_MERCHANT_TERMS_URL>
|
||||
KLARNA_MERCHANT_CHECKOUT_URL=<YOUR_KLARNA_MERCHANT_CHECKOUT_URL>
|
||||
KLARNA_MERCHANT_CONFIRMATION_URL=<YOUR_KLARNA_MERCHANT_CONFIRMATION_URL>
|
||||
KLARNA_PAYCOL_TERMS_URL=<YOUR_KLARNA_PAYCOL_TERMS_URL>
|
||||
KLARNA_PAYCOL_CHECKOUT_URL=<YOUR_KLARNA_PAYCOL_CHECKOUT_URL>
|
||||
KLARNA_PAYCOL_CONFIRMATION_URL=<YOUR_KLARNA_PAYCOL_CONFIRMATION_URL>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, you must enable the Klarna Payment Provider in at least one region to use it. You can do that using either the [Medusa Admin](!user-guide!/settings/regions/providers), or the [Admin API Routes](https://docs.medusajs.com/api/admin#regions_postregionsregionpaymentproviders).
|
||||
|
||||
Finally, try to place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store). You can use Klarna during checkout and to process the order's payment.
|
||||
@@ -1,354 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `PayPal Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[PayPal](https://www.paypal.com) is a payment processor used by millions around the world. It allows customers to purchase orders from your website using their PayPal account rather than the need to enter their card details.
|
||||
|
||||
As a developer, you can use PayPal’s SDKs and APIs to integrate PayPal as a payment method into your ecommerce store. You can test out the payment method in sandbox mode before going live with it as a payment method.
|
||||
|
||||
---
|
||||
|
||||
## Install the PayPal Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [PayPal account](https://www.paypal.com).
|
||||
- [PayPal developer account](https://developer.paypal.com).
|
||||
- [PayPal client ID and secret](https://developer.paypal.com/api/rest/).
|
||||
- For deployed Medusa applications, a [PayPal webhook ID](https://developer.paypal.com/api/rest/webhooks/rest/). When creating the Webhook, set the value to `{medusa_url}/paypal/hooks`, where `{medusa_url}` with the URL to your deployed Medusa application.
|
||||
|
||||
</Note>
|
||||
|
||||
To install the PayPal plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-payment-paypal
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "clientId", "The PayPal client ID."],
|
||||
["7", "clientSecret", "The PayPal client secret."],
|
||||
["8", "sandbox", "Whether to use sandbox mode."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-payment-paypal`,
|
||||
options: {
|
||||
clientId: process.env.PAYPAL_CLIENT_ID,
|
||||
clientSecret: process.env.PAYPAL_CLIENT_SECRET,
|
||||
sandbox: process.env.PAYPAL_SANDBOX,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Klarna Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`clientId`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [PayPal client ID](https://developer.paypal.com/api/rest/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`clientSecret`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [PayPal client secret](https://developer.paypal.com/api/rest/).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`sandbox`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean indicating whether to use sandbox mode. Enabling this is useful for testing.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`authWebhookId`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the PayPal webhook ID. This is only useful for deployed Medusa applications.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`capture`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean indicating whether to automatically capture payments when an order is placed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`. Payments are authorized when an order is placed and the admin user captures the payment manually.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
PAYPAL_SANDBOX=true
|
||||
PAYPAL_CLIENT_ID=<CLIENT_ID>
|
||||
PAYPAL_CLIENT_SECRET=<CLIENT_SECRET>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the PayPal Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, you must enable the PayPal Payment Provider in at least one region to use it. You can do that using either the [Medusa Admin](!user-guide!/settings/regions/providers), or the [Admin API Routes](https://docs.medusajs.com/api/admin#regions_postregionsregionpaymentproviders).
|
||||
|
||||
Finally, try to place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store). You can use PayPal during checkout and to process the order's payment.
|
||||
|
||||
---
|
||||
|
||||
## Storefront Setup
|
||||
|
||||
This section provides an example of how to add PayPal as a payment method in custom storefronts. For the Next.js storefront, refer to [this guide](../../../nextjs-starter/page.mdx#paypal-integration)
|
||||
|
||||
### Integration Steps Overview
|
||||
|
||||
1. Show PayPal’s button if the PayPal processor is available for the current cart.
|
||||
2. When the button is clicked, open PayPal’s payment portal and wait for the customer to authorize the payment.
|
||||
3. If the payment is authorized successfully, set PayPal’s Payment Sessionas the session used to perform the payment for the current cart, then update the Payment Session on the backend with the data received from PayPal’s payment portal. This data is essential to the backend to verify the authorization and perform additional payment processing later such as capturing payment.
|
||||
4. Complete the cart to create the order.
|
||||
|
||||
### Add to Custom Storefront
|
||||
|
||||
<Note>
|
||||
|
||||
This example assumes your storefront uses React. If not, the steps generally clarify how to implement it in your storefront.
|
||||
|
||||
</Note>
|
||||
|
||||
In your storefront, install the [PayPal React components library](https://www.npmjs.com/package/@paypal/react-paypal-js) and the [Medusa JS Client library](https://www.npmjs.com/package/@medusajs/medusa-js):
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @paypal/react-paypal-js @medusajs/medusa-js
|
||||
```
|
||||
|
||||
Then, add the Client ID as an environment variable based on the framework you’re using.
|
||||
|
||||
Next, create the file that holds the PayPal component with the following content:
|
||||
|
||||
export const storefrontHighlights = [
|
||||
["10", "", "Initialize the Medusa JS Client."],
|
||||
["16", "", "Retrieve the cart."],
|
||||
["18", "handlePayment", "Initialize the payment authorization using `actions.order.authorize()` and takes the customer to authorize the payment with PayPal in another page."],
|
||||
["28", "setPaymentSession", "Select the PayPal provider's payment session for the cart."],
|
||||
["40", "updatePaymentSession", "Update the payment session's data with the authorization data from PayPal."],
|
||||
["52", "complete", "Complete the cart and place the order."],
|
||||
["71", `"<CLIENT_ID>"`, "The PayPal client ID."],
|
||||
["81", "PayPalButtons", "Render a PayPal button that, when clicked, initializes the payment using PayPal."]
|
||||
]
|
||||
|
||||
```tsx highlights={storefrontHighlights}
|
||||
import {
|
||||
PayPalButtons,
|
||||
PayPalScriptProcessor,
|
||||
} from "@paypal/react-paypal-js"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
|
||||
function Paypal() {
|
||||
const client = new Medusa({
|
||||
baseUrl: "http://localhost:9000",
|
||||
maxRetries: 3,
|
||||
})
|
||||
const [errorMessage, setErrorMessage] = useState(undefined)
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const cart = "..." // TODO retrieve the cart here
|
||||
|
||||
const handlePayment = (data, actions) => {
|
||||
actions.order.authorize().then(async (authorization) => {
|
||||
if (authorization.status !== "COMPLETED") {
|
||||
setErrorMessage(
|
||||
`An error occurred, status: ${authorization.status}`
|
||||
)
|
||||
setProcessing(false)
|
||||
return
|
||||
}
|
||||
|
||||
const response = await client.carts.setPaymentSession(
|
||||
cart.id,
|
||||
{
|
||||
provider_id: "paypal",
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.cart) {
|
||||
setProcessing(false)
|
||||
return
|
||||
}
|
||||
|
||||
await client.carts.updatePaymentSession(
|
||||
cart.id,
|
||||
"paypal",
|
||||
{
|
||||
data: {
|
||||
data: {
|
||||
...authorization,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const { data, type } = await client.carts.complete(
|
||||
cart.id
|
||||
)
|
||||
|
||||
if (!data || type !== "order") {
|
||||
setProcessing(false)
|
||||
return
|
||||
}
|
||||
|
||||
// order successful
|
||||
alert("success")
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "10px", marginLeft: "10px" }}>
|
||||
{cart !== undefined && (
|
||||
<PayPalScriptProcessor
|
||||
options={{
|
||||
"client-id": "<CLIENT_ID>",
|
||||
currency: "EUR",
|
||||
intent: "authorize",
|
||||
}}
|
||||
>
|
||||
{errorMessage && (
|
||||
<span className="text-rose-500 mt-4">
|
||||
{errorMessage}
|
||||
</span>
|
||||
)}
|
||||
<PayPalButtons
|
||||
style={{ layout: "horizontal" }}
|
||||
onApprove={handlePayment}
|
||||
disabled={processing}
|
||||
/>
|
||||
</PayPalScriptProcessor>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Paypal
|
||||
```
|
||||
|
||||
A brief overview of what this component does:
|
||||
|
||||
1. You initialize the Medusa JS Client.
|
||||
2. You retrieve the cart. Ideally, the cart should be managed through a context. So, every time the cart has been updated the cart should be updated in the context to be accessed from all components.
|
||||
3. You render a PayPal button that, when clicked, initializes the payment using PayPal. You use the components from the PayPal React components library to render the button and you pass the `PayPalScriptProcessor` component the Client ID. Make sure to replace `<CLIENT_ID>` with the environment variable you added.
|
||||
4. When the button is clicked, the `handlePayment` function is executed. In this method, you initialize the payment authorization using `actions.order.authorize()`. It takes the customer to another page to log in with PayPal and authorize the payment.
|
||||
5. After the payment is authorized successfully on PayPal’s portal, the fulfillment function passed to `actions.order.authorize().then` is executed.
|
||||
6. In the fulfillment function, you select the PayPal provider's [payment session in the cart](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession). Then, you [update the payment session](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessionupdate)'s data in the Medusa application with the authorization data received from PayPal.
|
||||
7. You [complete the cart and place the order](https://docs.medusajs.com/api/store#carts_postcartscartcomplete). If successful, you just show a success alert.
|
||||
|
||||
You can then import this component where you want to show it in your storefront.
|
||||
|
||||
If you run the Medusa application and the storefront, you can use the PayPal button during checkout.
|
||||
@@ -1,447 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Stripe Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Stripe](https://stripe.com/) is a battle-tested and unified platform for transaction handling. Stripe supplies you with the technical components needed to handle transactions safely and all the analytical features necessary to gain insight into your sales. These features are also available in a safe test environment which allows for a concern-free development process.
|
||||
|
||||
---
|
||||
|
||||
## Install the PayPal Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Stripe account](https://stripe.com).
|
||||
- [Stripe API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard)
|
||||
- For deployed Medusa applications, a [Stripe webhook secret](https://docs.stripe.com/webhooks#add-a-webhook-endpoint). When creating the Webhook, set the endpoint URL to `{medusa_url}/stripe/hooks`, where `{medusa_url}` with the URL to your deployed Medusa application.
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Stripe plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-payment-stripe
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "api_key", "The Stripe API key."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-payment-stripe`,
|
||||
options: {
|
||||
api_key: process.env.STRIPE_API_KEY,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Stripe Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`api_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Stripe API key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`webhook_secret`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Stripe webhook secret](https://docs.stripe.com/webhooks#add-a-webhook-endpoint). This is only useful for deployed Medusa applications.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`capture`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean indicating whether to automatically capture payments when an order is placed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`. Payments are authorized when an order is placed and the admin user captures the payment manually.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`automatic_payment_methods`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A boolean value indicating whether to enable Stripe's automatic payment methods. This is useful if you're integrating services like Apple pay or Google pay.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`payment_description`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string used as the default description of a payment if none is available in `cart.context.payment_description`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`webhook_delay`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the delay in milliseconds before processing the webhook event.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`5000` (five seconds)
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`webhook_retries`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number of times to retry the webhook event processing in case of an error.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`3`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
STRIPE_API_KEY=<YOUR_STRIPE_API_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Stripe Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, you must enable the Stripe Payment Provider in at least one region to use it. You can do that using either the [Medusa Admin](!user-guide!/settings/regions/providers), or the [Admin API Routes](https://docs.medusajs.com/api/admin#regions_postregionsregionpaymentproviders).
|
||||
|
||||
Finally, try to place an order using either a [storefront](../../../nextjs-starter/page.mdx) or the [Store API Routes](https://docs.medusajs.com/api/store). You can use Stripe during checkout and to process the order's payment.
|
||||
|
||||
---
|
||||
|
||||
## Webhook Events
|
||||
|
||||
This plugin handles the following Stripe webhook events:
|
||||
|
||||
- `payment_intent.succeeded`: If the payment is associated with a payment collection, the plugin captures the payments within the webhook listener of this event. Otherwise, it checks first if an order is created and, if not, completes the cart which creates the order. It also captures the payment of the order associated with the cart if it's not captured already.
|
||||
- `payment_intent.amount_capturable_updated`: If no order is created for the cart associated with the payment, the webhook listener completes the cart and creates the order.
|
||||
- `payment_intent.payment_failed`: the webhook listener prints the error message received from Stripe into the logs.
|
||||
|
||||
---
|
||||
|
||||
## Storefront Setup
|
||||
|
||||
This section provides an example of how to add Stripe as a payment method in custom storefronts. For the Next.js storefront, refer to [this guide](../../../nextjs-starter/page.mdx#stripe-integration)
|
||||
|
||||
### Integration Steps Overview
|
||||
|
||||
1. When the user reaches the payment section during checkout, [create payment sessions](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsessions).
|
||||
2. If the user chooses Stripe, select the Stripe provider's [the payment session](https://docs.medusajs.com/api/store#carts_postcartscartpaymentsession) in the cart.
|
||||
3. After the user enters their card details and submits the form, confirm the payment with Stripe.
|
||||
4. If successful, [complete the cart](https://docs.medusajs.com/api/store#carts_postcartscartcomplete) in Medusa.
|
||||
|
||||
### Add to Custom Storefront
|
||||
|
||||
<Note>
|
||||
|
||||
This example assumes your storefront uses React. If not, the steps generally clarify how to implement it in your storefront.
|
||||
|
||||
</Note>
|
||||
|
||||
In your storefront, install [Stripe's React and JavaScript libraries](https://docs.stripe.com/stripe-js/react) and the [Medusa JS Client library](https://www.npmjs.com/package/@medusajs/medusa-js):
|
||||
|
||||
```bash npm2yarn
|
||||
npm install --save @stripe/react-stripe-js @stripe/stripe-js @medusajs/medusa-js
|
||||
```
|
||||
|
||||
Then, add [Stripe's publishable key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard) as an environment variable based on the framework you’re using.
|
||||
|
||||
After that, create a container component that holds the payment card component:
|
||||
|
||||
export const containerHighlights = [
|
||||
["6", `"<STRIPE_PUB_KEY>"`, "Stripe's publishable key."]
|
||||
]
|
||||
|
||||
```tsx
|
||||
import { useState } from "react"
|
||||
import { Elements } from "@stripe/react-stripe-js"
|
||||
import Form from "./Form"
|
||||
import { loadStripe } from "@stripe/stripe-js"
|
||||
|
||||
const stripePromise = loadStripe("<STRIPE_PUB_KEY>")
|
||||
|
||||
export default function Container() {
|
||||
const [clientSecret, setClientSecret] = useState()
|
||||
|
||||
// TODO set clientSecret
|
||||
|
||||
return (
|
||||
<div>
|
||||
{clientSecret && (
|
||||
<Elements
|
||||
stripe={stripePromise}
|
||||
options={{
|
||||
clientSecret,
|
||||
}}
|
||||
>
|
||||
<Form clientSecret={clientSecret} cartId={cartId} />
|
||||
</Elements>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In this component, you use Stripe’s `loadStripe` function outside of the component’s implementation to ensure that Stripe doesn’t re-load with every change. The function accepts Stripe's publishable key.
|
||||
|
||||
Then, inside the component’s implementation, you add a state variable `clientSecret` which you’ll retrieve in the next section.
|
||||
|
||||
The `Elements` Stripe component wraps a `Form` component that you’ll create next. The `Elements` component allows child elements to get access to the card’s inputs and their data using Stripe’s `useElements` hook.
|
||||
|
||||
Next, create a new file for the `Form` component with the following content:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
CardElement,
|
||||
useElements,
|
||||
useStripe,
|
||||
} from "@stripe/react-stripe-js"
|
||||
|
||||
export default function Form({ clientSecret, cartId }) {
|
||||
const stripe = useStripe()
|
||||
const elements = useElements()
|
||||
|
||||
async function handlePayment(e) {
|
||||
e.preventDefault()
|
||||
// TODO handle payment
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<CardElement />
|
||||
<button onClick={handlePayment}>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `useStripe` hook gives you access to the stripe instance to confirm the payment later. The `useElements` hook gives you access to the card element to retrieve the entered card details safely.
|
||||
|
||||
You’ll now implement the integration steps explained earlier in the `Container` component.
|
||||
|
||||
Start by initializing the Medusa client:
|
||||
|
||||
```tsx
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
|
||||
export default function Container() {
|
||||
const client = new Medusa({
|
||||
baseUrl: "http://localhost:9000",
|
||||
maxRetries: 3,
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Then, in the place of the `//TODO`, initialize the payment sessions and create a payment session if Stripe is available:
|
||||
|
||||
```tsx
|
||||
client.carts.createPaymentSessions(cart.id).then(({ cart }) => {
|
||||
// check if stripe is selected
|
||||
const isStripeAvailable = cart.payment_sessions?.some(
|
||||
(session) => session.provider_id === "stripe"
|
||||
)
|
||||
if (!isStripeAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
// select stripe payment session
|
||||
client.carts
|
||||
.setPaymentSession(cart.id, {
|
||||
provider_id: "stripe",
|
||||
})
|
||||
.then(({ cart }) => {
|
||||
setClientSecret(cart.payment_session.data.client_secret)
|
||||
})
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
It’s assumed you have access to the `cart` object throughout your storefront. Ideally, the `cart` should be managed through a context. In that case, you probably wouldn’t need a `clientSecret` state variable as you can use the client secret directly from the `cart` object.
|
||||
|
||||
</Note>
|
||||
|
||||
Once the client secret is set, the form is shown to the user.
|
||||
|
||||
The last step in the integration step is confirming the payment with Stripe and if it’s done successfully, completing the customer's order.
|
||||
|
||||
In the `Form` component, initialize the Medusa client or re-use the same client in the `Container` element
|
||||
|
||||
```tsx
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
|
||||
export default function Form() {
|
||||
const client = new Medusa({
|
||||
baseUrl: "http://localhost:9000",
|
||||
maxRetries: 3,
|
||||
})
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Then, replace the `//TODO` in the `handlePayment` function with the following content:
|
||||
|
||||
```jsx
|
||||
return stripe.confirmCardPayment(clientSecret, {
|
||||
payment_method: {
|
||||
card: elements.getElement(CardElement),
|
||||
billing_details: {
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
address: {
|
||||
city,
|
||||
country,
|
||||
line1,
|
||||
line2,
|
||||
postal_code,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).then(({ error, paymentIntent }) => {
|
||||
// TODO handle errors
|
||||
client.carts.complete(cartId).then(
|
||||
(resp) => console.log(resp)
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
You use the `confirmCardPayment` method in the `stripe` object passing it the client secret, which you can access in the cart object if it’s available through a context.
|
||||
|
||||
This method also requires as a second parameter an object of the customer’s information including `name`, `email`, and their address.
|
||||
|
||||
Once the promise resolves you handle any errors that could've occurred. If no errors occurred, you complete the customer’s order.
|
||||
|
||||
If you run the Medusa application and the storefront, you can use Stripe during checkout.
|
||||
@@ -1,232 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Algolia Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Algolia](https://www.algolia.com/) is a search engine service that allows developers to integrate advanced search functionalities into their websites including typo tolerance, recommended results, and quick responses.
|
||||
|
||||
Algolia is used for a wide range of use cases, including commerce stores. By integrating Algolia into your commerce application, you provide your customers with a better user experience and help them find what they’re looking for swifltly.
|
||||
|
||||
---
|
||||
|
||||
## Install the Algolia Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Algolia account](https://www.algolia.com/users/sign_up)
|
||||
- [Algolia app ID](https://support.algolia.com/hc/en-us/articles/11040113398673-Where-can-I-find-my-application-ID-and-the-index-name)
|
||||
- [Algolia API key](https://support.algolia.com/hc/en-us/articles/11972559809681-How-do-I-find-my-Admin-API-key)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Algolia plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-algolia
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "applicationId", "The Algolia app ID."],
|
||||
["7", "adminApiKey", "The Algolia API key."],
|
||||
["8", "settings", "Settings of indices created in Algolia."],
|
||||
["9", "products", "The name of an index to create. In this example, it's `products`."],
|
||||
["10", "indexSettings", "The settings of the index."],
|
||||
["11", "searchableAttributes", "The attributes that can be searched in the index."],
|
||||
["12", "attributesToRetrieve", "The attributes to retrieve in the search results."],
|
||||
["26", "transformer", "A function that shapes the object to be indexed."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-algolia`,
|
||||
options: {
|
||||
applicationId: process.env.ALGOLIA_APP_ID,
|
||||
adminApiKey: process.env.ALGOLIA_ADMIN_API_KEY,
|
||||
settings: {
|
||||
products: {
|
||||
indexSettings: {
|
||||
searchableAttributes: ["title", "description"],
|
||||
attributesToRetrieve: [
|
||||
"id",
|
||||
"title",
|
||||
"description",
|
||||
"handle",
|
||||
"thumbnail",
|
||||
"variants",
|
||||
"variant_sku",
|
||||
"options",
|
||||
"collection_title",
|
||||
"collection_handle",
|
||||
"images",
|
||||
],
|
||||
},
|
||||
transformer: (product) => ({
|
||||
objectID: product.id,
|
||||
// other attributes...
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Algolia Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`applicationId`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Algolia app ID.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`adminApiKey`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Algolia API key.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of settings. Its keys are names of indices to create in Algolia (in the example above, `products`), and values are an object of the index's settings.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings.[indexName].indexSettings`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of index settings. It accepts two properties:
|
||||
|
||||
- `searchableAttributes`: An array of field names that can be searched.
|
||||
- `attributesToRetrieve`: An array of field names retrieved in search results.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
If `settings` is provided, this property is required.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings.[indexName].transformer`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A function used to change the shape of the indexed records. For example, you can add details related to variants or custom relations, or filter out certain products.
|
||||
|
||||
The function accepts as a parameter that data model object to index, such as a product, and returns an object to be indexed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
ALGOLIA_APP_ID=<YOUR_APP_ID>
|
||||
ALGOLIA_ADMIN_API_KEY=<YOUR_ADMIN_API_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `POST` request to the `/store/products/search`:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/products/search" testApiMethod="POST" testBodyParams={{"q": "shirt"}}
|
||||
curl -X POST http://localhost:9000/store/products/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"q": "shirt"
|
||||
}'
|
||||
```
|
||||
|
||||
The response contains a `hits` array with the results from the Algolia search engine.
|
||||
|
||||
### Add or Update Products
|
||||
|
||||
If you add or update products in your Medusa application, it'll be reflected in the Algolia indices.
|
||||
|
||||
---
|
||||
|
||||
## Add Search to your Storefront
|
||||
|
||||
### Next.js Starter
|
||||
|
||||
Refer to the [Next.js Starter guide](../../../nextjs-starter/page.mdx#configure-algolia) to learn how to configure Algolia.
|
||||
|
||||
### Custom Storefront
|
||||
|
||||
To integrate Algolia's search functionalities in your custom storefront, refer to [Algolia's InstantSearch.js documentation](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/).
|
||||
@@ -1,248 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `MeiliSearch Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[MeiliSearch](https://www.meilisearch.com/) is a super-fast, open source search engine built in Rust. It comes with a wide range of features including typo-tolerance, filtering, and sorting.
|
||||
|
||||
MeiliSearch also provides a pleasant developer experience, as it is extremely intuitive and newcomer-friendly. So, even if you're new to the search engine ecosystem, [their documentation](https://docs.meilisearch.com/) is resourceful enough for everyone to go through and understand.
|
||||
|
||||
---
|
||||
|
||||
## Install the MeiliSearch Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [MeiliSearch installed](https://docs.meilisearch.com/learn/getting_started/quick_start.html#setup-and-installation)
|
||||
- [MeiliSearch master key](https://www.meilisearch.com/docs/learn/security/master_api_keys#protecting-a-meilisearch-instance)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Algolia plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-plugin-meilisearch
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "config", "The MeiliSearch connection configuration object."],
|
||||
["7", "host", "The MeiliSearch host."],
|
||||
["8", "apiKey", "The MeiliSearch master key."],
|
||||
["10", "settings", "Settings of indices created in MeiliSearch."],
|
||||
["11", "products", "The name of an index to create. In this example, it's `products`."],
|
||||
["12", "indexSettings", "The settings of the index."],
|
||||
["13", "searchableAttributes", "The attributes that can be searched in the index."],
|
||||
["18", "displayedAttributes", "The attributes to retrieve in the search results."],
|
||||
["27", "transformer", "A function that shapes the object to be indexed."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-plugin-meilisearch`,
|
||||
options: {
|
||||
config: {
|
||||
host: process.env.MEILISEARCH_HOST,
|
||||
apiKey: process.env.MEILISEARCH_API_KEY,
|
||||
},
|
||||
settings: {
|
||||
products: {
|
||||
indexSettings: {
|
||||
searchableAttributes: [
|
||||
"title",
|
||||
"description",
|
||||
"variant_sku",
|
||||
],
|
||||
displayedAttributes: [
|
||||
"id",
|
||||
"title",
|
||||
"description",
|
||||
"variant_sku",
|
||||
"thumbnail",
|
||||
"handle",
|
||||
],
|
||||
},
|
||||
transformer: (product) => ({
|
||||
id: product.id,
|
||||
// other attributes...
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### MeiliSearch Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`config`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of MeiliSearch connection configurations. It accepts two properties:
|
||||
|
||||
- `host`: A string indicating the MeiliSearch host. For example, `http://127.0.0.1:7700`.
|
||||
- `apiKey`: A string indicating the [MeiliSearch master key](https://www.meilisearch.com/docs/learn/security/master_api_keys#protecting-a-meilisearch-instance).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of settings. Its keys are names of indices to create in MeiliSearch (in the example above, `products`), and values are an object of the index's settings.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings.[indexName].indexSettings`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of index settings. It accepts two properties:
|
||||
|
||||
- `searchableAttributes`: An array of field names that can be searched.
|
||||
- `displayedAttributes`: An array of field names retrieved in search results.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
If `settings` is provided, this property is required.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings.[indexName].transformer`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A function used to change the shape of the indexed records. For example, you can add details related to variants or custom relations, or filter out certain products.
|
||||
|
||||
The function accepts as a parameter that data model object to index, such as a product, and returns an object to be indexed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`settings.[indexName].primaryKey`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating which field in the data model acts as a primary key of a document. It's used to enforce unique documents in an index. Learn more in [MeiliSearch's documentation](https://docs.meilisearch.com/learn/core_concepts/primary_key.html#primary-field).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`id`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
MEILISEARCH_HOST=<YOUR_MEILISEARCH_HOST>
|
||||
MEILISEARCH_API_KEY=<YOUR_MASTER_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [MeiliSearch running in the background](https://www.meilisearch.com/docs/learn/getting_started/quick_start#running-meilisearch).
|
||||
|
||||
</Note>
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, send a `POST` request to the `/store/products/search`:
|
||||
|
||||
```bash apiTesting testApiUrl="http://localhost:9000/store/products/search" testApiMethod="POST" testBodyParams={{"q": "shirt"}}
|
||||
curl -X POST http://localhost:9000/store/products/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"q": "shirt"
|
||||
}'
|
||||
```
|
||||
|
||||
The response contains a `hits` array with the results from the MeiliSearch search engine.
|
||||
|
||||
### Add or Update Products
|
||||
|
||||
If you add or update products in your Medusa application, it'll be reflected in the MeiliSearch indices.
|
||||
|
||||
---
|
||||
|
||||
## Add Search to your Storefront
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [MeiliSearch API key](https://www.meilisearch.com/docs/learn/security/master_api_keys#creating-an-api-key).
|
||||
|
||||
</Note>
|
||||
|
||||
### Next.js Starter
|
||||
|
||||
Refer to the [Next.js Starter guide](../../../nextjs-starter/page.mdx#configure-meilisearch) to learn how to configure MeiliSearch.
|
||||
|
||||
### Custom Storefront
|
||||
|
||||
To integrate MeiliSearch's search functionalities in your custom storefront, refer to [MeiliSearch's documentation](https://docs.meilisearch.com/learn/what_is_meilisearch/sdks.html#front-end-tools).
|
||||
@@ -1,153 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Shopify Source Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
If you're migrating from Shopify to Medusa, this plugin facilitates the process. It migrates data related to your products on Shopify to Medusa.
|
||||
|
||||
It also registers a scheduled job that runs periodically and ensures your data is synced between Shopify and Medusa.
|
||||
|
||||
---
|
||||
|
||||
## Install the Shopify Source Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Shopify account](https://accounts.shopify.com/lookup?rid=8ea3d7a5-0bd6-4645-8e1c-ebf19bd750c7)
|
||||
- [Shopify custom app](https://help.shopify.com/en/manual/apps/app-types/custom-apps) with `read_products` admin API access scope.
|
||||
- [Shopify API secret key](https://help.shopify.com/en/manual/apps/app-types/custom-apps#get-the-api-credentials-for-a-custom-app)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Shopify Source plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-source-shopify
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "domain", "The Shopify store's subdomain."],
|
||||
["7", "password", "The Shopify API secret key."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...,
|
||||
{
|
||||
resolve: "medusa-source-shopify",
|
||||
options: {
|
||||
domain: process.env.SHOPIFY_DOMAIN,
|
||||
password: process.env.SHOPIFY_PASSWORD,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Shopify Source Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`domain`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Shopify store's subdomain. Your store's domain is of the format `<DOMAIN>.myshopify.com`. The `<DOMAIN>` is the value of this option.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`password`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Shopify API secret key](https://help.shopify.com/en/manual/apps/app-types/custom-apps#get-the-api-credentials-for-a-custom-app).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ignore_threshold`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The products retrieved from Shopify are added to the cache. This option sets the number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`2`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
SHOPIFY_DOMAIN=<YOUR_SHOPIFY_DOMAIN>
|
||||
SHOPIFY_PASSWORD=<YOUR_SHOPIFY_PASSWORD>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This runs the migration script. The products are migrated from Shopify into Medusa.
|
||||
@@ -1,127 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Local File Storage Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
The Local File Storage plugin allows you to upload media assets, such as product images, to a local directory. This is useful during development.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
For production, it's recommended to use a storage plugin that hosts your images on a third-party service. This storage plugin doesn't handle advanced features such as presigned URLs.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Install the Local File Storage Plugin
|
||||
|
||||
To install the Local File Storage plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/file-local
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `@medusajs/file-local`,
|
||||
options: {
|
||||
// optional
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Local File Storage Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`upload_dir`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the relative path to upload the files to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`uploads/images`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`backend_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL of your Medusa application. This is helpful if you deploy your application or change the port used.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`http://localhost:9000`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, upload a product image either using the [Medusa Admin](!user-guide!/products/manage#manage-product-media) or the [Admin API routes](https://docs.medusajs.com/api/admin#uploads_postuploads).
|
||||
|
||||
---
|
||||
|
||||
## Next.js Starter Configuration
|
||||
|
||||
If you’re using the [Next.js Starter storefront](../../../nextjs-starter/page.mdx), add the following option to the exported object in `next.config.js`:
|
||||
|
||||
```js title="next.config.js"
|
||||
const { withStoreConfig } = require("./store-config")
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = withStoreConfig({
|
||||
// ...
|
||||
images: {
|
||||
domains: [
|
||||
// ...
|
||||
"{medusa_domain}",
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This adds the Medusa application's domain name into the configured images domain names. If you don't add the configuration, you’ll receive the error ["next/image Un-configured Host”](https://nextjs.org/docs/messages/next-image-unconfigured-host).
|
||||
|
||||
Make sure to replace `{medusa_domain}` with the domain of your Medusa application. For example, `localhost`.
|
||||
@@ -1,296 +0,0 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `MinIO Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[MinIO](https://min.io/) is an open-source object storage server compatible with the Amazon S3 API. It allows users to store photos, videos, backups, and more.
|
||||
|
||||
With the MinIO plugin, you'll benefit from basic and advanced storage functionalities, including public and private uploads, and presigned URLs.
|
||||
|
||||
---
|
||||
|
||||
## Install the MinIO Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Install MinIO](https://min.io/docs/minio/linux/index.html).
|
||||
- Change port to `9001` using the [console address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-console-address) and [address](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#minio.server.-address) CLI options.
|
||||
- [MinIO bucket with public access policy](https://min.io/docs/minio/linux/administration/console/managing-objects.html#creating-buckets).
|
||||
- [MinIO access and secret access key](https://min.io/docs/minio/linux/administration/console/security-and-access.html#id1)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the MinIO plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-file-minio
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "bucket", "The bucket to upload files to."],
|
||||
["7", "access_key_id", "The MinIO access key."],
|
||||
["8", "secret_access_key", "The MinIO secret access key."],
|
||||
["9", "endpoint", "The URL of your MinIO server."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-file-minio`,
|
||||
options: {
|
||||
bucket: process.env.MINIO_BUCKET,
|
||||
access_key_id: process.env.MINIO_ACCESS_KEY,
|
||||
secret_access_key: process.env.MINIO_SECRET_KEY,
|
||||
endpoint: process.env.MINIO_ENDPOINT,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### MinIO Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`bucket`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the bucket to upload files to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_key_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [MinIO access key](https://min.io/docs/minio/linux/administration/console/security-and-access.html#id1).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`secret_access_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [MinIO secret access key](https://min.io/docs/minio/linux/administration/console/security-and-access.html#id1).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`endpoint`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL of your MinIO server.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`private_bucket`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the bucket to use for private media, such as the CSV file of exported products.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Required if you're using import/export features that require a private bucket.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`private_access_key_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the MinIO access key to use for private uploads.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The value of `access_key_id`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`private_secret_access_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the MinIO secret access key to use for private uploads.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The value of `secret_access_key`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`download_url_duration`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the expiry time of presigned URLs in seconds.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`60`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
MINIO_BUCKET=<BUCKET>
|
||||
MINIO_ACCESS_KEY=<ACCESS_KEY>
|
||||
MINIO_SECRET_KEY=<SECRET_KEY>
|
||||
MINIO_ENDPOINT=<ENDPOINT>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, upload a product image either using the [Medusa Admin](!user-guide!/products/manage#manage-product-media) or the [Admin API routes](https://docs.medusajs.com/api/admin#uploads_postuploads).
|
||||
|
||||
---
|
||||
|
||||
## Next.js Starter Template Configuration
|
||||
|
||||
If you’re using the [Next.js Starter storefront](../../../nextjs-starter/page.mdx), add the following option to the exported object in `next.config.js`:
|
||||
|
||||
```jsx title="next.config.js"
|
||||
const { withStoreConfig } = require("./store-config")
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = withStoreConfig({
|
||||
// ...
|
||||
images: {
|
||||
domains: [
|
||||
// ...
|
||||
"{minio_domain}",
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This adds the MinIO domain into the configured images domain names. If you don't add the configuration, you’ll receive the error ["next/image Un-configured Host”](https://nextjs.org/docs/messages/next-image-unconfigured-host).
|
||||
|
||||
Make sure to replace `{minio_domain}` with the MinIO domain. For example, `127.0.0.1`.
|
||||
@@ -1,363 +0,0 @@
|
||||
import { Table, DetailsList } from "docs-ui"
|
||||
import AclErrorSection from "../../../troubleshooting/_sections/other/s3-acl.mdx"
|
||||
|
||||
export const metadata = {
|
||||
title: `S3 Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[Amazon S3](https://aws.amazon.com/s3/) is a cloud storage service that offers scalable object storage. It allows users to store and retrieve images, videos, and other media types.
|
||||
|
||||
With the S3 plugin, you'll benefit from basic and advanced storage functionalities, including public and private uploads, and presigned URLs.
|
||||
|
||||
---
|
||||
|
||||
## Preparations
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [AWS account](https://console.aws.amazon.com/console/home?nc2=h_ct&src=header-signin).
|
||||
- [S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html) with the "Public Access setting" enabled.
|
||||
- [AWS user with AmazonS3FullAccess permissions](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-and-attach-iam-policy.html).
|
||||
- [AWS user access key ID and secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)
|
||||
|
||||
</Note>
|
||||
|
||||
### Bucket Policies
|
||||
|
||||
Change your [bucket's policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/add-bucket-policy.html) to the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Id": "Policy1397632521960",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Stmt1397633323327",
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": "*"
|
||||
},
|
||||
"Action": "s3:GetObject",
|
||||
"Resource": "arn:aws:s3:::{bucket_name}/*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to replace `{bucket_name}` with the name of the bucket you created.
|
||||
|
||||
---
|
||||
|
||||
## Install the S3 Plugin
|
||||
|
||||
To install the MinIO plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-file-s3
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "bucket", "The bucket to upload files to."],
|
||||
["7", "s3_url", "The URL to the bucket."],
|
||||
["8", "access_key_id", "The AWS user's access key ID."],
|
||||
["9", "secret_access_key", "The AWS user's secret access key."],
|
||||
["10", "region", "The bucket's region code."],
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-file-s3`,
|
||||
options: {
|
||||
bucket: process.env.S3_BUCKET,
|
||||
s3_url: process.env.S3_URL,
|
||||
access_key_id: process.env.S3_ACCESS_KEY_ID,
|
||||
secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
|
||||
region: process.env.S3_REGION,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### S3 Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`bucket`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the bucket to upload files to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`s3_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL to your bucket. It’s in the form `https://<BUCKET_NAME>.s3.<REGION>.amazonaws.com`, where `<BUCKET_NAME>` is the name of the bucket and the `<REGION>` is the region the bucket is created in.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_key_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the AWS user's access key ID.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`secret_access_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the AWS user's secret access key.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`region`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the region code of your bucket. For example, `us-east-1`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`prefix`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating a prefix to apply on stored file names. If supplied, a `/` is added at the end of the prefix.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`download_file_duration`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the expiry time of presigned URLs in seconds.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
[S3's default expiration time](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html#PresignedUrl-Expiration)
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`cache_control`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating [how long objects remain in the CloudFront's cache](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`max-age=31536000` (a year)
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`aws_config_object`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of [AWS Configurations](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html) passed to all requests.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
S3_BUCKET=<YOUR_BUCKET_NAME>
|
||||
S3_URL=<YOUR_BUCKET_URL>
|
||||
S3_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
|
||||
S3_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
|
||||
S3_REGION=<YOUR_BUCKET_REGION>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the S3 Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, upload a product image either using the [Medusa Admin](!user-guide!/products/manage#manage-product-media) or the [Admin API routes](https://docs.medusajs.com/api/admin#uploads_postuploads).
|
||||
|
||||
---
|
||||
|
||||
## Next.js Starter Template Configuration
|
||||
|
||||
If you’re using the [Next.js Starter storefront](../../../nextjs-starter/page.mdx), add the following option to the exported object in `next.config.js`:
|
||||
|
||||
```jsx title="next.config.js"
|
||||
const { withStoreConfig } = require("./store-config")
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = withStoreConfig({
|
||||
// ...
|
||||
images: {
|
||||
domains: [
|
||||
// ...
|
||||
"{s3_domain}",
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This adds the S3's domain into the configured images domain names. If you don't add the configuration, you’ll receive the error ["next/image Un-configured Host”](https://nextjs.org/docs/messages/next-image-unconfigured-host).
|
||||
|
||||
Make sure to replace `{s3_domain}` with the S3 domain which is of the format `<BUCKET_NAME>.s3.<REGION>.amazonaws.com`.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<DetailsList
|
||||
sections={[
|
||||
{
|
||||
title: 'Error: AccessControlListNotSupported: The bucket does not allow ACLs',
|
||||
content: <AclErrorSection />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -1,282 +0,0 @@
|
||||
import { Table, DetailsList } from "docs-ui"
|
||||
import AclErrorSection from "../../../troubleshooting/_sections/other/s3-acl.mdx"
|
||||
|
||||
export const metadata = {
|
||||
title: `Spaces Plugin`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
## Features
|
||||
|
||||
[DigitalOcean Spaces](https://www.digitalocean.com/products/spaces) is an object storage service provided by DigitalOcean. Spaces is designed to make it easy and cost-effective to store medias such as images, videos, and more.
|
||||
|
||||
With the DigitalOcean plugin, you'll benefit from basic and advanced storage functionalities, including public and private uploads, and presigned URLs.
|
||||
|
||||
---
|
||||
|
||||
## Install the Spaces Plugin
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [DigitalOcean account](https://cloud.digitalocean.com/registrations/new)
|
||||
- [DigitalOcean Spaces bucket](https://docs.digitalocean.com/products/spaces/how-to/create/)
|
||||
- [DigitalOcean Spaces access and secret access keys](https://docs.digitalocean.com/products/spaces/how-to/manage-access/#access-keys)
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Spaces plugin, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install medusa-file-spaces
|
||||
```
|
||||
|
||||
Next, add the plugin into the `plugins` array in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["6", "bucket", "The bucket to upload files to."],
|
||||
["7", "spaces_url", "Either the Origin Endpoint or the CDN endpoint of your Spaces Object Storage bucket."],
|
||||
["8", "access_key_id", "The Spaces access key."],
|
||||
["9", "secret_access_key", "The Spaces secret access key."],
|
||||
["10", "region", "The region your Spaces Object Storage bucket is in."],
|
||||
["11", "endpoint", "The Spaces Origin Endpoint."],
|
||||
]
|
||||
|
||||
Then, add the following environment variables:
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: `medusa-file-spaces`,
|
||||
options: {
|
||||
bucket: process.env.SPACE_BUCKET,
|
||||
spaces_url: process.env.SPACE_URL,
|
||||
access_key_id: process.env.SPACE_ACCESS_KEY_ID,
|
||||
secret_access_key: process.env.SPACE_SECRET_ACCESS_KEY,
|
||||
region: process.env.SPACE_REGION,
|
||||
endpoint: process.env.SPACE_ENDPOINT,
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Spaces Plugin Options
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Option</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
<Table.HeaderCell>Default</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`bucket`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the bucket to upload files to.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`spaces_url`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating either the Origin Endpoint or the CDN endpoint of your Spaces Object Storage bucket.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`access_key_id`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Spaces access key](https://docs.digitalocean.com/products/spaces/how-to/manage-access/#access-keys).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`secret_access_key`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the [Spaces secret access key](https://docs.digitalocean.com/products/spaces/how-to/manage-access/#access-keys).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`region`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the region your Spaces Object Storage bucket is in. If you're unsure, you can find it in the Origin Endpoint whose format is `https://<bucket-name>.<region>.digitaloceanspaces.com`. For example, `nyc3`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`endpoint`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Spaces Origin Endpoint.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`download_url_duration`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the expiry time of presigned URLs in seconds.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`60`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
SPACE_URL=<YOUR_SPACE_URL>
|
||||
SPACE_BUCKET=<YOUR_SPACE_NAME>
|
||||
SPACE_REGION=<YOUR_SPACE_REGION>
|
||||
SPACE_ENDPOINT=<YOUR_SPACE_ENDPOINT>
|
||||
SPACE_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>
|
||||
SPACE_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Plugin
|
||||
|
||||
To test the plugin, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, upload a product image either using the [Medusa Admin](!user-guide!/products/manage#manage-product-media) or the [Admin API routes](https://docs.medusajs.com/api/admin#uploads_postuploads).
|
||||
|
||||
---
|
||||
|
||||
## Next.js Starter Template Configuration
|
||||
|
||||
If you’re using the [Next.js Starter storefront](../../../nextjs-starter/page.mdx), add the following option to the exported object in `next.config.js`:
|
||||
|
||||
```jsx title="next.config.js"
|
||||
const { withStoreConfig } = require("./store-config")
|
||||
|
||||
// ...
|
||||
|
||||
module.exports = withStoreConfig({
|
||||
// ...
|
||||
images: {
|
||||
domains: [
|
||||
// ...
|
||||
"{spaces_domain}",
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This adds the Spaces domain into the configured images domain names. If you don't add the configuration, you’ll receive the error ["next/image Un-configured Host”](https://nextjs.org/docs/messages/next-image-unconfigured-host).
|
||||
|
||||
Make sure to replace `{spaces_domain}` with the Spaces domain. It's of the format `<bucket-name>.<region>.digitaloceanspaces.com` or `<bucket-name>.<region>.cdn.digitaloceanspaces.com`
|
||||
@@ -12,7 +12,7 @@ There are two types of API keys:
|
||||
- `publishable`: A public key used in client applications, such as a storefront.
|
||||
- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
|
||||
|
||||
The API key’s type is stored in the `type` field of the [ApiKey data model](/references/api-key/models/ApiKey).
|
||||
The API key’s type is stored in the `type` property of the [ApiKey data model](/references/api-key/models/ApiKey).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const apiKey = await apiKeyModuleService.create({
|
||||
const apiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
@@ -50,7 +50,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
export async function POST(request: Request) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const apiKey = await apiKeyModuleService.create({
|
||||
const apiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
@@ -85,7 +85,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -104,7 +104,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -279,7 +279,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
}
|
||||
)
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
@@ -318,7 +318,7 @@ In this guide, you’ll find common examples of how you can use the API Key Modu
|
||||
revoked_by: params.user_id,
|
||||
})
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
|
||||
@@ -19,13 +19,13 @@ Store and manage API keys in your store. You can create both publishable and sec
|
||||
- Password reset tokens when a user or customer requests to reset their password.
|
||||
|
||||
```ts
|
||||
const pubApiKey = await apiKeyModuleService.create({
|
||||
const pubApiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
const secretApiKey = await apiKeyModuleService.create({
|
||||
const secretApiKey = await apiKeyModuleService.createApiKeys({
|
||||
title: "Authentication Key",
|
||||
type: "secret",
|
||||
created_by: "user_123",
|
||||
@@ -66,7 +66,7 @@ const revokedKey = await apiKeyModuleService.revoke("apk_1", {
|
||||
revoked_by: "user_123",
|
||||
})
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
const newKey = await apiKeyModuleService.createApiKeys({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
@@ -97,7 +97,7 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
api_keys: await apiKeyModuleService.listApiKeys(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -116,7 +116,7 @@ For example:
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
container.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const apiKeys = await apiKeyModuleService.list()
|
||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -136,7 +136,7 @@ For example:
|
||||
ModuleRegistrationName.API_KEY
|
||||
)
|
||||
|
||||
const apiKeys = await apiKeyModuleService.list()
|
||||
const apiKeys = await apiKeyModuleService.listApiKeys()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Then, the user is authenticated successfully, and their authentication details a
|
||||
|
||||
<Note>
|
||||
|
||||
Check out the [AuthIdentity](/references/auth/models/AuthIdentity) reference for the expected fields in `authIdentity`.
|
||||
Check out the [AuthIdentity](/references/auth/models/AuthIdentity) reference for the expected properties in `authIdentity`.
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
@@ -13,31 +13,18 @@ Before creating an actor type, you must define a data model the actor type belon
|
||||
The rest of this guide uses this `Manager` data model as an example:
|
||||
|
||||
```ts title="src/modules/manager/models/manager.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Entity()
|
||||
export class Manager extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
const Manager = model.define("manager", {
|
||||
id: model.id(),
|
||||
firstName: model.text(),
|
||||
lastName: model.text(),
|
||||
email: model.text(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
first_name: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
last_name: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
email: string
|
||||
}
|
||||
export default Manager
|
||||
```
|
||||
|
||||
The module’s main service must also have a `create` method to create a record of the `Manager` data model.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create Workflow
|
||||
@@ -45,7 +32,7 @@ The module’s main service must also have a `create` method to create a record
|
||||
Start by creating a workflow that does two things:
|
||||
|
||||
- Create a record of the `Manager` data model.
|
||||
- Sets the `app_metadata` field 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:
|
||||
|
||||
@@ -92,7 +79,7 @@ const createManagerStep = createStep(
|
||||
const managerModuleService: ManagerModuleService =
|
||||
container.resolve("managerModuleService")
|
||||
|
||||
const manager = await managerModuleService.create(
|
||||
const manager = await managerModuleService.createManager(
|
||||
managerData
|
||||
)
|
||||
|
||||
@@ -127,7 +114,7 @@ This workflow accepts the manager’s data and the associated auth identity’s
|
||||
The workflow has two steps:
|
||||
|
||||
1. Create the manager using the `createManagerStep`.
|
||||
2. Set the `app_metadata` field of the associated auth identity using the `setAuthAppMetadataStep` step imported from `@medusajs/core-flows`. You specify the actor type `manager` in the `actorType` property of the step’s input.
|
||||
2. Set the `app_metadata` property of the associated auth identity using the `setAuthAppMetadataStep` step imported from `@medusajs/core-flows`. You specify the actor type `manager` in the `actorType` property of the step’s input.
|
||||
|
||||
---
|
||||
|
||||
@@ -250,13 +237,12 @@ export async function GET(
|
||||
const managerModuleService: ManagerModuleService =
|
||||
req.scope.resolve("managerModuleService")
|
||||
|
||||
const manager = await managerModuleService.retrieve(
|
||||
const manager = await managerModuleService.retrieveManager(
|
||||
req.auth_context.actor_id
|
||||
)
|
||||
|
||||
res.json({ manager })
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This route is only accessible by authenticated managers. You access the manager’s ID using `req.auth_context.actor_id`.
|
||||
|
||||
@@ -235,11 +235,12 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authIdentity = await authModuleService.create({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
const authIdentity = await authModuleService
|
||||
.createAuthIdentities({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
res.json({ auth_identity: authIdentity })
|
||||
}
|
||||
@@ -258,11 +259,12 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authIdentity = await authModuleService.create({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
const authIdentity = await authModuleService
|
||||
.createAuthIdentities({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identity: authIdentity,
|
||||
@@ -293,7 +295,8 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
res.json({
|
||||
auth_identitys: await authModuleService.list(),
|
||||
auth_identitys:
|
||||
await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -312,7 +315,8 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identities: await authModuleService.list(),
|
||||
auth_identities:
|
||||
await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -339,12 +343,13 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authIdentity = await authModuleService.update({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
const authIdentity = await authModuleService
|
||||
.updateAuthIdentites({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
auth_identity: authIdentity,
|
||||
@@ -374,15 +379,16 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authIdentity = await authModuleService.update({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
const authIdentity = await authModuleService
|
||||
.updateAuthIdentites({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_identitys: await authModuleService.list(),
|
||||
auth_identity: authIdentity,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -409,7 +415,9 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
await authModuleService.delete(["authusr_123"])
|
||||
await authModuleService.deleteAuthIdentities([
|
||||
"authusr_123",
|
||||
])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
@@ -437,7 +445,9 @@ This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/j
|
||||
) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
await authModuleService.delete(["authusr_123"])
|
||||
await authModuleService.deleteAuthIdentities([
|
||||
"authusr_123",
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
res.json({
|
||||
authIdentitys: authModuleService.list(),
|
||||
authIdentitys:
|
||||
await authModuleService.listAuthIdentities(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -120,7 +121,8 @@ For example:
|
||||
const authModuleService: IAuthModuleService =
|
||||
container.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authIdentitys = await authModuleService.list()
|
||||
const authIdentitys = await authModuleService
|
||||
.listAuthIdentities()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -141,7 +143,8 @@ For example:
|
||||
container.resolve(
|
||||
ModuleRegistrationName.AUTH
|
||||
)
|
||||
const authIdentitys = await authModuleService.list()
|
||||
const authIdentitys = await authModuleService
|
||||
.listAuthIdentities()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ A cart has a shipping and billing address. Both of these addresses are represent
|
||||
|
||||
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 stores some of the product variant’s fields, 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>
|
||||
|
||||
@@ -36,8 +36,8 @@ If the shipping method is created from a shipping option, available through the
|
||||
|
||||
A shipping method can also be a custom method associated with this cart only.
|
||||
|
||||
### data Field
|
||||
### 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` field.
|
||||
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.
|
||||
|
||||
The `data` field 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.
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the Cart Module
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
@@ -57,7 +57,7 @@ In this guide, you’ll find common examples of how you can use the Cart Module
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
@@ -100,7 +100,9 @@ In this guide, you’ll find common examples of how you can use the Cart Module
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const cart = await cartModuleService.retrieve("cart_123")
|
||||
const cart = await cartModuleService.retrieveCart(
|
||||
"cart_123"
|
||||
)
|
||||
|
||||
res.json({ cart })
|
||||
}
|
||||
@@ -119,7 +121,9 @@ In this guide, you’ll find common examples of how you can use the Cart Module
|
||||
export async function GET(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.retrieve("cart_123")
|
||||
const cart = await cartModuleService.retrieveCart(
|
||||
"cart_123"
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
cart,
|
||||
|
||||
@@ -15,7 +15,7 @@ The Cart Module is the `@medusajs/cart` NPM package that provides cart-related f
|
||||
Store and manage carts, including their addresses, line items, shipping methods, and more.
|
||||
|
||||
```ts
|
||||
const cart = await cartModuleService.create({
|
||||
const cart = await cartModuleService.createCarts({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
@@ -76,7 +76,6 @@ For example:
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { res } from "express"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
@@ -86,7 +85,7 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
res.json({
|
||||
carts: await cartModuleService.list(),
|
||||
carts: await cartModuleService.listCarts(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -105,7 +104,7 @@ For example:
|
||||
const cartModuleService: ICartModuleService =
|
||||
container.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const carts = await cartModuleService.list()
|
||||
const carts = await cartModuleService.listCarts()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -125,7 +124,7 @@ For example:
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
|
||||
const carts = await cartModuleService.list()
|
||||
const carts = await cartModuleService.listCarts()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ The `LineItemAdjustment` data model represents changes on a line item, and the `
|
||||
|
||||

|
||||
|
||||
The `amount` field of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` field of the adjustment line.
|
||||
The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion is stored in the `promotion_id` property of the adjustment line.
|
||||
|
||||
---
|
||||
|
||||
## Discountable Option
|
||||
|
||||
The `LineItem` data model has an `is_discountable` field that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
} from "@medusajs/types"
|
||||
|
||||
// retrieve the cart
|
||||
const cart = await cartModuleService.retrieve("cart_123", {
|
||||
const cart = await cartModuleService.retrieveCart("cart_123", {
|
||||
relations: [
|
||||
"items.adjustments",
|
||||
"shipping_methods.adjustments",
|
||||
|
||||
@@ -18,7 +18,7 @@ A tax line indicates the tax rate of a line item or a shipping method. The [Line
|
||||
|
||||
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.
|
||||
|
||||
However, line items and shipping methods have an `is_tax_inclusive` field 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.
|
||||
|
||||
So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
|
||||
|
||||
@@ -38,7 +38,7 @@ When using the Cart and Tax modules together, you can use the `getTaxLines` meth
|
||||
|
||||
```ts
|
||||
// retrieve the cart
|
||||
const cart = await cartModuleService.retrieve("cart_123", {
|
||||
const cart = await cartModuleService.retrieveCart("cart_123", {
|
||||
relations: [
|
||||
"items.tax_lines",
|
||||
"shipping_methods.tax_lines",
|
||||
|
||||
@@ -26,7 +26,7 @@ In this guide, you’ll find common examples of how you can use the Currency Mod
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -42,10 +42,11 @@ In this guide, you’ll find common examples of how you can use the Currency Mod
|
||||
} from "@medusajs/currency"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
const currencyModuleService =
|
||||
await initializeCurrencyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -72,7 +73,8 @@ In this guide, you’ll find common examples of how you can use the Currency Mod
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
const currency = await currencyModuleService
|
||||
.retrieveCurrency("usd")
|
||||
|
||||
res.json({
|
||||
currency,
|
||||
@@ -95,7 +97,8 @@ In this guide, you’ll find common examples of how you can use the Currency Mod
|
||||
) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
const currency = await currencyModuleService
|
||||
.retrieveCurrency("usd")
|
||||
|
||||
return NextResponse.json({ currency })
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ The Currency Module is the `@medusajs/currency` NPM package that provides curren
|
||||
List and retrieve currencies stored in your application.
|
||||
|
||||
```ts
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
const currency = await currencyModuleService.retrieveCurrency(
|
||||
"usd"
|
||||
)
|
||||
```
|
||||
|
||||
### Support Currencies in Modules
|
||||
@@ -25,8 +27,10 @@ Other commerce modules use currency codes in their data models or operations. Yo
|
||||
An example with the Region Module:
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
const currency = await currencyModuleService.retrieve(
|
||||
const region = await regionModuleService.retrieveCurrency(
|
||||
"reg_123"
|
||||
)
|
||||
const currency = await currencyModuleService.retrieveCurrency(
|
||||
region.currency_code
|
||||
)
|
||||
```
|
||||
@@ -55,7 +59,7 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
currencies: await currencyModuleService.listCurrencies(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -74,7 +78,8 @@ For example:
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
container.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
const currencies = await currencyModuleService.list()
|
||||
const currencies = await currencyModuleService
|
||||
.listCurrencies()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -94,7 +99,8 @@ For example:
|
||||
ModuleRegistrationName.CURRENCY
|
||||
)
|
||||
|
||||
const currencies = await currencyModuleService.list()
|
||||
const currencies = await currencyModuleService
|
||||
.listCurrencies()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ export const metadata = {
|
||||
|
||||
In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
|
||||
|
||||
## `has_account` field
|
||||
## `has_account` Property
|
||||
|
||||
The [Customer data model](/references/customer/models/Customer) has a `has_account` field, which is a boolean that indicates whether a customer is registerd.
|
||||
The [Customer data model](/references/customer/models/Customer) has a `has_account` property, which is a boolean that indicates whether a customer is registerd.
|
||||
|
||||
When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
|
||||
|
||||
@@ -18,6 +18,6 @@ When this or another guest customer registers an account with the same email, a
|
||||
|
||||
## Email Uniqueness
|
||||
|
||||
The above behavior means that two `Customer` records may exist of the same email. However, the main difference is the `has_account` field’s value.
|
||||
The above behavior means that two `Customer` records may exist of the same email. However, the main difference is the `has_account` property's value.
|
||||
|
||||
So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email.
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the Customer Mod
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customer = await customerModuleService.create({
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
@@ -50,7 +50,7 @@ In this guide, you’ll find common examples of how you can use the Customer Mod
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customer = await customerModuleService.create({
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
@@ -85,7 +85,7 @@ In this guide, you’ll find common examples of how you can use the Customer Mod
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
@@ -109,7 +109,7 @@ In this guide, you’ll find common examples of how you can use the Customer Mod
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ The Customer Module is the `@medusajs/customer` NPM package that provides custom
|
||||
With the Customer Module, store and manage customers in your store.
|
||||
|
||||
```ts
|
||||
const customer = await customerModuleService.create({
|
||||
const customer = await customerModuleService.createCustomers({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
@@ -28,7 +28,7 @@ You can organize customers into groups. This has a lot of benefits and supports
|
||||
|
||||
```ts
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
await customerModuleService.createCustomerGroups({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
@@ -62,7 +62,7 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
res.json({
|
||||
customers: await customerModuleService.list(),
|
||||
customers: await customerModuleService.listCustomers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -81,7 +81,7 @@ For example:
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -99,7 +99,7 @@ For example:
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ The fulfillment is also associated with a shipping option of that provider, whic
|
||||
|
||||
---
|
||||
|
||||
## data Field
|
||||
## data Property
|
||||
|
||||
The `Fulfillment` data model has a `data` field that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
|
||||
The `Fulfillment` data model has a `data` property that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
|
||||
|
||||
For example, the `data` field can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
|
||||
For example, the `data` property can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
|
||||
|
||||
---
|
||||
|
||||
@@ -48,7 +48,7 @@ Once a shipment is created for the fulfillment, you can store its tracking numbe
|
||||
|
||||
## Fulfillment Status
|
||||
|
||||
The `Fulfillment` data model has three fields to keep track of the current status of the fulfillment:
|
||||
The `Fulfillment` data model has three properties to keep track of the current status of the fulfillment:
|
||||
|
||||
- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
|
||||
- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
|
||||
|
||||
@@ -34,9 +34,9 @@ Service zones can be more restrictive, such as restricting to certain cities or
|
||||
|
||||
You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
|
||||
|
||||
These rules are represented by the [ShippingOptionRule data model](/references/fulfillment/models/ShippingOptionRule). Its fields define the custom rule:
|
||||
These rules are represented by the [ShippingOptionRule data model](/references/fulfillment/models/ShippingOptionRule). Its properties define the custom rule:
|
||||
|
||||
- `attribute`: The name of a field or table that the rule applies to. For example, `customer_group`.
|
||||
- `attribute`: The name of a property or table that the rule applies to. For example, `customer_group`.
|
||||
- `operator`: The operator used in the condition. For example:
|
||||
- To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values.
|
||||
- To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values.
|
||||
@@ -59,8 +59,8 @@ A shipping option also belongs to a shipping profile, as each shipping profile d
|
||||
|
||||
---
|
||||
|
||||
## data Field
|
||||
## data Property
|
||||
|
||||
When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process.
|
||||
|
||||
The `ShippingOption` data model has a `data` field. It's an object that stores custom data relevant later when creating and processing a fulfillment.
|
||||
The `ShippingOption` data model has a `data` property. It's an object that stores custom data relevant later when creating and processing a fulfillment.
|
||||
|
||||
@@ -20,15 +20,15 @@ The `InventoryItem` data model mainly holds details related to the underlying st
|
||||
|
||||
An inventory level, represented by the [InventoryLevel data model](/references/inventory/models/InventoryLevel), holds the inventory and quantity details of an inventory item in a specific location.
|
||||
|
||||
It has three quantity-related fields:
|
||||
It has three quantity-related properties:
|
||||
|
||||
- `stocked_quantity`: The available stock quantity of an item in the associated location.
|
||||
- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock.
|
||||
- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This field doesn't play into the `stocked_quantity` or when checking whether an item is in stock.
|
||||
- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This property doesn't play into the `stocked_quantity` or when checking whether an item is in stock.
|
||||
|
||||
### Associated Location
|
||||
|
||||
The inventory level's location is determined by the `location_id` field. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module.
|
||||
The inventory level's location is determined by the `location_id` property. Medusa links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
await inventoryModuleService.createInventoryItems({
|
||||
sku: request.body.sku,
|
||||
title: request.body.title,
|
||||
requires_shipping: request.body.requires_shipping,
|
||||
@@ -52,7 +52,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
await inventoryModuleService.createInventoryItems({
|
||||
sku: body.sku,
|
||||
title: body.title,
|
||||
requires_shipping: body.requires_shipping,
|
||||
@@ -85,7 +85,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
await inventoryModuleService.listInventoryItems({})
|
||||
|
||||
res.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
@@ -106,7 +106,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
await initializeInventoryModule({})
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
await inventoryModuleService.listInventoryItems({})
|
||||
|
||||
return NextResponse.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
@@ -135,7 +135,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.retrieve(
|
||||
await inventoryModuleService.retrieveInventoryItem(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
@@ -167,7 +167,7 @@ In this document, you’ll find common examples of how you can use the Inventory
|
||||
await initializeInventoryModule({})
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.retrieve(
|
||||
await inventoryModuleService.retrieveInventoryItem(
|
||||
params.id
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This document explains how the Inventory Module is used within the Medusa applic
|
||||
|
||||
## Product Variant Creation
|
||||
|
||||
When a product variant is created and its `manage_inventory` field's value is `true`, the Medusa application creates an inventory item associated with that product variant.
|
||||
When a product variant is created and its `manage_inventory` property's value is `true`, the Medusa application creates an inventory item associated with that product variant.
|
||||
|
||||

|
||||
|
||||
@@ -32,7 +32,7 @@ When an order is placed, the Medusa application creates a reservation item for e
|
||||
|
||||
## Order Fulfillment
|
||||
|
||||
When an item in an order is fulfilled and the associated variant has its `manage_inventory` field set to `true`, the Medusa application:
|
||||
When an item in an order is fulfilled and the associated variant has its `manage_inventory` property set to `true`, the Medusa application:
|
||||
|
||||
- Subtracts the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item.
|
||||
- Resets the `reserved_quantity` to `0`.
|
||||
@@ -44,6 +44,6 @@ When an item in an order is fulfilled and the associated variant has its `manage
|
||||
|
||||
## Order Return
|
||||
|
||||
When an item in an order is returned and the associated variant has its `manage_inventory` field set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity.
|
||||
When an item in an order is returned and the associated variant has its `manage_inventory` property set to `true`, the Medusa application increments the `stocked_quantity` of the inventory item's level with the returned quantity.
|
||||
|
||||

|
||||
|
||||
@@ -18,7 +18,7 @@ Inventory items hold details of the underlying stock-kept item, as well as inven
|
||||
|
||||
```ts
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
await inventoryModuleService.createInventoryItems({
|
||||
sku: "SHIRT",
|
||||
title: "Green Medusa Shirt",
|
||||
requires_shipping: true,
|
||||
@@ -89,7 +89,7 @@ For example:
|
||||
|
||||
res.json({
|
||||
inventory_items:
|
||||
await inventoryModuleService.list({}),
|
||||
await inventoryModuleService.listInventoryItems({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -109,7 +109,7 @@ For example:
|
||||
container.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
await inventoryModuleService.listInventoryItems({})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -128,7 +128,7 @@ For example:
|
||||
container.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
await inventoryModuleService.listInventoryItems({})
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Each product variant has different inventory details. Medusa defines a link modu
|
||||
|
||||

|
||||
|
||||
A product variant, whose `manage_inventory` field is enabled, has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
|
||||
A product variant, whose `manage_inventory` property is enabled, has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ The details of the purchased products are represented by the [LineItem data mode
|
||||
|
||||
An order has one or more shipping methods used to handle item shipment. Each shipping method is represented by the [ShippingMethod data model](/references/order/models/ShippingMethod) that holds its details.
|
||||
|
||||
### data Field
|
||||
### data Property
|
||||
|
||||
When fulfilling the order, you may use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
|
||||
|
||||
The `ShippingMethod` data model has a `data` field. It’s an object used to store custom data relevant later for fulfillment.
|
||||
The `ShippingMethod` data model has a `data` property. It’s an object used to store custom data relevant later for fulfillment.
|
||||
|
||||
The Medusa application passes the `data` field to the Fulfillment Module when fulfilling items.
|
||||
The Medusa application passes the `data` property to the Fulfillment Module when fulfilling items.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Each of these actions is represented by the [OrderChangeAction data model](/refe
|
||||
|
||||
### Action Name
|
||||
|
||||
The `action` field of the `OrderChangeAction` holds the name of the action to perform. Based on the action, additional details are stored in the `details` object field of the data model.
|
||||
The `action` property of the `OrderChangeAction` holds the name of the action to perform. Based on the action, additional details are stored in the `details` object property of the data model.
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
@@ -162,7 +162,7 @@ The `action` field of the `OrderChangeAction` holds the name of the action to pe
|
||||
|
||||
Mark as received an item whose return was previously requested, but consider the items damaged.
|
||||
|
||||
This changes the order item’s `return_dismissed_quantity` field rather than its `return_received_quantity` field.
|
||||
This changes the order item’s `return_dismissed_quantity` property rather than its `return_received_quantity` property.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -223,7 +223,7 @@ The `action` field of the `OrderChangeAction` holds the name of the action to pe
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark a quantity of an item as shipped. This modifies the shipped_quantity field of an order item.
|
||||
Mark a quantity of an item as shipped. This modifies the `shipped_quantity` property of an order item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -243,7 +243,7 @@ The `action` field of the `OrderChangeAction` holds the name of the action to pe
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark a quantity of an item as fulfilled. This modifies the fulfilled_quantity field of an order item.
|
||||
Mark a quantity of an item as fulfilled. This modifies the `fulfilled_quantity` property of an order item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
@@ -277,7 +277,7 @@ The `action` field of the `OrderChangeAction` holds the name of the action to pe
|
||||
|
||||
## Order Change Confirmation
|
||||
|
||||
The `OrderChange` data model has a `status` field that indicates its current status. By default, it’s pending. At this point, the order change’s actions aren’t applied to the order yet.
|
||||
The `OrderChange` data model has a `status` property that indicates its current status. By default, it’s pending. At this point, the order change’s actions aren’t applied to the order yet.
|
||||
|
||||
To apply these changes to the order, you confirm the order change. When the order change is confirmed:
|
||||
|
||||
|
||||
@@ -12,17 +12,17 @@ Versioning means assigning a version number to a record, such as an order and it
|
||||
|
||||
---
|
||||
|
||||
## version Field
|
||||
## version Property
|
||||
|
||||
The `Order` and `OrderSummary` data models have a `version` field that indicates the current order version. By default, its value is `1`.
|
||||
The `Order` and `OrderSummary` data models have a `version` property that indicates the current order version. By default, its value is `1`.
|
||||
|
||||
The `OrderItem` data model also has a `version` field, but it indicates the version it belongs to. For example, original items in the order have version `1`. Then, when a new item is added, its version is `2`. If an existing item is modified, such as its quantity has been changed, its version is updated.
|
||||
The `OrderItem` data model also has a `version` property, but it indicates the version it belongs to. For example, original items in the order have version `1`. Then, when a new item is added, its version is `2`. If an existing item is modified, such as its quantity has been changed, its version is updated.
|
||||
|
||||
---
|
||||
|
||||
## Order Change Versioning
|
||||
|
||||
The `OrderChange` and `OrderChangeAction` data models also have a `version` field. When an order change is created, the `version` field’s value is the associated order’s version incremented.
|
||||
The `OrderChange` and `OrderChangeAction` data models also have a `version` property. When an order change is created, the `version` property’s value is the associated order’s version incremented.
|
||||
|
||||
So, if the order’s `version` is `1`, the order change’s version is `2`.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ The Order Module is the `@medusajs/order` NPM package that provides order-relate
|
||||
Store and manage your orders to retriev, create, cancel, and perform other operations.
|
||||
|
||||
```ts
|
||||
const order = await orderModuleService.create({
|
||||
const order = await orderModuleService.createOrders({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
{
|
||||
@@ -38,7 +38,7 @@ const order = await orderModuleService.create({
|
||||
Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
|
||||
|
||||
```ts
|
||||
const draftOrder = await orderModuleService.create({
|
||||
const draftOrder = await orderModuleService.createOrders({
|
||||
currency_code: "usd",
|
||||
// other details...
|
||||
status: "draft",
|
||||
@@ -122,7 +122,7 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.ORDER)
|
||||
|
||||
res.json({
|
||||
orders: await orderModuleService.list(),
|
||||
orders: await orderModuleService.listOrders(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -141,7 +141,7 @@ For example:
|
||||
const orderModuleService: IOrderModuleService =
|
||||
container.resolve(ModuleRegistrationName.ORDER)
|
||||
|
||||
const orders = await orderModuleService.list()
|
||||
const orders = await orderModuleService.listOrders()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -160,7 +160,7 @@ For example:
|
||||
container.resolve(
|
||||
ModuleRegistrationName.ORDER
|
||||
)
|
||||
const orders = await orderModuleService.list()
|
||||
const orders = await orderModuleService.listOrders()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -18,13 +18,13 @@ The [LineItemAdjustment data model](/references/order/models/LineItemAdjustment)
|
||||
|
||||

|
||||
|
||||
The `amount` field of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion can be stored in the `promotion_id` field of the adjustment line.
|
||||
The `amount` property of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion can be stored in the `promotion_id` property of the adjustment line.
|
||||
|
||||
---
|
||||
|
||||
## Discountable Option
|
||||
|
||||
The `LineItem` data model has an `is_discountable` field that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
// ...
|
||||
|
||||
// retrieve the order
|
||||
const order = await orderModuleService.retrieve("ord_123", {
|
||||
const order = await orderModuleService.retrieveOrder("ord_123", {
|
||||
relations: [
|
||||
"items.item.adjustments",
|
||||
"shipping_methods.adjustments",
|
||||
|
||||
@@ -22,7 +22,7 @@ A tax line indicates the tax rate of a line item or a shipping method. The [Line
|
||||
|
||||
By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then adding it to the item/method’s subtotal.
|
||||
|
||||
However, line items and shipping methods have an `is_tax_inclusive` field 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.
|
||||
|
||||
So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ The transaction’s main purpose is to ensure a correct balance between paid and
|
||||
|
||||
## Checking Outstanding Amount
|
||||
|
||||
The order’s total is stored in the `OrderSummary`'s `total` field. To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
|
||||
The order’s total is stored in the `OrderSummary`'s `total` property. To check the outstanding amount of the order, its transaction amounts are summed. Then, the following conditions are checked:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
@@ -73,7 +73,7 @@ The order’s total is stored in the `OrderSummary`'s `total` field. To check th
|
||||
|
||||
The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. For that, use the Payment Module or custom logic.
|
||||
|
||||
The `Transaction` data model has two fields that determine which data model and record holds the actual payment’s details:
|
||||
The `Transaction` data model has two properties that determine which data model and record holds the actual payment’s details:
|
||||
|
||||
- `reference`: indicates the table’s name in the database. For example, `payment` if you’re using the Payment Module.
|
||||
- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
|
||||
@@ -16,17 +16,17 @@ A payment collection can have multiple payment sessions. For example, during che
|
||||
|
||||
---
|
||||
|
||||
## data field
|
||||
## data Property
|
||||
|
||||
Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` field used to store that data.
|
||||
Payment providers may need additional data to process the payment later. The `PaymentSession` data model has a `data` property used to store that data.
|
||||
|
||||
For example, the customer's ID in Stripe is stored in the `data` field.
|
||||
For example, the customer's ID in Stripe is stored in the `data` property.
|
||||
|
||||
---
|
||||
|
||||
## Payment Session Status
|
||||
|
||||
The `status` field of a payment session indicates its current status. Its value can be:
|
||||
The `status` property of a payment session indicates its current status. Its value can be:
|
||||
|
||||
- `pending`: The payment session is awaiting authorization.
|
||||
- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
|
||||
|
||||
@@ -14,7 +14,7 @@ A payment carries along many of the data and relations of a payment session:
|
||||
|
||||
- It belongs to the same payment collection.
|
||||
- It’s associated with the same payment provider, which handles further payment processing.
|
||||
- It stores the payment session’s `data` field in its `data` field, as it’s still useful for the payment provider’s processing.
|
||||
- It stores the payment session’s `data` property in its `data` property, as it’s still useful for the payment provider’s processing.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ A [PriceSet](/references/pricing/models/PriceSet) represents a collection of pri
|
||||
|
||||
Each price within a price set can be applied for different conditions. These conditions are represented as rule types.
|
||||
|
||||
A [RuleType](/references/pricing/models/RuleType) defines custom conditions. A rule type has a unique `rule_attribute` which indicates the field this rule applies on. For example, `region_id`.
|
||||
A [RuleType](/references/pricing/models/RuleType) defines custom conditions. A rule type has a unique `rule_attribute` which indicates the property this rule applies on. For example, `region_id`.
|
||||
|
||||
This is referenced when setting a rule of a price. For example:
|
||||
|
||||
@@ -46,6 +46,6 @@ const priceSet = await pricingModuleService.addPrices({
|
||||
|
||||
## Price List
|
||||
|
||||
A [PriceList](/references/pricing/models/PriceList) is a group of prices only enabled if their conditions and rules are satisfied. A price list has optional `start_date` and `end_date` fields, which indicate the date range in which a price list can be applied.
|
||||
A [PriceList](/references/pricing/models/PriceList) is a group of prices only enabled if their conditions and rules are satisfied. A price list has optional `start_date` and `end_date` properties, which indicate the date range in which a price list can be applied.
|
||||
|
||||
Its associated prices are represented by the `Price` data model.
|
||||
@@ -27,7 +27,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
|
||||
// A rule type with \`rule_field=region_id\` should
|
||||
// already be present in the database
|
||||
const priceSet = await pricingModuleService.create([
|
||||
const priceSet = await pricingModuleService.createPriceSets([
|
||||
{
|
||||
rules: [{ rule_field: "region_id" }],
|
||||
prices: [
|
||||
@@ -62,7 +62,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
|
||||
// A rule type with \`rule_field=region_i\` should
|
||||
// already be present in the database
|
||||
const priceSet = await pricingModuleService.create([
|
||||
const priceSet = await pricingModuleService.createPriceSets([
|
||||
{
|
||||
rules: [{ rule_field: "region_id" }],
|
||||
prices: [
|
||||
@@ -103,7 +103,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
|
||||
res.json({ price_sets: priceSets })
|
||||
}
|
||||
@@ -122,7 +122,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
export async function GET(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
|
||||
return NextResponse.json({ price_sets: priceSets })
|
||||
}
|
||||
@@ -150,7 +150,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
const priceSet = await pricingModuleService.retrievePriceSet(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
@@ -180,7 +180,7 @@ In this document, you’ll find common examples of how you can use the Pricing M
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
const priceSet = await pricingModuleService.retrievePriceSet(
|
||||
params.id
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ With the Pricing Module, store the prices of a resource and manage them through
|
||||
Prices are grouped in a price set, allowing you to add more than one price for a resource based on different conditions, such as currency code.
|
||||
|
||||
```ts
|
||||
const priceSet = await pricingModuleService.create({
|
||||
const priceSet = await pricingModuleService.createPriceSets({
|
||||
rules: [],
|
||||
prices: [
|
||||
{
|
||||
@@ -124,7 +124,7 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
res.json({
|
||||
price_sets: await pricingModuleService.list(),
|
||||
price_sets: await pricingModuleService.listPriceSets(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -143,7 +143,7 @@ For example:
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -161,7 +161,7 @@ For example:
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
const priceSets = await pricingModuleService.listPriceSets()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ const ruleTypes = await pricingModuleService.createRuleTypes([
|
||||
},
|
||||
])
|
||||
|
||||
const priceSet = await pricingModuleService.create({
|
||||
const priceSet = await pricingModuleService.createPriceSets({
|
||||
rules: [
|
||||
{ rule_attribute: "region_id" },
|
||||
{ rule_attribute: "city" },
|
||||
@@ -422,7 +422,7 @@ const priceSet = await pricingModuleService.create({
|
||||
<TabsContent value="code">
|
||||
|
||||
```ts
|
||||
const priceList = pricingModuleService.createPriceList({
|
||||
const priceList = pricingModuleService.createPriceLists({
|
||||
name: "Test Price List",
|
||||
starts_at: Date.parse("01/10/2023"),
|
||||
ends_at: Date.parse("31/10/2023"),
|
||||
|
||||
@@ -8,7 +8,7 @@ In this document, you'll learn about price rules for price sets and price lists.
|
||||
|
||||
## Price Rule
|
||||
|
||||
Each rule of a price within a price set is represented by the [PriceRule data model](/references/pricing/models/PriceRule), which holds the value of a rule type. The `Price` data model has a `rules_count` field, which indicates how many rules, represented by `PriceRule`, are applied to the price.
|
||||
Each rule of a price within a price set is represented by the [PriceRule data model](/references/pricing/models/PriceRule), which holds the value of a rule type. The `Price` data model has a `rules_count` property, which indicates how many rules, represented by `PriceRule`, are applied to the price.
|
||||
|
||||

|
||||
|
||||
@@ -40,7 +40,7 @@ For example, to use the `zip_code` rule type on a price in a price set, the rule
|
||||
|
||||
## Price List Rules
|
||||
|
||||
Rules that can be applied to a price list are represented by the [PriceListRule data model](/references/pricing/models/PriceListRule). The `rules_count` field of a `PriceList` indicates how many rules are applied to it.
|
||||
Rules that can be applied to a price list are represented by the [PriceListRule data model](/references/pricing/models/PriceListRule). The `rules_count` property of a `PriceList` indicates how many rules are applied to it.
|
||||
|
||||
Each rule of a price list can have more than one value, representing its values by the [PriceListRuleValue data model](/references/pricing/models/PriceListRuleValue).
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.create([
|
||||
const products = await productModuleService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
@@ -63,7 +63,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
export async function POST(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const products = await productModuleService.create([
|
||||
const products = await productModuleService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
@@ -110,7 +110,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list()
|
||||
const products = await productModuleService.listProducts()
|
||||
|
||||
res.json({ products })
|
||||
}
|
||||
@@ -129,7 +129,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const products = await productModuleService.list()
|
||||
const products = await productModuleService.listProducts()
|
||||
|
||||
return NextResponse.json({ products })
|
||||
}
|
||||
@@ -157,7 +157,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const product = await productModuleService.retrieve(
|
||||
const product = await productModuleService.retrieveProduct(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
@@ -182,7 +182,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const { id } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const product = await productModuleService.retrieve(
|
||||
const product = await productModuleService.retrieveProduct(
|
||||
id
|
||||
)
|
||||
|
||||
@@ -212,7 +212,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.list({
|
||||
const data = await productModuleService.listProducts({
|
||||
handle: "shirt",
|
||||
})
|
||||
|
||||
@@ -233,7 +233,7 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.list({
|
||||
const data = await productModuleService.listProducts({
|
||||
handle: "shirt",
|
||||
})
|
||||
|
||||
@@ -263,7 +263,8 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const categories = await productModuleService.listCategories()
|
||||
const categories = await productModuleService
|
||||
.listProductCategories()
|
||||
|
||||
res.json({ categories })
|
||||
}
|
||||
@@ -282,7 +283,8 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const categories = await productModuleService.listCategories()
|
||||
const categories = await productModuleService
|
||||
.listProductCategories()
|
||||
|
||||
return NextResponse.json({ categories })
|
||||
}
|
||||
@@ -310,9 +312,10 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.listCategories({
|
||||
handle: request.params.handle,
|
||||
})
|
||||
const data = await productModuleService
|
||||
.listProductCategories({
|
||||
handle: request.params.handle,
|
||||
})
|
||||
|
||||
res.json({ category: data[0] })
|
||||
}
|
||||
@@ -335,9 +338,10 @@ In this guide, you’ll find common examples of how you can use the Product Modu
|
||||
const { handle } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.listCategories({
|
||||
handle,
|
||||
})
|
||||
const data = await productModuleService
|
||||
.listProductCategories({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ category: data[0] })
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ The Product Module is the `@medusajs/product` NPM package that provides product-
|
||||
Store and manage products. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
|
||||
|
||||
```ts
|
||||
const products = await productService.create([
|
||||
const products = await productService.createProducts([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
@@ -42,11 +42,11 @@ const products = await productService.create([
|
||||
The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
|
||||
|
||||
```ts
|
||||
const category = await productService.createCategory({
|
||||
const category = await productService.createProductCategories({
|
||||
name: "Shirts",
|
||||
})
|
||||
|
||||
const products = await productService.update([
|
||||
const products = await productService.updateProducts([
|
||||
{
|
||||
id: product.id,
|
||||
categories: [
|
||||
@@ -81,7 +81,9 @@ For example:
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
res.json({ products: await productModuleService.list() })
|
||||
res.json({
|
||||
products: await productModuleService.listProducts(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -99,7 +101,7 @@ For example:
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list()
|
||||
const products = await productModuleService.listProducts()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -117,7 +119,7 @@ For example:
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list()
|
||||
const products = await productModuleService.listProducts()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -48,6 +48,6 @@ Each product variant has different inventory details. Medusa defines a link modu
|
||||
|
||||

|
||||
|
||||
When the `manage_inventory` field of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
|
||||
When the `manage_inventory` property of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
|
||||
|
||||
Learn more about the `InventoryItem` data model in the [Inventory Concepts](../../inventory/concepts/page.mdx#inventoryitem)
|
||||
@@ -10,7 +10,7 @@ In this document, you’ll learn about promotion actions and how they’re compu
|
||||
|
||||
The Promotion Module's main service has a [computeActions method](/references/promotion/computeActions) that returns an array of actions to perform on a cart when one or more promotions are applied.
|
||||
|
||||
Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` field indicating the type of action.
|
||||
Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` property indicating the type of action.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ The [ApplicationMethod data model](/references/promotion/models/ApplicationMetho
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>
|
||||
|
||||
Field
|
||||
Property
|
||||
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
@@ -71,7 +71,7 @@ The [ApplicationMethod data model](/references/promotion/models/ApplicationMetho
|
||||
|
||||
When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to.
|
||||
|
||||
The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` field represents this relation.
|
||||
The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` property represents this relation.
|
||||
|
||||

|
||||
|
||||
@@ -83,7 +83,7 @@ In this example, the promotion is only applied on products in the cart having th
|
||||
|
||||
When the promotion’s type is `buyget`, you must specify the “buy X” condition. For example, a cart must have two shirts before the promotion can be applied.
|
||||
|
||||
The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` field represents this relation.
|
||||
The application method has a collection of `PromotionRule` items to define the “buy X” rule. The `buy_rules` property represents this relation.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -78,19 +78,19 @@ A promotion can be restricted by a set of rules, each rule is represented by the
|
||||
|
||||

|
||||
|
||||
A `PromotionRule`'s `attribute` field indicates the field's name to which this rule is applied. For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
|
||||
A `PromotionRule`'s `attribute` property indicates the property's name to which this rule is applied. For example, `customer_group_id`. Its value is stored in the `PromotionRuleValue` data model. So, a rule can have multiple values.
|
||||
|
||||
When testing whether a promotion can be applied to a cart, the rule's `attribute` field and its values are tested on the cart itself. For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
|
||||
When testing whether a promotion can be applied to a cart, the rule's `attribute` property and its values are tested on the cart itself. For example, the cart's customer must be part of the customer group(s) indicated in the promotion rule's value.
|
||||
|
||||
---
|
||||
|
||||
## Flexible Rules
|
||||
|
||||
The `PromotionRule`'s `operator` field adds more flexibility to the rule’s condition rather than simple equality (`eq`).
|
||||
The `PromotionRule`'s `operator` property adds more flexibility to the rule’s condition rather than simple equality (`eq`).
|
||||
|
||||
For example, to restrict the promotion to only `VIP` and `B2B` customer groups:
|
||||
|
||||
- Add a `PromotionRule` with its `attribute` field set to `customer_group_id` and `operator` field to `in`.
|
||||
- Add a `PromotionRule` with its `attribute` property set to `customer_group_id` and `operator` property to `in`.
|
||||
- Add two `PromotionRuleValue` associated with the rule: one with the value `VIP` and the other `B2B`.
|
||||
|
||||

|
||||
|
||||
@@ -25,7 +25,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -55,7 +55,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
await initializePromotionModule()
|
||||
const body = await request.json()
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -155,7 +155,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -191,7 +191,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
const promotionModuleService =
|
||||
await initializePromotionModule()
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -236,7 +236,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -256,7 +256,7 @@ In this document, you’ll find common examples of how you can use the Promotion
|
||||
await initializePromotionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ A promotion discounts an amount or percentage of a cart's items, shipping method
|
||||
The Promotion Module allows you to store and manage promotions.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -34,7 +34,7 @@ const promotion = await promotionModuleService.create({
|
||||
A promotion has rules that restricts when it's applied. For example, you can create a promotion that's only applied to VIP customers.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.create({
|
||||
const promotion = await promotionModuleService.createPromotions({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
@@ -94,7 +94,7 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
promotions: await promotionModuleService.listPromotions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -113,7 +113,7 @@ For example:
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
container.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotions = await promotionModuleService.list()
|
||||
const promotions = await promotionModuleService.listPromotions()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -131,7 +131,7 @@ For example:
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
container.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotions = await promotionModuleService.list()
|
||||
const promotions = await promotionModuleService.listPromotions()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.create({
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
@@ -49,7 +49,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
export async function POST(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.create({
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
@@ -83,7 +83,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.list(),
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -102,7 +102,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
regions: await regionModuleService.list(),
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -129,7 +129,8 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
const region = await regionModuleService
|
||||
.retrieveRegion("reg_123")
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
@@ -150,7 +151,8 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
const region = await regionModuleService
|
||||
.retrieveRegion("reg_123")
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
@@ -178,9 +180,10 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.update("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
const region = await regionModuleService
|
||||
.updateRegions("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
@@ -201,9 +204,10 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.update("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
const region = await regionModuleService
|
||||
.updateRegions("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
@@ -231,7 +235,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
await regionModuleService.delete("reg_123")
|
||||
await regionModuleService.deleteRegions("reg_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
@@ -252,7 +256,7 @@ In this guide, you’ll find common examples of how you can use the Region Modul
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
await regionModuleService.delete("reg_123")
|
||||
await regionModuleService.deleteRegions("reg_123")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ A region represents the area you sell products in. Each region can cover multipl
|
||||
You can manage your regions to create, update, retrieve, or delete them.
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.create({
|
||||
const region = await regionModuleService.createRegions({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
@@ -32,7 +32,7 @@ const region = await regionModuleService.create({
|
||||
As each region has a currency, you can support multiple currencies in your store by creating multiple regions.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.create([
|
||||
const regions = await regionModuleService.createRegions([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
@@ -49,7 +49,7 @@ const regions = await regionModuleService.create([
|
||||
Each region has its own settings, such as what countries belong to a region or its tax settings. Each region has different tax rates, payment providers, and more provided by other commerce modules.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.create([
|
||||
const regions = await regionModuleService.createRegions([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
@@ -89,7 +89,7 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.list(),
|
||||
regions: await regionModuleService.listRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -108,7 +108,7 @@ For example:
|
||||
const regionModuleService: IRegionModuleService =
|
||||
container.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const regions = await regionModuleService.list()
|
||||
const regions = await regionModuleService.listRegions()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -128,7 +128,7 @@ For example:
|
||||
ModuleRegistrationName.REGION
|
||||
)
|
||||
|
||||
const regions = await regionModuleService.list()
|
||||
const regions = await regionModuleService.listRegions()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.create({
|
||||
name: "B2B",
|
||||
})
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.createSalesChannels({
|
||||
name: "B2B",
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
@@ -49,9 +50,10 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.create({
|
||||
name: "B2B",
|
||||
})
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.createSalesChannels({
|
||||
name: "B2B",
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
@@ -80,7 +82,8 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: salesChannelModuleService.list(),
|
||||
sales_channels: salesChannelModuleService
|
||||
.listSalesChannels(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -99,7 +102,8 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
const salesChannels = await salesChannelModuleService
|
||||
.listSalesChannels()
|
||||
|
||||
return NextResponse.json({ sales_channels: salesChannels })
|
||||
}
|
||||
@@ -127,9 +131,10 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieve(
|
||||
"sc_123"
|
||||
)
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.retrieveSalesChannel(
|
||||
"sc_123"
|
||||
)
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
@@ -153,9 +158,10 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieve(
|
||||
"sc_123"
|
||||
)
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.retrieveSalesChannel(
|
||||
"sc_123"
|
||||
)
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
@@ -183,10 +189,11 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.update({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.updateSalesChannels({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
@@ -210,10 +217,11 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.update({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
const salesChannel = await salesChannelModuleService
|
||||
.updateSalesChannels({
|
||||
id: "sc_123",
|
||||
description: "Sales channel for B2B customers",
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
@@ -241,7 +249,7 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
await salesChannelModuleService.delete("sc_123")
|
||||
await salesChannelModuleService.deleteSalesChannels("sc_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
@@ -263,7 +271,7 @@ In this guide, you’ll find common examples of how you can use the Sales Channe
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
await salesChannelModuleService.delete("sc_123")
|
||||
await salesChannelModuleService.deleteSalesChannels("sc_123")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user