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:
1 parent
62dacdda75
commit
0462cc5acf
126 files changed
+1808
-14242
No files matched your search
@@ -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")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -27,14 +27,15 @@ Some use case examples for using a sales channel:
|
||||
Manage sales channels in your store. Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
|
||||
|
||||
```ts
|
||||
const salesChannels = await salesChannelModuleService.create([
|
||||
{
|
||||
name: "B2B",
|
||||
},
|
||||
{
|
||||
name: "Mobile App",
|
||||
},
|
||||
])
|
||||
const salesChannels = await salesChannelModuleService
|
||||
.createSalesChannels([
|
||||
{
|
||||
name: "B2B",
|
||||
},
|
||||
{
|
||||
name: "Mobile App",
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Product Availability
|
||||
@@ -73,7 +74,8 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: await salesChannelModuleService.list(),
|
||||
sales_channels: await salesChannelModuleService
|
||||
.listSalesChannels(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -92,7 +94,8 @@ For example:
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
container.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
const salesChannels = await salesChannelModuleService
|
||||
.listSalesChannels()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -112,7 +115,8 @@ For example:
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
const salesChannels = await salesChannelModuleService
|
||||
.listSalesChannels()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.createStockLocations(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
@@ -51,11 +52,12 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.createStockLocations(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({ stock_location: stockLocation })
|
||||
}
|
||||
@@ -84,7 +86,8 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
stock_locations: await stockLocationModuleService
|
||||
.listStockLocations({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -104,7 +107,8 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
return NextResponse.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
stock_locations: await stockLocationModuleService
|
||||
.listStockLocations({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -131,15 +135,16 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.update({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "US",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.updateStockLocations({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "US",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
@@ -163,15 +168,16 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
const stockLocation = await stockLocationModuleService.update({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.updateStockLocations({
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
stock_location: stockLocation,
|
||||
@@ -201,7 +207,9 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
await stockLocationModuleService.delete("sloc_123")
|
||||
await stockLocationModuleService.deleteStockLocations(
|
||||
"sloc_123"
|
||||
)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
@@ -223,7 +231,9 @@ In this document, you’ll find common examples of how you can use the Stock Loc
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
await stockLocationModuleService.delete("sloc_123")
|
||||
await stockLocationModuleService.deleteStockLocations(
|
||||
"sloc_123"
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,11 +15,12 @@ The Stock Location Module is the `@medusajs/stock-location-next` NPM package tha
|
||||
Store and manage stock locations. Stock locations are associated with data models of other modules that require a location, such as the [Inventory Module's InventoryLevel](../inventory/concepts/page.mdx#inventory-level).
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.createStockLocations(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Address Management
|
||||
@@ -27,17 +28,18 @@ const stockLocation = await stockLocationModuleService.create(
|
||||
Manage the address of each stock location.
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.update(
|
||||
{
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
}
|
||||
)
|
||||
const stockLocation = await stockLocationModuleService
|
||||
.updateStockLocations(
|
||||
{
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "us",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -64,7 +66,8 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
stock_locations: await stockLocationModuleService
|
||||
.listStockLocations({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -83,9 +86,8 @@ For example:
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
container.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocations = await stockLocationModuleService.list(
|
||||
{}
|
||||
)
|
||||
const stockLocations = await stockLocationModuleService
|
||||
.listStockLocations({})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -105,9 +107,8 @@ For example:
|
||||
ModuleRegistrationName.STOCK_LOCATION
|
||||
)
|
||||
|
||||
const stockLocations = await stockLocationModuleService.list(
|
||||
{}
|
||||
)
|
||||
const stockLocations = await stockLocationModuleService
|
||||
.listStockLocations({})
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -25,9 +25,14 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
supported_currencies: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
is_default: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
@@ -49,9 +54,14 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
export async function POST(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
supported_currencies: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
is_default: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
@@ -83,7 +93,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.list(),
|
||||
stores: await storeModuleService.listStores(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -101,7 +111,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
export async function GET(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const salesChannels = await storeModuleService.list()
|
||||
const salesChannels = await storeModuleService.listStores()
|
||||
|
||||
return NextResponse.json({
|
||||
stores: await storeModuleService.list(),
|
||||
@@ -131,7 +141,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.retrieve(
|
||||
const store = await storeModuleService.retrieveStore(
|
||||
"store_123"
|
||||
)
|
||||
|
||||
@@ -157,7 +167,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.retrieve(
|
||||
const store = await storeModuleService.retrieveStore(
|
||||
"store_123"
|
||||
)
|
||||
|
||||
@@ -187,9 +197,10 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.update("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
const store = await storeModuleService
|
||||
.updateStores("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
@@ -213,9 +224,10 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.update("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
const store = await storeModuleService
|
||||
.updateStores("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
return NextResponse.json({ store })
|
||||
}
|
||||
@@ -243,7 +255,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
await storeModuleService.delete("store_123")
|
||||
await storeModuleService.deleteStores("store_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
@@ -265,7 +277,7 @@ In this guide, you’ll find common examples of how you can use the Store Module
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
await storeModuleService.delete("store_123")
|
||||
await storeModuleService.deleteStores("store_123")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ The Store Module is the `@medusajs/store` NPM package that provides store-relate
|
||||
A store holds the main configurations of your commerce store, such as supported currencies, default region and sales channel, and more.
|
||||
|
||||
```ts
|
||||
const store = await storeModuleService.create({
|
||||
const store = await storeModuleService.createStores({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
@@ -26,7 +26,7 @@ const store = await storeModuleService.create({
|
||||
You can create multiple stores, each having its own configurations.
|
||||
|
||||
```ts
|
||||
const stores = await storeModuleService.create([
|
||||
const stores = await storeModuleService.createStores([
|
||||
{
|
||||
name: "USA Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
@@ -62,7 +62,7 @@ For example:
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.list(),
|
||||
stores: await storeModuleService.listStores(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -81,7 +81,7 @@ For example:
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const stores = await storeModuleService.list()
|
||||
const stores = await storeModuleService.listStores()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -101,7 +101,7 @@ For example:
|
||||
ModuleRegistrationName.STORE
|
||||
)
|
||||
|
||||
const stores = await storeModuleService.list()
|
||||
const stores = await storeModuleService.listStores()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_regions: await taxModuleService.list(),
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -110,7 +110,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_regions: await taxModuleService.list(),
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -138,7 +138,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
const taxRate = await taxModuleService.create({
|
||||
const taxRate = await taxModuleService.createTaxRates({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
@@ -175,7 +175,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxRate = await taxModuleService.create({
|
||||
const taxRate = await taxModuleService.createTaxRates({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
@@ -221,7 +221,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_rates: await taxModuleService.list(),
|
||||
tax_rates: await taxModuleService.listTaxRates(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -242,7 +242,7 @@ In this guide, you’ll find common examples of how you can use the Tax Module i
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_rates: await taxModuleService.list(),
|
||||
tax_rates: await taxModuleService.listTaxRates(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -25,7 +25,7 @@ const taxRegion = await taxModuleService.createTaxRegions({
|
||||
Manage each region's default tax rates and override them with conditioned tax rates.
|
||||
|
||||
```ts
|
||||
const taxRates = await taxModuleService.create([
|
||||
const taxRates = await taxModuleService.createTaxRates([
|
||||
{
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Default tax rate",
|
||||
|
||||
@@ -35,7 +35,7 @@ const taxLines = await taxModuleService.getTaxLines(
|
||||
)
|
||||
```
|
||||
|
||||
The context object is used to determine which tax regions and rates to use in the calculation. It includes fields related to the address and customer.
|
||||
The context object is used to determine which tax regions and rates to use in the calculation. It includes properties related to the address and customer.
|
||||
|
||||
The example above returns the tax lines based on the tax region for the United States.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ A tax region can have multiple tax rates, and each tax rate can have multiple ta
|
||||
|
||||

|
||||
|
||||
These two fields of the data model identify the rule’s target:
|
||||
These two properties of the data model identify the rule’s target:
|
||||
|
||||
- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
|
||||
- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
|
||||
|
||||
@@ -25,7 +25,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
@@ -48,7 +48,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
export async function POST(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
@@ -83,7 +83,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.list(),
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -102,7 +102,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
return NextResponse.json({
|
||||
users: await userModuleService.list(),
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -129,7 +129,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const user = await userModuleService.update({
|
||||
const user = await userModuleService.updateUsers({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
@@ -153,7 +153,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.update({
|
||||
const user = await userModuleService.updateUsers({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
@@ -186,7 +186,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
await userModuleService.delete(["user_123"])
|
||||
await userModuleService.deleteUsers(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
@@ -205,7 +205,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
await userModuleService.delete(["user_123"])
|
||||
await userModuleService.deleteUsers(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
@@ -292,7 +292,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const invite =
|
||||
await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
@@ -327,7 +327,7 @@ In this guide, you’ll find common examples of how you can use the User Module
|
||||
const invite =
|
||||
await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ The User Module is the `@medusajs/user` NPM package that provides user-related f
|
||||
Manage and store your users through Create, Read, Update, and Delete (CRUD) operations:
|
||||
|
||||
```ts
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
@@ -65,7 +65,7 @@ For example:
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.list(),
|
||||
users: await userModuleService.listUsers(),
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -84,7 +84,7 @@ For example:
|
||||
const userModuleService: IUserModuleService =
|
||||
container.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const users = await userModuleService.list()
|
||||
const users = await userModuleService.listUsers()
|
||||
}
|
||||
```
|
||||
|
||||
@@ -104,7 +104,7 @@ For example:
|
||||
ModuleRegistrationName.USER
|
||||
)
|
||||
|
||||
const users = await userModuleService.list()
|
||||
const users = await userModuleService.listUsers()
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ await userModuleService.updateInvites({
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: invite.email,
|
||||
})
|
||||
```
|
||||
@@ -45,7 +45,7 @@ await userModuleService.refreshInviteTokens(["invite_123"])
|
||||
Finally, you can create a user using the [create method of the User Module’s main service](/references/user/create):
|
||||
|
||||
```ts
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: "user@example.com",
|
||||
})
|
||||
```
|
||||
@@ -63,12 +63,12 @@ const { success, authIdentity } =
|
||||
})
|
||||
|
||||
// assuming authIdentity is defined
|
||||
const [, count] = await userModuleService.listAndCount({
|
||||
const [, count] = await userModuleService.listAndCountUsers({
|
||||
email: authIdentity.entity_id,
|
||||
})
|
||||
|
||||
if (!count) {
|
||||
const user = await userModuleService.create({
|
||||
const user = await userModuleService.createUsers({
|
||||
email: authIdentity.entity_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,13 +95,11 @@ The Next.js Starter storefront is compatible with Medusa's Stripe and PayPal plu
|
||||
|
||||
### Stripe Integration
|
||||
|
||||
{/* TODO add links to stripe module once supported. */}
|
||||
<Note type="check">
|
||||
|
||||
{/* <Note type="check">
|
||||
- [Stripe provider module](../commerce-modules/payment/payment-provider/stripe/page.mdx) installed in the Medusa application.
|
||||
|
||||
- [Stripe plugin](../plugins/payment/stripe/page.mdx) installed in the Medusa application.
|
||||
|
||||
</Note> */}
|
||||
</Note>
|
||||
|
||||
In your Next.js Starter project, set the following environment variables for the Stripe integration:
|
||||
|
||||
@@ -113,167 +111,6 @@ Where `<YOUR_PUBLISHABLE_KEY>` is your Stripe publishable key. You can retrieve
|
||||
|
||||
Then, restart the Next.js Starter storefront. You can now use Stripe during checkout.
|
||||
|
||||
### PayPal Integration
|
||||
|
||||
{/* TODO add links to paypal module once supported. */}
|
||||
|
||||
{/* <Note type="check">
|
||||
|
||||
- [PayPal plugin](../plugins/payment/paypal/page.mdx) installed in the Medusa application.
|
||||
|
||||
</Note> */}
|
||||
|
||||
In your Next.js Starter project, set the following environment variables for the PayPal integration:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_PAYPAL_CLIENT_ID=<YOUR_CLIENT_ID>
|
||||
```
|
||||
|
||||
Where `<YOUR_CLIENT_ID>` is your PayPal client ID. You can retrieve it from the [PayPal developer dashboard](https://developer.paypal.com/developer/applications/).
|
||||
|
||||
Then, restart the Next.js Starter storefront. You can now use PayPal during checkout.
|
||||
|
||||
---
|
||||
|
||||
## Search Engine
|
||||
|
||||
The Next.js Starter storefront is compatible with Medusa's MeiliSearch and Algolia plugins.
|
||||
|
||||
### Enable Search Engine
|
||||
|
||||
To enable the search engine, change the value of the feature in `store.config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"features": {
|
||||
"search": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, restart your Next.js storefront. You'll see a "Search" link in the navigation bar.
|
||||
|
||||
### Configure MeiliSearch
|
||||
|
||||
{/* TODO add link to meilisearch plugin/module once available. */}
|
||||
|
||||
{/* <Note type="check">
|
||||
|
||||
- [MeiliSearch plugin](../plugins/search/meilisearch/page.mdx) installed in the Medusa application.
|
||||
|
||||
</Note> */}
|
||||
|
||||
In your Next.js Starter storefront, set the following environment variables for the MeiliSearch integration:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_SEARCH_ENDPOINT=<YOUR_MEILISEARCH_URL>
|
||||
NEXT_PUBLIC_INDEX_NAME=products
|
||||
NEXT_PUBLIC_SEARCH_API_KEY=<YOUR_API_KEY>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<YOUR_MEILISEARCH_URL>` is the URL MeiliSearch is running on. The default is `http://127.0.0.1:7700`.
|
||||
- `NEXT_PUBLIC_INDEX_NAME` is the index name of the products in MeiliSearch. By default, it’s `products`.
|
||||
- `<YOUR_API_KEY>` is the API key used to search through MeiliSearch indexes. To create a new API Key, make sure that the MeiliSearch service is running and send the following request:
|
||||
|
||||
```bash
|
||||
curl \
|
||||
-X POST '<MEILISEARCH_URL>/keys' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Authorization: Bearer <MEILISEARCH_MASTER_KEY>' \
|
||||
--data-binary '{
|
||||
"description": "Search products",
|
||||
"actions": ["search"],
|
||||
"indexes": ["products"],
|
||||
"expiresAt": "2024-01-01T00:00:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
Make sure to replace `<MEILISEARCH_URL>` with the URL MeiliSearch is running on and `<MEILISEARCH_MASTER_KEY>` with your MeiliSearch master key.
|
||||
|
||||
Then, restart the Next.js Starter storefront. You can search through available products by clicking the search icon in the navigation bar.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
To make sure the Next.js Starter storefront properly displays the products in the search result, include in the `displayedAttributes` setting of the MeiliSearch plugin on the Medusa application at least the fields `title`, `handle`, `description`, and `thumbnail`.
|
||||
|
||||
</Note>
|
||||
|
||||
### Configure Algolia
|
||||
|
||||
{/* TODO add links to algolia plugin/module once available. */}
|
||||
|
||||
{/* <Note type="check">
|
||||
|
||||
- [Algolia plugin](../plugins/search/algolia/page.mdx) installed in the Medusa application.
|
||||
|
||||
</Note> */}
|
||||
|
||||
In your Next.js Starter's directory, install the `algoliasearch` package:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install algoliasearch
|
||||
```
|
||||
|
||||
Then, set the following environment variables for the Algolia integration:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_SEARCH_APP_ID=<YOUR_APP_ID>
|
||||
NEXT_PUBLIC_SEARCH_API_KEY=<YOUR_SEARCH_API_KEY>
|
||||
NEXT_PUBLIC_INDEX_NAME=products
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<YOUR_APP_ID>` and `<YOUR_SEARCH_API_KEY>` are the Algolia App ID and Algolia Search API Key respectively. You can retrieve them from Algolia by going to [API Keys in your account settings](https://www.algolia.com/account/api-keys/all).
|
||||
- `NEXT_PUBLIC_INDEX_NAME` is the index name of the products in Algolia. By default, it’s `products`.
|
||||
|
||||
Next, change the content of `src/lib/search-client.ts` to the following:
|
||||
|
||||
```ts title="src/lib/search-client.ts"
|
||||
import algoliasearch from "algoliasearch/lite"
|
||||
|
||||
const appId = process.env.NEXT_PUBLIC_SEARCH_APP_ID || ""
|
||||
|
||||
const apiKey = process.env.NEXT_PUBLIC_SEARCH_API_KEY || ""
|
||||
|
||||
export const searchClient = algoliasearch(appId, apiKey)
|
||||
|
||||
export const SEARCH_INDEX_NAME =
|
||||
process.env.NEXT_PUBLIC_INDEX_NAME || "products"
|
||||
```
|
||||
|
||||
And change the content of `src/app/(main)/search/actions.ts` to the following:
|
||||
|
||||
```ts title="src/app/(main)/search/actions.ts"
|
||||
"use server"
|
||||
|
||||
import {
|
||||
searchClient,
|
||||
SEARCH_INDEX_NAME,
|
||||
} from "@lib/search-client"
|
||||
|
||||
/**
|
||||
* Uses MeiliSearch or Algolia to search for a query
|
||||
* @param {string} query - search query
|
||||
*/
|
||||
export async function search(query: string) {
|
||||
const index = searchClient.initIndex(SEARCH_INDEX_NAME)
|
||||
const { hits } = await index.search(query)
|
||||
|
||||
return hits
|
||||
}
|
||||
```
|
||||
|
||||
Then, restart the Next.js Starter storefront. You can search through available products by clicking the search icon in the navigation bar.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
To make sure the Next.js Starter storefront properly displays the products in the search result, include in the `attributesToRetrieve` setting of the Algolia plugin on the Medusa application at least the fields `title`, `handle`, `description`, and `thumbnail`.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Change Medusa Application URL
|
||||
|
||||
@@ -8,6 +8,12 @@ export const metadata = {
|
||||
|
||||
This recipe provides the general steps to implement a B2B store with Medusa.
|
||||
|
||||
<Note type="soon" title="In Development">
|
||||
|
||||
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
In a B2B store, you provide different types of customers with relevant pricing, products, shopping experience, and more.
|
||||
@@ -180,6 +186,12 @@ This is useful in B2B sales, as you often negotiate special prices with each cus
|
||||
|
||||
You can create a B2B module that adds necessary data models to represent a B2B company. Then, you link that company to a customer group. Any customer belonging to that group also belongs to the company, meaning they're a B2B customer.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "!docs!/basics/modules-and-services",
|
||||
@@ -197,50 +209,37 @@ You can create a B2B module that adds necessary data models to represent a B2B c
|
||||
},
|
||||
]} />
|
||||
|
||||
<Card
|
||||
{/* <Card
|
||||
href="!docs!/advanced-development/modules/module-relationships"
|
||||
title="Create Module Relationships"
|
||||
text="Learn how to create a relationship between modules."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
className="mt-1"
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
In this section, you'll create a B2B module that has a `Company` data model. The `Company` data model has a relationship to the `CustomerGroup` data model of the Customer Module.
|
||||
|
||||
Start by creating the `src/modules/b2b` directory.
|
||||
|
||||
Then, create the file `src/modules/b2b/models/company.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/b2b/models/company.ts" highlights={[["23", "", "Field will be used to create a relationship to customer groups."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
```ts title="src/modules/b2b/models/company.ts" highlights={[["8", "", "The property will be used to create a relationship to customer groups."]]}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Entity()
|
||||
export class Company extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
const Company = model.define("company", {
|
||||
id: model.id(),
|
||||
name: model.text(),
|
||||
city: model.text(),
|
||||
country_code: model.text(),
|
||||
customer_group_id: model.text().nullable(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
name: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
city: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
country_code: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
customer_group_id?: string
|
||||
}
|
||||
export default Company
|
||||
```
|
||||
|
||||
This creates a `Company` data model with some relevant fields. Most importantly, it has a `customer_group_id` field. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
|
||||
This creates a `Company` data model with some relevant properties. Most importantly, it has a `customer_group_id` property. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
|
||||
|
||||
Next, create the migration in the file `src/modules/b2b/migrations/Migration20240516081502.ts` with the following content:
|
||||
|
||||
@@ -261,106 +260,22 @@ You can create a B2B module that adds necessary data models to represent a B2B c
|
||||
|
||||
You'll run the migration to reflect the data model in the database after finishing the module definition.
|
||||
|
||||
Before creating the module's main service, create the file `src/types/b2b/index.ts` with some helper types:
|
||||
|
||||
```ts title="src/types/b2b/index.ts"
|
||||
import { CustomerGroupDTO } from "@medusajs/types"
|
||||
|
||||
export type CompanyDTO = {
|
||||
id: string
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
customer_group?: CustomerGroupDTO
|
||||
}
|
||||
|
||||
export type CreateCompanyDTO = {
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can now create the module's main service at `src/modules/b2b/service.ts` with the following content:
|
||||
|
||||
export const mainServiceHighlights = [
|
||||
["42", "relationships", "Implement the relationship to the `CustomerGroup` data model in the Customer Module."],
|
||||
["56", "create", "Implement a create method to create a company."]
|
||||
]
|
||||
Then, create the module's main service at `src/modules/b2b/service.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/b2b/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
|
||||
import { ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { ModuleJoinerConfig, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { Company } from "./models/company"
|
||||
import { CompanyDTO, CreateCompanyDTO } from "../../types/b2b"
|
||||
|
||||
type InjectedDependencies = {
|
||||
companyService: ModulesSdkTypes.InternalModuleService<any>
|
||||
```ts title="src/modules/b2b/service.ts"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import Company from "./models/company"
|
||||
|
||||
class B2bModuleService extends MedusaService({
|
||||
Company,
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
type AllModelsDTO = {
|
||||
Company: {
|
||||
dto: CompanyDTO
|
||||
}
|
||||
}
|
||||
|
||||
class B2bModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
CompanyDTO,
|
||||
AllModelsDTO
|
||||
>(Company, []) {
|
||||
companyService_: ModulesSdkTypes.InternalModuleService<Company>
|
||||
|
||||
constructor({ companyService }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.companyService_ = companyService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "b2bModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: ["company"],
|
||||
args: {
|
||||
entity: Company.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.CUSTOMER,
|
||||
alias: "customer_group",
|
||||
primaryKey: "id",
|
||||
foreignKey: "customer_group_id",
|
||||
args: {
|
||||
methodSuffix: "CustomerGroups",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(data: CreateCompanyDTO): Promise<CompanyDTO> {
|
||||
const company = this.companyService_.create(data)
|
||||
|
||||
return company
|
||||
}
|
||||
}
|
||||
|
||||
export default B2bModuleService
|
||||
```
|
||||
|
||||
This creates a `B2bModuleService` that extends the service factory and implements:
|
||||
|
||||
- The module's relationship to the `CustomerGroup` data model in the Customer Module within the `__joinerConfig` method.
|
||||
- A `create` method to create a company.
|
||||
This creates a `B2bModuleService` that extends the service factory, which generates data-management functionalities for the `Company` data model.
|
||||
|
||||
Next, create the module definition at `src/modules/b2b/index.ts` with the following content:
|
||||
|
||||
@@ -398,7 +313,30 @@ export const mainServiceHighlights = [
|
||||
|
||||
To test out using the B2B Module, you'll add an API route to create a company.
|
||||
|
||||
Start by creating the file `src/workflows/create-company.ts` with the following content:
|
||||
Start by creating the file `src/types/b2b/index.ts` with some helper types:
|
||||
|
||||
```ts title="src/types/b2b/index.ts"
|
||||
import { CustomerGroupDTO } from "@medusajs/types"
|
||||
|
||||
export type CompanyDTO = {
|
||||
id: string
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
customer_group?: CustomerGroupDTO
|
||||
}
|
||||
|
||||
export type CreateCompanyDTO = {
|
||||
name: string
|
||||
city: string
|
||||
country_code: string
|
||||
customer_group_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then, create the file `src/workflows/create-company.ts` with the following content:
|
||||
|
||||
export const workflowHighlights = [
|
||||
["23", "tryToCreateCustomerGroupStep", "This step creates the customer group if its data is passed in the `customer_group` property."],
|
||||
@@ -419,7 +357,7 @@ export const workflowHighlights = [
|
||||
import { CreateCustomerGroupDTO } from "@medusajs/types"
|
||||
import { CompanyDTO, CreateCompanyDTO } from "../types/b2b"
|
||||
import B2bModuleService from "../modules/b2b/service"
|
||||
|
||||
|
||||
export type CreateCompanyWorkflowInput = CreateCompanyDTO & {
|
||||
customer_group?: CreateCustomerGroupDTO
|
||||
}
|
||||
@@ -471,7 +409,7 @@ export const workflowHighlights = [
|
||||
"b2bModuleService"
|
||||
)
|
||||
|
||||
const company = await b2bModuleService.create(
|
||||
const company = await b2bModuleService.createCompany(
|
||||
companyData
|
||||
)
|
||||
|
||||
@@ -498,10 +436,10 @@ export const workflowHighlights = [
|
||||
|
||||
You create a workflow with two steps:
|
||||
|
||||
1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` field.
|
||||
1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` property.
|
||||
2. The second one creates the company.
|
||||
|
||||
Then, create the file `src/api/admin/b2b/company/route.ts` with the following content:
|
||||
Finally, create the file `src/api/admin/b2b/company/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/admin/b2b/company/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
@@ -566,7 +504,7 @@ export const workflowHighlights = [
|
||||
|
||||
</Note>
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
## Add B2B Customers
|
||||
|
||||
@@ -687,18 +625,21 @@ The API route can check if the customer has any group with an associated company
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
For example, create the API route `src/api/store/b2b/check-customer/route.ts` with the following content:
|
||||
|
||||
export const checkCustomerHighlights = [
|
||||
["16", "retrieve", "Retrieve the customer along with its groups."],
|
||||
["20", "list", "List the companies that have a customer group ID matching any of the customer's group IDs."],
|
||||
["25", "", "Return whether there are any companies associated with the customer's groups."]
|
||||
["19", "retrieveCustomer", "Retrieve the customer along with its groups."],
|
||||
["26", "listCompanies", "List the companies that have a customer group ID matching any of the customer's group IDs."],
|
||||
["31", "", "Return whether there are any companies associated with the customer's groups."]
|
||||
]
|
||||
|
||||
```ts title="src/api/store/b2b/check-customer/route.ts" highlights={checkCustomerHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import type { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import B2bModuleService from "../../../../modules/b2b/service"
|
||||
@@ -713,11 +654,14 @@ export const checkCustomerHighlights = [
|
||||
"b2bModuleService"
|
||||
)
|
||||
|
||||
const customer = await customerModuleService.retrieve(req.auth.actor_id, {
|
||||
relations: ["groups"],
|
||||
})
|
||||
const customer = await customerModuleService.retrieveCustomer(
|
||||
req.auth_context.actor_id,
|
||||
{
|
||||
relations: ["groups"],
|
||||
}
|
||||
)
|
||||
|
||||
const companies = await b2bModuleService.list({
|
||||
const companies = await b2bModuleService.listCompanies({
|
||||
customer_group_id: customer.groups.map((group) => group.id),
|
||||
})
|
||||
|
||||
@@ -736,7 +680,10 @@ export const checkCustomerHighlights = [
|
||||
Before using the API route, create the file `src/api/middlewares.ts` with the following content:
|
||||
|
||||
```ts title="src/api/middlewares.ts"
|
||||
import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
|
||||
import {
|
||||
MiddlewaresConfig,
|
||||
authenticate,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
@@ -796,16 +743,12 @@ export const checkCustomerHighlights = [
|
||||
}
|
||||
```
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
## Customize Admin
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon!
|
||||
|
||||
</Note>
|
||||
|
||||
Based on your use case, you may need to customize the Medusa Admin to add new widgets or pages.
|
||||
|
||||
The Medusa Admin plugin can be extended to add widgets, new pages, and setting pages.
|
||||
@@ -851,9 +794,9 @@ Use the publishable API key you associated with your B2B sales channel in the st
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
title: "Storefront Tips",
|
||||
text: "Find tips on developing a custom storefront.",
|
||||
href: "/storefront-development",
|
||||
title: "Storefront Development",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
|
||||
@@ -9,6 +9,12 @@ export const metadata = {
|
||||
|
||||
This recipe provides the general steps to implement a B2B store with Medusa.
|
||||
|
||||
<Note type="soon" title="In Development">
|
||||
|
||||
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
## Overview
|
||||
|
||||
Commerce automation is essential for businesses to save costs, provide a better user experience, and avoid manual, repetitive tasks that lead to human errors. Businesses utilize automation in different domains, including marketing, customer support, and order management.
|
||||
@@ -49,13 +55,6 @@ The `inventory-item.updated` event is currently not emitted.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/advanced-development/modules/module-relationships",
|
||||
title: "Module Relationships",
|
||||
text: "Learn how to create module relationships.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
|
||||
<CardList items={[
|
||||
@@ -86,37 +85,27 @@ The `inventory-item.updated` event is currently not emitted.
|
||||
Then, create the file `src/modules/restock-notification/models/restock-notification.ts` with the following content:
|
||||
|
||||
export const restockModelHighlights = [
|
||||
["14", "email", "The email of the customer to send the notification to when the item is restocked."],
|
||||
["17", "variant_id", "The ID of the variant the customer is subscribed to."],
|
||||
["20", "sales_channel_id", "The ID of the sales channel the customer is viewing the product variant from."]
|
||||
["5", "email", "The email of the customer to send the notification to when the item is restocked."],
|
||||
["6", "variant_id", "The ID of the variant the customer is subscribed to."],
|
||||
["7", "sales_channel_id", "The ID of the sales channel the customer is viewing the product variant from."]
|
||||
]
|
||||
|
||||
```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import {
|
||||
Entity,
|
||||
PrimaryKey,
|
||||
Property,
|
||||
} from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
export class RestockNotification extends BaseEntity {
|
||||
@PrimaryKey({ columnType: "text" })
|
||||
id!: string
|
||||
```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights}
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
email: string
|
||||
const RestockNotification = model.define("restock_notification", {
|
||||
id: model.id(),
|
||||
email: model.text(),
|
||||
variant_id: model.text(),
|
||||
sales_channel_id: model.text(),
|
||||
})
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
variant_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
sales_channel_id: string
|
||||
}
|
||||
export default RestockNotification
|
||||
```
|
||||
|
||||
This creates a `RestockNotification` data model with the following fields:
|
||||
This creates a `RestockNotification` data model with the following properties:
|
||||
|
||||
- `id`: An automatically generated ID.
|
||||
- `email`: The email of the customer to send the notification to when the item is restocked.
|
||||
- `variant_id`: The ID of the variant the customer is subscribed to. This will later be used to form a relationship with the `ProductVariant` data model of the Product Module.
|
||||
- `sales_channel_id`: The ID of the sales channel the customer is viewing the product variant from. This will later be used to form a relationship with the `SalesChannel` data model of the Sales Channel Module.
|
||||
@@ -142,117 +131,23 @@ export const restockModelHighlights = [
|
||||
```
|
||||
|
||||
You'll run the migration to reflect the changes on the database after finishing the module's definition.
|
||||
|
||||
Now, create the file `src/types/restock-notification/index.ts` that holds common types to be used in the module's main service:
|
||||
|
||||
```ts title="src/types/restock-notification/index.ts"
|
||||
import { ProductVariantDTO, SalesChannelDTO } from "@medusajs/types"
|
||||
|
||||
export type RestockNotificationDTO = {
|
||||
id: string
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
variant?: ProductVariantDTO
|
||||
sales_channel?: SalesChannelDTO
|
||||
}
|
||||
|
||||
export type CreateRestockNotificationDTO = {
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Then, create the module's main service at `src/modules/restock-notification/service.ts` with the following content:
|
||||
|
||||
export const restockModuleService = [
|
||||
["17", "abstractModuleServiceFactory", "Extend the service factory to have basic data management features."],
|
||||
["43", "", "Define a relationship to the `ProductVariant` data model of the Product Module."],
|
||||
["52", "", "Define a relationship to the `SalesChannel` data model of the Sales Channel Module."],
|
||||
["61", "create", "Implement the method to create a restock notification."]
|
||||
]
|
||||
```ts title="src/modules/restock-notification/service.ts"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import RestockNotification from "./models/restock-notification"
|
||||
|
||||
```ts title="src/modules/restock-notification/service.ts" highlights={restockModuleService} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import { Modules, ModulesSdkUtils } from "@medusajs/utils"
|
||||
import { RestockNotification } from "./models/restock-notification"
|
||||
import { ModuleJoinerConfig, ModulesSdkTypes } from "@medusajs/types"
|
||||
import { CreateRestockNotificationDTO, RestockNotificationDTO } from "../../types/restock-notification"
|
||||
|
||||
type InjectedDependencies = {
|
||||
restockNotificationService: ModulesSdkTypes.InternalModuleService<any>
|
||||
class RestockNotificationModuleService extends MedusaService({
|
||||
RestockNotification,
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
type AllModelDTOs = {
|
||||
RestockNotification: {
|
||||
dto: RestockNotificationDTO
|
||||
}
|
||||
}
|
||||
|
||||
class RestockNotificationModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
RestockNotificationDTO,
|
||||
AllModelDTOs
|
||||
>(RestockNotification, []) {
|
||||
restockNotificationService_: ModulesSdkTypes.InternalModuleService<RestockNotification>
|
||||
|
||||
constructor({ restockNotificationService }: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.restockNotificationService_ = restockNotificationService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "restockNotificationModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: "restock_notification",
|
||||
args: {
|
||||
entity: RestockNotification.name,
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
alias: "variant",
|
||||
primaryKey: "id",
|
||||
foreignKey: "variant_id",
|
||||
args: {
|
||||
methodSuffix: "Variants",
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceName: Modules.SALES_CHANNEL,
|
||||
alias: "sales_channel",
|
||||
primaryKey: "id",
|
||||
foreignKey: "sales_channel_id",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(data: CreateRestockNotificationDTO): Promise<RestockNotificationDTO> {
|
||||
const restockNotification = await this.restockNotificationService_.create(
|
||||
data
|
||||
)
|
||||
|
||||
return restockNotification
|
||||
}
|
||||
}
|
||||
|
||||
export default RestockNotificationModuleService
|
||||
```
|
||||
|
||||
In the module's main service, you:
|
||||
|
||||
- Extend the service factory to have basic data management features.
|
||||
- Define a relationship to the `ProductVariant` data model of the Product Module.
|
||||
- Define a relationship to the `SalesChannel` data model of the Sales Channel Module.
|
||||
- Implement the `create` method to create a restock notification.
|
||||
The module's main service extends the service factory which generates basic management features for the `RestockNotification` data model.
|
||||
|
||||
Next, create the module's definition file `src/modules/restock-notification/index.ts` with the following content:
|
||||
|
||||
@@ -290,18 +185,19 @@ export const restockModuleService = [
|
||||
|
||||
Create the file `src/api/store/restock-notification/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import RestockNotificationModuleService
|
||||
from "../../../modules/restock-notification/service"
|
||||
import {
|
||||
CreateRestockNotificationDTO,
|
||||
} from "../../../types/restock-notification"
|
||||
|
||||
type RestockNotificationReq = CreateRestockNotificationDTO
|
||||
|
||||
type RestockNotificationReq = {
|
||||
email: string
|
||||
variant_id: string
|
||||
sales_channel_id: string
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest<RestockNotificationReq>,
|
||||
@@ -312,7 +208,7 @@ export const restockModuleService = [
|
||||
"restockNotificationModuleService"
|
||||
)
|
||||
|
||||
await restockNotificationModuleService.create(
|
||||
await restockNotificationModuleService.createRestockNotifications(
|
||||
req.body
|
||||
)
|
||||
|
||||
@@ -326,13 +222,15 @@ export const restockModuleService = [
|
||||
|
||||
### Create Inventory Item Updated Subscriber
|
||||
|
||||
To handle the sending of the restock notifications, create a subscriber that listens to the `inventory-item.updated` event, then sends a notification using the Notification Module to subscribed emails.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
The `inventory-item.updated` event is currently not emitted. The subscriber will only work once the event is emitted.
|
||||
The `inventory-item.updated` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
To handle the sending of the restock notifications, create the file `src/subscribers/inventory-item-update.ts` with the following content:
|
||||
{/* To handle the sending of the restock notifications, create the file `src/subscribers/inventory-item-update.ts` with the following content: */}
|
||||
|
||||
export const subscriberHighlights = [
|
||||
["48", "inventoryVariantLinkService", "Retrieve an instance of the link service for the product-variant-inventory-item link module."],
|
||||
@@ -343,19 +241,19 @@ export const subscriberHighlights = [
|
||||
["93", "salesChannelLocations", "Retrieve the stock locations linked to the restock notification's sales channel."],
|
||||
["107", "availableQuantity", "Retrieve the available quantity of the variant in the retrieved stock locations."],
|
||||
["116", "continue", "Only send the notification if the available quantity is greater than `0`"],
|
||||
["119", "create", "Send the notification to the customer using the Notification Module."],
|
||||
["119", "createNotifications", "Send the notification to the customer using the Notification Module."],
|
||||
["122", '"test_template"', "Replace with the actual template used for sending the email."],
|
||||
["123", "data", "The data to send along to the third-party service sending the notification."],
|
||||
["131", "delete", "Delete the restock notification to not send the notification again."]
|
||||
["131", "deleteRestockNotifications", "Delete the restock notification to not send the notification again."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-23" expandButtonLabel="Show Imports"
|
||||
{/* ```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-20" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
IInventoryServiceNext,
|
||||
IInventoryService,
|
||||
INotificationModuleService,
|
||||
RemoteQueryFunction,
|
||||
} from "@medusajs/types"
|
||||
@@ -364,9 +262,6 @@ export const subscriberHighlights = [
|
||||
Modules,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
RestockNotificationDTO,
|
||||
} from "../types/restock-notification"
|
||||
import {
|
||||
RemoteLink,
|
||||
} from "@medusajs/modules-sdk"
|
||||
@@ -388,7 +283,7 @@ export const subscriberHighlights = [
|
||||
RestockNotificationModuleService = container.resolve(
|
||||
"restockNotificationModuleService"
|
||||
)
|
||||
const inventoryModuleService: IInventoryServiceNext =
|
||||
const inventoryModuleService: IInventoryService =
|
||||
container.resolve(Modules.INVENTORY)
|
||||
const notificationModuleService: INotificationModuleService =
|
||||
container.resolve(
|
||||
@@ -430,7 +325,7 @@ export const subscriberHighlights = [
|
||||
},
|
||||
})
|
||||
|
||||
const restockNotifications: RestockNotificationDTO[] =
|
||||
const restockNotifications =
|
||||
await remoteQuery(restockQuery)
|
||||
|
||||
const salesChannelLocationService = remoteLink.getLinkModule(
|
||||
@@ -468,7 +363,7 @@ export const subscriberHighlights = [
|
||||
continue
|
||||
}
|
||||
|
||||
notificationModuleService.create({
|
||||
notificationModuleService.createNotifications({
|
||||
to: restockNotification.email,
|
||||
channel: "email",
|
||||
template: "test_template",
|
||||
@@ -480,7 +375,8 @@ export const subscriberHighlights = [
|
||||
})
|
||||
|
||||
// delete the restock notification
|
||||
await restockNotificationModuleService.delete(restockNotification.id)
|
||||
await restockNotificationModuleService
|
||||
.deleteRestockNotifications(restockNotification.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +393,7 @@ export const subscriberHighlights = [
|
||||
- Retrieve the restock notifications of those variants.
|
||||
- For each restock notification, you:
|
||||
- Retrieve its quantity based on the stock location associated with the restock notification's sales channel.
|
||||
- If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification.
|
||||
- If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification. */}
|
||||
|
||||
</Details>
|
||||
|
||||
@@ -509,11 +405,11 @@ Customer support is essential to build a store's brand and customer loyalty. Thi
|
||||
|
||||
You can use the Notification Module to send notifications when an action is triggered, such as when a customer or their order is updated.
|
||||
|
||||
<Note title="Tip">
|
||||
{/* <Note title="Tip">
|
||||
|
||||
The [Events reference](../../events-reference/page.mdx) shows an extensive list of events triggered by the each commerce module.
|
||||
|
||||
</Note>
|
||||
</Note> */}
|
||||
|
||||
Medusa also provides Notification Provider Modules that integrate with third-party services, such as SendGrid.
|
||||
|
||||
@@ -564,28 +460,39 @@ Scheduled jobs are coming soon.
|
||||
For example, create the file `src/workflows/sync-products.ts` with the following content:
|
||||
|
||||
export const syncProductsWorkflowHighlight = [
|
||||
["9", "retrieveStoreStep", "A step that retrieves the store by its ID."],
|
||||
["25", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
|
||||
["45", "syncProductsStep", "A step to sync the product with a third-party service."],
|
||||
["48", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
|
||||
["52", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
|
||||
["57", "sync", "Sync the product data in the third-party service."],
|
||||
["61", "", "Pass products data before sync to compensation function."],
|
||||
["64", "", "A compensation function to revert the sync when an error occurs."],
|
||||
["70", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
|
||||
["79", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
|
||||
["85", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
|
||||
["87", "update", "Update the `last_sync_date` of the store."],
|
||||
["95", "", "Pass previous last sync date to compensation function."],
|
||||
["98", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
|
||||
["114", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
|
||||
["20", "retrieveStoreStep", "A step that retrieves the store by its ID."],
|
||||
["36", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
|
||||
["56", "syncProductsStep", "A step to sync the product with a third-party service."],
|
||||
["59", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
|
||||
["63", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
|
||||
["68", "sync", "Sync the product data in the third-party service."],
|
||||
["72", "", "Pass products data before sync to compensation function."],
|
||||
["75", "", "A compensation function to revert the sync when an error occurs."],
|
||||
["81", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
|
||||
["90", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
|
||||
["96", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
|
||||
["98", "update", "Update the `last_sync_date` of the store."],
|
||||
["106", "", "Pass previous last sync date to compensation function."],
|
||||
["109", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
|
||||
["125", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight}
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, IStoreModuleService, ProductDTO, StoreDTO } from "@medusajs/types"
|
||||
import { StepResponse, createStep, createWorkflow } from "@medusajs/workflows-sdk"
|
||||
|
||||
```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
||||
import {
|
||||
ModuleRegistrationName
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
ProductDTO,
|
||||
StoreDTO
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
StepResponse,
|
||||
createStep,
|
||||
createWorkflow
|
||||
} from "@medusajs/workflows-sdk"
|
||||
|
||||
type RetrieveStoreStepInput = {
|
||||
id: string
|
||||
}
|
||||
@@ -596,7 +503,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.retrieve(id)
|
||||
const store = await storeModuleService.retrieveStore(id)
|
||||
|
||||
return new StepResponse({ store })
|
||||
}
|
||||
@@ -612,7 +519,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list({
|
||||
const products = await productModuleService.listProducts({
|
||||
updated_at: {
|
||||
$gt: last_sync_date,
|
||||
},
|
||||
@@ -633,7 +540,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
"productSyncModuleService"
|
||||
)
|
||||
|
||||
const productsBeforeSync = await productSyncModuleService.list({
|
||||
const productsBeforeSync = await productSyncModuleService.listProductSyncs({
|
||||
id: products.map((product) => product.id),
|
||||
})
|
||||
|
||||
@@ -668,7 +575,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
|
||||
const prevLastSyncDate = store.metadata.last_sync_date
|
||||
|
||||
await storeModuleService.update(store.id, {
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_sync_date: (new Date()).toString(),
|
||||
},
|
||||
@@ -683,7 +590,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
await storeModuleService.update(id, {
|
||||
await storeModuleService.updateStores(id, {
|
||||
metadata: {
|
||||
last_sync_date,
|
||||
},
|
||||
@@ -722,7 +629,7 @@ export const syncProductsWorkflowHighlight = [
|
||||
This creates a workflow with the following steps:
|
||||
|
||||
1. Retrieve the store by its ID.
|
||||
2. Retrieve products to update based on the last date and time the products were synced. The last sync date is retrieved from the store's `metadata` field.
|
||||
2. Retrieve products to update based on the last date and time the products were synced. The last sync date is retrieved from the store's `metadata` property.
|
||||
3. Sync the retrieved products with a third-party service. It's assumed that the connection to the third-party service is implemented within a custom module's main service.
|
||||
4. Update the last sync date of the store to the current date.
|
||||
|
||||
@@ -742,7 +649,15 @@ The `order.placed` event is currently not emitted.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
<Card
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
{/* <CardList items={[
|
||||
{
|
||||
href: "!docs!/basics/events-and-subscribers",
|
||||
title: "Create a Subscriber",
|
||||
@@ -757,7 +672,7 @@ The `order.placed` event is currently not emitted.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
]} /> */}
|
||||
|
||||
---
|
||||
|
||||
@@ -786,13 +701,6 @@ Medusa's commerce features are geared towards automating RMA flows and ensuring
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/events-reference",
|
||||
title: "Events Reference",
|
||||
text: "Check out triggered events by each commerce module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
@@ -891,7 +799,7 @@ The `order.placed` event is currently not emitted.
|
||||
}
|
||||
|
||||
// retrieve the order
|
||||
const order = await orderModuleService.retrieve(orderId)
|
||||
const order = await orderModuleService.retrieveOrder(orderId)
|
||||
|
||||
if (!order ||
|
||||
!order.customer_id ||
|
||||
@@ -901,7 +809,7 @@ The `order.placed` event is currently not emitted.
|
||||
return
|
||||
}
|
||||
|
||||
const [, count] = await orderModuleService.listAndCount({
|
||||
const [, count] = await orderModuleService.listAndCountOrders({
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
|
||||
@@ -948,13 +856,6 @@ Scheduled jobs are coming soon.
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/events-reference",
|
||||
title: "Events Reference",
|
||||
text: "Check out triggered events in each commerce module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/basics/scheduled-jobs",
|
||||
title: "Scheduled Jobs",
|
||||
@@ -969,18 +870,18 @@ Scheduled jobs are coming soon.
|
||||
For example, create the file `src/subscribers/send-products-newsletter.ts` with the following content:
|
||||
|
||||
export const newsletterHighlights = [
|
||||
["35", "store", "Retrieve the first store in the application."],
|
||||
["37", "products", "Retrieve the products created since the last newsletter send date."],
|
||||
["43", "", "Check whether more than 10 products have been created before proceeding."],
|
||||
["47", "customers", "Retrieve all customers, assuming they're considered subscribed."],
|
||||
["49", "create", "Send a notification (newsletter) to each customer using the Notification Module."],
|
||||
["52", '"email"', "Send the notification through the email channel."],
|
||||
["53", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
|
||||
["55", "products", "Pass the created products to the template."],
|
||||
["60", "update", "Update the store's `last_newsletter_send_date` field with the current date."]
|
||||
["33", "store", "Retrieve the first store in the application."],
|
||||
["35", "products", "Retrieve the products created since the last newsletter send date."],
|
||||
["41", "", "Check whether more than 10 products have been created before proceeding."],
|
||||
["45", "customers", "Retrieve all customers, assuming they're considered subscribed."],
|
||||
["47", "createNotifications", "Send a notification (newsletter) to each customer using the Notification Module."],
|
||||
["50", '"email"', "Send the notification through the email channel."],
|
||||
["51", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
|
||||
["53", "products", "Pass the created products to the template."],
|
||||
["58", "updateStores", "Update the store's `last_newsletter_send_date` property with the current date."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-16" expandButtonLabel="Show Imports"
|
||||
```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-14" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
@@ -988,13 +889,11 @@ export const newsletterHighlights = [
|
||||
import {
|
||||
ModuleRegistrationName,
|
||||
} from "@medusajs/modules-sdk"
|
||||
import {
|
||||
NotificationModuleService,
|
||||
} from "@medusajs/notification"
|
||||
import {
|
||||
ICustomerModuleService,
|
||||
IProductModuleService,
|
||||
IStoreModuleService,
|
||||
INotificationModuleService,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default async function productCreateHandler({
|
||||
@@ -1011,13 +910,13 @@ export const newsletterHighlights = [
|
||||
container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const notificationModuleService:
|
||||
NotificationModuleService = container.resolve(
|
||||
INotificationModuleService = container.resolve(
|
||||
ModuleRegistrationName.NOTIFICATION
|
||||
)
|
||||
|
||||
const store = (await storeModuleService.list())[0]
|
||||
const store = (await storeModuleService.listStores())[0]
|
||||
|
||||
const products = await productModuleService.list({
|
||||
const products = await productModuleService.listProducts({
|
||||
created_at: {
|
||||
$gt: store.metadata.last_newsletter_send_date,
|
||||
},
|
||||
@@ -1027,9 +926,9 @@ export const newsletterHighlights = [
|
||||
return
|
||||
}
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
const customers = await customerModuleService.listCustomers()
|
||||
|
||||
await notificationModuleService.create(
|
||||
await notificationModuleService.createNotifications(
|
||||
customers.map((customer) => ({
|
||||
to: customer.email,
|
||||
channel: "email",
|
||||
@@ -1040,7 +939,7 @@ export const newsletterHighlights = [
|
||||
}))
|
||||
)
|
||||
|
||||
await storeModuleService.update(store.id, {
|
||||
await storeModuleService.updateStores(store.id, {
|
||||
metadata: {
|
||||
last_newsletter_send_date: (new Date()).toString(),
|
||||
},
|
||||
@@ -1055,7 +954,7 @@ export const newsletterHighlights = [
|
||||
In the subscriber function, you:
|
||||
|
||||
1. Retrieve the first store in our application.
|
||||
2. Retrieve products created since the last time a newsletter is sent. The last send date is stored in the store's `metadata` field.
|
||||
2. Retrieve products created since the last time a newsletter is sent. The last send date is stored in the store's `metadata` property.
|
||||
3. If the count of last created products is less than 10, stop execution.
|
||||
4. Retrieve all customers. Here, it's assumed that all customers are considered subscribed for simplicity.
|
||||
5. Use the Notification Module to send a notification to the customer. This uses the Notification Provider Module configured for the `email` channel.
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -102,7 +102,7 @@ export const serviceHighlights = [
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
You can store the product's ID in the external system using the `metadata` field of the `Product` data model in the Product Module. Alternatively, you can create a [data model](!docs!/basics/data-models) in your module to store data related to the external system.
|
||||
You can store the product's ID in the external system using the `metadata` property of the `Product` data model in the Product Module. Alternatively, you can create a [data model](!docs!/basics/data-models) in your module to store data related to the external system.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -203,13 +203,13 @@ export const workflowHighlights = [
|
||||
.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const createdProductData = await productModuleService
|
||||
.retrieve(productId)
|
||||
.retrieveProduct(productId)
|
||||
|
||||
const erpProduct = await erpModuleService.createProduct(
|
||||
createdProductData
|
||||
)
|
||||
|
||||
await productModuleService.update(productId, {
|
||||
await productModuleService.updateProducts(productId, {
|
||||
metadata: {
|
||||
erp_id: erpProduct.id
|
||||
}
|
||||
@@ -229,7 +229,7 @@ export const workflowHighlights = [
|
||||
.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
await erpModuleService.deleteProduct(erpId)
|
||||
await productModuleService.update(productId, {
|
||||
await productModuleService.updateProducts(productId, {
|
||||
metadata: {}
|
||||
})
|
||||
})
|
||||
@@ -249,7 +249,7 @@ export const workflowHighlights = [
|
||||
|
||||
- Retrieves the product's data using the Product Module's main service.
|
||||
- Create the product in the ERP system using the ERP Module's main service.
|
||||
- Updates the product in Medusa by setting the ID of the ERP product in the product's `metadata` field.
|
||||
- Updates the product in Medusa by setting the ID of the ERP product in the product's `metadata` property.
|
||||
|
||||
The step also has a compensation function that rolls back changes when an error occurs. It deletes the product in the ERP system and removes the ID of the ERP product in the Medusa product.
|
||||
|
||||
@@ -334,7 +334,7 @@ For example, suppose an administrator changes the product data in the ERP system
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
await productService.update(id, updatedData)
|
||||
await productService.updateProducts(id, updatedData)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
|
||||
@@ -34,16 +34,22 @@ In a marketplace, an admin user has a store where they manage their products and
|
||||
|
||||
Create a Marketplace Module that holds and manages these relationships.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/modules-and-services",
|
||||
href: "!docs!/basics/modules-and-services",
|
||||
title: "Create a Module",
|
||||
text: "Learn how to create a module",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/module-relationships",
|
||||
href: "!docs!/advanced-development/modules/module-relationships",
|
||||
title: "Module Relationships",
|
||||
text: "Create relationships between modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -60,80 +66,57 @@ Create a Marketplace Module that holds and manages these relationships.
|
||||
Then, create the file `src/modules/marketplace/models/store-user.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-user.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreUser extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
user_id: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreUser = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
user_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreUser
|
||||
```
|
||||
|
||||
This creates a `StoreUser` data model with the `store_id` and `user_id` fields. These fields will be used later to establish relationships to the Store and User modules.
|
||||
This creates a `StoreUser` data model with the `store_id` and `user_id` properties.
|
||||
|
||||
{/* These properties will be used later to establish relationships to the Store and User modules. */}
|
||||
|
||||
Next, create the file `src/modules/marketplace/models/store-product.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-product.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreProduct extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
product_id: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreProduct = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
product_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreProduct
|
||||
```
|
||||
|
||||
This creates a `StoreProduct` data model with the `store_id` and `product_id` fields. These fields will be used later to establish relationships to the Store and Product modules.
|
||||
This creates a `StoreProduct` data model with the `store_id` and `product_id` properties.
|
||||
|
||||
{/* These properties will be used later to establish relationships to the Store and Product modules. */}
|
||||
|
||||
Finally, create the file `src/modules/marketplace/models/store-order.ts` with the following content:
|
||||
|
||||
```ts title="src/modules/marketplace/models/store-order.ts"
|
||||
import { BaseEntity } from "@medusajs/utils"
|
||||
import { Entity, PrimaryKey, Property } from "@mikro-orm/core"
|
||||
|
||||
@Entity()
|
||||
class StoreOrder extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
store_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
order_id: string
|
||||
|
||||
@Property({ columnType: "text" })
|
||||
parent_order_id?: string
|
||||
}
|
||||
|
||||
import { model } from "@medusajs/utils"
|
||||
|
||||
const StoreOrder = model.define("store_user", {
|
||||
id: model.id(),
|
||||
store_id: model.text(),
|
||||
order_id: model.text(),
|
||||
parent_order_id: model.text(),
|
||||
})
|
||||
|
||||
export default StoreOrder
|
||||
```
|
||||
|
||||
This creates a `StoreOrder` data model with the `store_id`, `order_id`, and `parent_order_id` fields. The `store_id` and `order_id` fields will be used to establish relationships to the Store and Order modules. You’ll learn about the use of `parent_order_id` in a later section.
|
||||
This creates a `StoreOrder` data model with the `store_id`, `order_id`, and `parent_order_id` properties.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
You can add relationships to data models of other modules in a similar manner.
|
||||
|
||||
</Note>
|
||||
{/* The `store_id` and `order_id` properties will be used to establish relationships to the Store and Order modules. You’ll learn about the use of `parent_order_id` in a later section. */}
|
||||
|
||||
To reflect these changes on the database, create the migration `src/modules/marketplace/migrations/Migration20240514143248.ts` with the following content:
|
||||
|
||||
@@ -159,244 +142,34 @@ Create a Marketplace Module that holds and manages these relationships.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You’ll run the migration after registering the module in the Medusa configurations.
|
||||
|
||||
Next, you’ll create types that you’ll use throughout your customizations.
|
||||
|
||||
Create the file `src/types/marketplace/index.ts` with the following content:
|
||||
|
||||
```ts title="src/types/marketplace/index.ts"
|
||||
import {
|
||||
StoreDTO,
|
||||
UserDTO,
|
||||
ProductDTO,
|
||||
OrderDTO,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export type StoreUserDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
user_id: string
|
||||
store?: StoreDTO
|
||||
user?: UserDTO
|
||||
}
|
||||
|
||||
export type StoreProductDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
product_id: string
|
||||
store?: StoreDTO
|
||||
product?: ProductDTO
|
||||
}
|
||||
|
||||
export type StoreOrderDTO = {
|
||||
id: string
|
||||
store_id: string
|
||||
order_id: string
|
||||
parent_order_id?: string
|
||||
store?: StoreDTO
|
||||
order?: OrderDTO
|
||||
}
|
||||
|
||||
export type CreateStoreUserDTO = {
|
||||
store_id: string
|
||||
user_id: string
|
||||
}
|
||||
|
||||
export type CreateStoreProductDTO = {
|
||||
store_id: string
|
||||
product_id: string
|
||||
}
|
||||
|
||||
export type CreateStoreOrderDTO = {
|
||||
store_id: string
|
||||
order_id: string
|
||||
parent_order_id?: string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
Then, create the module’s main service at `src/modules/marketplace/service.ts` with the following content:
|
||||
|
||||
export const mainServiceHighlights = [
|
||||
["48", "abstractModuleServiceFactory", "Extends the service factory to generate basic data management features."],
|
||||
["102", "relationships", "Defines relationships to the Store, User, Product, and Order modules."],
|
||||
["131", "create", "Method to create a `StoreUser`."],
|
||||
["141", "createStoreProduct", "Method to create a `StoreProduct`."],
|
||||
["150", "createStoreOrder", "Method to create a `StoreOrder`."]
|
||||
["6", "MedusaService", "Extends the service factory to generate data management features."]
|
||||
]
|
||||
|
||||
```ts title="src/modules/marketplace/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-17" expandButtonLabel="Show Imports"
|
||||
import { ModulesSdkUtils, Modules } from "@medusajs/utils"
|
||||
```ts title="src/modules/marketplace/service.ts" highlights={mainServiceHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
|
||||
import { MedusaService } from "@medusajs/utils"
|
||||
import StoreUser from "./models/store-user"
|
||||
import StoreProduct from "./models/store-product"
|
||||
import StoreOrder from "./models/store-order"
|
||||
import {
|
||||
CreateStoreOrderDTO,
|
||||
CreateStoreProductDTO,
|
||||
CreateStoreUserDTO,
|
||||
StoreOrderDTO,
|
||||
StoreProductDTO,
|
||||
StoreUserDTO,
|
||||
} from "../../types/marketplace"
|
||||
import {
|
||||
ModuleJoinerConfig,
|
||||
ModulesSdkTypes,
|
||||
} from "@medusajs/types"
|
||||
|
||||
type InjectedDependencies = {
|
||||
storeUserService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
storeProductService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
storeOrderService: ModulesSdkTypes.InternalModuleService<
|
||||
any
|
||||
>
|
||||
}
|
||||
|
||||
type AllModelsDTO = {
|
||||
StoreUser: {
|
||||
dto: StoreUserDTO
|
||||
},
|
||||
StoreProduct: {
|
||||
dto: StoreProductDTO
|
||||
},
|
||||
StoreOrder: {
|
||||
dto: StoreOrderDTO
|
||||
}
|
||||
}
|
||||
|
||||
const generateMethodsFor = [
|
||||
|
||||
class MarketplaceModuleService extends MedusaService({
|
||||
StoreUser,
|
||||
StoreProduct,
|
||||
StoreOrder,
|
||||
]
|
||||
|
||||
class MarketplaceModuleService extends ModulesSdkUtils
|
||||
.abstractModuleServiceFactory<
|
||||
InjectedDependencies,
|
||||
StoreUserDTO,
|
||||
AllModelsDTO
|
||||
>(
|
||||
StoreUser, generateMethodsFor
|
||||
) {
|
||||
storeUserService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreUser
|
||||
>
|
||||
storeProductService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreProduct
|
||||
>
|
||||
storeOrderService_: ModulesSdkTypes.InternalModuleService<
|
||||
StoreOrder
|
||||
>
|
||||
|
||||
constructor({
|
||||
storeUserService,
|
||||
storeProductService,
|
||||
storeOrderService,
|
||||
}: InjectedDependencies) {
|
||||
// @ts-ignore
|
||||
super(...arguments)
|
||||
this.storeUserService_ = storeUserService
|
||||
this.storeProductService_ = storeProductService
|
||||
this.storeOrderService_ = storeOrderService
|
||||
}
|
||||
|
||||
__joinerConfig(): ModuleJoinerConfig {
|
||||
return {
|
||||
serviceName: "marketplaceModuleService",
|
||||
alias: [
|
||||
{
|
||||
name: ["store_user"],
|
||||
args: {
|
||||
entity: StoreUser.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["store_product"],
|
||||
args: {
|
||||
entity: StoreProduct.name,
|
||||
methodSuffix: "StoreProducts",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ["store_order"],
|
||||
args: {
|
||||
entity: StoreOrder.name,
|
||||
methodSuffix: "StoreOrders",
|
||||
},
|
||||
},
|
||||
],
|
||||
relationships: [
|
||||
{
|
||||
serviceName: Modules.STORE,
|
||||
alias: "store",
|
||||
primaryKey: "id",
|
||||
foreignKey: "store_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.USER,
|
||||
alias: "user",
|
||||
primaryKey: "id",
|
||||
foreignKey: "user_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.PRODUCT,
|
||||
alias: "product",
|
||||
primaryKey: "id",
|
||||
foreignKey: "product_id",
|
||||
},
|
||||
{
|
||||
serviceName: Modules.ORDER,
|
||||
alias: "order",
|
||||
primaryKey: "id",
|
||||
foreignKey: "order_id",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
data: CreateStoreUserDTO
|
||||
): Promise<StoreUserDTO> {
|
||||
const storeUser = await this.storeUserService_.create(
|
||||
data
|
||||
)
|
||||
|
||||
return storeUser
|
||||
}
|
||||
|
||||
async createStoreProduct(
|
||||
data: CreateStoreProductDTO
|
||||
): Promise<StoreProductDTO> {
|
||||
const storeProduct = await this.storeProductService_
|
||||
.create(data)
|
||||
|
||||
return storeProduct
|
||||
}
|
||||
|
||||
async createStoreOrder(
|
||||
data: CreateStoreOrderDTO
|
||||
): Promise<StoreOrderDTO> {
|
||||
const storeOrder = await this.storeOrderService_
|
||||
.create(data)
|
||||
|
||||
return storeOrder
|
||||
}
|
||||
}
|
||||
|
||||
}){
|
||||
// TODO add custom methods
|
||||
}
|
||||
|
||||
export default MarketplaceModuleService
|
||||
```
|
||||
|
||||
The module’s main service:
|
||||
|
||||
- Extends the service factory to generate basic data management features.
|
||||
- Defines relationships to the Store, User, Product, and Order modules.
|
||||
- Defines `create` methods for the `StoreUser`, `StoreProduct`, and `StoreOrder` data models.
|
||||
The module’s main service extends the service factory to generate data management features for the `StoreUser`, `StoreProduct`, and `StoreOrder` data models.
|
||||
|
||||
Finally, create the module definition at `src/modules/marketplace/index.ts` with the following content:
|
||||
|
||||
@@ -439,39 +212,49 @@ export const mainServiceHighlights = [
|
||||
|
||||
To attach admin users to their own stores, create a subscriber that listens to the `user.created` event and attaches the user to the store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
- The `user.created` event is currently not emitted.
|
||||
- Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/user-created.ts` with the following content:
|
||||
|
||||
export const userSubscriberHighlights = [
|
||||
["10", "", "The event data payload with the created user's ID."],
|
||||
["24", "", "Retrieve the created user."],
|
||||
["26", "", "Create a store for that user using the Store Module."],
|
||||
["30", "", "Create a relationship between the user and the store by creating a `StoreUser` record."]
|
||||
["13", "data", "The event data payload with the created user's ID."],
|
||||
["27", "retrieveUser", "Retrieve the created user."],
|
||||
["29", "createStores", "Create a store for that user using the Store Module."],
|
||||
["33", "createStoreUsers", "Create a relationship between the user and the store by creating a `StoreUser` record."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/user-created.ts" highlights={userSubscriberHighlights} collapsibleLines="1-8" expandButtonLabel="Show Imports"
|
||||
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/medusa"
|
||||
```ts title="src/subscribers/user-created.ts" highlights={userSubscriberHighlights} collapsibleLines="1-11" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IUserModuleService,
|
||||
IStoreModuleService,
|
||||
} from "@medusajs/types"
|
||||
import MarketplaceModuleService from "../modules/marketplace/service"
|
||||
|
||||
|
||||
export default async function userCreatedHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || { data }
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const userModuleService: IUserModuleService = container.resolve(
|
||||
ModuleRegistrationName.USER
|
||||
)
|
||||
@@ -481,21 +264,21 @@ export const userSubscriberHighlights = [
|
||||
const marketplaceModuleService: MarketplaceModuleService = container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
const user = await userModuleService.retrieve(id)
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
|
||||
const user = await userModuleService.retrieveUser(id)
|
||||
|
||||
const store = await storeModuleService.createStores({
|
||||
name: `${user.first_name}'s Store`,
|
||||
})
|
||||
|
||||
const storeUser = await marketplaceModuleService.create({
|
||||
const storeUser = await marketplaceModuleService.createStoreUsers({
|
||||
store_id: store.id,
|
||||
user_id: user.id,
|
||||
})
|
||||
|
||||
|
||||
console.log(`Created StoreUser ${storeUser.id}`)
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "user.created",
|
||||
}
|
||||
@@ -515,7 +298,7 @@ export const userSubscriberHighlights = [
|
||||
|
||||
At the end of the output, you should see the message `Created StoreUser {store_user_id}` where the `{store_user_id}` is the ID of the created `StoreUser`.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
@@ -523,37 +306,47 @@ export const userSubscriberHighlights = [
|
||||
|
||||
Similar to the previous section, to attach products to stores, create a subscriber that listens to the `product.created` event. In the subscriber, you attach the product to the store it’s created in.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
- The `product.created` event is currently not emitted.
|
||||
- Module Relationships is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/product-created.ts` with the following content:
|
||||
|
||||
export const productSubscriberHighlights = [
|
||||
["21", "", "Retrieve the created product."],
|
||||
["23", "", "This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution."],
|
||||
["27", "", "Create a relationship between the product and the store by creating a `StoreProduct` record."]
|
||||
["24", "retrieveProduct", "Retrieve the created product."],
|
||||
["26", "", "This subscriber requires the store ID to be set in `product.metadata.store_id`. If not, the subscriber ends execution."],
|
||||
["30", "createStoreProducts", "Create a relationship between the product and the store by creating a `StoreProduct` record."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={productSubscriberHighlights} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/medusa"
|
||||
```ts title="src/subscribers/product-created.ts" highlights={productSubscriberHighlights} collapsibleLines="1-10" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
SubscriberArgs,
|
||||
SubscriberConfig,
|
||||
} from "@medusajs/medusa"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import {
|
||||
IProductModuleService,
|
||||
} from "@medusajs/types"
|
||||
import MarketplaceModuleService from "../modules/marketplace/service"
|
||||
|
||||
|
||||
export default async function productCreateHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || data
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const productModuleService: IProductModuleService = container.resolve(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
@@ -561,19 +354,19 @@ export const productSubscriberHighlights = [
|
||||
const marketplaceModuleService: MarketplaceModuleService = container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
const product = await productModuleService.retrieve(id)
|
||||
|
||||
|
||||
const product = await productModuleService.retrieveProduct(id)
|
||||
|
||||
if (!product.metadata?.store_id) {
|
||||
return
|
||||
}
|
||||
|
||||
await marketplaceModuleService.createStoreProduct({
|
||||
|
||||
await marketplaceModuleService.createStoreProducts({
|
||||
store_id: product.metadata.store_id as string,
|
||||
product_id: id,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "product.created",
|
||||
}
|
||||
@@ -607,7 +400,7 @@ export const productSubscriberHighlights = [
|
||||
|
||||
This returns the created product. In the next section, you’ll implement the API route to fetch the products of a store.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
@@ -616,16 +409,22 @@ export const productSubscriberHighlights = [
|
||||
|
||||
To allow admin users to view their store’s products, create an API route that uses the remote query to fetch the products based on the logged-in user’s store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Retrieving module relationship data using the remote query is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/api-routes",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "API Routes",
|
||||
text: "Learn how to create an API Route in Medusa.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/remote-query",
|
||||
href: "!docs!/advanced-development/modules/remote-query",
|
||||
title: "Remote Query",
|
||||
text: "Use the remote query to fetch data across modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -633,14 +432,14 @@ To allow admin users to view their store’s products, create an API route that
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/api/admin/marketplace/products/route.ts` with the following content:
|
||||
|
||||
export const productRoutesHighlights = [
|
||||
["26", "", "Retrieve the store of the logged-in user."],
|
||||
["37", "", "Build a query that retrieves the products of that store."],
|
||||
["49", "", "Retrieve the products using remote query."]
|
||||
["26", "storeUsers", "Retrieve the store of the logged-in user."],
|
||||
["38", "query", "Build a query that retrieves the products of that store."],
|
||||
["50", "remoteQuery", "Retrieve the products using remote query."]
|
||||
]
|
||||
|
||||
```ts title="src/api/admin/marketplace/products/route.ts" highlights={productRoutesHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
@@ -656,7 +455,7 @@ export const productRoutesHighlights = [
|
||||
} from "@medusajs/utils"
|
||||
import MarketplaceModuleService
|
||||
from "../../../../modules/marketplace/service"
|
||||
|
||||
|
||||
export async function GET(
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
@@ -668,18 +467,19 @@ export const productRoutesHighlights = [
|
||||
const remoteQuery: RemoteQueryFunction = req.scope.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY
|
||||
)
|
||||
|
||||
const storeUsers = await marketplaceModuleService.list({
|
||||
user_id: req.auth.actor_id,
|
||||
})
|
||||
|
||||
|
||||
const storeUsers = await marketplaceModuleService
|
||||
.listStoreUsers({
|
||||
user_id: req.auth_context.actor_id,
|
||||
})
|
||||
|
||||
if (!storeUsers.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"This user doesn't have an associated store."
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "store_product",
|
||||
fields: [
|
||||
@@ -691,9 +491,9 @@ export const productRoutesHighlights = [
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
|
||||
res.json({
|
||||
store_id: storeUsers[0].store_id,
|
||||
products: result.map((data) => data.product),
|
||||
@@ -741,39 +541,39 @@ export const productRoutesHighlights = [
|
||||
|
||||
This will return the product you created in the previous section, if the `{jwt_token}` belongs to the user of the same store ID.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Split Orders Based on Stores
|
||||
|
||||
An order may contain items from different stores. To ensure that users can only view and manage their orders, create a subscriber that listens to the `order.placed` event and handles splitting the order into multiple orders based on the items’ stores.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
While this section showcases the implementation, the `order.placed` event is still not emitted in Medusa V2.
|
||||
The `order.placed` event is still not emitted in Medusa V2.
|
||||
|
||||
</Note>
|
||||
|
||||
An order may contain items from different stores. To ensure that users can only view and manage their orders, create a subscriber that listens to the `order.placed` event and handles splitting the order into multiple orders based on the items’ stores.
|
||||
|
||||
<Card
|
||||
href="/v2/basics/events-and-subscribers"
|
||||
href="!docs!/basics/events-and-subscribers"
|
||||
title="Create a Subscriber"
|
||||
text="Learn how to create a subscriber in Medusa."
|
||||
startIcon={<AcademicCapSolid />}
|
||||
showLinkIcon={false}
|
||||
/>
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/subscribers/order-created.ts` with the following content:
|
||||
|
||||
export const orderSubscriberHighlights = [
|
||||
["35", "", "Loop over the created order’s items."],
|
||||
["57", "", "Group the items by their store ID."],
|
||||
["72", "", "If the items have the same store ID, then associate the created order with the store."],
|
||||
["88", "", "If there are items from more than one store in the order, create child orders and associate each of them with the store."],
|
||||
["91", "parent_order_id", "The `parent_order_id` field in the `StoreOrder` data model points to the original order."]
|
||||
["72", "createStoreOrders", "If the items have the same store ID, then associate the created order with the store."],
|
||||
["88", "createStoreOrders", "If there are items from more than one store in the order, create child orders and associate each of them with the store."],
|
||||
["91", "parent_order_id", "The `parent_order_id` property in the `StoreOrder` data model points to the original order."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/order-created.ts" highlights={orderSubscriberHighlights} collapsibleLines="1-13" expandButtonLabel="Show Imports"
|
||||
@@ -789,12 +589,12 @@ export const orderSubscriberHighlights = [
|
||||
import MarketplaceModuleService
|
||||
from "../modules/marketplace/service"
|
||||
import { createOrdersWorkflow } from "@medusajs/core-flows"
|
||||
|
||||
|
||||
export default async function orderCreatedHandler({
|
||||
data,
|
||||
container,
|
||||
}: SubscriberArgs<{ id: string }>) {
|
||||
const { id } = data.data || data
|
||||
const { id } = "data" in data ? data.data : data
|
||||
const orderModuleService: IOrderModuleService =
|
||||
container.resolve(
|
||||
ModuleRegistrationName.ORDER
|
||||
@@ -804,23 +604,23 @@ export const orderSubscriberHighlights = [
|
||||
container.resolve(
|
||||
"marketplaceModuleService"
|
||||
)
|
||||
|
||||
|
||||
const storeToOrders: Record<string, CreateOrderDTO> = {}
|
||||
|
||||
const order = await orderModuleService.retrieve(id, {
|
||||
|
||||
const order = await orderModuleService.retrieveOrder(id, {
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
|
||||
await Promise.all(order.items?.map(async (item) => {
|
||||
const storeProduct = await marketplaceModuleService
|
||||
.listStoreProducts({
|
||||
product_id: item.product_id,
|
||||
})
|
||||
|
||||
|
||||
if (!storeProduct.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const storeId = storeProduct[0].store_id
|
||||
|
||||
if (!storeToOrders[storeId]) {
|
||||
@@ -830,32 +630,32 @@ export const orderSubscriberHighlights = [
|
||||
items: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const { id, ...itemDetails } = item
|
||||
|
||||
|
||||
storeToOrders[storeId].items.push(itemDetails)
|
||||
}))
|
||||
|
||||
|
||||
const storeToOrdersKeys = Object.keys(storeToOrders)
|
||||
|
||||
|
||||
if (!storeToOrdersKeys.length) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
storeToOrdersKeys.length === 1 &&
|
||||
storeToOrders[0].items.length === order.items.length
|
||||
) {
|
||||
// The order is composed of items from one store, so
|
||||
// associate the order as-is with the store.
|
||||
await marketplaceModuleService.createStoreOrder({
|
||||
await marketplaceModuleService.createStoreOrders({
|
||||
store_id: storeToOrdersKeys[0],
|
||||
order_id: order.id,
|
||||
})
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// create store orders for each child order
|
||||
await Promise.all(
|
||||
storeToOrdersKeys.map(async (storeId) => {
|
||||
@@ -863,8 +663,8 @@ export const orderSubscriberHighlights = [
|
||||
.run({
|
||||
input: storeToOrders[storeId],
|
||||
})
|
||||
|
||||
await marketplaceModuleService.createStoreOrder({
|
||||
|
||||
await marketplaceModuleService.createStoreOrders({
|
||||
store_id: storeId,
|
||||
order_id: result.id,
|
||||
parent_order_id: order.id,
|
||||
@@ -872,7 +672,7 @@ export const orderSubscriberHighlights = [
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const config: SubscriberConfig = {
|
||||
event: "order.placed",
|
||||
}
|
||||
@@ -883,13 +683,13 @@ export const orderSubscriberHighlights = [
|
||||
- Loop over the created order’s items.
|
||||
- Group the items by their store ID.
|
||||
- If the items have the same store ID, then associate the created order with the store.
|
||||
- If there are items from more than one store in the order, create child orders and associate each of them with the store. Here, you use the `parent_order_id` field in the `StoreOrder` data model to point to the original order.
|
||||
- If there are items from more than one store in the order, create child orders and associate each of them with the store. Here, you use the `parent_order_id` property in the `StoreOrder` data model to point to the original order.
|
||||
|
||||
To test this out, create an order in your store. That will run the subscriber and create the child orders.
|
||||
|
||||
The next section covers how to retrieve the store’s orders.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
|
||||
---
|
||||
@@ -898,16 +698,22 @@ export const orderSubscriberHighlights = [
|
||||
|
||||
Similar to products, to allow admin users to view their store’s orders, create an API route that uses the remote query to fetch the orders based on the logged-in user’s store.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Retrieving module relationship data using the remote query is coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "/v2/basics/api-routes",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "API Routes",
|
||||
text: "Learn how to create an API Route in Medusa.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "/v2/advanced-development/modules/remote-query",
|
||||
href: "!docs!/advanced-development/modules/remote-query",
|
||||
title: "Remote Query",
|
||||
text: "Use the remote query to fetch data across modules.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -915,7 +721,7 @@ Similar to products, to allow admin users to view their store’s orders, create
|
||||
},
|
||||
]} />
|
||||
|
||||
<Details summaryContent="Example">
|
||||
{/* <Details summaryContent="Example">
|
||||
|
||||
Create the file `src/api/admin/marketplace/orders/route.ts` with the following content:
|
||||
|
||||
@@ -937,7 +743,7 @@ export const orderRoutesHighlights = [
|
||||
} from "@medusajs/utils"
|
||||
import MarketplaceModuleService
|
||||
from "../../../../modules/marketplace/service"
|
||||
|
||||
|
||||
export async function GET(
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
@@ -949,11 +755,11 @@ export const orderRoutesHighlights = [
|
||||
const remoteQuery: RemoteQueryFunction = req.scope.resolve(
|
||||
ContainerRegistrationKeys.REMOTE_QUERY
|
||||
)
|
||||
|
||||
const storeUsers = await marketplaceModuleService.list({
|
||||
user_id: req.auth.actor_id,
|
||||
|
||||
const storeUsers = await marketplaceModuleService.listStoreUsers({
|
||||
user_id: req.auth_context.actor_id,
|
||||
})
|
||||
|
||||
|
||||
const query = remoteQueryObjectFromString({
|
||||
entryPoint: "store_order",
|
||||
fields: [
|
||||
@@ -965,14 +771,13 @@ export const orderRoutesHighlights = [
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
|
||||
res.json({
|
||||
orders: result.map((data) => data.order),
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
This creates a `GET` API route at `/admin/marketplace/orders`. In the API route, you:
|
||||
@@ -990,7 +795,7 @@ export const orderRoutesHighlights = [
|
||||
|
||||
This will return the orders you created in the previous section if the `{jwt_token}` belongs to the user of the same store ID.
|
||||
|
||||
</Details>
|
||||
</Details> */}
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -114,15 +114,11 @@ Using the tax-inclusive feature, merchants can specify prices including taxes pe
|
||||
}
|
||||
},
|
||||
{
|
||||
href: "#",
|
||||
href: "/storefront-development/products/price",
|
||||
title: "Display Product Price in Storefront",
|
||||
text: "Learn how to display the correct product price in a storefront.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false,
|
||||
badge: {
|
||||
variant: "blue",
|
||||
children: "Guide Soon"
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ You also have freedom in how you choose to implement the storefront, allowing yo
|
||||
|
||||
## Store Personalized Data
|
||||
|
||||
The Cart Module's `LineItem` data model has a `metadata` field that holds any custom data. You can pass the customer's customization in that field when adding a product to the cart.
|
||||
The Cart Module's `LineItem` data model has a `metadata` property that holds any custom data. You can pass the customer's customization in the request body's `metadata` field when adding a product to the cart.
|
||||
|
||||
For example, if you’re asking customers to enter a message to put in a letter they’re purchasing, use the `metadata` attribute of the `LineItem` data model to set the personalized information entered by the customer:
|
||||
|
||||
@@ -81,9 +81,9 @@ Medusa provides a Next.js Starter storefront with basic ecommerce functionalitie
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
title: "Build Your Own Storefront",
|
||||
text: "Find tips on how to create a storefront.",
|
||||
href: "/storefront-development",
|
||||
title: "Storefront Development",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ Medusa's modular architecture removes any restrictions you may have while making
|
||||
|
||||
POS systems make the checkout process smoother by integrating a barcode scanner. Merchants scan a product by its barcode to check its details or add it to the customer's purchase.
|
||||
|
||||
The Product Module's `ProductVariant` data model has the fields to implement this integration, mainly the `barcode` attribute. Other notable fields include `ean`, `upc`, and `hs_code`, among others.
|
||||
The Product Module's `ProductVariant` data model has the properties to implement this integration, mainly the `barcode` attribute. Other notable properties include `ean`, `upc`, and `hs_code`, among others.
|
||||
|
||||
To search through product variants by their barcode, create a custom API Route and call it within your POS.
|
||||
|
||||
@@ -53,14 +53,14 @@ To search through product variants by their barcode, create a custom API Route a
|
||||
|
||||
<CardList itemsPerRow={2} items={[
|
||||
{
|
||||
href: "#",
|
||||
href: "/commerce-modules/product",
|
||||
title: "Product Module",
|
||||
text: "Learn about the Product Module.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "https://docs.medusajs.com/api/admin",
|
||||
href: "!docs!/basics/api-routes",
|
||||
title: "Create API Route",
|
||||
text: "Learn how to create an API Route.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
@@ -100,7 +100,7 @@ To search through product variants by their barcode, create a custom API Route a
|
||||
|
||||
// retrieve product variants by barcode
|
||||
const productVariants = await productModuleService
|
||||
.listVariants({
|
||||
.listProductVariants({
|
||||
// @ts-ignore
|
||||
barcode,
|
||||
})
|
||||
|
||||
@@ -141,9 +141,9 @@ Medusa provides a Next.js Starter. Since you've customized your Medusa project,
|
||||
showLinkIcon: false
|
||||
},
|
||||
{
|
||||
href: "!docs!/storefront-development/tips",
|
||||
href: "/storefront-development",
|
||||
title: "Option 2: Build Custom Storefront",
|
||||
text: "Find tips to build your own storefront.",
|
||||
text: "Find guides for your storefront development.",
|
||||
startIcon: <AcademicCapSolid />,
|
||||
showLinkIcon: false
|
||||
}
|
||||
|
||||
@@ -1,104 +1,4 @@
|
||||
export const filesMap = [
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/analytics/segment/page.mdx",
|
||||
"pathname": "/_plugins/analytics/segment"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/cms/contentful/page.mdx",
|
||||
"pathname": "/_plugins/cms/contentful"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/cms/strapi/page.mdx",
|
||||
"pathname": "/_plugins/cms/strapi"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/erp/brightpearl/page.mdx",
|
||||
"pathname": "/_plugins/erp/brightpearl"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/fulfillment/manual/page.mdx",
|
||||
"pathname": "/_plugins/fulfillment/manual"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/fulfillment/webshipper/page.mdx",
|
||||
"pathname": "/_plugins/fulfillment/webshipper"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/notifications/mailchimp/page.mdx",
|
||||
"pathname": "/_plugins/notifications/mailchimp"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/notifications/sendgrid/page.mdx",
|
||||
"pathname": "/_plugins/notifications/sendgrid"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/notifications/slack/page.mdx",
|
||||
"pathname": "/_plugins/notifications/slack"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/notifications/twilio-sms/page.mdx",
|
||||
"pathname": "/_plugins/notifications/twilio-sms"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/other/discount-generator/page.mdx",
|
||||
"pathname": "/_plugins/other/discount-generator"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/other/ip-lookup/page.mdx",
|
||||
"pathname": "/_plugins/other/ip-lookup"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/other/restock-notifications/page.mdx",
|
||||
"pathname": "/_plugins/other/restock-notifications"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/other/wishlist/page.mdx",
|
||||
"pathname": "/_plugins/other/wishlist"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/page.mdx",
|
||||
"pathname": "/_plugins"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/payment/klarna/page.mdx",
|
||||
"pathname": "/_plugins/payment/klarna"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/payment/paypal/page.mdx",
|
||||
"pathname": "/_plugins/payment/paypal"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/payment/stripe/page.mdx",
|
||||
"pathname": "/_plugins/payment/stripe"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/search/algolia/page.mdx",
|
||||
"pathname": "/_plugins/search/algolia"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/search/meilisearch/page.mdx",
|
||||
"pathname": "/_plugins/search/meilisearch"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/source/shopify/page.mdx",
|
||||
"pathname": "/_plugins/source/shopify"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/storage/local/page.mdx",
|
||||
"pathname": "/_plugins/storage/local"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/storage/minio/page.mdx",
|
||||
"pathname": "/_plugins/storage/minio"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/storage/s3/page.mdx",
|
||||
"pathname": "/_plugins/storage/s3"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/_plugins/storage/spaces/page.mdx",
|
||||
"pathname": "/_plugins/storage/spaces"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/admin-widget-injection-zones/page.mdx",
|
||||
"pathname": "/admin-widget-injection-zones"
|
||||
|
||||
@@ -1721,7 +1721,7 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
||||
children: [
|
||||
{
|
||||
path: "/references/file-provider-module",
|
||||
title: "Create File Module",
|
||||
title: "Create File Provider",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1749,7 +1749,7 @@ export const sidebar = sidebarAttachHrefCommonOptions([
|
||||
},
|
||||
{
|
||||
path: "/references/notification-provider-module",
|
||||
title: "Create Notification Module",
|
||||
title: "Create Notification Provider",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user