chore: reorganize docs apps (#7228)
* reorganize docs apps * add README * fix directory * add condition for old docs
This commit is contained in:
1 parent
224ebb2154
commit
4fe28f5a95
6187 files changed
+601447
-598226
No files matched your search
@@ -0,0 +1,821 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Admin Widget Injection Zones`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This documentation page includes the list of injection zones you can add Admin Widgets to.
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell className="w-1/3">Injection Zone Name</Table.HeaderCell>
|
||||
<Table.HeaderCell className="w-1/3">Description</Table.HeaderCell>
|
||||
<Table.HeaderCell className="w-1/3">Additional Props</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`login.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added before the login form.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`login.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added after the login form.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`order.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the orders list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`order.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the order list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`order.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the order details page
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `OrderDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
order, // Order object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`order.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the end of the order details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `OrderDetailsWidgetProps` imported from `@medusajs/admin`
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
order, // Order object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`draft_order.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the draft orders list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`draft_order.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the draft orders list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`draft_order.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the draft order details page
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `DraftOrderDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
draftOrder, // DraftOrder object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`draft_order.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the draft order details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `DraftOrderDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
draftOrder, // DraftOrder object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the customers list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the customers list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the customer details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `CustomerDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
customer, // Customer object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the customer details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `CustomerDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
customer, // Customer object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer_group.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the customer groups list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer_group.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the customer groups list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer_group.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the customer group details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `CustomerGroupDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
customerGroup, // CustomerGroup object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`customer_group.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the customer group details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `CustomerGroupDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
customerGroup, // CustomerGroup object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the product list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the products list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the product details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `ProductDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
product, // Product object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the product details page
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `ProductDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
product, // Product object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product_collection.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the product collections list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product_collection.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the product collections list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product_collection.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the product collection details page
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `ProductCollectionDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
productCollection, // Collection object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`product_collection.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the product collection details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `ProductCollectionDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
productCollection, // Collection object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`price_list.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the Pricing list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`price_list.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the Pricing list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`price_list.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of a price list's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `PriceListDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
priceList, // PriceList object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`price_list.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of a price list's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `PriceListDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
priceList, // PriceList object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`discount.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the discounts list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`discount.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the discounts list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`discount.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of a discount's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `DiscountDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
discount, // Discount object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`discount.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of a discount's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `DiscountDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
discount, // Discount object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`gift_card.list.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of the gift cards list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`gift_card.list.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of the gift cards list page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`gift_card.details.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of a gift card's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `GiftCardDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
giftCard, // Product object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`gift_card.details.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of a gift card's details page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `GiftCardDetailsWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
giftCard, // Product object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`custom_gift_card.before`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the top of a custom gift card's page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `GiftCardCustomWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
giftCard, // GiftCard object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`custom_gift_card.after`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Added at the bottom of a custom gift card's page.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Type `GiftCardCustomWidgetProps` imported from `@medusajs/admin`.
|
||||
|
||||
```ts noCopy noReport
|
||||
{
|
||||
giftCard, // GiftCard object
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `In-Memory Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The In-Memory Cache Module uses a plain JavaScript Map object to store the cached data. This module is used by default in your Medusa application.
|
||||
|
||||
This module is helpful for development or when you’re testing out Medusa, but it’s not recommended to be used in production.
|
||||
|
||||
For production, it’s recommended to use modules like [Redis Cache Module](../redis/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Install the In-Memory Cache Module
|
||||
|
||||
To install the In-Memory Cache Module, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/cache-inmemory
|
||||
```
|
||||
|
||||
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
cacheService: {
|
||||
resolve: "@medusajs/cache-inmemory",
|
||||
options: {
|
||||
// optional options
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### In-Memory Cache Module 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>
|
||||
|
||||
`ttl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`30` seconds
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,173 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Cache Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Cache Module uses Redis to cache data in your store. In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Install the Redis Cache Module
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Redis installed and Redis server running](https://redis.io/docs/getting-started/installation/).
|
||||
|
||||
</Note>
|
||||
|
||||
To install Redis Cache Module, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/cache-redis
|
||||
```
|
||||
|
||||
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["8", "redisUrl", "The Redis connection URL."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js" highlights={highlights}
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
cacheService: {
|
||||
resolve: "@medusajs/cache-redis",
|
||||
options: {
|
||||
redisUrl: process.env.CACHE_REDIS_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Redis Cache Module 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>
|
||||
|
||||
`redisUrl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Redis connection URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`redisOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ttl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The number of seconds an item can live in the cache before it’s removed.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`30` seconds
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`namespace`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string used to prefix all cached keys with `{namespace}:`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`medusa`. So, all cached keys are prefixed with `medusa:`.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the following environment variables:
|
||||
|
||||
```bash
|
||||
CACHE_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'cache-redis' established
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
export const metadata = {
|
||||
title: `Local Event Bus Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Local Event Bus Module uses Node EventEmitter to implement Medusa's pub/sub events system. The Node EventEmitter is limited to a single process environment.
|
||||
|
||||
This module is useful for development and testing, but it’s not recommended to be used in production.
|
||||
|
||||
For production, it’s recommended to use modules like [Redis Event Bus Module](../redis/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Install the Local Event Bus Module
|
||||
|
||||
To install Local Event Bus Module, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/event-local
|
||||
```
|
||||
|
||||
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
eventBus: {
|
||||
resolve: "@medusajs/event-local",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Local Event Bus installed. This is not recommended for production.
|
||||
```
|
||||
@@ -0,0 +1,221 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Redis Event Bus Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Redis Event Bus Module uses Redis to implement Medusa's pub/sub events system.
|
||||
|
||||
It's powered by BullMQ and `io-redis`. BullMQ is responsible for the message queue and worker, and `io-redis` is the underlying Redis client that BullMQ connects to for events storage.
|
||||
|
||||
In production, it's recommended to use this module.
|
||||
|
||||
---
|
||||
|
||||
## Install the Redis Events Bus Module
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Redis installed and Redis server running](https://redis.io/docs/getting-started/installation/).
|
||||
|
||||
</Note>
|
||||
|
||||
To install Redis Event Bus Module, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/event-redis
|
||||
```
|
||||
|
||||
Next, add the module into the `modules` property of the exported object in `medusa-config.js`:
|
||||
|
||||
export const highlights = [
|
||||
["8", "redisUrl", "The Redis connection URL."]
|
||||
]
|
||||
|
||||
```js title="medusa-config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
eventBus: {
|
||||
resolve: "@medusajs/event-redis",
|
||||
options: {
|
||||
redisUrl: process.env.EVENTS_REDIS_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Redis Event Bus Module 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>
|
||||
|
||||
`redisUrl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the Redis connection URL.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`redisOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of Redis options. Refer to the [Redis API Reference](https://redis.github.io/ioredis/index.html#RedisOptions) for details on accepted properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`queueName`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating BullMQ's queue name.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`events-queue`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`queueOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to the BullMQ constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.QueueOptions.html) for allowed properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`workerOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to the BullMQ Worker constructor. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/interfaces/v3.WorkerOptions.html) for allowed properties.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`jobOptions`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of options to pass to jobs added to the BullMQ queue. Refer to [BullMQ's API reference](https://api.docs.bullmq.io/modules/v3.html#BulkJobOptions) for allowed properties.
|
||||
|
||||
</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
|
||||
EVENTS_REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test the Module
|
||||
|
||||
To test the module, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You'll see the following message in the terminal's logs:
|
||||
|
||||
```bash noCopy noReport
|
||||
Connection to Redis in module 'event-redis' established
|
||||
```
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Architectural Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This section includes documentation for official Medusa architectural plugins.
|
||||
|
||||
<ChildDocs />
|
||||
@@ -0,0 +1,317 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the API Key Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the API Key Module in your application.
|
||||
|
||||
## Create an API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const apiKey = await apiKeyModuleService.create({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
api_key: apiKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeApiKeyModule,
|
||||
} from "@medusajs/api-key"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const apiKey = await apiKeyModuleService.create({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: apiKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List API Keys
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeApiKeyModule,
|
||||
} from "@medusajs/api-key"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Revoke an API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({
|
||||
api_key: revokedKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeApiKeyModule,
|
||||
} from "@medusajs/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(params.id)
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: revokedKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Verify Token
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const authenticatedToken =
|
||||
await apiKeyModuleService.authenticate(request.params.id)
|
||||
|
||||
res.json({
|
||||
is_authenticated: !!authenticatedToken,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeApiKeyModule,
|
||||
} from "@medusajs/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const authenticatedToken =
|
||||
await apiKeyModuleService.authenticate(request.params.id)
|
||||
|
||||
return NextResponse.json({
|
||||
is_authenticated: !!authenticatedToken,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Roll API Key
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
|
||||
res.json({
|
||||
api_key: newKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeApiKeyModule,
|
||||
} from "@medusajs/api-key"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const apiKeyModuleService = await initializeApiKeyModule()
|
||||
|
||||
const revokedKey = await apiKeyModuleService.revoke(params.id)
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
api_key: newKey,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/api-key) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,154 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `API Key Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The API Key Module is the `@medusajs/api-key` NPM package that provides API-key-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### API Key Types and Management
|
||||
|
||||
Store and manage API keys in your store. You can create both publishable and secret API keys for different use cases, such as:
|
||||
|
||||
- Publishable API Key associated with resources like sales channels.
|
||||
- Authentication token for admin users to access Admin API Routes.
|
||||
- Password reset tokens when a user or customer requests to reset their password.
|
||||
|
||||
```ts
|
||||
const pubApiKey = await apiKeyModuleService.create({
|
||||
title: "Publishable API key",
|
||||
type: "publishable",
|
||||
created_by: "user_123",
|
||||
})
|
||||
|
||||
const secretApiKey = await apiKeyModuleService.create({
|
||||
title: "Authentication Key",
|
||||
type: "secret",
|
||||
created_by: "user_123",
|
||||
})
|
||||
```
|
||||
|
||||
### Token Verification
|
||||
|
||||
Verify tokens of secret API keys to authenticate users or actions, such as verifying a password reset token.
|
||||
|
||||
```ts
|
||||
const authenticatedToken =
|
||||
await apiKeyModuleService.authenticate("sk_123")
|
||||
|
||||
if (!authenticatedToken) {
|
||||
console.error("Couldn't verify token")
|
||||
} else {
|
||||
console.log("Token verified successfully!")
|
||||
}
|
||||
```
|
||||
|
||||
### Revoke Keys
|
||||
|
||||
Revoke keys to disable their use permenantly.
|
||||
|
||||
```ts
|
||||
const revokedKey = await apiKeyModuleService.revoke("apk_1")
|
||||
```
|
||||
|
||||
### Roll API Keys
|
||||
|
||||
Roll API keys by revoking a key then re-creating it.
|
||||
|
||||
```ts
|
||||
const revokedKey = await apiKeyModuleService.revoke("apk_1")
|
||||
|
||||
const newKey = await apiKeyModuleService.create({
|
||||
title: revokedKey.title,
|
||||
type: revokedKey.type,
|
||||
created_by: revokedKey.created_by,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure API Key Module
|
||||
|
||||
After installing the `@medusajs/api-key` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
apiKey: {
|
||||
resolve: "@medusajs/api-key",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use API Key Module's Service
|
||||
|
||||
You can use the API Key Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.API_KEY` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
res.json({
|
||||
api_keys: await apiKeyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
container.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const apiKeys = await apiKeyModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IApiKeyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const apiKeyModuleService: IApiKeyModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.API_KEY
|
||||
)
|
||||
const apiKeys = await apiKeyModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,19 @@
|
||||
export const metadata = {
|
||||
title: `Relations between API Key Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the API Key Module and other Commerce Modules.
|
||||
|
||||
## Sales Channel Module
|
||||
|
||||
You can create a publishable API key and associate it with a sales channel. The Medusa application forms a relation between the `ApiKey` and the `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
This is useful to avoid passing the sales channel's ID as a parameter of every request, and instead pass the publishable API key in the header of any request to the Store API route.
|
||||
|
||||
Learn more about this in the [Sales Channel Module's documentation](../../sales-channel/publishable-api-keys/page.mdx).
|
||||
@@ -0,0 +1,50 @@
|
||||
export const metadata = {
|
||||
title: `API Key Tokens`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how the API Key module generates, revokes, and verifies tokens.
|
||||
|
||||
## API Key Types
|
||||
|
||||
There are two types of API keys:
|
||||
|
||||
- `publishable`: A public key used in client applications, such as a storefront.
|
||||
- `secret`: A secret key used for authentication and verification purposes, such as an admin user’s authentication token or a password reset token.
|
||||
|
||||
The API key’s type is stored in the `type` field of the `ApiKey` data model.
|
||||
|
||||
---
|
||||
|
||||
## Publishable Token Generation
|
||||
|
||||
When you create a publishable API key, its token is generated using [the `randomBytes` method of Node.js’s crypto package](https://nodejs.org/docs/latest-v18.x/api/crypto.html#cryptorandombytessize-callback). The token is `32` characters long and is hex-encoded. It’s stored in the `token` field of the `ApiKey` data model.
|
||||
|
||||
---
|
||||
|
||||
## Secret Token Generation
|
||||
|
||||
When you create a secret API key, three tokens are generated:
|
||||
|
||||
- A token that’s `32` characters long and hex-encoded. It’s generated using the `randomBytes` method of Node.js’s crypto package.
|
||||
- A salt token that’s `15` characters long and hex-encoded. It’s also generated using the `randomBytes` method.
|
||||
- A hashed token is generated from the token and salt token using [the `scrypt` method of Node.js’s crypto package](https://nodejs.org/docs/latest-v18.x/api/crypto.html#x509tostring). It’s `64` characters long and hex-encoded.
|
||||
|
||||
The salt and hashed tokens are stored in the `ApiKey` data model’s `salt` and `token` fields, respectively.
|
||||
|
||||
---
|
||||
|
||||
## API Key Expiration
|
||||
|
||||
An API key expires when it’s revoked using the `revoke` method of the module’s main service. The method sets the API key’s `revoked_at` and `revoked_by` fields accordingly.
|
||||
|
||||
The associated token is no longer usable or verifiable.
|
||||
|
||||
---
|
||||
|
||||
## Token Verification
|
||||
|
||||
To verify a token received as an input or in a request, the `authenticate` method of the module’s main service goes through all non-expired API keys. It recalculates the hash token using the supplied token and the API key’s `salt` field.
|
||||
|
||||
If the calculated hashed token matches the one in the database, the token is considered verified.
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
sidebar_label: "Authentication Flows"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `Authentication Flows with the Auth Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about how the Auth Provider is used in an authentication flow.
|
||||
|
||||
## How to Authenticate a User
|
||||
|
||||
To authenticate a user, you use the `authenticate` method of the Auth Module's main service (`IAuthModuleService`). For example:
|
||||
|
||||
```ts
|
||||
const data = await authModuleService.authenticate(
|
||||
"emailpass",
|
||||
// passed to auth provider
|
||||
{
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
This method calls the `authenticate` method of the specified provider and returns its data.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn about the parameters and return type of the `IAuthModuleService`'s `authenticate` method in [this reference](/references/auth/authenticate).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Basic Authentication Flow
|
||||
|
||||
If the `authenticate` method returns the following object:
|
||||
|
||||
```ts
|
||||
data = {
|
||||
success: true,
|
||||
authUser: {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Then, the user is authenticated successfully, and their authentication details are available within the `authUser` object.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `authUser` in [this guide](../persisting-auth-user/page.mdx#what-is-an-authuser).
|
||||
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Authentication with Third-Party Service Flow
|
||||
|
||||
If the `authenticate` method returns the following object:
|
||||
|
||||
```ts
|
||||
data = {
|
||||
success: true,
|
||||
location: "https://....",
|
||||
}
|
||||
```
|
||||
|
||||
It means the authentication process requires the user to perform an action with a third-party service. For example, when using the `google` provider, the user goes to the URL specified in the `location`'s value to log in with their Google account.
|
||||
|
||||

|
||||
|
||||
### validateCallback
|
||||
|
||||
Providers handling this authentication flow must implement the `validateCallback` method. It implements the logic to validate the authentication with the third-party service.
|
||||
|
||||
So, once the user performs the required action, the third-party service must redirect to an API route that uses the `validateCallback` method of the `IAuthModuleService`. The method calls the specified provider’s `validateCallback` method passing it the authentication details it received in the second parameter:
|
||||
|
||||
```ts
|
||||
const data = await authModuleService.validateCallback(
|
||||
"google",
|
||||
// passed to auth provider
|
||||
{
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the parameters and return type of the `IAuthModuleService`'s `validateCallback` method in [this reference](/references/auth/validateCallback).
|
||||
|
||||
</Note>
|
||||
|
||||
If the authentication is successful, the auth provider’s `validateCallback` method returns the same data as the basic authentication:
|
||||
|
||||
```ts
|
||||
data = {
|
||||
success: true,
|
||||
authUser: {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
@@ -0,0 +1,39 @@
|
||||
export const metadata = {
|
||||
title: `Auth Providers`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how the Auth Module handles authentication using providers.
|
||||
|
||||
## What's an Auth Provider?
|
||||
|
||||
An auth provider is a TypeScript or JavaScript class used to authenticate customers and users. Each provider implements the authentication logic based on its purpose.
|
||||
|
||||
For example, the `emailpass` provider authenticates a user using their email and password, whereas the `google` provider authenticates users using their Google account.
|
||||
|
||||
---
|
||||
|
||||
## The Auth Provider Class
|
||||
|
||||
An auth provider implements the `AbstractAuthModuleProvider` class imported from the `@medusajs/utils` package. The provider must implement the `authenticate` method that authenticates a user.
|
||||
|
||||
When you call the `authenticate` method of the Auth Module's main service (`IAuthModuleService`), the method resolves the specified provider and calls its `authenticate` method passing it the second parameter it received:
|
||||
|
||||
```ts
|
||||
const data = await authModuleService.authenticate(
|
||||
"emailpass",
|
||||
// passed to auth provider
|
||||
{
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
It also returns the same data returned by the auth provider.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn about the parameters and return type of the `IAuthModuleService`'s `authenticate` method in [this reference](/references/auth/authenticate).
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,451 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Auth Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Auth Module in your application.
|
||||
|
||||
## Authenticate User
|
||||
|
||||
<Note>
|
||||
|
||||
This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/jsonwebtoken) to create the authentication token.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import {
|
||||
IAuthModuleService,
|
||||
AuthenticationInput,
|
||||
} from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const { success, authUser, location, error } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new MedusaError(MedusaError.Types.UNAUTHORIZED, error)
|
||||
}
|
||||
|
||||
if (location) {
|
||||
res.redirect(location)
|
||||
return
|
||||
}
|
||||
|
||||
const { jwt_secret } =
|
||||
req.scope.resolve("configModule").projectConfig
|
||||
const token = jwt.sign(authUser, jwt_secret)
|
||||
|
||||
res.status(200).json({ token })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
const url = new URL(request.url)
|
||||
|
||||
const { success, authUser, location, error } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers),
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
body: await request.json(),
|
||||
authScope: "admin",
|
||||
protocol: url.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
if (location) {
|
||||
return NextResponse.redirect(location)
|
||||
return
|
||||
}
|
||||
|
||||
const token = jwt.sign(authUser, "supersecret")
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
}, {
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Validate Callback
|
||||
|
||||
<Note>
|
||||
|
||||
This example uses the [jsonwebtoken NPM package](https://www.npmjs.com/package/jsonwebtoken) to create the authentication token.
|
||||
|
||||
</Note>
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import {
|
||||
IAuthModuleService,
|
||||
AuthenticationInput,
|
||||
} from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const { success, authUser, error, successRedirectUrl } =
|
||||
await authModuleService.validateCallback("google", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new MedusaError(MedusaError.Types.UNAUTHORIZED, error)
|
||||
}
|
||||
|
||||
const { jwt_secret } =
|
||||
req.scope.resolve("configModule").projectConfig
|
||||
const token = jwt.sign(authUser, jwt_secret)
|
||||
|
||||
if (successRedirectUrl) {
|
||||
const url = new URL(successRedirectUrl!)
|
||||
url.searchParams.append("auth_token", token)
|
||||
|
||||
return res.redirect(url.toString())
|
||||
}
|
||||
|
||||
res.status(200).json({ token })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
const url = new URL(request.url)
|
||||
|
||||
const { success, authUser, location, error } =
|
||||
await authModuleService.authenticate("google", {
|
||||
url: request.url,
|
||||
headers: Object.fromEntries(request.headers),
|
||||
query: Object.fromEntries(url.searchParams),
|
||||
body: await request.json(),
|
||||
authScope: "admin",
|
||||
protocol: url.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
const token = jwt.sign(authUser, "supersecret")
|
||||
|
||||
if (successRedirectUrl) {
|
||||
const url = new URL(successRedirectUrl!)
|
||||
url.searchParams.append("auth_token", token)
|
||||
|
||||
return NextResponse.redirect(url.toString())
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
}, {
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Auth User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authUser = await authModuleService.create({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
res.json({ auth_user: authUser })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authUser = await authModuleService.create({
|
||||
provider: "emailpass",
|
||||
entity_id: "user@example.com",
|
||||
scope: "admin",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_user: authUser,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Auth Users
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
res.json({
|
||||
auth_users: await authModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
return NextResponse.json({
|
||||
auth_users: await authModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update an Auth User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authUser = await authModuleService.update({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
auth_user: authUser,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
const authUser = await authModuleService.update({
|
||||
id: "authusr_123",
|
||||
provider_metadata: {
|
||||
test: true,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
auth_users: await authModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete an Auth User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
await authModuleService.delete(["authusr_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeAuthModule,
|
||||
} from "@medusajs/auth"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const authModuleService = await initializeAuthModule()
|
||||
|
||||
await authModuleService.delete(["authusr_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/auth) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,232 @@
|
||||
---
|
||||
sidebar_label: "Module Options"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Auth Module Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the options of the Auth Module.
|
||||
|
||||
## providers
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
auth: {
|
||||
resolve: "@medusajs/auth",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
name: "emailpass",
|
||||
scopes: {
|
||||
store: {},
|
||||
admin: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "google",
|
||||
scopes: {
|
||||
admin: {
|
||||
clientID: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
callbackURL: process.env.GOOGLE_CALLBACK_URL,
|
||||
successRedirectUrl:
|
||||
process.env.GOOGLE_SUCCESS_REDIRECT_URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `providers` option is an array of objects indicating the auth providers to register, their scopes, and configurations.
|
||||
|
||||
Each object accepts the following properties:
|
||||
|
||||
- `name`: The provider's name, which is set in the auth provider class's `PROVIDER` field. For example, `emailpass` or `google`.
|
||||
- `scopes`: An object of scopes. The keys are a scope's name, which in the Medusa application would be either `admin` or `store`. The value is an object of configurations for that scope. Each provider accepts different scope configurations as detailed below.
|
||||
|
||||
### emailpass Scope Configurations
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Configuration</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>
|
||||
|
||||
`hashConfig`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object of configurations to use when hashing the user's
|
||||
password. Refer to [scrypt-kdf](https://www.npmjs.com/package/scrypt-kdf#-hash)'s
|
||||
documentation for accepted options.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
```ts noCopy noReport noLineNumbers
|
||||
const hashConfig = {
|
||||
logN: 15,
|
||||
r: 8,
|
||||
p: 1
|
||||
}
|
||||
```
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### google Scope Configurations
|
||||
|
||||
<Note type="check">
|
||||
|
||||
Follow [this Google documentation](https://developers.google.com/identity/protocols/oauth2/web-server#prerequisites) to enable Google's APIs and retrieve the necessary credentials.
|
||||
|
||||
</Note>
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Configuration</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 [Google API Client ID](https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid).
|
||||
|
||||
</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 [Google Client Secret](https://support.google.com/cloud/answer/6158849?hl=en#zippy=%2Cstep-create-a-new-client-secret).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`callbackURL`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL to redirect to in your app after the user completes their authentication in Google.
|
||||
|
||||
The Medusa application provides the API route `/auth/[scope]/google/callback` that you can use, where `[scope]` is the scope this config belongs to.
|
||||
For example, `/auth/store/google/callback`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`successRedirectUrl`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the URL to redirect to in your app after the authentication has been successful.
|
||||
|
||||
If not provided, the Medusa application's callback route just returns a JSON with the JWT token of the auth user.
|
||||
|
||||
</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
|
||||
GOOGLE_CLIENT_ID=<YOUR_GOOGLE_CLIENT_ID>
|
||||
GOOGLE_CLIENT_SECRET=<YOUR_GOOGLE_CLIENT_SECRET>
|
||||
GOOGLE_CALLBACK_URL=<YOUR_GOOGLE_CALLBACK_URL>
|
||||
GOOGLE_SUCCESS_REDIRECT_URL=<YOUR_GOOGLE_SUCCESS_REDIRECT_URL>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auth CORS
|
||||
|
||||
The Medusa application's authentication API routes are defined under the `/auth` prefix that requires setting the `auth_cors` configuration. So, before using these routes, make sure to set that configuration.
|
||||
|
||||
Refer to [Medusa's configuration guide](/references/medusa-config#auth_cors) for more details.
|
||||
@@ -0,0 +1,159 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Auth Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Auth Module is the `@medusajs/auth` NPM package that provides authentication-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Authenticate Users
|
||||
|
||||
With the Auth Module, authenticate users using their credentials.
|
||||
|
||||
```ts
|
||||
const { success, authUser, error } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!success) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
// user is authenticated
|
||||
```
|
||||
|
||||
### Third-Party and Social Authentication
|
||||
|
||||
The Auth Module supports a variety of authentication methods, such as authenticating with third-party services and social platforms.
|
||||
|
||||
```ts
|
||||
// in authentication API route
|
||||
const { success, authUser, location } =
|
||||
await authModuleService.authenticate("google", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
|
||||
if (!authUser && location) {
|
||||
res.redirect(location)
|
||||
return
|
||||
}
|
||||
|
||||
// in callback API route
|
||||
const { success, authUser } =
|
||||
await authModuleService.validateCallback("google", {
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
authScope: "admin",
|
||||
protocol: req.protocol,
|
||||
} as AuthenticationInput)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Auth Module
|
||||
|
||||
After installing the `@medusajs/auth` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
auth: {
|
||||
resolve: "@medusajs/auth",
|
||||
options: {
|
||||
providers: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Auth Module's Service
|
||||
|
||||
You can use the Auth Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.AUTH` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const authModuleService: IAuthModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
res.json({
|
||||
authUsers: authModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const authModuleService: IAuthModuleService =
|
||||
container.resolve(ModuleRegistrationName.AUTH)
|
||||
|
||||
const authUsers = await authModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IAuthModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const authModuleService: IAuthModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.AUTH
|
||||
)
|
||||
const authUsers = await authModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
sidebar_label: "Persisting Auth User"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `Persisting Auth User Authentication`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what the `AuthUser` is and how to persist its authentication.
|
||||
|
||||
## What is an AuthUser?
|
||||
|
||||
As explained in the [Auth Provider](../auth-providers/page.mdx) guide, when a user or customer is authenticated, you receive an `authUser` object:
|
||||
|
||||
```ts
|
||||
const { success, authUser } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
The `authUser` object is a record of the `AuthUser` data model. It has details about the authenticated user or customer, such as their ID, email, and other details.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `AuthUser`'s fields in [this reference](/references/auth/models/AuthUser).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Persisting Authentication
|
||||
|
||||
While the Auth Module provides the authentication functionality, it doesn’t provide the functionality to persist the authentication, as that depends on your application’s requirements.
|
||||
|
||||
For example, the Medusa application’s authentication route signs the `authUser` object into a JSON Web Token (JWT):
|
||||
|
||||
```ts
|
||||
const {
|
||||
success,
|
||||
authUser,
|
||||
} = await service.authenticate(auth_provider, authData)
|
||||
|
||||
// ...
|
||||
const {
|
||||
jwt_secret,
|
||||
} = req.scope.resolve("configModule").projectConfig
|
||||
|
||||
const token = jwt.sign(authUser, jwt_secret)
|
||||
```
|
||||
|
||||
Then, the token is passed in the header of subsequent requests in the Authorization Bearer header.
|
||||
|
||||
An authentication middleware verifies the token and attaches the associated `authUser`'s details to the `auth` property of the request object passed to the subsequent middlewares and route.
|
||||
|
||||
If the authentication middleware can’t verify the token, the user isn’t authenticated and they’re asked to login again.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
sidebar_label: "User Creation"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `User Creation with Auth Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the user-creation flow and how to use it with the User Module.
|
||||
|
||||
## Auth User Creation in Authentication Flow
|
||||
|
||||
In the [Auth Provider](../auth-providers/page.mdx) documentation, you learned about the authentication flows supported by the Auth Module. These flows are used when an `AuthUser` is already available for the specified authentication data, such as email/password credentials.
|
||||
|
||||
However, the `emailpass` and `google` providers support creating an `AuthUser` if none exists. If an email is provided that doesn’t have an `AuthUser` associated with it (checked via its `entity_id` field) for the specified provider (checked via its `provider` field), a new `AuthUser` is created for that email and provider.
|
||||
|
||||

|
||||
|
||||
So, by default, your authentication flow supports both sign-in and sign-up flows.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
This step actually occurs at different points of the authentication flow for each of the providers. For the `emailpass` provider, it occurs before checking that the password is correct. For the `google` provider, you must go through the full authentication flow to retrieve the user details, such as email, from Google.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Creating a User in the User Module
|
||||
|
||||
The User Module provides user and invite management functionalities. However, it doesn’t provide authentication functionalities or store any related data.
|
||||
|
||||
By combining the User and Auth Modules, you can use the Auth Module for authenticating users, and the User Module to manage those users.
|
||||
|
||||
So, when a user is authenticated, and you receive the `AuthUser` object, you can use it to create a user if it doesn’t exist:
|
||||
|
||||
```ts
|
||||
const { success, authUser } =
|
||||
await authModuleService.authenticate("emailpass", {
|
||||
// ...
|
||||
})
|
||||
|
||||
// assuming authUser is defined
|
||||
const [, count] = await userModuleService.listAndCount({
|
||||
email: authUser.entity_id,
|
||||
})
|
||||
|
||||
if (!count) {
|
||||
const user = await userModuleService.create({
|
||||
email: authUser.entity_id,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
export const metadata = {
|
||||
title: `Cart Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll get an overview of the main concepts of a cart.
|
||||
|
||||
## Shipping and Billing Addresses
|
||||
|
||||
A cart has a shipping and billing address. Both of these addresses are represented by the `Address` data model.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Line Items
|
||||
|
||||
A line item, represented by the `LineItem` data model, is a product added to the cart. A cart has multiple line items.
|
||||
|
||||
A line item stores some of the product’s fields, such as the `product_title` and `product_description`. It also stores data related to the item’s quantity and price.
|
||||
|
||||
<Note>
|
||||
|
||||
A product can be from the [Product Module](../../product/page.mdx) but can also be a custom item used only in this cart.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Shipping Methods
|
||||
|
||||
A shipping method, represented by the `ShippingMethod` data model, is the method used to fulfill the items in the cart after the order is placed. A cart can have more than one shipping method.
|
||||
|
||||
If the shipping method is created from a shipping option, typically available through the Fulfillment Module, its ID is stored in the `shipping_option_id`.
|
||||
|
||||
A shipping method can also be a custom method associated with this cart only.
|
||||
|
||||
### data Field
|
||||
|
||||
When fulfilling the order after its placed, you may use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout process.
|
||||
|
||||
The `ShippingMethod` data model has a `data` field. It's an object used to store custom data relevant later for fulfillment.
|
||||
|
||||
In the Medusa application, it's the `data` is passed to the Fulfillment Module when fulfilling items.
|
||||
@@ -0,0 +1,485 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Cart Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Cart Module in your application.
|
||||
|
||||
## Create a Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ cart })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
cart,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Cart by ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const cart = await cartModuleService.retrieve("cart_123")
|
||||
|
||||
res.json({ cart })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const cart = await cartModuleService.retrieve("cart_123")
|
||||
|
||||
return NextResponse.json({
|
||||
cart,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Item to Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const lineItem = await cartModuleService.addLineItems({
|
||||
cart_id: "cart_123",
|
||||
title: "Shirt",
|
||||
quantity: 2,
|
||||
unit_price: 5000,
|
||||
})
|
||||
|
||||
res.json({
|
||||
line_item: lineItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const lineItem = await cartModuleService.addLineItems({
|
||||
cart_id: "cart_123",
|
||||
title: "Shirt",
|
||||
quantity: 2,
|
||||
unit_price: 5000,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
line_item: lineItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Shipping Method to Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const shippingMethod =
|
||||
await cartModuleService.addShippingMethods({
|
||||
cart_id: "cart_123",
|
||||
name: "Custom shipping",
|
||||
amount: 1000,
|
||||
})
|
||||
|
||||
res.json({
|
||||
shipping_method: shippingMethod,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const shippingMethod =
|
||||
await cartModuleService.addShippingMethods({
|
||||
cart_id: "cart_123",
|
||||
name: "Custom shipping",
|
||||
amount: 1000,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
shipping_method: shippingMethod,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Item Adjustment Line
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const itemAdjustment =
|
||||
await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
amount: 500,
|
||||
code: "50%OFF",
|
||||
})
|
||||
|
||||
res.json({
|
||||
adjustment: itemAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const itemAdjustment =
|
||||
await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
amount: 500,
|
||||
code: "50%OFF",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
adjustment: itemAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Shipping Method Adjustment Line
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const shippingMethodAdjustment =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
amount: 500,
|
||||
code: "FREESHIPPING",
|
||||
})
|
||||
|
||||
res.json({
|
||||
adjustment: shippingMethodAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
const shippingMethodAdjustment =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
amount: 500,
|
||||
code: "FREESHIPPING",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
adjustment: shippingMethodAdjustment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove Line Item from Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
await cartModuleService.deleteLineItems(["cali_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCartModule,
|
||||
} from "@medusajs/cart"
|
||||
|
||||
export async function DELETE(
|
||||
request: Request
|
||||
) {
|
||||
const cartModuleService = await initializeCartModule()
|
||||
|
||||
await cartModuleService.deleteLineItems(["cali_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove Shipping Method from Cart
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
await cartModuleService.deleteLineItems(["cali_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
await cartModuleService.deleteShippingMethods(["casm_123"])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/cart) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,147 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Cart Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Cart Module is the `@medusajs/cart` NPM package that provides cart-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Cart Management
|
||||
|
||||
Store and manage carts, including their addresses, line items, shipping methods, and more.
|
||||
|
||||
```ts
|
||||
const cart = await cartModuleService.create({
|
||||
currency_code: "usd",
|
||||
shipping_address: {
|
||||
address_1: "1512 Barataria Blvd",
|
||||
country_code: "us",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Apply Promotions
|
||||
|
||||
Apply promotions or discounts to line items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
|
||||
```ts
|
||||
const lineAdjustments =
|
||||
await cartModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
code: "50OFF",
|
||||
amount: 500,
|
||||
})
|
||||
|
||||
const shippingAdjustments =
|
||||
await cartModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
code: "FREESHIPPING",
|
||||
amount: 1000,
|
||||
})
|
||||
```
|
||||
|
||||
### Cart Context and Scoping
|
||||
|
||||
A cart is scoped to a sales channel, region, and a customer.
|
||||
|
||||
When used with their respective modules and other commerce modules, you benefit from features like:
|
||||
|
||||
- Checking product availability in a sales channel.
|
||||
- Retrieving pricing per region.
|
||||
- Applying promotions based on the customer's group.
|
||||
|
||||
---
|
||||
|
||||
## Configure Cart Module
|
||||
|
||||
After installing the `@medusajs/cart` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
cart: {
|
||||
resolve: "@medusajs/cart",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Cart Module's Service
|
||||
|
||||
You can use the Cart Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.CART` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { res } from "express"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const cartModuleService: ICartModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
res.json({
|
||||
carts: await cartModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const cartModuleService: ICartModuleService =
|
||||
container.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const carts = await cartModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { ICartModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const cartModuleService: ICartModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.CART
|
||||
)
|
||||
const carts = await cartModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,132 @@
|
||||
---
|
||||
sidebar_label: "Promotions Adjustments"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `Promotions Adjustments in Carts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how a promotion is applied to a cart’s line items and shipping methods using adjustment lines.
|
||||
|
||||
## What are Adjustment Lines?
|
||||
|
||||
An adjustment line indicates a change to an item or a shipping method’s amount. It’s used to apply promotions or discounts on a cart.
|
||||
|
||||
The `LineItemAdjustment` data model represents adjustment lines for a line item, and the `ShippingMethodAdjustment` data model represents adjustment lines for a shipping method.
|
||||
|
||||

|
||||
|
||||
The `amount` field of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion can be stored in the `promotion_id` field of the adjustment line.
|
||||
|
||||
---
|
||||
|
||||
## Promotion Actions
|
||||
|
||||
When using the Cart and Promotion modules together, such as in the Medusa application, use the `computeActions` method of the Promotion Module’s main service. It retrieves the actions of line items and shipping methods.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about actions in the [Promotion Module’s documentation](../../promotion/actions/page.mdx).
|
||||
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
import {
|
||||
ComputeActionAdjustmentLine,
|
||||
ComputeActionItemLine,
|
||||
ComputeActionShippingLine,
|
||||
// ...
|
||||
} from "@medusajs/types"
|
||||
|
||||
// retrieve the cart
|
||||
const cart = await cartModuleService.retrieve("cart_123", {
|
||||
relations: [
|
||||
"items.adjustments",
|
||||
"shipping_methods.adjustments",
|
||||
],
|
||||
})
|
||||
|
||||
// retrieve line item adjustments
|
||||
const lineItemAdjustments: ComputeActionItemLine[] = []
|
||||
cart.items.forEach((item) => {
|
||||
const filteredAdjustments = item.adjustments?.filter(
|
||||
(adjustment) => adjustment.code !== undefined
|
||||
) as unknown as ComputeActionAdjustmentLine[]
|
||||
if (filteredAdjustments.length) {
|
||||
lineItemAdjustments.push({
|
||||
...item,
|
||||
adjustments: filteredAdjustments,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// retrieve shipping method adjustments
|
||||
const shippingMethodAdjustments: ComputeActionShippingLine[] =
|
||||
[]
|
||||
cart.shipping_methods.forEach((shippingMethod) => {
|
||||
const filteredAdjustments =
|
||||
shippingMethod.adjustments?.filter(
|
||||
(adjustment) => adjustment.code !== undefined
|
||||
) as unknown as ComputeActionAdjustmentLine[]
|
||||
if (filteredAdjustments.length) {
|
||||
shippingMethodAdjustments.push({
|
||||
...shippingMethod,
|
||||
adjustments: filteredAdjustments,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// compute actions
|
||||
const actions = await promotionModuleService.computeActions(
|
||||
["promo_123"],
|
||||
{
|
||||
items: lineItemAdjustments,
|
||||
shipping_methods: shippingMethodAdjustments,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `computeActions` method in [this reference](/references/promotion/computeActions).
|
||||
|
||||
</Note>
|
||||
|
||||
Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the cart’s line item and the shipping method’s adjustments.
|
||||
|
||||
```ts
|
||||
import {
|
||||
AddItemAdjustmentAction,
|
||||
AddShippingMethodAdjustment,
|
||||
// ...
|
||||
} from "@medusajs/types"
|
||||
|
||||
// ...
|
||||
|
||||
await cartModuleService.setLineItemAdjustments(
|
||||
cart.id,
|
||||
actions.filter(
|
||||
(action) => action.action === "addItemAdjustment"
|
||||
) as AddItemAdjustmentAction[]
|
||||
)
|
||||
|
||||
await cartModuleService.setShippingMethodAdjustments(
|
||||
cart.id,
|
||||
actions.filter(
|
||||
(action) =>
|
||||
action.action === "addShippingMethodAdjustment"
|
||||
) as AddShippingMethodAdjustment[]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Discountable Option
|
||||
|
||||
The `LineItem` data model has an `is_discountable` field that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
||||
|
||||
When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
|
||||
@@ -0,0 +1,39 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Cart Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Cart Module and other Commerce Modules.
|
||||
|
||||
## Customer Module
|
||||
|
||||
A cart is scoped to the customer using it. The Medusa application forms a relation between the `Cart` and `Customer` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Payment Module
|
||||
|
||||
The Payment Module allows you to associate payments with a cart. The Medusa application forms a relation between the `Cart` and `PaymentCollection` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Promotion Module
|
||||
|
||||
A promotion can be applied on line items and shipping methods of a cart. The Medusa application forms a relation between the `Cart` and `Promotion` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Sales Channel Module
|
||||
|
||||
A cart is scoped to a sales channel. The Medusa application forms a relation between the `Cart` and `SalesChannel` data models.
|
||||
|
||||

|
||||
@@ -0,0 +1,79 @@
|
||||
export const metadata = {
|
||||
title: `Tax Lines in Cart Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about tax lines in a cart and how to retrieve tax lines with the Tax Module.
|
||||
|
||||
## What are Tax Lines?
|
||||
|
||||
A tax line indicates the tax rate of a line item or a shipping method. The `LineItemTaxLine` data model represents a line item’s tax line, and the `ShippingMethodTaxLine` data model represents a shipping method’s tax line.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Tax Inclusivity
|
||||
|
||||
By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount, and then added to the item/method’s subtotal.
|
||||
|
||||
However, line items and shipping methods have an `is_tax_inclusive` field that, when enabled, indicates that the item or method’s price already includes taxes.
|
||||
|
||||
So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
|
||||
|
||||
<Note>
|
||||
|
||||
The following diagram is a simplified showcase of how a subtotal is calculated from the taxes perspective.
|
||||
|
||||
</Note>
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Retrieving Tax Lines
|
||||
|
||||
When using the Cart and Tax modules together, such as in the Medusa application, you can use the `getTaxLines` method of the Tax Module’s main service. It retrieves the tax lines for a cart’s line items and shipping methods.
|
||||
|
||||
```ts
|
||||
// retrieve the cart
|
||||
const cart = await cartModuleService.retrieve("cart_123", {
|
||||
relations: [
|
||||
"items.tax_lines",
|
||||
"shipping_methods.tax_lines",
|
||||
"shipping_address",
|
||||
],
|
||||
})
|
||||
|
||||
// retrieve the tax lines
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
...(cart.items as TaxableItemDTO[]),
|
||||
...(cart.shipping_methods as TaxableShippingDTO[]),
|
||||
],
|
||||
{
|
||||
address: {
|
||||
...cart.shipping_address,
|
||||
country_code:
|
||||
cart.shipping_address.country_code || "us",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Then, use the returned tax lines to set the line items and shipping methods’ tax lines:
|
||||
|
||||
```ts
|
||||
// set line item tax lines
|
||||
await cartModuleService.setLineItemTaxLines(
|
||||
cart.id,
|
||||
taxLines.filter((line) => "line_item_id" in line)
|
||||
)
|
||||
|
||||
// set shipping method tax lines
|
||||
await cartModuleService.setLineItemTaxLines(
|
||||
cart.id,
|
||||
taxLines.filter((line) => "shipping_line_id" in line)
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,111 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Currency Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Currency Module in your application.
|
||||
|
||||
## List Currencies
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICurrencyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCurrencyModule,
|
||||
} from "@medusajs/currency"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
|
||||
return NextResponse.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Currency by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICurrencyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
|
||||
res.json({
|
||||
currency,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCurrencyModule,
|
||||
} from "@medusajs/currency"
|
||||
|
||||
export async function GET(
|
||||
request: Request
|
||||
) {
|
||||
const currencyModuleService = await initializeCurrencyModule()
|
||||
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
|
||||
return NextResponse.json({ currency })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Currency Module interface reference](/references/currency) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,119 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Currency Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Currency Module is the `@medusajs/currency` NPM package that provides currency-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Currency Retrieval
|
||||
|
||||
List and retrieve currencies stored in your application.
|
||||
|
||||
By default, the module, on application start, adds currencies defined in [this file](https://github.com/medusajs/medusa/blob/develop/packages/utils/src/defaults/currencies.ts) to the database.
|
||||
|
||||
```ts
|
||||
const currency = await currencyModuleService.retrieve("usd")
|
||||
```
|
||||
|
||||
### Support Currencies in Modules
|
||||
|
||||
Other commerce modules use currency codes in their models or operations. You can use the Currency Module to retrieve a currency code and its details.
|
||||
|
||||
An example with the Region Module:
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
const currency = await currencyModuleService.retrieve(
|
||||
region.currency_code
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Currency Module
|
||||
|
||||
After installing the `@medusajs/currency` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
currency: {
|
||||
resolve: "@medusajs/currency",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Currency Module's Service
|
||||
|
||||
You can use the Currency Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.CURRENCY` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICurrencyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
res.json({
|
||||
currencies: await currencyModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { ICurrencyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
container.resolve(ModuleRegistrationName.CURRENCY)
|
||||
|
||||
const currencies = await currencyModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { ICurrencyModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const currencyModuleService: ICurrencyModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.CURRENCY
|
||||
)
|
||||
|
||||
const currencies = await currencyModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,23 @@
|
||||
export const metadata = {
|
||||
title: `Customer Accounts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how registered and unregistered accounts are distinguished in the Medusa application.
|
||||
|
||||
## `has_account` field
|
||||
|
||||
The `Customer` data model has a `has_account` field, which is a boolean that indicates whether a customer is registerd.
|
||||
|
||||
When a guest customer places an order, a new `Customer` record is created with `has_account` set to `false`.
|
||||
|
||||
When this or another guest customer registers an account with the same email, a new `Customer` record is created with `has_account` set to `true`.
|
||||
|
||||
---
|
||||
|
||||
## Email Uniqueness
|
||||
|
||||
The above behavior means that two `Customer` records may exist of the same email. However, the main difference is the `has_account` field’s value.
|
||||
|
||||
So, there can only be one guest customer (having `has_account=false`) and one registered customer (having `has_account=true`) with the same email. If another customer tries to create an account with the same email, they’ll get an error that an account with this email already exists.
|
||||
@@ -0,0 +1,236 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Customer Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Customer Module in your application.
|
||||
|
||||
## Create a Customer
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customer = await customerModuleService.create({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
|
||||
res.json({
|
||||
customer,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCustomerModule,
|
||||
} from "@medusajs/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customer = await customerModuleService.create({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
customer,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Customer Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
res.json({
|
||||
customer_group: customerGroup,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCustomerModule,
|
||||
} from "@medusajs/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
customer_group: customerGroup,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add a Customer to a Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeCustomerModule,
|
||||
} from "@medusajs/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({}, { status: 200 })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Remove a Customer from a Group
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
await customerModuleService.removeCustomerFromGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
import {
|
||||
initialize as initializeCustomerModule,
|
||||
} from "@medusajs/customer"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const customerModuleService = await initializeCustomerModule()
|
||||
|
||||
await customerModuleService.removeCustomerFromGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: "cusgroup_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({}, { status: 200 })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/customer) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,120 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Customer Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Customer Module is the `@medusajs/customer` NPM package that provides customer-related features in your Medusa and Node.js applications.
|
||||
|
||||
## Features
|
||||
|
||||
### Customer Management
|
||||
|
||||
With the Customer Module, store and manage customers in your store.
|
||||
|
||||
```ts
|
||||
const customer = await customerModuleService.create({
|
||||
first_name: "Peter",
|
||||
last_name: "Hayes",
|
||||
email: "peter.hayes@example.com",
|
||||
})
|
||||
```
|
||||
|
||||
### Customer Organization
|
||||
|
||||
You can organize customers into groups. This has a lot of benefits and supports more use cases, such as provide discounts for specific customer groups using the Promotion Module.
|
||||
|
||||
```ts
|
||||
const customerGroup =
|
||||
await customerModuleService.createCustomerGroup({
|
||||
name: "VIP",
|
||||
})
|
||||
|
||||
await customerModuleService.addCustomerToGroup({
|
||||
customer_id: "cus_123",
|
||||
customer_group_id: customerGroup.id,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Customer Module
|
||||
|
||||
After installing the `@medusajs/customer` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
customer: {
|
||||
resolve: "@medusajs/customer",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Customer Module's Service
|
||||
|
||||
You can use the Customer Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.CUSTOMER` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
res.json({
|
||||
customers: await customerModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
container.resolve(ModuleRegistrationName.CUSTOMER)
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const customerModuleService: ICustomerModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const customers = await customerModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,16 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Customer Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Customer Module and other Commerce Modules.
|
||||
|
||||
## Cart Module
|
||||
|
||||
A customer has a cart to purchase items from the store. The Medusa application forms a relation between the `Customer` and the `Cart` data models.
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
export const metadata = {
|
||||
title: `Fulfillment Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about some basic fulfillment concepts.
|
||||
|
||||
## Fulfillment Set
|
||||
|
||||
A fulfillment set is a general form or way of fulfillment. For example, shipping is a form of fulfillment, and pick-up is another form of fulfillment. Each of these can be created as fulfillment sets.
|
||||
|
||||
A fulfillment set is represented by the `FulfillmentSet` data model. All other configurations, options, and management features are related to a fulfillment set, in one way or another. So, the first step when using the Fulfillment Module is to create a fulfillment set.
|
||||
|
||||
```ts
|
||||
const fulfillmentSets = await fulfillmentModuleService.create(
|
||||
[
|
||||
{
|
||||
name: "Shipping",
|
||||
type: "shipping",
|
||||
},
|
||||
{
|
||||
name: "Pick-up",
|
||||
type: "pick-up",
|
||||
},
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Zone
|
||||
|
||||
A service zone is a collection of geographical zones or areas. It’s used to restrict available shipping options to a defined set of locations.
|
||||
|
||||
A service zone is represented by the `ServiceZone` data model. It’s associated with a fulfillment set, as each service zone is specific to a form of fulfillment. For example, if a customer chooses to pick up items, you can restrict the available shipping options based on their location.
|
||||
|
||||
A fulfillment set can have multiple service zones, each restricting different locations.
|
||||
|
||||

|
||||
|
||||
A service zone can have multiple geographical zones, each represented by the `GeoZone` data model. It holds location-related details to narrow down supported areas, such as country, city, or province code.
|
||||
|
||||
---
|
||||
|
||||
## Shipping Profile
|
||||
|
||||
A shipping profile defines a type of items that are shipped in a similar manner. For example, a `default` shipping profile is used for all item types, but the `digital` shipping profile is used for digital items that aren’t shipped and delivered conventionally.
|
||||
|
||||
A shipping profile is represented by the `ShippingProfile` data model. It only defines the profile’s details, but it’s associated with the shipping options available for the item type.
|
||||
@@ -0,0 +1,25 @@
|
||||
export const metadata = {
|
||||
title: `Fulfillment Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what a fulfillment provider is.
|
||||
|
||||
## What’s a Fulfillment Provider?
|
||||
|
||||
A fulfillment provider implements the functionality of fulfilling items, typically using a third-party integration. It’s represented by the `FulfillmentProvider` data model.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Fulfillment Provider?
|
||||
|
||||
A fulfillment provider is a TypeScript or JavaScript class that extends the `AbstractFulfillmentProviderService` class imported from `@medusajs/utils`. You can create it part of your Medusa application codebase, in a plugin, or in a provider module.
|
||||
|
||||
---
|
||||
|
||||
## Configure Fulfillment Providers
|
||||
|
||||
The Fulfillment Module accepts a `providers` option that allows you to register providers in your application. When the Medusa application starts, it registers these modules and adds records of the `FulfillmentProvider` for them.
|
||||
|
||||
Learn more about the `providers` option in [this documentation](../module-options/page.mdx).
|
||||
@@ -0,0 +1,55 @@
|
||||
export const metadata = {
|
||||
title: `Item Fulfillment`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the concepts of item fulfillment.
|
||||
|
||||
## Fulfillment Data Model
|
||||
|
||||
A fulfillment is the shipping and delivery of one or more items to the customer. It’s represented by the `Fulfillment` data model.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Processing
|
||||
|
||||
A fulfillment is associated with a fulfillment provider that handles all its processing, such as creating a shipment for the fulfillment’s items.
|
||||
|
||||
The fulfillment is also associated with a shipping option of that provider, which determines how the item is shipped.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## data Field
|
||||
|
||||
The `Fulfillment` data model has a `data` field that holds any necessary data for the third-party fulfillment provider to process the fulfillment.
|
||||
|
||||
For example, the `data` field can hold the ID of the fulfillment in the third-party provider. The associated fulfillment provider then uses it whenever it retrieves the fulfillment’s details.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Items
|
||||
|
||||
A fulfillment is used to fulfill one or more items. Each item is represented by the `FulfillmentItem` data model.
|
||||
|
||||
The fulfillment item holds details relevant to fulfilling the item, such as barcode, SKU, and quantity to fulfill.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Label
|
||||
|
||||
Once a shipment is created for the fulfillment, you can store its tracking number, URL, or other related details as a label, represented by the `FulfillmentLabel` data model.
|
||||
|
||||
---
|
||||
|
||||
## Fulfillment Status
|
||||
|
||||
The `Fulfillment` data model has three fields to keep track of the current status of the fulfillment:
|
||||
|
||||
- `packed_at`: The date the fulfillment was packed. If set, then the fulfillment has been packed.
|
||||
- `shipped_at`: The date the fulfillment was shipped. If set, then the fulfillment has been shipped.
|
||||
- `delivered_at`: The date the fulfillment was delivered. If set, then the fulfillment has been delivered.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
sidebar_label: "Module Options"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Fulfillment Module Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the options of the Fulfillment Module.
|
||||
|
||||
## providers
|
||||
|
||||
The `providers` option is an array of either fulfillment provider modules, fulfillment plugins, or path to a file that holds a fulfillment provider.
|
||||
|
||||
When the Medusa application starts, these providers are registered and can be used to process fulfillments.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
fulfillment: {
|
||||
resolve: "@medusajs/fulfillment",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: `@medusajs/fulfillment-manual`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `providers` option is an array of objects that accept the following properties:
|
||||
|
||||
- `resolve`: A string indicating the package name of the fulfillment provider module or the fulfillment plugin, or the path to the file defining the fulfillment provider.
|
||||
- `options`: An optional object of options to pass to the fulfillment provider.
|
||||
@@ -0,0 +1,198 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Fulfillment Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Fulfillment Module is the `@medusajs/fulfillment` NPM package that provides fulfillment-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Fulfillment Management
|
||||
|
||||
Create fulfillments and keep track of their statuses, items, and more.
|
||||
|
||||
```ts
|
||||
const fulfillment =
|
||||
await fulfillmentModuleService.createFulfillment({
|
||||
location_id: "loc_123",
|
||||
provider_id: "webshipper",
|
||||
delivery_address: {
|
||||
country_code: "us",
|
||||
city: "Strongsville",
|
||||
address_1: "18290 Royalton Rd",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
sku: "SHIRT",
|
||||
quantity: 1,
|
||||
barcode: "123456",
|
||||
},
|
||||
],
|
||||
labels: [],
|
||||
order: {},
|
||||
})
|
||||
```
|
||||
|
||||
### Integrate Third-Party Fulfillment Providers
|
||||
|
||||
Use third-party fulfillment providers to provide customers with shipping options and fulfill their orders.
|
||||
|
||||
```ts
|
||||
const shippingOption =
|
||||
await fulfillmentModuleService.createShippingOptions({
|
||||
name: "Express shipping",
|
||||
// ...
|
||||
provider_id: "webshipper",
|
||||
})
|
||||
```
|
||||
|
||||
### Restrict By Location and Rules
|
||||
|
||||
Shipping options can be restricted to specific geographical locations. You can also specify custom rules to restrict shipping options.
|
||||
|
||||
```ts
|
||||
const serviceZone =
|
||||
await fulfillmentModuleService.createServiceZones({
|
||||
name: "US",
|
||||
fulfillment_set_id: "fset_123",
|
||||
geo_zones: [
|
||||
{
|
||||
type: "country",
|
||||
country_code: "us",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption =
|
||||
await fulfillmentModuleService.createShippingOptions({
|
||||
name: "Express Shipping",
|
||||
// restrict location
|
||||
service_zone_id: serviceZone.id,
|
||||
// restrict by custom rules
|
||||
rules: [
|
||||
{
|
||||
field: "customer_group",
|
||||
operator: "eq",
|
||||
value: "vip",
|
||||
},
|
||||
],
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### Support Different Fulfillment Forms
|
||||
|
||||
Support various fulfillment forms, such as shipping or pick up.
|
||||
|
||||
```ts
|
||||
const fulfillmentSets = await fulfillmentModuleService.create(
|
||||
[
|
||||
{
|
||||
name: "Shipping",
|
||||
type: "shipping",
|
||||
},
|
||||
{
|
||||
name: "Pick-up",
|
||||
type: "pick-up",
|
||||
},
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Fulfillment Module
|
||||
|
||||
After installing the `@medusajs/fulfillment` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
fulfillment: {
|
||||
resolve: "@medusajs/fulfillment",
|
||||
providers: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Fulfillment Module's Service
|
||||
|
||||
You can use the Fulfillment Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.API_KEY` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.FULFILLMENT)
|
||||
|
||||
res.json({
|
||||
fulfillments:
|
||||
await fulfillmentModuleService.listFulfillments(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService =
|
||||
container.resolve(ModuleRegistrationName.FULFILLMENT)
|
||||
|
||||
const fulfillments =
|
||||
await fulfillmentModuleService.listFulfillments()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const fulfillmentModuleService: IFulfillmentModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.FULFILLMENT
|
||||
)
|
||||
const fulfillments =
|
||||
await fulfillmentModuleService.listFulfillments()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Fulfillment Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Fulfillment Module and other Commerce Modules.
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
When the Fulfillment and Stock Location modules are used together, a fulfillment set can be conditioned to a specific stock location.
|
||||
|
||||
The Medusa application forms a relation between the `FulfillmentSet` and `StockLocation` data models.
|
||||
|
||||

|
||||
@@ -0,0 +1,66 @@
|
||||
export const metadata = {
|
||||
title: `Shipping Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about shipping options and their rules.
|
||||
|
||||
## What’s a Shipping Option?
|
||||
|
||||
A shipping option is a way of shipping an item. Each fulfillment provider provides a set of shipping options. For example, a provider may provide a shipping option for express shipping and another for standard shipping.
|
||||
|
||||
When the customer places their order, they choose a shipping option to be used to fulfill their items.
|
||||
|
||||
A shipping option is represented by the `ShippingOption` data model.
|
||||
|
||||
---
|
||||
|
||||
## Service Zone Restrictions
|
||||
|
||||
A shipping option is restricted by a service zone, which specifies in what locations can a shipping option be used.
|
||||
|
||||
For example, a fulfillment provider may have a shipping option that can be used in the United States, and another in Canada.
|
||||
|
||||

|
||||
|
||||
Service zones can be more restrictive, such as restricting to certain cities or province codes.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Shipping Option Rules
|
||||
|
||||
You can restrict shipping options by custom rules, such as the item’s weight or the customer’s group.
|
||||
|
||||
These rules are represented by the `ShippingOptionRule` data model. Its fields define the custom rule:
|
||||
|
||||
- `field`: The name of an field or table that the rule applies to. For example, `customer_group`.
|
||||
- `value`: One or more values.
|
||||
- `operator`: The operator used in the condition. For example:
|
||||
- To allow multiple values, use the operator `in`, which validates that the provided values are in the rule’s values.
|
||||
- To create a negation condition that considers `value` against the rule, use `nin`, which validates that the provided values aren’t in the rule’s values.
|
||||
- Check out more operators in [this reference](/references/fulfillment/types/fulfillment.RuleOperatorType).
|
||||
|
||||

|
||||
|
||||
A shipping option can have multiple rules. For example, a shipping option is available if the customer belongs to the VIP group and the total weight is less than 2000g.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Shipping Profile and Types
|
||||
|
||||
A shipping option belongs to a type. For example, a shipping option’s type may be `express`, while another `standard`. The type is represented by the `ShippingOptionType` data model.
|
||||
|
||||
A shipping option also belongs to a shipping profile, as each shipping profile defines the type of items to be shipped in a similar manner.
|
||||
|
||||
---
|
||||
|
||||
## data Field
|
||||
|
||||
When fulfilling an item, you might use a third-party fulfillment provider that requires additional custom data to be passed along from the checkout or order-creation process.
|
||||
|
||||
The `ShippingOption` data model has a `data` field. It's an object that stores custom data relevant later when creating and processing a fulfillment.
|
||||
@@ -0,0 +1,41 @@
|
||||
export const metadata = {
|
||||
title: `Inventory Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the main concepts in the Inventory Module, and how data is stored and related.
|
||||
|
||||
## InventoryItem
|
||||
|
||||
An inventory item, represented by the `InventoryItem` data model, is a stock-kept item, such as a product, whose inventory can be managed.
|
||||
|
||||
The `InventoryItem` data model mainly holds details related to the underlying stock item, but has relations to other data models that include its inventory details.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## InventoryLevel
|
||||
|
||||
An inventory level, represented by the `InventoryLevel` data model, holds the inventory and quantity details of an inventory item in a specific location.
|
||||
|
||||
It has three quantity-related fields:
|
||||
|
||||
- `stocked_quantity`: The available stock quantity of an item in the associated location.
|
||||
- `reserved_quantity`: The quantity reserved from the available `stocked_quantity`. It indicates the quantity that's still not removed from stock, but considered as unavailable when checking whether an item is in stock.
|
||||
- `incoming_quantity`: The incoming stock quantity of an item into the associated location. This field doesn't play into the `stocked_quantity` or when checking whether an item is in stock.
|
||||
|
||||
### Associated Location
|
||||
|
||||
The inventory level's location is determined by the `location_id` field. The Medusa application links the `InventoryLevel` data model with the `StockLocation` data model from the Stock Location Module.
|
||||
|
||||
Learn more about this relation in [this guide](../relations-to-other-modules/page.mdx#stock-location-module)
|
||||
|
||||
---
|
||||
|
||||
## ReservationItem
|
||||
|
||||
A reservation item, represented by the `ReservationItem` data model, represents unavailable quantity of an inventory item in a location. It's used when an order is placed but not fulfilled yet.
|
||||
|
||||
The reserved quantity is associated with a location, so it has a similar relation to that of the `InventoryLevel` with the Stock Location Module.
|
||||
@@ -0,0 +1,572 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Inventory Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Inventory Module in your application.
|
||||
|
||||
## Create an Inventory Item
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
sku: request.body.sku,
|
||||
title: request.body.title,
|
||||
requires_shipping: request.body.requires_shipping,
|
||||
})
|
||||
|
||||
res.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
sku: body.sku,
|
||||
title: body.title,
|
||||
requires_shipping: body.requires_shipping,
|
||||
})
|
||||
|
||||
return NextResponse.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Inventory Items
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
|
||||
res.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
|
||||
return NextResponse.json({ inventory_items: inventoryItems })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve an Inventory Item by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.retrieve(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.retrieve(
|
||||
params.id
|
||||
)
|
||||
|
||||
return NextResponse.json({ inventory_item: inventoryItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create an Inventory Level
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryLevel =
|
||||
await inventoryModuleService.createInventoryLevels({
|
||||
inventory_item_id: request.body.inventory_item_id,
|
||||
location_id: request.body.location_id,
|
||||
stocked_quantity: request.body.quantity,
|
||||
})
|
||||
|
||||
res.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryLevel =
|
||||
await inventoryModuleService.createInventoryLevels({
|
||||
inventory_item_id: body.inventory_item_id,
|
||||
location_id: body.location_id,
|
||||
stocked_quantity: body.quantity,
|
||||
})
|
||||
|
||||
return NextResponse.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Adjust Inventory in Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryLevel =
|
||||
await inventoryModuleService.adjustInventory(
|
||||
request.body.inventory_item_id,
|
||||
request.body.location_id,
|
||||
request.body.new_quantity
|
||||
)
|
||||
|
||||
res.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const inventoryLevel =
|
||||
await inventoryModuleService.adjustInventory(
|
||||
body.inventory_item_id,
|
||||
body.location_id,
|
||||
body.new_quantity
|
||||
)
|
||||
|
||||
return NextResponse.json({ inventory_level: inventoryLevel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Confirm Inventory Item Availability
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
res.json({
|
||||
is_available: await inventoryModuleService.confirmInventory(
|
||||
request.body.inventory_item_id,
|
||||
request.body.location_id,
|
||||
request.body.required_quantity
|
||||
),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
return NextResponse.json({
|
||||
is_available: await inventoryModuleService.confirmInventory(
|
||||
body.inventory_item_id,
|
||||
body.location_id,
|
||||
body.required_quantity
|
||||
),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Reservation Item
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const reservationItem =
|
||||
await inventoryModuleService.createReservationItems({
|
||||
inventory_item_id: request.body.inventory_item_id,
|
||||
location_id: request.body.location_id,
|
||||
quantity: request.body.reserved_quantity,
|
||||
})
|
||||
|
||||
res.json({ reservation_item: reservationItem })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const reservationItem =
|
||||
await inventoryModuleService.createReservationItems({
|
||||
inventory_item_id: body.inventory_item_id,
|
||||
location_id: body.location_id,
|
||||
quantity: body.reserved_quantity,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
reservation_item: reservationItem,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Quantity Details
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const reservedQuantity =
|
||||
await inventoryModuleService.retrieveReservedQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
const stockedQuantity =
|
||||
await inventoryModuleService.retrieveStockedQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
const availableQuantity =
|
||||
await inventoryModuleService.retrieveAvailableQuantity(
|
||||
request.params.inventory_item_id,
|
||||
[request.params.location_id]
|
||||
)
|
||||
|
||||
res.json({
|
||||
reserved_quantity: reservedQuantity,
|
||||
stocked_quantity: stockedQuantity,
|
||||
available_quantity: availableQuantity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
inventory_item_id: string
|
||||
location_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
|
||||
const reservedQuantity =
|
||||
await inventoryModuleService.retrieveReservedQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
const stockedQuantity =
|
||||
await inventoryModuleService.retrieveStockedQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
const availableQuantity =
|
||||
await inventoryModuleService.retrieveAvailableQuantity(
|
||||
params.inventory_item_id,
|
||||
[params.location_id]
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
reserved_quantity: reservedQuantity,
|
||||
stocked_quantity: stockedQuantity,
|
||||
available_quantity: availableQuantity,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Reservation
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
await inventoryModuleService.deleteReservationItems([
|
||||
request.params.reservation_id,
|
||||
])
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializeInventoryModule,
|
||||
} from "@medusajs/inventory-next"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
reservation_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const inventoryModuleService =
|
||||
await initializeInventoryModule({})
|
||||
|
||||
await inventoryModuleService.deleteReservationItems([
|
||||
params.reservation_id,
|
||||
])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/inventory-next) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,49 @@
|
||||
export const metadata = {
|
||||
title: `Inventory Module in Medusa Flows`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document explains how the Inventory Module is used within the Medusa application's flows.
|
||||
|
||||
## Product Variant Creation
|
||||
|
||||
When a product variant is created and its `manage_inventory` field's value is `true`, the Medusa application uses the Inventory Module to create an inventory item associated with that product variant.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Add to Cart
|
||||
|
||||
When a product variant with `manage_inventory` set to `true` is added to cart, the Medusa application uses the Inventory Module to check whether there's sufficient stocked quantity. If not, an error is thrown and the product variant won't be added to the cart.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Order Placed
|
||||
|
||||
When an order is placed, the Medusa application uses the Inventory Module to create a reservation item for each product variant with `manage_inventory` set to `true`. It also sets the `reserved_quantity` of the inventory level associated with the inventory item to the ordered quantity of that item.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Order Fulfillment
|
||||
|
||||
When an item in an order is fulfilled and the associated variant has its `manage_inventory` field set to `true`, the Medusa application uses the Inventory Module to:
|
||||
|
||||
- Subtract the `reserved_quantity` from the `stocked_quantity` in the inventory level associated with the variant's inventory item.
|
||||
- Reset the `reserved_quantity` to `0`.
|
||||
- Delete the associated reservation item.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Order Return
|
||||
|
||||
When an item in an order is returned and the associated variant has its `manage_inventory` field set to `true`, the Medusa application uses the Inventor Module to increment the `stocked_quantity` of the inventory level associated with the inventory item with the returned quantity.
|
||||
|
||||

|
||||
@@ -0,0 +1,155 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Inventory Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Inventory Module is the `@medusajs/inventory-next` NPM package that provides inventory-related features in your Medusa and Node.js applications.
|
||||
|
||||
<Note>
|
||||
|
||||
This module is an updated version of the [Inventory Module part of the multi-warehouse feature](../../modules/multiwarehouse/inventory-module.md). In Medusa V2, the previous version of the module will no longer be in use.
|
||||
|
||||
</Note>
|
||||
|
||||
## Features
|
||||
|
||||
### Inventory Items Management
|
||||
|
||||
Store and manage inventory of any stock-kept item, such as product variants.
|
||||
|
||||
Inventory items hold details of the underlying stock-kept item, as well as inventory details such as whether the item requires shipping.
|
||||
|
||||
```ts
|
||||
const inventoryItem =
|
||||
await inventoryModuleService.create({
|
||||
sku: "SHIRT",
|
||||
title: "Green Medusa Shirt",
|
||||
requires_shipping: true,
|
||||
})
|
||||
```
|
||||
|
||||
### Inventory Across Locations
|
||||
|
||||
Inventory items' quantities are set per locations through inventory levels. This gives you more flexibility in managing the quantity of a stock-kept item across different locations, such as different warehouses.
|
||||
|
||||
```ts
|
||||
const inventoryLevel =
|
||||
await inventoryModuleService.createInventoryLevels([{
|
||||
inventory_item_id: "iitem_123",
|
||||
location_id: "sloc_123",
|
||||
stocked_quantity: 20,
|
||||
}])
|
||||
```
|
||||
|
||||
### Reservation Management
|
||||
|
||||
Reserve quantities of inventory items at specific locations for orders or other purposes. The reserved quantity isn't considered for purchase, but can be deleted to revert the reservation.
|
||||
|
||||
```ts
|
||||
const reservationItem =
|
||||
await inventoryModuleService.createReservationItems([{
|
||||
inventory_item_id: "iitem_123",
|
||||
location_id: "sloc_123",
|
||||
quantity: 10,
|
||||
}])
|
||||
```
|
||||
|
||||
### Check Inventory Availability
|
||||
|
||||
Check whether an inventory item has the necessary quantity for purchase. Any reserved quantity is considered unavailable, even if the inventory item's quantity hasn't been adjusted yet.
|
||||
|
||||
```ts
|
||||
const isAvailable =
|
||||
await inventoryModuleService.confirmInventory(
|
||||
inventoryItemId,
|
||||
[locationId],
|
||||
neededQuantity
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Inventory Module
|
||||
|
||||
After installing the `@medusajs/inventory-next` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
inventoryService: {
|
||||
resolve: "@medusajs/inventory-next",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Inventory Module's Service
|
||||
|
||||
You can use the Inventory Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.INVENTORY` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
request.scope.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
res.json({
|
||||
inventory_items:
|
||||
await inventoryModuleService.list({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
container.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IInventoryService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const inventoryModuleService: IInventoryService =
|
||||
context.container.resolve(ModuleRegistrationName.INVENTORY)
|
||||
|
||||
const inventoryItems =
|
||||
await inventoryModuleService.list({})
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,27 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Inventory Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Inventory Module and other Commerce Modules.
|
||||
|
||||
## Product Module
|
||||
|
||||
Each product variant has different inventory details. The Medusa application forms a relation between the `ProductVariant` and `InventoryItem` data models.
|
||||
|
||||

|
||||
|
||||
A product variant, whose `manage_inventory` field is enabled, has an associated inventory item. Through that inventory's items relations in the Inventory Module, you can manage and check the variant's inventory quantity.
|
||||
|
||||
---
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
Reservation items and inventory items are associated with a location. The Medusa application forms a relation between the `ReservationItem` and `StockLocation` data models, and the `InventoryLevel` and `StockLocation` data models.
|
||||
|
||||

|
||||
|
||||
The stock location provides address details of the location.
|
||||
@@ -0,0 +1,51 @@
|
||||
export const metadata = {
|
||||
title: `Order Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about orders and their general fields.
|
||||
|
||||
## Order Data Model
|
||||
|
||||
An order represents a customer purchase in your store. The `Order` data model holds the information about that purchase.
|
||||
|
||||
---
|
||||
|
||||
## Order Items
|
||||
|
||||
The items purchased in the order are represented by the `OrderItem` data model. An order can have multiple items.
|
||||
|
||||

|
||||
|
||||
Learn more about order items in [this guide](../order-items/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Order’s Shipping Method
|
||||
|
||||
An order has one or more shipping methods used to handle item shipment. Each shipping method is represented by the `ShippingMethod` data model that holds its details.
|
||||
|
||||
### data Field
|
||||
|
||||
When fulfilling the order, you may use a third-party fulfillment provider that requires additional custom data to be passed along from the order creation process.
|
||||
|
||||
The `ShippingMethod` data model has a `data` field. It’s an object used to store custom data relevant later for fulfillment.
|
||||
|
||||
In the Medusa application, the `data` is passed to the Fulfillment Module when fulfilling items.
|
||||
|
||||
---
|
||||
|
||||
## Order Totals
|
||||
|
||||
The order’s total amounts (including tax total, total after an item is returned, etc…) are represented by the `OrderSummary` data model. An order can have multiple summaries.
|
||||
|
||||
---
|
||||
|
||||
## Order Payments
|
||||
|
||||
Payments made on an order, whether they’re capture or refund payments, are recorded as transactions represented by the `Transaction` data model.
|
||||
|
||||
An order can have multiple transactions. The sum of these transactions must be equal to the order summary’s total. Otherwise, there’s an outstanding amount.
|
||||
|
||||
Learn more about transactions in [this guide](../transactions/page.mdx).
|
||||
@@ -0,0 +1,301 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Order Change`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how to make changes to an order, such as return or exchange an item.
|
||||
|
||||
## What's an Order Change?
|
||||
|
||||
An order change is modifying the order’s items for business purposes. For example, when a customer requests to return an item, or when you suggest exchanging an item with another.
|
||||
|
||||
Order changes are represented by the `OrderChange` data model.
|
||||
|
||||
---
|
||||
|
||||
## Order Change Actions
|
||||
|
||||
An order change can have multiple underlying actions. For example, to exchange an item, you have to:
|
||||
|
||||
- Mark an item as returned.
|
||||
- Arrange to receive the item from the customer.
|
||||
- Add the new item to be sent to the customer.
|
||||
- Fulfill the item and send it to the customer.
|
||||
|
||||
Each of these actions is represented by the `OrderChangeAction` data model.
|
||||
|
||||
### Action Name
|
||||
|
||||
The `action` field of the `OrderChangeAction` holds the name of the action to perform. Based on the action, additional details are stored in the `details` object field of the data model.
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Action Name</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Expected details</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ITEM_ADD`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Adds a new item to the order.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity of the item to add.
|
||||
- `unit_price`: The price of the item.
|
||||
- `reference_id`: The ID of the item to add.
|
||||
|
||||
For example:
|
||||
|
||||
`{ quantity: 1; unit_price: 1000; reference_id: "orditem_123" }`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`ITEM_REMOVE`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Removes an item from the order.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity of the item to remove.
|
||||
- `unit_price`: The price of the item.
|
||||
- `reference_id`: The ID of the item to remove.
|
||||
|
||||
For example:
|
||||
|
||||
`{ quantity: 1; unit_price: 1000; reference_id: "orditem_123" }`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`WRITE_OFF_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Removes a quantity of an item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity to remove of the item.
|
||||
- `reference_id`: The ID of the item to remove its quantity.
|
||||
|
||||
`{ quantity: 1; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`RETURN_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Request a return of an item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity to return of the item.
|
||||
- `reference_id`: The ID of the item to return.
|
||||
|
||||
`{ quantity: 1; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`RECEIVE_RETURN_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark as received an item whose return was previously requested.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity received of the item.
|
||||
- `reference_id`: The ID of the item to mark its return as received.
|
||||
|
||||
`{ quantity: 1; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`RECEIVE_DAMAGED_RETURN_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark as received an item whose return was previously requested, but consider the items damaged.
|
||||
|
||||
This changes the order item’s `return_dismissed_quantity` field rather than its `return_received_quantity` field.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity received of the item.
|
||||
- `reference_id`: The ID of the item to mark its return as received.
|
||||
|
||||
`{ quantity: 1; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`CANCEL_RETURN`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Cancel a previously requested return of an item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity to cancel its return request.
|
||||
- `unit_price`: The price of the item.
|
||||
- `reference_id`: The ID of the item to cancel its return request.
|
||||
|
||||
`{ quantity: 1; unit_price: 1000; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`SHIPPING_ADD`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Add a shipping method to an item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `amount`: The amount of the shipping.
|
||||
- `reference_id`: The ID of the shipping method to add.
|
||||
|
||||
`{ amount: 1000; reference_id: "orditem_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`SHIP_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark a quantity of an item as shipped. This modifies the shipped_quantity field of an order item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity of the item to ship.
|
||||
- `reference_id`: The ID of the item to mark its specified quantity as shipped.
|
||||
|
||||
`{ quantity: 1; reference_id: "ordli_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`FULFILL_ITEM`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Mark a quantity of an item as fulfilled. This modifies the fulfilled_quantity field of an order item.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
- `quantity`: The quantity of the item to ship.
|
||||
- `reference_id`: The ID of the item to mark its specified quantity as shipped.
|
||||
|
||||
`{ quantity: 1; reference_id: "ordli_123"}`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`CANCEL`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Cancel the order change.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Doesn’t expect any data.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
|
||||
### Action Chaining
|
||||
|
||||
Actions are chained one after the other, similar to the example in the earlier section. This also allows you to cancel or undo some actions.
|
||||
|
||||
For example, if you request an order return with `RETURN_ITEM` action, you can later cancel it with the `CANCEL_RETURN` action.
|
||||
|
||||
The actions are ordered by the `ordering` field of the `OrderChangeAction` data model.
|
||||
|
||||
### Action Amount
|
||||
|
||||
The `OrderChangeAction` data model has an `amount` field that indicates a change in the order’s amount that this action incurs.
|
||||
|
||||
---
|
||||
|
||||
## Order Change Confirmation
|
||||
|
||||
The `OrderChange` data model has a `status` field that indicates its current status. By default, it’s pending. At this point, the order change’s actions aren’t applied to the order yet.
|
||||
|
||||
To apply these changes to the order, you must confirm the order change. When the order change is confirmed:
|
||||
|
||||
- The status of the order change is changed to `confirmed`.
|
||||
- The order’s items are changed based on the order change’s actions. For example, an item is added, or an existing item’s quantity is changed.
|
||||
- The order summary is modified to reflect new changes.
|
||||
|
||||
<Note>
|
||||
|
||||
If there are changes to the order’s total amount due to refunding or capturing payment, they must be added as transactions first. Otherwise, they won’t be accounted for when modifying the order summary
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,37 @@
|
||||
export const metadata = {
|
||||
title: `Order Item`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the order item and the general details it holds.
|
||||
|
||||
## OrderItem Data Model
|
||||
|
||||
Each item in an order is represented by the `OrderItem` data model. It holds details related to the quantity of the item and the underlying product details.
|
||||
|
||||
---
|
||||
|
||||
## Order Item Quantity
|
||||
|
||||
The `OrderItem` data model has the following quantity fields:
|
||||
|
||||
- `quantity`: The quantity ordered.
|
||||
- `fulfilled_quantity`: The quantity that’s been fulfilled.
|
||||
- `shipped_quantity`: The quantity that’s been shipped.
|
||||
- `return_requested_quantity`: The quantity requested to be returned, but hasn’t been received yet.
|
||||
- `return_received_quantity`: The quantity that’s been received from the customer due to a return request.
|
||||
- `return_dismissed_quantity`: The quantity that’s been received from the customer due to a return request, but the items are damaged.
|
||||
- `written_off_quantity`: The quantity removed from the originally ordered quantity.
|
||||
|
||||
---
|
||||
|
||||
## Item’s Product Details
|
||||
|
||||
The details of the purchased products are represented by the `LineItem` data model. Not only does a line item hold the details of the product, but also details related to its price, adjustments due to promotions, and taxes.
|
||||
|
||||
<Note>
|
||||
|
||||
The `LineItem` data model is similar to the Cart Module’s `LineItem` data model. So, when using both modules, the data can be copied between the modules as-is.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,33 @@
|
||||
export const metadata = {
|
||||
title: `Order Versioning`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how an order and its details are versioned.
|
||||
|
||||
## What's Versioning?
|
||||
|
||||
Versioning means assigning a version number to a record, such as an order and its items. This is useful to view the different versions of the order following changes in its lifetime.
|
||||
|
||||
---
|
||||
|
||||
## version Field
|
||||
|
||||
The `Order` and `OrderSummary` data models have a `version` field that indicates the current order version. By default, its value is `1`.
|
||||
|
||||
The `OrderItem` data model also has a `version` field, but it indicates the version it belongs to. For example, original items in the order have version `1`. Then, when a new item is added, its version is `2`. If an existing item is modified, such as its quantity has been changed, its version is updated.
|
||||
|
||||
---
|
||||
|
||||
## Order Change Versioning
|
||||
|
||||
The `OrderChange` and `OrderChangeAction` data models also have a `version` field. When an order change is created, the `version` field’s value is the associated order’s version incremented.
|
||||
|
||||
So, if the order’s `version` is `1`, the order change’s version is `2`.
|
||||
|
||||
Then, once the order change is confirmed and applied to the order, the versions of the order and order summary change to that of the order change.
|
||||
|
||||
Order items change depending on the version they were added or modified in, as explained in the earlier section.
|
||||
|
||||

|
||||
@@ -0,0 +1,177 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Order Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Order Module is the `@medusajs/order` NPM package that provides order-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Order Management
|
||||
|
||||
Store and manage your orders by retrieving, creating, canceling, and performing other operations.
|
||||
|
||||
```ts
|
||||
const order = await orderModuleService.create({
|
||||
currency_code: "usd",
|
||||
items: [
|
||||
{
|
||||
title: "Shirt",
|
||||
quantity: 1,
|
||||
unit_price: 3000,
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
name: "Express shipping",
|
||||
amount: 3000,
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Draft Orders
|
||||
|
||||
Allow merchants to create orders on behalf of their customers as draft orders that later are transformed to regular orders.
|
||||
|
||||
```ts
|
||||
const draftOrder = await orderModuleService.create({
|
||||
currency_code: "usd",
|
||||
// other details...
|
||||
status: "draft",
|
||||
})
|
||||
```
|
||||
|
||||
### Returns, Exchanges, and Other Order Changes
|
||||
|
||||
Orders can be changed to return items from the customer, exchange an item with another, change the quantity of an item, or other changes.
|
||||
|
||||
Changes are only applied after confirmation, and order history is preserved through versioning.
|
||||
|
||||
```ts
|
||||
const orderChange =
|
||||
await orderModuleService.createOrderChange({
|
||||
order_id: "ord_123",
|
||||
})
|
||||
|
||||
// add item to the order
|
||||
await orderModuleService.addOrderAction({
|
||||
order_change_id: orderChange.id,
|
||||
action: "ADD_ITEM",
|
||||
details: {
|
||||
quantity: 1,
|
||||
unit_price: 4000,
|
||||
reference_id: "orditem_123",
|
||||
},
|
||||
})
|
||||
|
||||
// confirm order change and apply it to order
|
||||
await orderModuleService.confirmOrderChange("ord_123")
|
||||
```
|
||||
|
||||
### Apply Promotions
|
||||
|
||||
Apply promotions or discounts to the order's items and shipping methods by adding adjustment lines that are factored into their subtotals.
|
||||
|
||||
```ts
|
||||
const lineAdjustments =
|
||||
await orderModuleService.addLineItemAdjustments({
|
||||
item_id: "cali_123",
|
||||
code: "50OFF",
|
||||
amount: 500,
|
||||
})
|
||||
|
||||
const shippingAdjustments =
|
||||
await orderModuleService.addShippingMethodAdjustments({
|
||||
shipping_method_id: "casm_123",
|
||||
code: "FREESHIPPING",
|
||||
amount: 1000,
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Order Module
|
||||
|
||||
After installing the `@medusajs/order` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
order: {
|
||||
resolve: "@medusajs/order",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Order Module's Service
|
||||
|
||||
You can use the Order Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.ORDER` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IOrderModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const orderModuleService: IOrderModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.ORDER)
|
||||
|
||||
res.json({
|
||||
orders: await orderModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IOrderModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const orderModuleService: IOrderModuleService =
|
||||
container.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const orders = await orderModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IOrderModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const orderModuleService: IOrderModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.API_KEY
|
||||
)
|
||||
const orders = await orderModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
sidebar_label: "Promotions Adjustments"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `Promotions Adjustments in Orders`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how a promotion is applied to an order’s items and shipping methods using adjustment lines.
|
||||
|
||||
## What are Adjustment Lines?
|
||||
|
||||
An adjustment line indicates a change to a line item or a shipping method’s amount. It’s used to apply promotions or discounts on an order.
|
||||
|
||||
The `LineItemAdjustment` data model represents adjustment lines for a line item, and the `ShippingMethodAdjustment` data model represents adjustment lines for a shipping method.
|
||||
|
||||

|
||||
|
||||
The `amount` field of the adjustment line indicates the amount to be discounted from the original amount. Also, the ID of the applied promotion can be stored in the `promotion_id` field of the adjustment line.
|
||||
|
||||
---
|
||||
|
||||
## Promotion Actions
|
||||
|
||||
When using the Order and Promotion modules together, such as in the Medusa application, use the `computeActions` method of the Promotion Module’s main service. It retrieves the actions of line items and shipping methods.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about actions in the [Promotion Module’s documentation](../../promotion/actions/).
|
||||
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
import {
|
||||
ComputeActionAdjustmentLine,
|
||||
ComputeActionItemLine,
|
||||
ComputeActionShippingLine,
|
||||
// ...
|
||||
} from "@medusajs/types"
|
||||
|
||||
// ...
|
||||
|
||||
// retrieve the order
|
||||
const order = await orderModuleService.retrieve("ord_123", {
|
||||
relations: [
|
||||
"items.item.adjustments",
|
||||
"shipping_methods.adjustments",
|
||||
],
|
||||
})
|
||||
// retrieve the line item adjustments
|
||||
const lineItemAdjustments: ComputeActionItemLine[] = []
|
||||
order.items.forEach((item) => {
|
||||
const filteredAdjustments = item.adjustments?.filter(
|
||||
(adjustment) => adjustment.code !== undefined
|
||||
) as unknown as ComputeActionAdjustmentLine[]
|
||||
if (filteredAdjustments.length) {
|
||||
lineItemAdjustments.push({
|
||||
...item,
|
||||
...item.detail,
|
||||
adjustments: filteredAdjustments,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
//retrieve shipping method adjustments
|
||||
const shippingMethodAdjustments: ComputeActionShippingLine[] =
|
||||
[]
|
||||
order.shipping_methods.forEach((shippingMethod) => {
|
||||
const filteredAdjustments =
|
||||
shippingMethod.adjustments?.filter(
|
||||
(adjustment) => adjustment.code !== undefined
|
||||
) as unknown as ComputeActionAdjustmentLine[]
|
||||
if (filteredAdjustments.length) {
|
||||
shippingMethodAdjustments.push({
|
||||
...shippingMethod,
|
||||
adjustments: filteredAdjustments,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// compute actions
|
||||
const actions = await promotionModuleService.computeActions(
|
||||
["promo_123"],
|
||||
{
|
||||
items: lineItemAdjustments,
|
||||
shipping_methods: shippingMethodAdjustments,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `computeActions` method accepts the existing adjustments of line items and shipping methods to compute the actions accurately.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `computeActions` method in [this reference](/references/promotion/computeActions).
|
||||
|
||||
</Note>
|
||||
|
||||
Then, use the returned `addItemAdjustment` and `addShippingMethodAdjustment` actions to set the order’s line items and the shipping method’s adjustments.
|
||||
|
||||
```ts
|
||||
import {
|
||||
AddItemAdjustmentAction,
|
||||
AddShippingMethodAdjustment,
|
||||
// ...
|
||||
} from "@medusajs/types"
|
||||
|
||||
// ...
|
||||
|
||||
await orderModuleService.setLineItemAdjustments(
|
||||
order.id,
|
||||
actions.filter(
|
||||
(action) => action.action === "addItemAdjustment"
|
||||
) as AddItemAdjustmentAction[]
|
||||
)
|
||||
|
||||
await orderModuleService.setShippingMethodAdjustments(
|
||||
order.id,
|
||||
actions.filter(
|
||||
(action) =>
|
||||
action.action === "addShippingMethodAdjustment"
|
||||
) as AddShippingMethodAdjustment[]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Discountable Option
|
||||
|
||||
The `LineItem` data model has an `is_discountable` field that indicates whether promotions can be applied to the line item. It’s enabled by default.
|
||||
|
||||
When disabled, a promotion can’t be applied to a line item. In the context of the Promotion Module, the promotion isn’t applied to the line item even if it matches its rules.
|
||||
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Order Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Order Module and other Commerce Modules.
|
||||
|
||||
## Sales Channel Module
|
||||
|
||||
Orders can be scoped to a sales channel. The Medusa application forms a relation between the `Order` and the `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
This is useful for omnichannel purposes. Associating an order with a sales channel in turn ensures that associated products, inventory, and other details are also scoped to that sales channel.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
sidebar_label: "Tax Lines"
|
||||
---
|
||||
|
||||
export const metadata = {
|
||||
title: `Tax Lines in Order Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about tax lines in an order.
|
||||
|
||||
## What are Tax Lines?
|
||||
|
||||
A tax line indicates the tax rate of a line item or a shipping method. The `LineItemTaxLine` data model represents a line item’s tax line, and the `ShippingMethodTaxLine` data model represents a shipping method’s tax line.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Tax Inclusivity
|
||||
|
||||
By default, the tax amount is calculated by taking the tax rate from the line item or shipping method’s amount and then added to the item/method’s subtotal.
|
||||
|
||||
However, line items and shipping methods have an `is_tax_inclusive` field that, when enabled, indicates that the item or method’s price already includes taxes.
|
||||
|
||||
So, instead of calculating the tax rate and adding it to the item/method’s subtotal, it’s calculated as part of the subtotal.
|
||||
|
||||
<Note>
|
||||
|
||||
The following diagram is a simplified showcase of how a subtotal is calculated from the tax perspective.
|
||||
|
||||
</Note>
|
||||
|
||||

|
||||
@@ -0,0 +1,79 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Transactions`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about an order’s transactions and usefulness.
|
||||
|
||||
## What is a Transaction?
|
||||
|
||||
A transaction represents any order payment process, such as capturing or refunding an amount. It’s represented by the `Transaction` data model.
|
||||
|
||||
The transaction’s main purpose is to ensure a correct balance between paid and outstanding amounts.
|
||||
|
||||
---
|
||||
|
||||
## Transaction Reference
|
||||
|
||||
The Order Module doesn’t provide payment processing functionalities, so it doesn’t store payments that can be processed. For that, use the Payment Module or custom logic.
|
||||
|
||||
The `Transaction` data model has two fields that determine which data model and record holds the actual payment’s details:
|
||||
|
||||
- `reference`: indicates the table’s name in the database. For example, `payment` if you’re using the Payment Module.
|
||||
- `reference_id`: indicates the ID of the record in the table. For example, `pay_123`.
|
||||
|
||||
---
|
||||
|
||||
## Checking Outstanding Amount
|
||||
|
||||
The order’s total is stored in the `OrderSummary`'s `total` field. To check the outstanding amount of the order, the transaction amounts of an order are summed. Then:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Condition</Table.HeaderCell>
|
||||
<Table.HeaderCell>Result</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
summary’s total - transaction amounts total = 0
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
There’s no outstanding amount.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
summary’s total - transaction amounts total > 0
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The customer owes additional payment to the merchant.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
summary’s total - transaction amounts total < 0
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The merchant owes the customer a refund.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ChildDocs } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Commerce Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this section of the documentation, you'll find guides and references related to Medusa's Commerce Modules.
|
||||
|
||||
<ChildDocs onlyTopLevel={true} />
|
||||
|
||||
---
|
||||
|
||||
## How to Use Modules
|
||||
|
||||
The Commerce Modules can be used in many use cases, including:
|
||||
|
||||
- Medusa Application: The Medusa application uses the Commerce Modules to expose commerce features through the REST APIs.
|
||||
- Serverless Application: Use the Commerce Modules in a serverless application, such as a Next.js application, without having to manage a fully-fledged ecommerce system. You can use it by installing it in your Node.js project as an NPM package.
|
||||
- Node.js Application: Use the Commerce Modules in any Node.js application by installing it with NPM.
|
||||
@@ -0,0 +1,377 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Payment Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Payment Module in your application.
|
||||
|
||||
## Create a Payment Collection
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const paymentCollection =
|
||||
await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
})
|
||||
|
||||
res.json({
|
||||
payment_collection: paymentCollection,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentCollection =
|
||||
await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payment_collection: paymentCollection,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const paymentSession =
|
||||
await paymentModuleService.createPaymentSession(
|
||||
"pay_col_123",
|
||||
{
|
||||
currency_code: "usd",
|
||||
provider_id: "system",
|
||||
amount: 4000,
|
||||
data: {},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
payment_session: paymentSession,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentSession =
|
||||
await paymentModuleService.createPaymentSession(
|
||||
"pay_col_123",
|
||||
{
|
||||
currency_code: "usd",
|
||||
provider_id: "system",
|
||||
amount: 4000,
|
||||
data: {},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
payment_session: paymentSession,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Payment Sessions of Payment Collection
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const paymentSessions =
|
||||
await paymentModuleService.listPaymentSessions({
|
||||
payment_collection_id: ["pay_col_123"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
payment_sessions: paymentSessions,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const paymentSessions =
|
||||
await paymentModuleService.listPaymentSessions({
|
||||
payment_collection_id: ["pay_col_123"],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payment_sessions: paymentSessions,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Authorize Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const payment =
|
||||
await paymentModuleService.authorizePaymentSession(
|
||||
"payses_123",
|
||||
{}
|
||||
)
|
||||
|
||||
res.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payment =
|
||||
await paymentModuleService.authorizePaymentSession(
|
||||
"payses_123",
|
||||
{}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Payments of Payment Session
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const payments = await paymentModuleService.listPayments({
|
||||
session_id: "payses_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
payments,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function GET(
|
||||
request: Request
|
||||
) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payments = await paymentModuleService.listPayments({
|
||||
session_id: "payses_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payments,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Capture Payment
|
||||
|
||||
<CodeTabs groupId="app-type" isCodeCodeTabs={true}>
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
const payment = await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_123",
|
||||
})
|
||||
|
||||
res.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePaymentModule,
|
||||
} from "@medusajs/payment"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const paymentModuleService = await initializePaymentModule()
|
||||
|
||||
const payment = await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_123",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
payment,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/payment) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
sidebar_label: "Module Options"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Payment Module Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the options of the Payment Module.
|
||||
|
||||
## All Module 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>
|
||||
|
||||
`webhook_delay`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A number indicating the delay in milliseconds before processing a webhook event.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`5000`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`webhook_retries`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The 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.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`providers`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An array of payment providers to install and register. Learn more [in this section](#providers).
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
---
|
||||
|
||||
## providers
|
||||
|
||||
The `providers` option is an array of either payment provider modules, payment plugins, or path to a file that holds a payment provider.
|
||||
|
||||
When the Medusa application starts, these providers are registered and can be used to process payments.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
payment: {
|
||||
resolve: "@medusajs/payment",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/payment-stripe",
|
||||
options: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: "medusa-payment-paypal",
|
||||
options: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `providers` option is an array of objects that accept the following properties:
|
||||
|
||||
- `resolve`: A string indicating the package name of the payment provider module or the payment plugin, or the path to the file defining the payment provider.
|
||||
- `options`: An optional object of options to pass to the payment provider.
|
||||
@@ -0,0 +1,164 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Payment Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Payment Module is the `@medusajs/payment` NPM package that provides payment-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Add Payment Functionalities to Any Resource
|
||||
|
||||
The Payment Module provides payment functionalities that allow you to process payment of any resource, such as a cart.
|
||||
|
||||
All payment processing starts with creating a payment collection.
|
||||
|
||||
```ts
|
||||
const paymentCollection =
|
||||
await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
})
|
||||
```
|
||||
|
||||
### Authorize, Capture, and Refund Payment
|
||||
|
||||
The Payment Module provides essential features to receive and handle payments, including authorizing, capturing, and refunding payment.
|
||||
|
||||
```ts
|
||||
await paymentModuleService.capturePayment({
|
||||
payment_id: "pay_1",
|
||||
})
|
||||
```
|
||||
|
||||
### Integrate Third-Party Payment Providers
|
||||
|
||||
Use payment providers like Stripe and PayPal to handle and process payments.
|
||||
|
||||
```ts
|
||||
const payment =
|
||||
await paymentModuleService.createPaymentSession(
|
||||
"pay_col_1",
|
||||
{
|
||||
provider_id: "stripe",
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
data: {
|
||||
// necessary data for the payment provider
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Handle Webhook Events
|
||||
|
||||
The Payment Module allows you to handle webhook events from third-party providers and process the associated payment.
|
||||
|
||||
```ts
|
||||
await paymentModuleService.processEvent({
|
||||
provider: "stripe",
|
||||
payload: {
|
||||
// webhook payload
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Payment Module
|
||||
|
||||
After installing the `@medusajs/payment` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
apiKey: {
|
||||
resolve: "@medusajs/payment",
|
||||
options: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Payment Module's Service
|
||||
|
||||
You can use the Payment Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.PAYMENT` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.PAYMENT)
|
||||
|
||||
res.json({
|
||||
payment_collections:
|
||||
await paymentModuleService.listPaymentCollections(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
container.resolve(ModuleRegistrationName.API_KEY)
|
||||
|
||||
const payment_collections =
|
||||
await paymentModuleService.listPaymentCollections()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IPaymentModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const paymentModuleService: IPaymentModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.API_KEY
|
||||
)
|
||||
|
||||
const payment_collections =
|
||||
await paymentModuleService.listPaymentCollections()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,37 @@
|
||||
export const metadata = {
|
||||
title: `Payment Collection`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what a payment collection is and how to use it with the Cart Module.
|
||||
|
||||
## What's a Payment Collection?
|
||||
|
||||
A payment collection stores payment details related to a resource, such as a cart or an order. It’s represented by the `PaymentCollection` data model.
|
||||
|
||||
Every purchase or request for payment starts with a payment collection. The collection holds details necessary to complete the payment, including:
|
||||
|
||||
- The payment sessions that represents the payment amount to authorize.
|
||||
- The payments that are created when a payment session is authorized. They can be captured and refunded.
|
||||
- The payment providers that handle the processing of each payment session, including the authorization, capture, and refund.
|
||||
|
||||
---
|
||||
|
||||
## Usage with the Cart Module
|
||||
|
||||
The Cart Module provides cart management features. However, it doesn’t provide any features related to accepting payment.
|
||||
|
||||
With the Payment Module, you can create a payment collection for the cart and handle the payment functionalities.
|
||||
|
||||
The Medusa application creates a link between the `PaymentCollection` and `Cart` data models. It also implements the payment flow during checkout as explained in [this documentation](../payment-flow/page.mdx).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Multiple Payments
|
||||
|
||||
The payment collection supports multiple payment sessions and payments. You can use this to accept payments in increments or split payments across payment providers.
|
||||
|
||||

|
||||
@@ -0,0 +1,144 @@
|
||||
export const metadata = {
|
||||
title: `Payment Flow`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the payment flow implemented by the Medusa application. This is the recommended flow to follow when accepting a payment for a resource using the Payment Module.
|
||||
|
||||
<Note>
|
||||
|
||||
The flow described is in the checkout context. However, you can apply it in any use case.
|
||||
|
||||
</Note>
|
||||
|
||||
## Flow Overview
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 1. Create a Payment Collection
|
||||
|
||||
The payment collection holds all details related to a resource’s payment operations. So, you start off by creating a payment collection.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const paymentCollection =
|
||||
await paymentModuleService.createPaymentCollections({
|
||||
region_id: "reg_123",
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
})
|
||||
```
|
||||
|
||||
You can then link the payment collection to another resource, such as a cart in the Cart Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `createPaymentCollections` method in [this reference](/references/payment/createPaymentCollections).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 2. Create Payment Sessions
|
||||
|
||||
The payment collection has one or more payment sessions, each being a payment amount to be authorized by a payment provider.
|
||||
|
||||
So, after creating the payment collection, create at least one payment session for a provider.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const paymentSession =
|
||||
await paymentModuleService.createPaymentSession(
|
||||
paymentCollection.id,
|
||||
{
|
||||
provider_id: "stripe",
|
||||
currency_code: "usd",
|
||||
amount: 5000,
|
||||
data: {
|
||||
// any necessary data for the
|
||||
// payment provider
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You can also create payment sessions for every supported payment provider to allow customers to choose from them.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `createPaymentSession` method in [this reference](/references/payment/createPaymentSession).
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## 3. Authorize Payment Session
|
||||
|
||||
Once the customer chooses a payment session, start the authorization process. This may involve some action performed by the third-party payment provider, such as entering a 3DS code.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
const payment =
|
||||
await paymentModuleService.authorizePaymentSession(
|
||||
paymentSession.id,
|
||||
{}
|
||||
)
|
||||
```
|
||||
|
||||
When the payment authorization is successful, a payment is created and returned.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `authorizePaymentSession` method in [this reference](/references/payment/authorizePaymentSession).
|
||||
|
||||
</Note>
|
||||
|
||||
### Handling Additional Action
|
||||
|
||||
If the payment authorization isn’t successful, either because it requires additional action or for another reason, the method updates the payment session with the new status and throws an error.
|
||||
|
||||
In that case, you can catch that error and, if there are required actions, handle them accordingly, then retry the authorization.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
try {
|
||||
const payment =
|
||||
await paymentModuleService.authorizePaymentSession(
|
||||
paymentSession.id,
|
||||
{}
|
||||
)
|
||||
} catch (e) {
|
||||
// retrieve the payment session again
|
||||
const updatedPaymentSession = (
|
||||
await paymentModuleService.listPaymentSessions({
|
||||
id: [paymentSession.id],
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (updatedPaymentSession.status === "requires_more") {
|
||||
// TODO perform required action
|
||||
// TODO authorize payment again.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Payment Flow Complete
|
||||
|
||||
The payment flow is complete once the payment session is authorized and the payment is created.
|
||||
|
||||
You can then use the payment to capture the amount using the [capturePayment method](/references/payment/capturePayment). You can also refund captured amounts using the [refundPayment method](/references/payment/refundPayment).
|
||||
|
||||
<Note>
|
||||
|
||||
Some payment providers allow capturing the payment automatically once it’s authorized. In that case, you don’t need to do it manually.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,47 @@
|
||||
export const metadata = {
|
||||
title: `Payment Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what a payment provider is.
|
||||
|
||||
## What's a Payment Provider?
|
||||
|
||||
A payment provider handles payment processing. It can integrate third-party payment providers, such as Stripe or PayPal.
|
||||
|
||||
To authorize a payment amount with a payment provider, a payment session is created and associated with that payment provider. The payment provider is then used to handle the authorization.
|
||||
|
||||
After the payment session is authorized, the payment provider is associated with the resulting payment and handles its payment processing, such as to capture or refund payment.
|
||||
|
||||
---
|
||||
|
||||
## System Payment Provider
|
||||
|
||||
The Payment Module provides a `system` payment provider that acts as a placeholder payment provider. It doesn’t handle payment processing and delegates that to the merchant. It acts similarly to a cash-on-delivery (COD) payment method.
|
||||
|
||||
---
|
||||
|
||||
## How are Payment Providers Created?
|
||||
|
||||
A payment provider is a TypeScript or JavaScript class that extends the `AbstractPaymentProvider` imported from `@medusajs/utils`. It can then be used in a payment plugin or exported in a provider module.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Refer to [this guide](/references/payment/provider) on how to create a payment provider for the Payment Module.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Configure Payment Providers
|
||||
|
||||
The Payment Module accepts a `providers` option that allows you to register providers in your application.
|
||||
|
||||
Learn more about this option in [this documentation](../module-options/page.mdx#providers).
|
||||
|
||||
---
|
||||
|
||||
## PaymentProvider Data Model
|
||||
|
||||
When the Medusa application starts and registers the payment providers, it also creates a record of the `PaymentProvider` data model if none exists. This data model is used to reference a payment provider and determine whether it’s installed in the application.
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
sidebar_label: "Stripe"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Stripe Provider Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the Stripe provider module and how to install and use it in the Payment Module.
|
||||
|
||||
## 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, allowing for a concern-free development process.
|
||||
|
||||
---
|
||||
|
||||
## Install the Stripe Provider Module
|
||||
|
||||
<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}/hooks/payment/stripe`, where `{medusa_url}` with the URL to your deployed Medusa application.
|
||||
|
||||
</Note>
|
||||
|
||||
To install the Stripe provider module, run the following command in the directory of your Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/payment-stripe
|
||||
```
|
||||
|
||||
Next, add the module to the array of providers passed to the Payment Module:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
payment: {
|
||||
resolve: "@medusajs/payment",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "@medusajs/payment-stripe",
|
||||
options: {
|
||||
credentials: {
|
||||
usd: {
|
||||
apiKey: process.env.STRIPE_USD_API_KEY,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module 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>
|
||||
|
||||
`credentials`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
An object where each entry is a stripe provider installation. The object’s keys are the name suffix of the provider, where the provider name will be formatted as `stripe-{key}`. For example, `stripe-usd`.
|
||||
|
||||
Each value is an object that accepts the following properties:
|
||||
|
||||
- `apiKey`: A string indicating the Stripe API key.
|
||||
- `webhookSecret`: (optional in development) A string indicating the Stripe webhook secret. This is only useful for deployed Medusa applications.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
\-
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`capture`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Whether to automatically capture payment after authorization.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
No
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`false`
|
||||
|
||||
</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 integrate 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.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
STRIPE_USD_API_KEY=<YOUR_STRIPE_API_KEY>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Use Provider
|
||||
|
||||
To use the Stripe provider, create a payment session for the provider:
|
||||
|
||||
```ts
|
||||
const paymentSession =
|
||||
await paymentModuleService.createPaymentSession(
|
||||
"pay_col_123",
|
||||
{
|
||||
provider_id: "stripe-usd",
|
||||
amount: 5000,
|
||||
currency_code: "usd",
|
||||
data: {
|
||||
// any necessary data
|
||||
// to pass to stripe
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
export const metadata = {
|
||||
title: `Payment Session`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what a payment session is.
|
||||
|
||||
## What's a Payment Session?
|
||||
|
||||
A payment session, represented by the `PaymentSession` data model, is a payment amount to be authorized. It’s associated with a payment provider that handles authorizing it.
|
||||
|
||||
A payment collection can have multiple payment sessions. For example, during checkout, when a customer chooses between paying with Stripe or PayPal, each of these payment options is a payment session associated with a payment provider (Stripe or PayPal).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## data field
|
||||
|
||||
Payment providers may need some additional data to process the payment later. The `PaymentSession` data model has a `data` field used to store that data.
|
||||
|
||||
For example, for Stripe, you must pass Stripe’s customer ID when processing the payment. So, when you create a payment session, the Stripe payment provider creates the customer in Stripe and stores the ID in the `data` field.
|
||||
|
||||
---
|
||||
|
||||
## Payment Session Status
|
||||
|
||||
The `status` field of a payment session indicates its current status. Its value can be:
|
||||
|
||||
- `pending`: The payment session is awaiting authorization.
|
||||
- `requires_more`: The payment session requires an action before it’s authorized. For example, to enter a 3DS code.
|
||||
- `authorized`: The payment session is authorized.
|
||||
- `error`: An error occurred while authorizing the payment.
|
||||
- `canceled`: The authorization of the payment session has been canceled.
|
||||
@@ -0,0 +1,37 @@
|
||||
export const metadata = {
|
||||
title: `Payment`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what a payment is and how it's created, captured, and refunded.
|
||||
|
||||
## What's a Payment?
|
||||
|
||||
When a payment session is authorized, a payment, represented by the `Payment` data model, is created. This payment is an authorized amount that’s later captured or refunded.
|
||||
|
||||
A payment carries along many of the data and relations of a payment session:
|
||||
|
||||
- It belongs to the same payment collection.
|
||||
- It’s associated with the same payment provider, which handles further payment processing.
|
||||
- It stores the payment session’s `data` field in its `data` field, as it’s still useful for the payment provider’s processing.
|
||||
|
||||
---
|
||||
|
||||
## Capture Payments
|
||||
|
||||
When a payment is captured, a capture, represented by the `Capture` data model, is created. It holds details related to the capture, such as the amount, the capture date, and more.
|
||||
|
||||
The payment can also be captured incrementally, each time a capture record is created for that amount.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Refund Payments
|
||||
|
||||
An amount of a payment can be refunded if it’s already captured. Once it’s refunded, a refund, represented by the `Refund` data model, is created. It holds details related to the refund, such as the amount, refund date, and more.
|
||||
|
||||
A payment can be refunded multiple times, and each time a refund record is created for that refund.
|
||||
|
||||

|
||||
@@ -0,0 +1,27 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Payment Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Payment Module and other Commerce Modules.
|
||||
|
||||
## Cart Module
|
||||
|
||||
The Payment Module can be used with the Cart Module to accept payment for the cart during checkout.
|
||||
|
||||
Learn more about this relation in [this documentation](../payment-collection/page.mdx#usage-with-the-cart-module).
|
||||
|
||||
---
|
||||
|
||||
## Region Module
|
||||
|
||||
You can specify for each region which payment providers are available. The Medusa application forms a relation between the `PaymentProvider` and the `Region` data models.
|
||||
|
||||

|
||||
|
||||
This increases the flexibility of your store.
|
||||
|
||||
For example, paired with other modules, such as the Cart Module, you only show during checkout the payment providers associated with the cart's region.
|
||||
@@ -0,0 +1,44 @@
|
||||
export const metadata = {
|
||||
title: `Webhook Events`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how the Payment Module supports listening to webhook events.
|
||||
|
||||
## What's a Webhook Event?
|
||||
|
||||
A webhook event is sent from a third-party payment provider to your application. It indicates a change in a payment’s status. This is useful in different cases such as when a payment is being processed asynchronously or when a request is interrupted.
|
||||
|
||||
---
|
||||
|
||||
## processEvent Method
|
||||
|
||||
The Payment Module’s main service (`IPaymentModuleService`) provides a `processEvent` method used to handle incoming webhook events from third-party providers. The method delegates the handling to the associated payment provider, which returns the event's details.
|
||||
|
||||
If the event's details indicate that the payment should be authorized, then the `authorizePaymentSession` of the main service is executed on the specified payment session.
|
||||
|
||||
If the event's details indicate that the payment should be captured, then the `capturePayment` of the main service is executed on the payment of the specified payment session.
|
||||
|
||||

|
||||
|
||||
You can use this method in your webhook listener API routes or endpoints.
|
||||
|
||||
<Note>
|
||||
|
||||
Medusa V2 implements a webhook listener at the `/hooks/payment/[provider]` API route, where `[provider]` is the ID of the provider (for example, `stripe`). You can use that webhook listener in your third-party payment provider's configurations.
|
||||
|
||||
</Note>
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
await paymentModuleService.processEvent({
|
||||
provider: "stripe",
|
||||
payload: {
|
||||
// webhook event data
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Learn more about the method’s parameters and return types in [this reference](/references/payment/processEvent).
|
||||
@@ -0,0 +1,75 @@
|
||||
export const metadata = {
|
||||
title: `Pricing Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the main concepts in the Pricing Module, and how data is stored and related.
|
||||
|
||||
## Price
|
||||
|
||||
The `Price` data model represents any price.
|
||||
|
||||
Prices can be conditioned by the `min_quantity` and `max_quantity` fields, which are helpful when calculating the price for a specific quantity.
|
||||
|
||||
If a price has its `min_quantity` or `max_quantity` fields set, they’re only considered for the price calculation if they have a lower `min_quantity` or a higher `max_quantity` than the quantity specified for calculation.
|
||||
|
||||
---
|
||||
|
||||
## Price Set
|
||||
|
||||
A `PriceSet` represents a collection of prices that are linked to a resource (for example, a product or a shipping option). Each of these prices are represented by the `Price` data module.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Prices with Rules
|
||||
|
||||
### Rule Type
|
||||
|
||||
Each price within a price set can be applied for different conditions. These conditions are represented as rule types.
|
||||
|
||||
A `RuleType` defines custom conditions. Each rule type has a unique `rule_field`, referenced in rule values, such as when setting a rule of a price.
|
||||
|
||||
### Price Rule
|
||||
|
||||
Each rule of a price within a price set is represented by the `PriceRule` data model, which holds the value of a rule type. The `Price` data model has a `rules_count` field, which indicates how many rules, represented by `PriceRule`, are applied to the price.
|
||||
|
||||

|
||||
|
||||
For example, you create a `zip_code` rule type. Then, a price within the price set can have the rule value `10557`, indicating that the price can only be applied within the `10557` zip code.
|
||||
|
||||
Each price within the price set can have different values for the same rule type.
|
||||
|
||||
For example:
|
||||
|
||||

|
||||
|
||||
Each price can have multiple rules applied to it as well.
|
||||
|
||||
For example, a price can have the rules `zip_code` and `region_id` applied to it. In this case, the value of each rule is represented by a `PriceRule`.
|
||||
|
||||

|
||||
|
||||
### PriceSetRuleType
|
||||
|
||||
The `PriceSetRuleType` data model indicates what rules the prices can have within a price set. It creates a relation between the `PriceSet` and `RuleType` entities.
|
||||
|
||||
For example, to use the `zip_code` rule type on a price in a price set, the rule type must first be enabled on the price set through the `PriceSetRuleType`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Price List
|
||||
|
||||
A `PriceList` is a group of prices only enabled if their rules are satisfied. A price list has optional `start_date` and `end_date` fields, which indicate the date range in which a price list can be applied.
|
||||
|
||||
Its associated prices are represented by the `Price` data model.
|
||||
|
||||
Each rule that can be applied to a price list is represented by the `PriceListRule` data model. The `rules_count` field of a `PriceList` indicates how many rules are applied to it.
|
||||
|
||||
Each rule of a price list can have more than one value, representing its values by the `PriceListRuleValue` data model.
|
||||
|
||||

|
||||
@@ -0,0 +1,489 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Pricing Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Pricing Module in your application.
|
||||
|
||||
## Create a Price Set
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
// A rule type with \`rule_field=region_id\` should
|
||||
// already be present in the database
|
||||
const priceSet = await pricingModuleService.create([
|
||||
{
|
||||
rules: [{ rule_field: "region_id" }],
|
||||
prices: [
|
||||
{
|
||||
currency_code: request.body.currency_code,
|
||||
amount: request.body.amount,
|
||||
rules: {
|
||||
region_id: request.body.region_id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
const body = await request.json()
|
||||
|
||||
// A rule type with \`rule_field=region_i\` should
|
||||
// already be present in the database
|
||||
const priceSet = await pricingModuleService.create([
|
||||
{
|
||||
rules: [{ rule_field: "region_id" }],
|
||||
prices: [
|
||||
{
|
||||
currency_code: body.currency_code,
|
||||
amount: body.amount,
|
||||
rules: {
|
||||
region_id: body.region_id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Price Sets
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
|
||||
res.json({ price_sets: priceSets })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
|
||||
return NextResponse.json({ price_sets: priceSets })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Price Set by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
params.id
|
||||
)
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Rule Type
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceSet = await pricingModuleService.retrieve(
|
||||
params.id
|
||||
)
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Prices with Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId: request.body.price_set_id,
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
rules: {
|
||||
region_id: request.body.region_id,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
const body = await request.json()
|
||||
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId: body.price_set_id,
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
rules: {
|
||||
region_id: body.region_id,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({ price_set: priceSet })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Price List
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import {
|
||||
IPricingModuleService,
|
||||
PriceListType,
|
||||
} from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceLists =
|
||||
await pricingModuleService.createPriceLists([
|
||||
{
|
||||
title: "My Sale",
|
||||
description: "This is my sale",
|
||||
type: PriceListType.SALE,
|
||||
starts_at: Date.parse("01/10/2023").toString(),
|
||||
ends_at: Date.parse("31/10/2023").toString(),
|
||||
rules: {
|
||||
region_id: ["DE", "DK"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: "pset_124",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ price_lists: priceLists })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
import { PriceListType } from "@medusajs/medusa"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const priceLists =
|
||||
await pricingModuleService.createPriceLists([
|
||||
{
|
||||
title: "My Sale",
|
||||
description: "This is my sale",
|
||||
type: PriceListType.SALE,
|
||||
starts_at: Date.parse("01/10/2023").toString(),
|
||||
ends_at: Date.parse("31/10/2023").toString(),
|
||||
rules: {
|
||||
region_id: ["DE", "DK"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: "pset_124",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ price_lists: priceLists })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Calculate Prices For a Currency
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{
|
||||
id: [request.params.id],
|
||||
},
|
||||
{
|
||||
context: {
|
||||
currency_code: request.params.currency_code,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({ price })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
currency_code: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const price = await pricingModuleService.calculatePrices({
|
||||
id: [params.id],
|
||||
}, {
|
||||
context: {
|
||||
currency_code: params.currency_code,
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ price })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/pricing) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,185 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Pricing Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Pricing Module is the `@medusajs/pricing` NPM package that provides pricing-related features in your Medusa and Node.js applications.
|
||||
|
||||
## Features
|
||||
|
||||
### Price Management
|
||||
|
||||
With the Pricing Module, store the prices of a resource and manage them through the main interface's methods.
|
||||
|
||||
Prices are grouped in a price set, allowing you to add more than one price for a resource based on different conditions, such as currency code.
|
||||
|
||||
```ts
|
||||
const priceSet = await pricingModuleService.create({
|
||||
rules: [],
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "USD",
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
min_quantity: 0,
|
||||
max_quantity: 4,
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced Rule Engine
|
||||
|
||||
Create custom rules and apply them to prices. This gives you more flexibility in how you condition prices, filter them, and ensure the best prices are retrieved for custom contexts.
|
||||
|
||||
```ts
|
||||
const ruleTypes = await pricingModuleService.createRuleTypes([
|
||||
{
|
||||
name: "Region",
|
||||
rule_field: "region_id",
|
||||
},
|
||||
])
|
||||
|
||||
const priceSet = await pricingModuleService.addPrices({
|
||||
priceSetId,
|
||||
prices: [
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "EUR",
|
||||
rules: {
|
||||
region_id: "PL",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Price Lists
|
||||
|
||||
Price lists allow you to group prices and apply them only in specific conditions. You can also use them to override existing prices for the specified conditions.
|
||||
|
||||
```ts
|
||||
const priceList = await pricingModuleService.createPriceLists({
|
||||
title: "My Sale",
|
||||
type: "sale",
|
||||
starts_at: Date.parse("01/10/2023"),
|
||||
ends_at: Date.parse("31/10/2023"),
|
||||
rules: {
|
||||
region_id: ["DE", "DK"],
|
||||
},
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Price Calculation Strategy
|
||||
|
||||
The module’s main service provides a `calculatePrices` method to retrieve the best price for a given context.
|
||||
|
||||
You can use your custom rules here to find the best price for the specified rule values.
|
||||
|
||||
```ts
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSetId] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR",
|
||||
region_id: "PL",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Pricing Module
|
||||
|
||||
After installing the `@medusajs/pricing` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
pricingService: {
|
||||
resolve: "@medusajs/pricing",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Pricing Module's Service
|
||||
|
||||
You can use the Pricing Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.PRICING` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
res.json({
|
||||
price_sets: await pricingModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IPricingModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const pricingModuleService: IPricingModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRICING)
|
||||
|
||||
const priceSets = await pricingModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,503 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Prices Calculation`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn how prices are calculated under the hood when you use the `calculatePrices` method of the Pricing Module interface.
|
||||
|
||||
## Overview
|
||||
|
||||
The `calculatePrices` method accepts the ID of one or more price sets and a context. For each price set, it selects two types of prices that best match the context; one belongs to a price list, and one doesn't.
|
||||
|
||||
Then, it returns an array of price objects, each price for every supplied price set ID.
|
||||
|
||||
---
|
||||
|
||||
## Calculation Context
|
||||
|
||||
The context is an object passed to the `calculatePrices` method. It must contain at least the `currency_code`. Only prices in the price sets with the same currency code are considered for the price selection.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
async function calculatePrice(
|
||||
priceSetId: string,
|
||||
currencyCode: string
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSetId] },
|
||||
{
|
||||
context: {
|
||||
currency_code: currencyCode,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The context object can also contain any custom rules, with the key being the `rule_field` of a rule type and its value being the rule's value.
|
||||
|
||||
For example:
|
||||
|
||||
```ts
|
||||
import {
|
||||
initialize as initializePricingModule,
|
||||
} from "@medusajs/pricing"
|
||||
|
||||
async function calculatePrice(
|
||||
priceSetId: string,
|
||||
currencyCode: string
|
||||
) {
|
||||
const pricingModuleService = await initializePricingModule()
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSetId] },
|
||||
{
|
||||
context: {
|
||||
currency_code: currencyCode,
|
||||
region_id: "US",
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prices Selection
|
||||
|
||||
For each price set, the method selects two prices:
|
||||
|
||||
- The calculated price: a price that belongs to a price list. If there are no prices associated with a price list, it’ll be the same as the original price.
|
||||
- The original price: a price that doesn't belong to a price list.
|
||||
|
||||
### Calculated Price Selection Process
|
||||
|
||||

|
||||
|
||||
- Find the price set’s associated valid price lists. A price list is considered valid if:
|
||||
- The current date is between its start and end dates.
|
||||
- The price list's rules satisfy the context's rules.
|
||||
- If valid price lists are found, the prices within them are sorted by their amount in ascending order. The one having the lowest amount is selected as the calculated price.
|
||||
- If no valid price list is found, the selected calculated price will be the same as the original price.
|
||||
|
||||
### Original Price Selection Process
|
||||
|
||||

|
||||
|
||||
- If the price list associated with the calculated price is of type `override`, the selected original price is set to the calculated price.
|
||||
- If no rules are provided in the context other than the `currency_code`, the default price is selected as the original price. The default price is a price having no rules applied to it.
|
||||
- Otherwise, if a price exists in any price set with the same rules provided in the context, it's selected as the original price.
|
||||
- If no price exists with the same rules as the context, all prices satisfying any combination of the provided rules are retrieved.
|
||||
- The prices are sorted in descending order by the associated `Price`'s `rules_count`, the `default_priority` of the rule types, and the `priority` of the associated `PriceRule`. The `priority` field has a higher precedence than the `default_priority`.
|
||||
- The highest price sorted is selected as the original price since it's considered the best price.
|
||||
|
||||
---
|
||||
|
||||
## Returned Calculated Price
|
||||
|
||||
After the original and calculated prices are selected, the method will use them to create the following price object for each pr
|
||||
|
||||
```ts
|
||||
const price = {
|
||||
id: priceSetId,
|
||||
is_calculated_price_price_list:
|
||||
!!calculatedPrice?.price_list_id,
|
||||
calculated_amount: parseInt(
|
||||
calculatedPrice?.amount || ""
|
||||
) || null,
|
||||
|
||||
is_original_price_price_list:
|
||||
!!originalPrice?.price_list_id,
|
||||
original_amount: parseInt(
|
||||
originalPrice?.amount || ""
|
||||
) || null,
|
||||
|
||||
currency_code: calculatedPrice?.currency_code || null,
|
||||
|
||||
calculated_price: {
|
||||
price_id: calculatedPrice?.id || null,
|
||||
price_list_id: calculatedPrice?.price_list_id || null,
|
||||
price_list_type: calculatedPrice?.price_list_type || null,
|
||||
min_quantity: parseInt(
|
||||
calculatedPrice?.min_quantity || ""
|
||||
) || null,
|
||||
max_quantity: parseInt(
|
||||
calculatedPrice?.max_quantity || ""
|
||||
) || null,
|
||||
},
|
||||
|
||||
original_price: {
|
||||
price_id: originalPrice?.id || null,
|
||||
price_list_id: originalPrice?.price_list_id || null,
|
||||
price_list_type: originalPrice?.price_list_type || null,
|
||||
min_quantity: parseInt(
|
||||
originalPrice?.min_quantity || ""
|
||||
) || null,
|
||||
max_quantity: parseInt(
|
||||
originalPrice?.max_quantity || ""
|
||||
) || null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `id`: The ID of the price set from which the price was selected.
|
||||
- `is_calculated_price_price_list`: whether the calculated price belongs to a price list. As mentioned earlier, if no valid price list is found, the calculated price is set to the original price, which doesn't belong to a price list.
|
||||
- `calculated_amount`: The amount of the calculated price, or `null` if there isn't a calculated price.
|
||||
- `is_original_price_price_list`: whether the original price belongs to a price list. As mentioned earlier, if the price list of the calculated price is of type `override`, the original price will be the same as the calculated price.
|
||||
- `original_amount`: The amount of the original price, or `null` if there isn't an original price.
|
||||
- `currency_code`: The currency code of the calculated price, or `null` if there isn't a calculated price.
|
||||
- `calculated_price`: An object containing the calculated price's price details and potentially its associated price list.
|
||||
- `original_price`: An object containing the original price's price details and potentially its associated price list.
|
||||
|
||||
The method returns an array of these price objects.
|
||||
|
||||
---
|
||||
|
||||
## Example
|
||||
|
||||
Consider the following rule types and price sets:
|
||||
|
||||
```ts
|
||||
const ruleTypes = await pricingModuleService.createRuleTypes([
|
||||
{
|
||||
name: "Region",
|
||||
rule_field: "region_id",
|
||||
},
|
||||
{
|
||||
name: "City",
|
||||
rule_field: "city",
|
||||
},
|
||||
])
|
||||
|
||||
const priceSet = await pricingModuleService.create({
|
||||
rules: [
|
||||
{ rule_field: "region_id" },
|
||||
{ rule_field: "city" },
|
||||
],
|
||||
prices: [
|
||||
//default
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "EUR",
|
||||
rules: {},
|
||||
},
|
||||
// prices with rules
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
rules: {
|
||||
region_id: "PL",
|
||||
},
|
||||
},
|
||||
{
|
||||
amount: 450,
|
||||
currency_code: "EUR",
|
||||
rules: {
|
||||
city: "krakow",
|
||||
},
|
||||
},
|
||||
{
|
||||
amount: 500,
|
||||
currency_code: "EUR",
|
||||
rules: {
|
||||
city: "warsaw",
|
||||
region_id: "PL",
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Default Price Selection
|
||||
|
||||
<Tabs defaultValue="code">
|
||||
<TabsList>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
<TabsTrigger value="result">Result</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContentWrapper>
|
||||
<TabsContent value="code">
|
||||
|
||||
```ts
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSet.id] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="result">
|
||||
|
||||
The returned price is:
|
||||
|
||||
```ts
|
||||
const price = {
|
||||
id: "<PRICE_SET_ID>",
|
||||
is_calculated_price_price_list: false,
|
||||
calculated_amount: 500,
|
||||
|
||||
is_original_price_price_list: false,
|
||||
original_amount: 500,
|
||||
|
||||
currency_code: "EUR",
|
||||
|
||||
calculated_price: {
|
||||
price_id: "<DEFAULT_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
|
||||
original_price: {
|
||||
price_id: "<DEFAULT_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Original price selection: since there are no provided rules in the context, the original price is the default price.
|
||||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||||
|
||||
</TabsContent>
|
||||
</TabsContentWrapper>
|
||||
</Tabs>
|
||||
|
||||
### Exact Match Rule in Context
|
||||
|
||||
<Tabs defaultValue="code">
|
||||
<TabsList>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
<TabsTrigger value="result">Result</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContentWrapper>
|
||||
<TabsContent value="code">
|
||||
|
||||
```ts
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSet.id] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR",
|
||||
region_id: "PL"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="result">
|
||||
|
||||
The returned price is:
|
||||
|
||||
```ts
|
||||
const price = {
|
||||
id: "<PRICE_SET_ID>",
|
||||
is_calculated_price_price_list: false,
|
||||
calculated_amount: 400,
|
||||
|
||||
is_original_price_price_list: false,
|
||||
original_amount: 400,
|
||||
|
||||
currency_code: "EUR",
|
||||
|
||||
calculated_price: {
|
||||
price_id: "<SECOND_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
|
||||
original_price: {
|
||||
price_id: "<SECOND_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Original price selection: Since the second price of the price set has the same context as the provided one, it's selected as the original price.
|
||||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||||
|
||||
</TabsContent>
|
||||
</TabsContentWrapper>
|
||||
</Tabs>
|
||||
|
||||
### Context without Exact Matching Rules
|
||||
|
||||
<Tabs defaultValue="code">
|
||||
<TabsList>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
<TabsTrigger value="result">Result</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContentWrapper>
|
||||
<TabsContent value="code">
|
||||
|
||||
```ts
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSet.id] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR",
|
||||
region_id: "PL",
|
||||
city: "krakow"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="result">
|
||||
|
||||
The returned price is:
|
||||
|
||||
```ts
|
||||
const price = {
|
||||
id: "<PRICE_SET_ID>",
|
||||
is_calculated_price_price_list: false,
|
||||
calculated_amount: 500,
|
||||
|
||||
is_original_price_price_list: false,
|
||||
original_amount: 500,
|
||||
|
||||
currency_code: "EUR",
|
||||
|
||||
calculated_price: {
|
||||
price_id: "<FOURTH_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
|
||||
original_price: {
|
||||
price_id: "<FOURTH_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Original price selection: The fourth price in the price list is selected based on the following process:
|
||||
- There are no prices having the same rules as the context.
|
||||
- Retrieve all prices matching some combination of the rules in the context.
|
||||
- Sort the prices in descending order by their number of rules, each rule type's default priority, and the priority of the rule values.
|
||||
- All rule types, in this case, don't have a priority. So, the sorting depends on the number of rules each price has.
|
||||
- The fourth price has two rules, and the second and third have one rule. So, the fourth price is selected as the original price.
|
||||
- Calculated price selection: since there are no associated price lists, the calculated price is set to the original price.
|
||||
|
||||
</TabsContent>
|
||||
</TabsContentWrapper>
|
||||
</Tabs>
|
||||
|
||||
### Price Selection with Price List
|
||||
|
||||
<Tabs defaultValue="code">
|
||||
<TabsList>
|
||||
<TabsTrigger value="code">Code</TabsTrigger>
|
||||
<TabsTrigger value="result">Result</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContentWrapper>
|
||||
<TabsContent value="code">
|
||||
|
||||
```ts
|
||||
const priceList = pricingModuleService.createPriceList({
|
||||
name: "Test Price List",
|
||||
starts_at: Date.parse("01/10/2023"),
|
||||
ends_at: Date.parse("31/10/2023"),
|
||||
rules: {
|
||||
region_id: ['PL']
|
||||
},
|
||||
type: "sale"
|
||||
prices: [
|
||||
{
|
||||
amount: 400,
|
||||
currency_code: "EUR",
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
{
|
||||
amount: 450,
|
||||
currency_code: "EUR",
|
||||
price_set_id: priceSet.id,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const price = await pricingModuleService.calculatePrices(
|
||||
{ id: [priceSet.id] },
|
||||
{
|
||||
context: {
|
||||
currency_code: "EUR",
|
||||
region_id: "PL",
|
||||
city: "krakow"
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
<TabsContent value="result">
|
||||
|
||||
The returned price is:
|
||||
|
||||
```ts
|
||||
const price = {
|
||||
id: "<PRICE_SET_ID>",
|
||||
is_calculated_price_price_list: true,
|
||||
calculated_amount: 400,
|
||||
|
||||
is_original_price_price_list: false,
|
||||
original_amount: 500,
|
||||
|
||||
currency_code: "EUR",
|
||||
|
||||
calculated_price: {
|
||||
price_id: "<FOURTH_PRICE_ID>",
|
||||
price_list_id: null,
|
||||
price_list_type: null,
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
|
||||
original_price: {
|
||||
price_id: "<PL_PRICE_ID_1>",
|
||||
price_list_id: "<PRICE_LIST_ID>",
|
||||
price_list_type: "sale",
|
||||
min_quantity: null,
|
||||
max_quantity: null,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Original price selection: The process is explained in the Context without Exact Matching Rules section.
|
||||
- Calculated price selection: The first price of the price list is selected based on the following process:
|
||||
- The price set has a price list associated with it. So, retrieve its prices and sort them in ascending order by their amount.
|
||||
- Since the first price of the price list has the lowest amount, it's selected as the calculated price.
|
||||
|
||||
</TabsContent>
|
||||
</TabsContentWrapper>
|
||||
</Tabs>
|
||||
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Pricing Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Pricing Module and other Commerce Modules.
|
||||
|
||||
## Product Module
|
||||
|
||||
A product variant’s prices are stored as prices belonging to a price set. The Medusa application forms a relation between the `ProductVariant` and the `PriceSet`.
|
||||
|
||||

|
||||
|
||||
So, when you want to add prices for a product variant, you create a price set and add the prices to it. You can then benefit from adding rules to prices or using the `calculatePrices` method to retrieve the price of a product variant within a specified context.
|
||||
@@ -0,0 +1,400 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Product Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Product Module in your application.
|
||||
|
||||
## Create Product
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.create([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: [
|
||||
{
|
||||
value: "Black",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
res.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const products = await productModuleService.create([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: [
|
||||
{
|
||||
value: "Black",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({ products })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Products
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.list()
|
||||
|
||||
res.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.list()
|
||||
|
||||
return NextResponse.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
## Retrieve a Product by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.list({
|
||||
id: request.params.id,
|
||||
})
|
||||
|
||||
res.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { id } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.list({
|
||||
id,
|
||||
})
|
||||
|
||||
return NextResponse.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Product by its Handle
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.list()
|
||||
|
||||
res.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.list()
|
||||
|
||||
return NextResponse.json({ products: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
## Retrieve a Product by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.list({
|
||||
handle: request.params.handle,
|
||||
})
|
||||
|
||||
res.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { handle } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.list({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ product: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Categories
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.listCategories()
|
||||
|
||||
res.json({ categories: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.listCategories()
|
||||
|
||||
return NextResponse.json({ categories: data })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Category by Handle
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="medusa" value="Medusa API Router">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const data = await productModuleService.listCategories({
|
||||
handle: request.params.handle,
|
||||
})
|
||||
|
||||
res.json({ category: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="nextjs" value="Next.js App Router">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeProductModule,
|
||||
} from "@medusajs/product"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Record<string, any> }) {
|
||||
|
||||
const { handle } = params
|
||||
const productModuleService = await initializeProductModule()
|
||||
|
||||
const data = await productModuleService.listCategories({
|
||||
handle,
|
||||
})
|
||||
|
||||
return NextResponse.json({ category: data[0] })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Product Module interface reference](/references/product) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,138 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Product Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Product Module is the `@medusajs/product` NPM package that provides product-related features in your Medusa and Node.js applications.
|
||||
|
||||
## Features
|
||||
|
||||
### Products Management
|
||||
|
||||
With the Product Module, store products and manage them through the main interface methods. Products have custom options, such as color or size, and each variant in the product sets the value for these options.
|
||||
|
||||
```ts
|
||||
const products = await productService.create([
|
||||
{
|
||||
title: "Medusa Shirt",
|
||||
options: [
|
||||
{
|
||||
title: "Color",
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Black Shirt",
|
||||
options: [
|
||||
{
|
||||
value: "Black",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Product Organization
|
||||
|
||||
The Product Module provides different data models used to organize products, including categories, collections, tags, and more.
|
||||
|
||||
```ts
|
||||
const category = await productService.createCategory({
|
||||
name: "Shirts",
|
||||
})
|
||||
|
||||
const products = await productService.update([
|
||||
{
|
||||
id: product.id,
|
||||
categories: [
|
||||
{
|
||||
id: category.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Product Module
|
||||
|
||||
After installing the `@medusajs/product` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
productService: {
|
||||
resolve: "@medusajs/product",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Product Module's Service
|
||||
|
||||
You can use the Product Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.PRODUCT` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const productModuleService: IProductModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
res.json({ products: await productModuleService.list() })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const productModuleService: IProductModuleService =
|
||||
container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const productModuleService: IProductModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PRODUCT)
|
||||
|
||||
const products = await productModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,45 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Product Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Product Module and other Commerce Modules.
|
||||
|
||||
{/* TODO add relation to:
|
||||
|
||||
- shipping profile
|
||||
|
||||
*/}
|
||||
|
||||
## Pricing Module
|
||||
|
||||
A product variant’s prices are stored as money amounts belonging to a price set. The Medusa application forms a relation between the `ProductVariant` and the `PriceSet` data models.
|
||||
|
||||

|
||||
|
||||
So, to add prices for a product variant, create a price set and add the prices as money amounts to it.
|
||||
|
||||
Learn more about the `PriceSet` data model in the [Pricing Concepts](../../pricing/concepts/page.mdx#price-list)
|
||||
|
||||
---
|
||||
|
||||
## Sales Channel Module
|
||||
|
||||
A product can have different availability in different sales channels. The Medusa application forms a relation between the `Product` and `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Inventory Module
|
||||
|
||||
Each product variant has different inventory details. The Medusa application forms a relation between the `ProductVariant` and `InventoryItem` data models.
|
||||
|
||||

|
||||
|
||||
When the `manage_inventory` field of a product variant is enabled, you can manage the variant's inventory in different locations through this relation.
|
||||
|
||||
Learn more about the `InventoryItem` data model in the [Inventory Concepts](../../inventory/concepts/page.mdx#inventoryitem)
|
||||
@@ -0,0 +1,141 @@
|
||||
export const metadata = {
|
||||
title: `Promotion Actions`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about promotion actions and how they’re computed and used.
|
||||
|
||||
## computeActions Method
|
||||
|
||||
The `IPromotionModuleService` has a `computeActions` method that returns an array of actions to perform on a cart when one or more promotions are applied.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [the method’s reference](/references/promotion/computeActions) for full details on this method’s parameters and return type.
|
||||
|
||||
</Note>
|
||||
|
||||
Actions inform you what adjustment must be made to a cart item or shipping method. Each action is an object having the `action` field indicating the type of action.
|
||||
|
||||
---
|
||||
|
||||
## Action Types
|
||||
|
||||
### `addItemAdjustment` Action
|
||||
|
||||
The `addItemAdjustment` action indicates that an adjustment must be made to an item. For example, removing $5 off its amount.
|
||||
|
||||
This action has the following format:
|
||||
|
||||
```ts
|
||||
export interface AddItemAdjustmentAction {
|
||||
action: "addItemAdjustment"
|
||||
item_id: string
|
||||
amount: number
|
||||
code: string
|
||||
description?: string
|
||||
}
|
||||
```
|
||||
|
||||
When the Medusa application receives this action type, it creates a new record of the `LineItemAdjustment` data model in the Cart Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [this reference](/references/promotion/interfaces/promotion.AddItemAdjustmentAction) for details on the object’s properties.
|
||||
|
||||
</Note>
|
||||
|
||||
### `removeItemAdjustment` Action
|
||||
|
||||
The `removeItemAdjustment` action indicates that an adjustment must be removed from a line item. For example, remove the $5 discount.
|
||||
|
||||
When the Medusa application uses the `computeActions` method, it passes any previous item adjustments in the `items` property of the second parameter.
|
||||
|
||||
This action has the following format:
|
||||
|
||||
```ts
|
||||
export interface RemoveItemAdjustmentAction {
|
||||
action: "removeItemAdjustment"
|
||||
adjustment_id: string
|
||||
description?: string
|
||||
code: string
|
||||
}
|
||||
```
|
||||
|
||||
When the Medusa application receives this action type, it removes the `LineItemAdjustment` with the specified ID in the `adjustment_id` property.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [this reference](/references/promotion/interfaces/promotion.RemoveItemAdjustmentAction) for details on the object’s properties.
|
||||
|
||||
</Note>
|
||||
|
||||
### `addShippingMethodAdjustment` Action
|
||||
|
||||
The `addShippingMethodAdjustment` action indicates that an adjustment must be made on a shipping method. For example, make the shipping method free.
|
||||
|
||||
This action has the following format:
|
||||
|
||||
```ts
|
||||
export interface AddShippingMethodAdjustment {
|
||||
action: "addShippingMethodAdjustment"
|
||||
shipping_method_id: string
|
||||
amount: number
|
||||
code: string
|
||||
description?: string
|
||||
}
|
||||
```
|
||||
|
||||
When the Medusa application receives this action type, it creates a new record of the `ShippingMethodAdjustment` data model in the Cart Module.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [this reference](/references/promotion/interfaces/promotion.AddShippingMethodAdjustment) for details on the object’s properties.
|
||||
|
||||
</Note>
|
||||
|
||||
### `removeShippingMethodAdjustment` Action
|
||||
|
||||
The `removeShippingMethodAdjustment` action indicates that an adjustment must be removed from a shipping method. For example, remove the free shipping discount.
|
||||
|
||||
When the Medusa application uses the `computeActions` method, it passes any previous shipping method adjustments in the `shipping_methods` property of the second parameter.
|
||||
|
||||
This action has the following format:
|
||||
|
||||
```ts
|
||||
export interface RemoveShippingMethodAdjustment {
|
||||
action: "removeShippingMethodAdjustment"
|
||||
adjustment_id: string
|
||||
code: string
|
||||
}
|
||||
```
|
||||
|
||||
When the Medusa application receives this action type, it removes the `ShippingMethodAdjustment` with the specified ID in the `adjustment_id` property.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [this reference](/references/promotion/interfaces/promotion.RemoveShippingMethodAdjustment) for details on the object’s properties.
|
||||
|
||||
</Note>
|
||||
|
||||
### `campaignBudgetExceeded` Action
|
||||
|
||||
When the `campaignBudgetExceeded` action is returned, the promotions within a campaign can no longer be used as the campaign budget has been exceeded.
|
||||
|
||||
This action has the following format:
|
||||
|
||||
```ts
|
||||
export interface CampaignBudgetExceededAction {
|
||||
action: "campaignBudgetExceeded"
|
||||
code: string
|
||||
}
|
||||
```
|
||||
|
||||
The Medusa Application considers this an error, so it doesn’t apply the promotions on the cart.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to [this reference](/references/promotion/interfaces/promotion.CampaignBudgetExceededAction) for details on the object’s properties.
|
||||
|
||||
</Note>
|
||||
@@ -0,0 +1,202 @@
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Promotion Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the concepts in the Promotion Module.
|
||||
|
||||
## Promotion
|
||||
|
||||
A promotion, represented by the `Promotion` data model, represents a discount applied on cart items, shipping methods, or entire orders.
|
||||
|
||||
A promotion has two types:
|
||||
|
||||
- `standard`: A standard promotion with rules.
|
||||
- `buyget`: “A buy X get Y” promotion with rules.
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>
|
||||
|
||||
`standard` Promotion Examples
|
||||
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
|
||||
`buyget` Promotion Examples
|
||||
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
A coupon code that gives customers 10% off their entire order.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Buy two shirts and get another for free.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
A coupon code that gives customers $15 off any shirt in their order.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Buy two shirts and get 10% off the entire order.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
A discount applied automatically for VIP customers that removes 10% off their shipping method’s amount.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Spend $100 and get free shipping.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### PromotionRule
|
||||
|
||||
A promotion can be restricted by a set of rules, each rule is represented by the `PromotionRule` data model. For example, you can create a promotion that only customers of the `VIP` customer group can use it.
|
||||
|
||||

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

|
||||
|
||||
In this case, a customer’s group must be in the `VIP` and `B2B` set of values to use the promotion.
|
||||
|
||||
---
|
||||
|
||||
## Application Method
|
||||
|
||||
The `ApplicationMethod` data model defines how a promotion is applied:
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>
|
||||
|
||||
field
|
||||
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell>
|
||||
|
||||
Purpose
|
||||
|
||||
</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`type`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Does the promotion discount a fixed amount or a percentage?
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`target_type`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Is the promotion applied on a cart item, shipping method, or the entire order?
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`allocation`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Is the discounted amount applied on each item or split between the applicable items?
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Target Promotion Rules
|
||||
|
||||
When the promotion is applied to a cart item or a shipping method, you can restrict which items/shipping methods the promotion is applied to.
|
||||
|
||||
The `ApplicationMethod` data model has a collection of `PromotionRule` records to restrict which items or shipping methods the promotion applies to. The `target_rules` field represents this relation.
|
||||
|
||||

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

|
||||
|
||||
In this example, the cart must have two products with the SKU `SHIRT` for the promotion to be applied.
|
||||
|
||||
---
|
||||
|
||||
## Campaign
|
||||
|
||||
A `Campaign` combines promotions under the same conditions, such as start and end dates.
|
||||
|
||||

|
||||
|
||||
### Campaign Limits
|
||||
|
||||
Each campaign has a budget represented by the `CampaignBudget` data model. The budget limits how many times the promotion can be used.
|
||||
|
||||
There are two types of budgets:
|
||||
|
||||
- `spend`: An amount that, when crossed, the promotion becomes unusable. For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
|
||||
- `usage`: The number of times that a promotion can be used. For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Full Data Models Diagram
|
||||
|
||||

|
||||
@@ -0,0 +1,277 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Promotion Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Promotion Module in your application.
|
||||
|
||||
## Create a Promotion
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePromotionModule,
|
||||
} from "@medusajs/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService =
|
||||
await initializePromotionModule()
|
||||
const body = await request.json()
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Campaign
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const campaign = await promotionModuleService.createCampaigns(
|
||||
{
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
promotions: [
|
||||
{
|
||||
id: "promo_123",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
res.json({ campaign })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePromotionModule,
|
||||
} from "@medusajs/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService =
|
||||
await initializePromotionModule()
|
||||
const body = await request.json()
|
||||
|
||||
const campaign = await promotionModuleService.createCampaigns(
|
||||
{
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
promotions: [
|
||||
{
|
||||
id: "promo_123",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({ campaign })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create a Promotion with Flexible Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
field: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePromotionModule,
|
||||
} from "@medusajs/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService =
|
||||
await initializePromotionModule()
|
||||
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
field: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({ promotion })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Promotions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializePromotionModule,
|
||||
} from "@medusajs/promotion"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const promotionModuleService =
|
||||
await initializePromotionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/promotion) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,155 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Promotion Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Promotion Module is the `@medusajs/promotion` NPM package that provides promotion-related features in your Medusa and Node.js applications.
|
||||
|
||||
## Features
|
||||
|
||||
### Discount Functionalities
|
||||
|
||||
A promotion discounts an amount or percentage of a cart's items, shipping methods, or the entire order.
|
||||
|
||||
The Promotion Module allows you to store and manage promotions.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Flexible Promotion Rules
|
||||
|
||||
A promotion has rules that restricts when it's applied. For example, you can create a promotion that's only applied to VIP customers.
|
||||
|
||||
```ts
|
||||
const promotion = await promotionModuleService.create({
|
||||
code: "10%OFF",
|
||||
type: "standard",
|
||||
application_method: {
|
||||
type: "percentage",
|
||||
target_type: "order",
|
||||
value: "10",
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
field: "customer_group_id",
|
||||
operator: "eq",
|
||||
values: ["VIP"],
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Campaign Management
|
||||
|
||||
A campaign combines promotions under the same conditions, such as start and end dates.
|
||||
|
||||
A campaign can also have an identifier for tracking purposes, such as tracking through tools like Google Analytics.
|
||||
|
||||
```ts
|
||||
const campaign = await promotionModuleService.createCampaigns(
|
||||
{
|
||||
name: "Summer Discounts",
|
||||
campaign_identifier: "G-123445",
|
||||
starts_at: new Date("2024-05-02"),
|
||||
ends_at: new Date("2024-07-20"),
|
||||
promotions: [
|
||||
{
|
||||
id: "promo_123",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Promotion Module
|
||||
|
||||
After installing the `@medusajs/promotion` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
promotion: {
|
||||
resolve: "@medusajs/promotion",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use the Promotion Module's Service
|
||||
|
||||
You can use the Promotion Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.PROMOTION` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
res.json({
|
||||
promotions: await promotionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
container.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotions = await promotionModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IPromotionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const promotionModuleService: IPromotionModuleService =
|
||||
context.container.resolve(ModuleRegistrationName.PROMOTION)
|
||||
|
||||
const promotions = await promotionModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Promotion Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Promotion Module and other Commerce Modules.
|
||||
|
||||
## Cart Module
|
||||
|
||||
When the Cart and Promotion modules are used together, the Medusa application links the `Promotion` data model to the `Cart`, `LineItemAdjustment`, and `ShippingMethodAdjustment` data models of the Cart Module.
|
||||
|
||||

|
||||
|
||||
A promotion is linked to a cart when a promotion is applied on the cart. Adjustments on items and shipping methods, represented by the `LineItemAdjustment` and `ShippingMethodAdjustment` data models, are linked to the promotion as well.
|
||||
@@ -0,0 +1,266 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Region Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Region Module in your application.
|
||||
|
||||
## Create a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.create({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
|
||||
res.json({
|
||||
region,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeRegionModule,
|
||||
} from "@medusajs/region"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.create({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
|
||||
res.json({
|
||||
region,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Regions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeRegionModule,
|
||||
} from "@medusajs/region"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
return NextResponse.json({
|
||||
regions: await regionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Region by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeRegionModule,
|
||||
} from "@medusajs/region"
|
||||
|
||||
export async function GET(
|
||||
request: Request
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.retrieve("reg_123")
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const region = await regionModuleService.update("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
res.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeRegionModule,
|
||||
} from "@medusajs/region"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
const region = await regionModuleService.update("reg_123", {
|
||||
automatic_taxes: false,
|
||||
})
|
||||
|
||||
return NextResponse.json({ region })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
await regionModuleService.delete("reg_123")
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeRegionModule,
|
||||
} from "@medusajs/region"
|
||||
|
||||
export async function DELETE(
|
||||
request: Request
|
||||
) {
|
||||
const regionModuleService = await initializeRegionModule()
|
||||
|
||||
await regionModuleService.delete("reg_123")
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Region Module interface reference](/references/region) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,151 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Region Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Region Module is the `@medusajs/region` NPM package that provides region-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## What is a Region?
|
||||
|
||||
A region represents the area you sell products in. Each region can cover multiple countries, but uses a single currency.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Region Management
|
||||
|
||||
You can manage your regions to create, update, retrieve, or delete them.
|
||||
|
||||
```ts
|
||||
const region = await regionModuleService.create({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Currency Support
|
||||
|
||||
As each region has a currency, you can support multiple currencies in your store by creating multiple regions.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.create([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
},
|
||||
{
|
||||
name: "United States of America",
|
||||
currency_code: "usd",
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Different Settings Per Region
|
||||
|
||||
Each region has its own settings, such as what countries belong to a region or its tax settings. When using other commerce modules such as the Tax Module or Payment Module, each region has different tax rates, payment providers, and more.
|
||||
|
||||
```ts
|
||||
const regions = await regionModuleService.create([
|
||||
{
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
countries: ["dk", "de", "fr", "it", "pt"],
|
||||
automatic_taxes: true,
|
||||
},
|
||||
{
|
||||
name: "United States of America",
|
||||
currency_code: "usd",
|
||||
countries: ["us"],
|
||||
automatic_taxes: false,
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Region Module
|
||||
|
||||
After installing the `@medusajs/region` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
region: {
|
||||
resolve: "@medusajs/region",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Region Module's Service
|
||||
|
||||
You can use the Region Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.REGION` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
res.json({
|
||||
regions: await regionModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
container.resolve(ModuleRegistrationName.REGION)
|
||||
|
||||
const regions = await regionModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IRegionModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const regionModuleService: IRegionModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.REGION
|
||||
)
|
||||
|
||||
const regions = await regionModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Region Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Region Module and other Commerce Modules.
|
||||
|
||||
## Cart Module
|
||||
|
||||
Carts are associated with a region. The Medusa application forms a relation between the `Region` and the `Cart` data models.
|
||||
|
||||

|
||||
|
||||
The cart then uses the same currency as the region. During checkout, the cart's taxes, payment, and fulfillment providers are available based on the region's settings.
|
||||
@@ -0,0 +1,301 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Sales Channel Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Sales Channel Module in your application.
|
||||
|
||||
## Create a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.create({
|
||||
name: request.body.name,
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeSalesChannelModule,
|
||||
} from "@medusajs/sales-channel"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
const body = await request.json()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.create({
|
||||
name: body.name,
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Sales Channels
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: salesChannelModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeSalesChannelModule,
|
||||
} from "@medusajs/sales-channel"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
|
||||
return NextResponse.json({ sales_channels: salesChannels })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Sales Channel by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieve(
|
||||
request.params.id
|
||||
)
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeSalesChannelModule,
|
||||
} from "@medusajs/sales-channel"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
const body = await request.json()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.retrieve(
|
||||
params.id
|
||||
)
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannel = await salesChannelModuleService.update({
|
||||
id: request.params.id,
|
||||
description: request.body.description,
|
||||
})
|
||||
|
||||
res.json({
|
||||
sales_channel: salesChannel,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeSalesChannelModule,
|
||||
} from "@medusajs/sales-channel"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
const body = await request.json()
|
||||
|
||||
const salesChannel = await salesChannelModuleService.update({
|
||||
id: params.id,
|
||||
description: body.description,
|
||||
})
|
||||
|
||||
return NextResponse.json({ sales_channel: salesChannel })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Sales Channel
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
await salesChannelModuleService.delete(request.params.id)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeSalesChannelModule,
|
||||
} from "@medusajs/sales-channel"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const salesChannelModuleService =
|
||||
await initializeSalesChannelModule()
|
||||
|
||||
await salesChannelModuleService.delete(params.id)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Sales Channel Module interface reference](/references/sales-channel) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,134 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Sales Channel Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Sales Channel Module is the `@medusajs/sales-channel` NPM package that provides sales-channel-related features in your Medusa and Node.js applications.
|
||||
|
||||
## What's a Sales Channel?
|
||||
|
||||
A sales channel indicates an online or offline platform that you sell products on.
|
||||
|
||||
Some use case examples for using a sales channel:
|
||||
|
||||
- Implement a B2B Ecommerce Store.
|
||||
- Specify different products for each channel you sell in.
|
||||
- Support Omnichannel in your ecommerce store.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Sales Channel Management
|
||||
|
||||
Store and manage sales channels in your store.
|
||||
|
||||
Each sales channel has different meta information such as name or description, allowing you to easily differentiate between sales channels.
|
||||
|
||||
```ts
|
||||
const salesChannels = await salesChannelModuleService.create([
|
||||
{
|
||||
name: "B2B",
|
||||
},
|
||||
{
|
||||
name: "Mobile App",
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Product Availability
|
||||
|
||||
By combining the Product and Sales Channel modules, you can specify a product's availability per sales channel.
|
||||
|
||||
For example, B2B customers viewing products only see products in the B2B sales channel.
|
||||
|
||||
### Cart and Order Scoping
|
||||
|
||||
Carts, available through the Cart Module, are scoped to a sales channel. Paired with the product availability feature, you benefit from more features like allowing only products available in sales channel in a cart.
|
||||
|
||||
Orders are also scoped to a sales channel due to the relation between the Sales Channel and Order modules.
|
||||
|
||||
---
|
||||
|
||||
## Configure Sales Channel Module
|
||||
|
||||
After installing the `@medusajs/sales-channel` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
salesChannel: {
|
||||
resolve: "@medusajs/sales-channel",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Sales Channel Module's Service
|
||||
|
||||
You can use the Sales Channel Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.SALES_CHANNEL` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
res.json({
|
||||
sales_channels: await salesChannelModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
container.resolve(ModuleRegistrationName.SALES_CHANNEL)
|
||||
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { ISalesChannelModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const salesChannelModuleService: ISalesChannelModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.SALES_CHANNEL
|
||||
)
|
||||
const salesChannels = await salesChannelModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,78 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Publishable API Keys with Sales Channels`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn what publishable API keys are and how to use them with sales channels.
|
||||
|
||||
## Without Publishable API Keys
|
||||
|
||||
When using multiple sales channels, you’ll need to specify the ID of a storefront’s sales channel. This ensures that you retrieve the products available in that sales channel and associate the sales channel with the storefront’s carts and orders.
|
||||
|
||||
The Store API routes accept the sales channel’s IDs differently. For example, the [List Products API route](https://docs.medusajs.com/api/store#products) accepts the sales channel’s ID as a query parameter, whereas the [Create Cart API route](https://docs.medusajs.com/api/store#carts_postcart) accepts it in the request’s body.
|
||||
|
||||
This approach is tedious and error-prone as your storefront scales and as you develop multiple types of storefronts.
|
||||
|
||||
---
|
||||
|
||||
## Introducing Publishable API Keys
|
||||
|
||||
The API Key module allows you to create keys for different usages. One of those usages is to create a publishable API key.
|
||||
|
||||
A publishable API key is a client key scoped to one or more sales channels. When passed in the header of a request, the Medusa application infers the associated sales channels.
|
||||
|
||||
So, instead of the previous approach of manually passing the sales channel’s ID based on the API route, you always pass the publishable API key in the header of your requests:
|
||||
|
||||
```bash
|
||||
curl http://localhost:9000/store/products \
|
||||
x-publishable-api-key: {your_publishable_api_key}
|
||||
```
|
||||
|
||||
The Medusa JS Client and Medusa React both provide the option to pass the publishable API key during initialization:
|
||||
|
||||
<CodeTabs groupId="client-id">
|
||||
<CodeTab label="JS Client" value="js-client">
|
||||
|
||||
```ts
|
||||
const medusa = new Medusa({
|
||||
maxRetries: 3,
|
||||
baseUrl: "http://localhost:9000",
|
||||
publishableApiKey,
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Medusa React" value="medusa-react">
|
||||
|
||||
```tsx
|
||||
import { MedusaProvider } from "medusa-react"
|
||||
|
||||
// define query client...
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<MedusaProvider
|
||||
queryClientProviderProps={{ client: queryClient }}
|
||||
baseUrl="http://localhost:9000"
|
||||
// ...
|
||||
publishableApiKey={publishableApiKey}
|
||||
>
|
||||
<MyStorefront />
|
||||
</MedusaProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
Then, all requests using the client/hooks automatically include the publishable API key in the header.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Publishable API Key?
|
||||
|
||||
To create a publishable API key, either use the Medusa Admin or the [Admin API Routes](https://docs.medusajs.com/api/admin#publishable-api-keys).
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Sales Channel Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Sales Channel Module and other Commerce Modules.
|
||||
|
||||
## Product Module
|
||||
|
||||
A product has different availability for different sales channels. The Medusa application forms a relation between the `Product` and the `SalesChannel` data models.
|
||||
|
||||
A product can be available in more than one sales channel. Then, you can retrieve only the products of a sales channel.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Cart Module
|
||||
|
||||
A cart is associated with the sales channel it's created in. The Medusa application forms a relation between the `Cart` and the `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
For example, if a customer adds an item to the cart in a mobile app, the cart is associated with the mobile app's sales channel. However, if a customer adds an item to the cart in a web storefront, the cart is associated with the storefront's sales channel.
|
||||
|
||||
---
|
||||
|
||||
## Order Module
|
||||
|
||||
An order is associated with the sales channel it's created in. The Medusa application forms a relation between the `Order` and the `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
For example, if an order is created in a mobile app, it'll be associated with its sales channel. If another order is created through a POS system, it'll be associated with the POS's sales channel.
|
||||
|
||||
---
|
||||
|
||||
## API Key Module
|
||||
|
||||
A publishable API key allows you to easily specify the sales channel scope in a client request. The Medusa application forms a relation between the `ApiKey` and the `SalesChannel` data models.
|
||||
|
||||

|
||||
|
||||
Using the API Key Module, you create a publishable key and associate it with a sales channel.
|
||||
|
||||
Instead of passing the sales channel's ID in every request either in the query or body parameters, you always pass the API key in the header of your requests. The Medusa application then infers the sales channel scope from it.
|
||||
@@ -0,0 +1,19 @@
|
||||
export const metadata = {
|
||||
title: `Stock Location Concepts`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about the main concepts in the Stock Location Module, and how data is stored and related.
|
||||
|
||||
## Stock Location
|
||||
|
||||
A stock location, represented by the `StockLocation` data model, represents a location where stock items are kept. For example, a warehouse.
|
||||
|
||||
The `InventoryLevel` and `ReservationItem` data models of the Inventory Module are associated with the `StockLocation` to provide inventory details per location. Learn more about this relation in [this guide](../relations-to-other-modules/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## StockLocationAddress
|
||||
|
||||
The `StockLocationAddress` data model belongs to the `StockLocation` data model. It provides more detailed information of the location, such as country code or street address.
|
||||
@@ -0,0 +1,257 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Stock Location Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll find common examples of how you can use the Stock Location Module in your application.
|
||||
|
||||
## Create a Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
name: request.body.name,
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStockLocationModule,
|
||||
} from "@medusajs/stock-location-next"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
name: body.name,
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({ stock_location: stockLocation })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Stock Locations
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStockLocationModule,
|
||||
} from "@medusajs/stock-location-next"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
return NextResponse.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Add Address to Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocation = await stockLocationModuleService.update(
|
||||
{
|
||||
id: request.params.id,
|
||||
address: {
|
||||
country_code: request.body.country_code,
|
||||
city: request.body.city,
|
||||
address_1: request.body.address_1,
|
||||
postal_code: request.body.postal_code,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStockLocationModule,
|
||||
} from "@medusajs/stock-location-next"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
const body = await request.json()
|
||||
|
||||
const stockLocation = await stockLocationModuleService.update(
|
||||
{
|
||||
id: params.id,
|
||||
address: {
|
||||
country_code: body.country_code,
|
||||
city: body.city,
|
||||
address_1: body.address_1,
|
||||
postal_code: body.postal_code,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
stock_location: stockLocation,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Stock Location
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
await stockLocationModuleService.delete(request.params.id)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStockLocationModule,
|
||||
} from "@medusajs/stock-location-next"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const stockLocationModuleService =
|
||||
await initializeStockLocationModule({})
|
||||
|
||||
await stockLocationModuleService.delete(params.id)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/stock-location) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,136 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Stock Location Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Stock Location Module is the `@medusajs/stock-location-next` NPM package that provides stock-location-related features in your Medusa and Node.js applications.
|
||||
|
||||
It's mainly useful with the [Inventory Module](../inventory/page.mdx).
|
||||
|
||||
<Note>
|
||||
|
||||
This module is an updated version of the [Stock Location Module part of the multi-warehouse feature](../../modules/multiwarehouse/stock-location-module.md). In Medusa V2, the previous version of the module will no longer be in use.
|
||||
|
||||
</Note>
|
||||
|
||||
## Features
|
||||
|
||||
### Stock Location Management
|
||||
|
||||
Store and manage stock locations. Stock locations can be associated with other models that require a location, such as the [Inventory Module's InventoryLevel](../inventory/concepts/page.mdx#inventory-level).
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.create(
|
||||
{
|
||||
name: "Warehouse 1",
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Address Management
|
||||
|
||||
A stock location can have detailed address details.
|
||||
|
||||
```ts
|
||||
const stockLocation = await stockLocationModuleService.update(
|
||||
{
|
||||
id: "sloc_123",
|
||||
address: {
|
||||
country_code: "US",
|
||||
city: "New York City",
|
||||
address_1: "52 Stone St",
|
||||
postal_code: "10004",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Stock Location Module
|
||||
|
||||
After installing the `@medusajs/stock-location` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
stockLocationService: {
|
||||
resolve: "@medusajs/stock-location-next",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Stock Location Module's Service
|
||||
|
||||
You can use the Stock Location Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.STOCK_LOCATION` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
request.scope.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
res.json({
|
||||
stock_locations: await stockLocationModuleService.list({}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
container.resolve(ModuleRegistrationName.STOCK_LOCATION)
|
||||
|
||||
const stockLocations = await stockLocationModuleService.list(
|
||||
{}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IStockLocationService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const stockLocationModuleService: IStockLocationService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.STOCK_LOCATION
|
||||
)
|
||||
|
||||
const stockLocations = await stockLocationModuleService.list(
|
||||
{}
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export const metadata = {
|
||||
title: `Relations between Stock Location Module and Other Modules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
When Commerce Modules are used together in a Medusa application, the Medusa application handles building the relations between these modules.
|
||||
|
||||
This document showcases the relation between the Stock Location Module and other Commerce Modules.
|
||||
|
||||
## Inventory Module
|
||||
|
||||
Reservation items and inventory items are associated with a location. The Medusa application forms a relation between the `ReservationItem` and `StockLocation` data models, and the `InventoryLevel` and `StockLocation` data models.
|
||||
|
||||

|
||||
|
||||
With this relation, stock and reserved quantity is specific to a location, providing you with more flexibility. The location can be as simple as having only a name, and more flexible by adding more address details to it.
|
||||
@@ -0,0 +1,286 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Store Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Store Module in your application.
|
||||
|
||||
## Create a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStoreModule,
|
||||
} from "@medusajs/store"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.create({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Stores
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStoreModule,
|
||||
} from "@medusajs/store"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const salesChannels = await storeModuleService.list()
|
||||
|
||||
return NextResponse.json({
|
||||
stores: await storeModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Store by its ID
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.retrieve(
|
||||
"store_123"
|
||||
)
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStoreModule,
|
||||
} from "@medusajs/store"
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.retrieve(
|
||||
"store_123"
|
||||
)
|
||||
|
||||
return NextResponse.json({ store })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const store = await storeModuleService.update("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
res.json({
|
||||
store,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStoreModule,
|
||||
} from "@medusajs/store"
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
const store = await storeModuleService.update("store_123", {
|
||||
name: "Change Store",
|
||||
})
|
||||
|
||||
return NextResponse.json({ store })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a Store
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
await storeModuleService.delete(request.params.id)
|
||||
|
||||
res.status(200)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeStoreModule,
|
||||
} from "@medusajs/store"
|
||||
|
||||
type ContextType = {
|
||||
params: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const storeModuleService = await initializeStoreModule()
|
||||
|
||||
await storeModuleService.delete(params.id)
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [Store Module interface reference](/references/store) provides a reference to all the methods available for use with examples for each.
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Store Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Store Module is the `@medusajs/store` NPM package that provides store-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Store Management
|
||||
|
||||
A store holds the main configurations of your commerce store, such as supported currencies, default region and sales channel, and more.
|
||||
|
||||
```ts
|
||||
const store = await storeModuleService.create({
|
||||
name: "My Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Tenancy Support
|
||||
|
||||
You can create multiple stores, each having its own configurations.
|
||||
|
||||
```ts
|
||||
const store1 = await storeModuleService.create({
|
||||
name: "USA Store",
|
||||
supported_currency_codes: ["usd"],
|
||||
})
|
||||
|
||||
const store2 = storeModuleService.create({
|
||||
name: "Europe Store",
|
||||
supported_currency_codes: [
|
||||
"eur",
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Store Module
|
||||
|
||||
After installing the `@medusajs/store` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
store: {
|
||||
resolve: "@medusajs/store",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Use Store Module's Service
|
||||
|
||||
You can use the Store Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.STORE` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
request: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
request.scope.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
res.json({
|
||||
stores: await storeModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
container.resolve(ModuleRegistrationName.STORE)
|
||||
|
||||
const stores = await storeModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const storeModuleService: IStoreModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.STORE
|
||||
)
|
||||
const stores = await storeModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,359 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the Tax Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the Tax Module in your application.
|
||||
|
||||
## Create a Tax Region
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "US",
|
||||
default_tax_rate: {
|
||||
rate: 10,
|
||||
name: "Default rate",
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
tax_region: taxRegion,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeTaxModule,
|
||||
} from "@medusajs/tax"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "US",
|
||||
default_tax_rate: {
|
||||
rate: 10,
|
||||
name: "Default rate",
|
||||
},
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
tax_region: taxRegion,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Tax Regions
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_regions: await taxModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeTaxModule,
|
||||
} from "@medusajs/tax"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_regions: await taxModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Create Tax Rate with Rules
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
const taxRate = await taxModuleService.create({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
{
|
||||
reference: "product",
|
||||
reference_id: "prod_123",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
res.json({
|
||||
tax_rate: taxRate,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeTaxModule,
|
||||
} from "@medusajs/tax"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxRate = await taxModuleService.create({
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Custom rate",
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
{
|
||||
reference: "product",
|
||||
reference_id: "prod_123",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
tax_rate: taxRate,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Tax Rates
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_rates: await taxModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeTaxModule,
|
||||
} from "@medusajs/tax"
|
||||
|
||||
export async function GET(
|
||||
request: Request
|
||||
) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
return NextResponse.json({
|
||||
tax_rates: await taxModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Get Tax Lines
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
postal_code: "123456",
|
||||
},
|
||||
customer: {
|
||||
id: "cus_123",
|
||||
email: "user@example.com",
|
||||
customer_groups: ["VIP"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_lines: taxLines,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeTaxModule,
|
||||
} from "@medusajs/tax"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const taxModuleService = await initializeTaxModule()
|
||||
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
postal_code: "123456",
|
||||
},
|
||||
customer: {
|
||||
id: "cus_123",
|
||||
email: "user@example.com",
|
||||
customer_groups: ["VIP"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
tax_lines: taxLines,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/tax) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
sidebar_label: "Module Options"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Tax Module Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the options of the Tax Module.
|
||||
|
||||
## providers
|
||||
|
||||
The `providers` option is an array of either tax provider modules, tax plugins, or path to a file that defines a tax provider.
|
||||
|
||||
When the Medusa application starts, these providers are registered and can be used to retrieve tax lines.
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
tax: {
|
||||
resolve: "@medusajs/tax",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
resolve: "my-provider",
|
||||
options: {
|
||||
// ...
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The objects in the array accept the following properties:
|
||||
|
||||
- `resolve`: A string indicating the package name of the tax provider module or the tax plugin, or the path to the file defining the tax provider.
|
||||
- `options`: An object of options to pass to the tax provider.
|
||||
@@ -0,0 +1,164 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Tax Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Tax Module is the `@medusajs/tax` NPM package that provides tax-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Tax Settings Per Region
|
||||
|
||||
Set different tax settings for each region. This is especially useful with the [Region Module](../region/page.mdx).
|
||||
|
||||
```ts
|
||||
const taxRegion = await taxModuleService.createTaxRegions({
|
||||
country_code: "us",
|
||||
})
|
||||
```
|
||||
|
||||
### Tax Rates and Rules
|
||||
|
||||
Manage each region's default tax rates and override them with conditioned tax rates.
|
||||
|
||||
```ts
|
||||
const taxRates = await taxModuleService.create([
|
||||
{
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Default tax rate",
|
||||
is_default: true,
|
||||
rate: 10,
|
||||
},
|
||||
{
|
||||
tax_region_id: "txreg_123",
|
||||
name: "Shirt product type",
|
||||
is_default: false,
|
||||
rate: 15,
|
||||
rules: [
|
||||
{
|
||||
reference: "product_type",
|
||||
reference_id: "ptyp_1",
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Retrieve Cart's Tax Lines
|
||||
|
||||
Calculate and retrieve the tax lines of a cart's line items and shipping methods with tax providers. Use different tax providers for each region to handle tax-line retrieval differently.
|
||||
|
||||
```ts
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure Tax Module
|
||||
|
||||
After installing the `@medusajs/tax` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
tax: {
|
||||
resolve: "@medusajs/tax",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## How to Use Tax Module's Service
|
||||
|
||||
You can use the Tax Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.TAX` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const taxModuleService: ITaxModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
|
||||
res.json({
|
||||
tax_regions: await taxModuleService.listTaxRegions(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const taxModuleService: ITaxModuleService =
|
||||
container.resolve(ModuleRegistrationName.TAX)
|
||||
|
||||
const taxRegions = await taxModuleService.listTaxRegions()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { ITaxModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const taxModuleService: ITaxModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.TAX
|
||||
)
|
||||
const taxRegions = await taxModuleService.listTaxRegions()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,66 @@
|
||||
export const metadata = {
|
||||
title: `Tax Calculation with the Tax Provider`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn how tax lines are calculated and what a tax provider is.
|
||||
|
||||
## Tax Lines Calculation
|
||||
|
||||
Tax lines are calculated and retrieved using the `getTaxLines` method of the Tax Module’s main service (`ITaxModuleService`). It accepts an array of line items and shipping methods, and the context of the calculation.
|
||||
|
||||
It returns the tax lines that are used to adjust the cart’s tax and grand totals.
|
||||
|
||||
```ts
|
||||
const taxLines = await taxModuleService.getTaxLines(
|
||||
[
|
||||
{
|
||||
id: "cali_123",
|
||||
product_id: "prod_123",
|
||||
unit_price: 1000,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
id: "casm_123",
|
||||
shipping_option_id: "so_123",
|
||||
unit_price: 2000,
|
||||
},
|
||||
],
|
||||
{
|
||||
address: {
|
||||
country_code: "us",
|
||||
},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The context object is used to determine which tax regions and rates to use in the calculation. The object includes fields related to the address and customer.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about the `getTaxLines` method’s parameters and return type in [this reference](/references/tax/getTaxLines).
|
||||
|
||||
</Note>
|
||||
|
||||
The example above returns the tax lines based on the tax region for the United States.
|
||||
|
||||
---
|
||||
|
||||
## Tax Provider Usage in the Calculation
|
||||
|
||||
The tax lines retrieved by the `getTaxLines` method are actually retrieved from the tax region’s provider.
|
||||
|
||||
A tax provider is a TypeScript or JavaScript class that implements the logic to shape tax lines. Each tax region has a tax provider.
|
||||
|
||||
The Tax Module provides a `system` tax provider that only transforms calculated item and shipping tax rates into the required return type.
|
||||
|
||||
---
|
||||
|
||||
## Create Tax Provider
|
||||
|
||||
You can create a tax provider by implementing the `ITaxProvider` imported from `@meduasjs/types`.
|
||||
|
||||
Then, you can add the tax provider to the [providers](../module-options/page.mdx#providers) option and use it to retrieve tax lines.
|
||||
|
||||
Refer to [this reference](/modules/tax/provider) to learn more about creating a tax provider.
|
||||
@@ -0,0 +1,36 @@
|
||||
export const metadata = {
|
||||
title: `Tax Rates and Rules`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about tax rates and rules.
|
||||
|
||||
## What are Tax Rates?
|
||||
|
||||
A tax rate is a percentage amount used to calculate the tax amount for each taxable item’s price, such as line items or shipping methods, in a cart. The sum of all calculated tax amounts are then added to the cart’s total as a tax total.
|
||||
|
||||
Each tax region has a default tax rate. This tax rate is applied to all taxable items of a cart in that region.
|
||||
|
||||
### Combinable Tax Rates
|
||||
|
||||
Tax regions can have parent tax regions. To inherit the tax rates of the parent tax region, set the `is_combinable` of the child’s tax rates to `true`.
|
||||
|
||||
Then, when tax rates are retrieved for a taxable item in the child region, both the child and the parent tax regions’ applicable rates are returned. Otherwise, only the child tax region’s applicable rates are returned.
|
||||
|
||||
---
|
||||
|
||||
## What are Tax Rules?
|
||||
|
||||
You can also create tax rates that override the default for specific conditions or rules. For example, the default tax rate is 10%, but for products of type “Shirt” is %15.
|
||||
|
||||
A tax region can have multiple tax rates, and each tax rate can have multiple tax rules. The `TaxRateRule` data model represents a tax rate’s rule.
|
||||
|
||||

|
||||
|
||||
These two fields of the data model identify the rule’s target:
|
||||
|
||||
- `reference`: the name of the table in the database that this rule points to. For example, `product_type`.
|
||||
- `reference_id`: the ID of the data model’s record that this points to. For example, a product type’s ID.
|
||||
|
||||
So, to override the default tax rate for product types “Shirt”, you create a tax rate and associate with it a tax rule whose `reference` is `product_type` and `reference_id` the ID of the “Shirt” product type.
|
||||
@@ -0,0 +1,25 @@
|
||||
export const metadata = {
|
||||
title: `Tax Region`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you’ll learn about tax regions and how to use them with the Region Module.
|
||||
|
||||
## What is a Tax Region?
|
||||
|
||||
A tax region, represented by the `TaxRegion` data model, stores settings related to a region that your store serves.
|
||||
|
||||
Tax regions can inherit settings and rules from a parent tax region.
|
||||
|
||||
Each tax region has tax rules and a tax provider.
|
||||
|
||||
---
|
||||
|
||||
## Settings Per Region with the Region Module
|
||||
|
||||
By combining the Tax and [Region](../../region/page.mdx) Modules, as done in the Medusa application, each region has its own tax providers and rates.
|
||||
|
||||
This adds more flexibility to your commerce store.
|
||||
|
||||

|
||||
@@ -0,0 +1,411 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `Examples of the User Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this guide, you’ll find common examples of how you can use the User Module in your application.
|
||||
|
||||
## Create a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const user = await userModuleService.create({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
res.json({ user })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.create({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## List Users
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
return NextResponse.json({
|
||||
users: await userModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Update a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const user = await userModuleService.update({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
res.json({ user })
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function POST(
|
||||
request: Request
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const user = await userModuleService.update({
|
||||
id: "user_123",
|
||||
last_name: "Smith",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Delete a User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function DELETE(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
await userModuleService.delete(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function DELETE(
|
||||
request: Request
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
await userModuleService.delete(["user_123"])
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Invite User
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
res.json({
|
||||
invite,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
invite,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Accept Invite
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const invite =
|
||||
await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.create({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
|
||||
await userModuleService.updateInvites({
|
||||
id: invite.id,
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
res.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invite =
|
||||
await userModuleService.validateInviteToken("secret_123")
|
||||
|
||||
const user = await userModuleService.create({
|
||||
email: invite.email,
|
||||
metadata: invite.metadata,
|
||||
})
|
||||
|
||||
await userModuleService.updateInvites({
|
||||
id: invite.id,
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
user,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## Refresh Invite
|
||||
|
||||
<CodeTabs groupId="app-type">
|
||||
<CodeTab label="Medusa API Router" value="medusa">
|
||||
|
||||
```ts
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function POST(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const invites = await userModuleService.refreshInviteTokens([
|
||||
"invite_123",
|
||||
])
|
||||
|
||||
res.json({
|
||||
invite: invites[0],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Next.js App Router" value="nextjs">
|
||||
|
||||
```ts
|
||||
import { NextResponse } from "next/server"
|
||||
|
||||
import {
|
||||
initialize as initializeUserModule,
|
||||
} from "@medusajs/user"
|
||||
|
||||
export async function POST(
|
||||
request: Request,
|
||||
{ params }: ContextType
|
||||
) {
|
||||
const userModuleService = await initializeUserModule()
|
||||
|
||||
const invites = await userModuleService.refreshInviteTokens([
|
||||
"invite_123",
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
invite: invites[0],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
|
||||
---
|
||||
|
||||
## More Examples
|
||||
|
||||
The [module interface reference](/references/user) provides a reference to all the methods available for use with examples for each.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
sidebar_label: "Module Options"
|
||||
---
|
||||
|
||||
import { Table } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `User Module Options`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this document, you'll learn about the options of the User Module.
|
||||
|
||||
## Module Options
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
user: {
|
||||
resolve: "@medusajs/user",
|
||||
options: {
|
||||
jwt_secret: process.env.JWT_SECRET,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
<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>
|
||||
|
||||
`jwt_secret`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
A string indicating the secret used to sign the invite tokens.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Make sure to add the necessary environment variables for the above options in `.env`:
|
||||
|
||||
```bash
|
||||
JWT_SECRET=supersecret
|
||||
```
|
||||
@@ -0,0 +1,127 @@
|
||||
import { CodeTabs, CodeTab } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: `User Module`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The User Module is the `@medusajs/user` NPM package that provides user-related features in your Medusa and Node.js applications.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### User Management
|
||||
|
||||
Manage and store your users through Create, Read, Update, and Delete (CRUD) operations:
|
||||
|
||||
```ts
|
||||
const user = await userModuleService.create({
|
||||
email: "user@example.com",
|
||||
first_name: "John",
|
||||
last_name: "Smith",
|
||||
})
|
||||
```
|
||||
|
||||
### Invite Users
|
||||
|
||||
Invite users to join your store and manage those invites, with expiry and revalidation features.
|
||||
|
||||
```ts
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user2@example.com",
|
||||
})
|
||||
|
||||
// refresh token later
|
||||
await userModuleService.refreshInviteTokens([invite.id])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configure User Module
|
||||
|
||||
After installing the `@medusajs/user` package in your Medusa application, add it to the `modules` object in `medusa-config.js`:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const modules = {
|
||||
// ...
|
||||
user: {
|
||||
resolve: "@medusajs/user",
|
||||
options: {
|
||||
jwt_secret: process.env.JWT_SECRET,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Module Options
|
||||
|
||||
Refer to [this documentation](./module-options/page.mdx) for details on the module's options.
|
||||
|
||||
---
|
||||
|
||||
## How to Use User Module's Service
|
||||
|
||||
You can use the User Module's main service by resolving from the Medusa container the resource `ModuleRegistrationName.USER` imported from `@medusajs/modules-sdk`.
|
||||
|
||||
For example:
|
||||
|
||||
<CodeTabs groupId="resource-type">
|
||||
<CodeTab label="API Route" value="api-route">
|
||||
|
||||
```ts title="src/api/store/custom/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export async function GET(
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
): Promise<void> {
|
||||
const userModuleService: IUserModuleService =
|
||||
req.scope.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
res.json({
|
||||
users: await userModuleService.list(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Subscriber" value="subscribers">
|
||||
|
||||
```ts title="src/subscribers/custom-handler.ts"
|
||||
import { SubscriberArgs } from "@medusajs/medusa"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
export default async function subscriberHandler({
|
||||
container,
|
||||
}: SubscriberArgs) {
|
||||
const userModuleService: IUserModuleService =
|
||||
container.resolve(ModuleRegistrationName.USER)
|
||||
|
||||
const users = await userModuleService.list()
|
||||
}
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
<CodeTab label="Workflow Step" value="workflow-step">
|
||||
|
||||
```ts title="src/workflows/hello-world/step1.ts"
|
||||
import { createStep } from "@medusajs/workflows-sdk"
|
||||
import { IUserModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
|
||||
const step1 = createStep("step-1", async (_, context) => {
|
||||
const userModuleService: IUserModuleService =
|
||||
context.container.resolve(
|
||||
ModuleRegistrationName.USER
|
||||
)
|
||||
const users = await userModuleService.list()
|
||||
})
|
||||
```
|
||||
|
||||
</CodeTab>
|
||||
</CodeTabs>
|
||||
@@ -0,0 +1,61 @@
|
||||
export const metadata = {
|
||||
title: `User Creation Flows`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
This document provides some possible flows to create a user.
|
||||
|
||||
## Using the Auth Module
|
||||
|
||||
The Auth Module allows you to create a user with different providers, such as creating a user with an email and password or creating a user using their Google account.
|
||||
|
||||
Learn more about this creation flow in [this Auth Module guide](../../auth/user-creation/page.mdx).
|
||||
|
||||
---
|
||||
|
||||
## Invite Users
|
||||
|
||||
Another possible flow to create a user is by sending them an invite. Then, once they accept it, you create a new user for them:
|
||||
|
||||
```ts
|
||||
// create invite
|
||||
const invite = await userModuleService.createInvites({
|
||||
email: "user@example.com",
|
||||
})
|
||||
|
||||
// later, accept invite and create user
|
||||
const invite =
|
||||
await userModuleService.validateInviteToken("secret123")
|
||||
|
||||
await userModuleService.updateInvites({
|
||||
id: invite.id,
|
||||
accepted: true,
|
||||
})
|
||||
|
||||
const user = await userModuleService.create({
|
||||
email: invite.email,
|
||||
})
|
||||
```
|
||||
|
||||
### Invite Expiry
|
||||
|
||||
An invite has an expiry date. You can renew the expiry date and refresh the token using the `refreshInviteTokens` method:
|
||||
|
||||
```ts
|
||||
await userModuleService.refreshInviteTokens(["invite_123"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Straightforward Creation
|
||||
|
||||
Finally, you can create a user using the `create` method of the User Module’s main service (`IUserModuleService`):
|
||||
|
||||
```ts
|
||||
const user = await userModuleService.create({
|
||||
email: "user@example.com",
|
||||
})
|
||||
```
|
||||
|
||||
However, the User Module doesn’t handle authentication methods or store a user’s password. For that, you’d need to use the [Auth Module](../../auth/page.mdx).
|
||||
@@ -0,0 +1,401 @@
|
||||
import { Table } from "docs-ui"
|
||||
import TypeList from "@/components/TypeList"
|
||||
|
||||
export const metadata = {
|
||||
title: `Medusa Admin Configurations`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
You can configure the Medusa Admin plugin to change its PORT, the Medusa server's URL, and more. You can also extend the Webpack configurations.
|
||||
|
||||
## Plugin Options
|
||||
|
||||
The plugin accepts the following options:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
serve: true,
|
||||
autoRebuild: true,
|
||||
backend: "https://example.com",
|
||||
path: "/app",
|
||||
outDir: "build",
|
||||
develop: {
|
||||
open: true,
|
||||
port: 7001,
|
||||
logLevel: "error",
|
||||
stats: "normal",
|
||||
allowedHosts: "auto",
|
||||
webSocketURL: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
<TypeList types={[
|
||||
{
|
||||
name: "serve",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "true",
|
||||
description: "Whether to serve the admin dashboard when the Medusa backend starts. If set to `false`, you can serve the admin dashboard using the [dev command](#develop-command-options).",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "autoRebuild",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "false",
|
||||
description: "Whether the admin UI should be rebuilt if there are any changes or if a missing build is detected when the backend starts. If not set, you must [manually build the admin dashboard](#build-command-options).",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "backend",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The URL of the Medusa backend. Its value is only used if the `serve` option is set to `false`.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "path",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "/app",
|
||||
description: "The path the admin server should run on when running the Medusa backend in production. It must be prefixed with a slash `/`, but it can't end with a `/`, which throws an error. It also can't be one of the reserved paths: \"admin\" and \"store\".",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "outDir",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The directory to output the admin build to. By default, the plugin builds the admin to the `build` directory in the root of the Medusa backend directory.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "develop",
|
||||
type: "object",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "Options for the admin development server.",
|
||||
expandable: false,
|
||||
children: [
|
||||
{
|
||||
name: "open",
|
||||
type: "boolean",
|
||||
optional: true,
|
||||
defaultValue: "true",
|
||||
description: "Whether the browser should be opened when the admin development server starts.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "port",
|
||||
type: "number",
|
||||
optional: true,
|
||||
defaultValue: "7001",
|
||||
description: "The port the admin dashboard runs on.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "logLevel",
|
||||
type: "\"error\" \\| \"none\" \\| \"warn\" \\| \"info\" \\| \"log\" \\| \"verbose\"",
|
||||
optional: true,
|
||||
defaultValue: "error",
|
||||
description: "The log level of the admin development server.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "stats",
|
||||
type: "\"normal\" \\| \"debug\"",
|
||||
optional: true,
|
||||
defaultValue: "normal",
|
||||
description: "The verbosity of the admin development server.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "allowedHosts",
|
||||
type: "\"auto\" \\| \"all\" \\| string[]",
|
||||
optional: true,
|
||||
defaultValue: "auto",
|
||||
description: "The development server's allowed hosts.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "webSocketURL",
|
||||
type: "string \\| object \\| undefined",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The URL to a web socket server",
|
||||
expandable: false,
|
||||
children: [
|
||||
{
|
||||
name: "hostname",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's hostname.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's password.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "path name",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's path name.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "port",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's port.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
type: "string",
|
||||
optional: true,
|
||||
defaultValue: "",
|
||||
description: "The web socket's username.",
|
||||
expandable: false,
|
||||
children: []
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]} sectionTitle="Plugin Options" />
|
||||
|
||||
---
|
||||
|
||||
## Admin CLI Commands
|
||||
|
||||
The `medusa-admin` CLI tool is installed in your Medusa application. It's used within the `package.json`'s scripts to build admin assets.
|
||||
|
||||
### build
|
||||
|
||||
The `build` command in the admin CLI allows you to manually build the admin dashboard. For example:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"build:admin": "medusa-admin build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 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>
|
||||
|
||||
`--deployment`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Build admin assets for deployment. When this option is added, plugin options are not loaded from `medusa-config.js` anymore, and
|
||||
the backend URL is loaded from the `MEDUSA_ADMIN_BACKEND_URL` environment variable.
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### develop
|
||||
|
||||
The `develop` command in the admin CLI allows you to run the admin dashboard in development separately from the Medusa application. For example:
|
||||
|
||||
```json title="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
// other scripts...
|
||||
"dev:admin": "medusa-admin develop"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 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>
|
||||
|
||||
`--backend <url>`, `-b <url>`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The URL of the Medusa backend.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The value of the environment variable `MEDUSA_ADMIN_BACKEND_URL`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`--port <url>`, `-p <url>`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The port to run the admin on.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
`7001`
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
## Change the Medusa Application URL
|
||||
|
||||
### In Development
|
||||
|
||||
To change the Medusa application's URL that the admin sends request to, disable the `serve` plugin option and set the `backend` option to the new URL.
|
||||
|
||||
However, this requires you to also set-up the [develop command](#develop-command-options) as the admin will no longer start with the Medusa application.
|
||||
|
||||
For example:
|
||||
|
||||
```js title="medusa-config.js"
|
||||
const plugins = [
|
||||
// ...
|
||||
{
|
||||
resolve: "@medusajs/admin",
|
||||
/** @type {import('@medusajs/admin').PluginOptions} */
|
||||
options: {
|
||||
serve: false,
|
||||
backend: "http://localhost:9001",
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### In Production
|
||||
|
||||
<Note>
|
||||
|
||||
This assumes that you've deployed the admin separately and you're passing the `--deployment` option to the [build command](#build-command-options).
|
||||
|
||||
</Note>
|
||||
|
||||
To change the backend's URL that the admin sends request to, set the environment variable `MEDUSA_ADMIN_BACKEND_URL` to the backend's URL.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
MEDUSA_ADMIN_BACKEND_URL=https://example.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Environment Variables
|
||||
|
||||
To set environment variables that you want to access in your admin dashboard's customizations (such as in [widgets](!docs!/advanced-development/admin/widgets) or [UI routes](!docs!/advanced-development/admin/ui-routes)), your environment variables must be prefixed with `MEDUSA_ADMIN_`. Otherwise, it won't be loaded within the admin.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
MEDUSA_ADMIN_CUSTOM_API_KEY=123...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Webpack Configurations
|
||||
|
||||
<Note type="warning">
|
||||
|
||||
This is an advanced feature and requires knowledge of configuring webpack. If configured wrongly, it may lead to the admin application breaking.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
|
||||
Plugins can't include webpack customizations.
|
||||
|
||||
</Note>
|
||||
|
||||
The Medusa Admin uses [Webpack](https://webpack.js.org/) to define the configurations for both the Medusa Admin plugin and your customizations.
|
||||
|
||||
You can extend the default webpack configurations defined in the admin plugin to add your custom configurations, such as to support styling your extensions with CSS Modules.
|
||||
|
||||
To do that, create the file `src/admin/webpack.config.js` that uses the `withCustomWebpackConfig` method imported from `@medusajs/admin` to export the extended configurations:
|
||||
|
||||
```js title="src/admin/webpack.config.js"
|
||||
import { withCustomWebpackConfig } from "@medusajs/admin"
|
||||
|
||||
export default withCustomWebpackConfig((config, webpack) => {
|
||||
config.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
"process.env": {
|
||||
NODE_ENV: JSON.stringify("production"),
|
||||
API_URL:
|
||||
JSON.stringify("https://api.medusa-commerce.com"),
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
return config
|
||||
})
|
||||
```
|
||||
|
||||
The method `withCustomWebpackConfig` accepts a callback function that must return an object of [webpack configuration](https://webpack.js.org/configuration/). The callback function accepts two parameters:
|
||||
|
||||
1. The first parameter is an object that holds the default webpack configuration. Add your configurations to this object, then return it. Not returning the default configurations breaks the application.
|
||||
2. The second parameter is the webpack instance.
|
||||
@@ -0,0 +1,49 @@
|
||||
export const metadata = {
|
||||
title: `Contribute by Translating Admins`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
The Medusa Admin supports multiple languages, with the default being English. We highly appreciate your contribution by translation to other languages you're fluent with, as it ensures a wider support of languages.
|
||||
|
||||
This type of contribution is a no-code contribution, meaning you don't need advanced technical skills to contribute.
|
||||
|
||||
---
|
||||
|
||||
## How to Contribute Translation
|
||||
|
||||
1. Clone the [Medusa monorepository](https://github.com/medusajs/medusa) to your local machine:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/medusajs/medusa.git
|
||||
```
|
||||
|
||||
If you already have it cloned, make sure to pull the latest changes from the `develop` branch.
|
||||
|
||||
2. Create a branch that'll be used to open the pull request later:
|
||||
|
||||
```bash
|
||||
git check -b feat/translate-<LANGUAGE>
|
||||
```
|
||||
|
||||
Where `<LANGUAGE>` is your language name. For example, `feat/translate-da`.
|
||||
|
||||
3. Create a new directory under `packages/admin-ui/ui/public/locales` with its name being the [ISO 2 character code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of your language. For example, `da`. In the new directory, create the file `translation.json`.
|
||||
|
||||
4. Copy the content of the English translation file located at `packages/admin-ui/ui/public/locales/en/translation.json` and paste it in your new `translation.json` file.
|
||||
|
||||
5. In the file, leave the key names as-is, and only translate the values.
|
||||
|
||||
7. In the file `packages/admin-ui/ui/src/i18n/index.ts`, add the new language to the `supportedLanguages` array as an object. The object accepts two properties: `locale` for the ISO 2 character code, and `name` for the name of the language. The name of the language should be the translated name, not the English name. For example:
|
||||
|
||||
```ts title="packages/admin-ui/ui/src/i18n/index.ts"
|
||||
export const supportedLanguages = [
|
||||
// other languages...
|
||||
{
|
||||
locale: "da",
|
||||
name: "Dansk",
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
8. Once you're done, push the changes into your branch and open a pull request on GitHub.
|
||||
Loaded 100 of 6187 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user