docs: editing and general fixes of medusa's learning resources (#7261)
* docs: editing and general fixes of medusa's learning resources * fix build script * update ui dependency * fix build * adjust next.js steps
This commit is contained in:
@@ -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`
|
||||
Reference in New Issue
Block a user