docs: added B2B recipe (#4901)

* added b2b recipe

* added redirect rule

* fix build errors

* fix broken links
This commit is contained in:
Shahed Nasser
2023-08-29 19:50:14 +03:00
committed by GitHub
parent e086e3efbd
commit 33ffdaf42d
7 changed files with 885 additions and 174 deletions

View File

@@ -5,23 +5,18 @@ addHowToData: true
import DocCardList from '@theme/DocCardList';
import DocCard from '@theme/DocCard';
import Icons from '@theme/Icon';
import LearningPath from '@site/src/components/LearningPath';
# Role-Based Access Control (RBAC) Recipe
# Implement Role-Based Access Control (RBAC)
This document guides you through the different documentation resources that will help you build a marketplace with Medusa.
In this document, you'll get a high-level overview of how you can implement role-based access control (RBAC) in your Medusa backend.
## Overview
Role-Based Access Control (RBAC) refers to the level of access a user has. Typically, in e-commerce, you may require RBAC if you want users to only be able to perform certain actions.
For example, you may want a content-manager user who can only access CMS functionalities and another manager user who can only access order functionalities. RBAC is also useful in [marketplace use cases](./marketplace.mdx).
For example, you may want a content-manager user who can only access CMS functionalities and another manager user who can only access order functionalities. RBAC is also useful in [marketplace use cases](../../../recipes/marketplace.mdx).
This recipe gives you a high-level approach to implementing RBAC in Medusa. The examples included in this recipe provide a simple implementation to give you an idea of how you can implement this functionality in your Medusa backend.
You may also follow this path that takes you through the different documentation pages that will help you to implement RBAC in Medusa.
<LearningPath pathName="rbac" />
This guide gives you a high-level approach to implementing RBAC in Medusa. The examples included in this guide provide a simple implementation to give you an idea of how you can implement this functionality in your Medusa backend.
---
@@ -59,7 +54,7 @@ Example Implementation
This is an example implementation of how you can create the Role and Permission entities, and extend the `User` and `Store` entities.
Creating an entity requires creating an entity class, a repository, and a migration. You can learn more [here](../development/entities/create.mdx). Youll be creating the migration at the end of this example section.
Creating an entity requires creating an entity class, a repository, and a migration. You can learn more [here](../../../development/entities/create.mdx). Youll be creating the migration at the end of this example section.
Create the file `src/models/role.ts` with the following content:
@@ -275,7 +270,7 @@ This ensures that your TypeScript validation and editor autocomplete recognize t
Finally, you need to create a migration to reflect these changes in the database.
You can learn about creating migrations [here](../development/entities/migrations/create.md). An example of a migration file based on the entities created above:
You can learn about creating migrations [here](../../../development/entities/migrations/create.md). An example of a migration file based on the entities created above:
<!-- eslint-disable max-len -->
@@ -442,7 +437,7 @@ export default (rootDirectory: string): Router | Router[] => {
}
```
This assumes you already have a router with all necessary CORS configurations and body parsing middlewares. If not, you can refer to the [Create Endpoint documentation](../development/endpoints/create.mdx) for more details.
This assumes you already have a router with all necessary CORS configurations and body parsing middlewares. If not, you can refer to the [Create Endpoint documentation](../../../development/endpoints/create.mdx) for more details.
Make sure to use the permission middleware after all router configurations if you want the middleware to work on your custom admin routes.
@@ -802,4 +797,4 @@ Finally, login with the user you created, then try to access any endpoint other
## Additional Development
If your use case requires other changes or functionality implementations, check out the [Medusa Development section](../development/overview.mdx) of the documentation for all available development guides.
If your use case requires other changes or functionality implementations, check out the [Medusa Development section](../../../development/overview.mdx) of the documentation for all available development guides.

View File

@@ -0,0 +1,457 @@
---
addHowToData: true
---
import DocCardList from '@theme/DocCardList';
import DocCard from '@theme/DocCard';
import Icons from '@theme/Icon';
import LearningPath from '@site/src/components/LearningPath';
# B2B / Wholesale Recipe
This document guides you through the different documentation resources that will help you build a B2B store with Medusa.
## Overview
In a B2B store, the seller provides other businesses with wholesale products and prices. Medusa allows you to build a B2B store in different ways:
1. **Fully B2B:** You can use Medusa as-is and cater to businesses only. You can also specify different pricing for different businesses using [Customer Groups](../user-guide/customers/groups.mdx) and [Price Lists](../user-guide/price-lists/index.md).
2. **B2B and B2C:** You can serve both businesses and customers within the same store, supplying different products or prices for each.
This recipe covers the high-level steps to implement the second case, a B2B and B2C store.
<LearningPath pathName="b2b" />
---
## Create B2B Sales Channel
In Medusa, a sales channel allows you to set product availability per channel. In this case, you can create a B2B sales channel that will include only your wholesale products.
You can create a sales channel either through the Medusa admin or through the Admin REST APIs.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/sales-channels/manage',
label: 'Option 1: Use Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the sales channel using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/sales-channels/admin/manage',
label: 'Option 2: Using the REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the sales channel using the REST APIs.',
}
},
]} />
---
## Create a Publishable API Key
Publishable API keys can be associated with one or more sales channels. Then, in a client such as a storefront, you can pass the publishable API key in the header of your requests to ensure all products retrieved belong to the associated sales channel(s).
You can create a publishable API key either through the Medusa admin or through the Admin REST APIs.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/settings/publishable-api-keys',
label: 'Option 1: Use Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the publishable API key using the Medusa admin.',
}
},
{
type: 'link',
href: '/development/publishable-api-keys/admin/manage-publishable-api-keys',
label: 'Option 2: Using the REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the publishable API key using the REST APIs.',
}
},
]} />
---
## Add Wholesale Products
Using the Medusa admin or the Admin REST APIs, you can add your wholesale products to your store. You can also use the Product Import feature to import your products from an existing CSV file.
After or while you add your products, make sure to set the products availability to the B2B sales channel youve created.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/products/manage',
label: 'Add Products Using Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the product using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/products/admin/manage-products',
label: 'Add Products Using REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the product using the REST APIs.',
}
},
{
type: 'link',
href: '/user-guide/products/import',
label: 'Import Products Using Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Import the products using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/products/admin/import-products',
label: 'Import Products Using REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Import the product using the REST APIs.',
}
},
]} />
---
## Create B2B Customer Groups
To apply different prices for your B2B customers, you need to create a customer group that indicates this customer is a B2B customer.
You can either create a single B2B customer group that all your B2B customers will fall into, or you can create different B2B customer groups for every B2B customer. The second approach is useful if you want to apply different prices for different businesses.
While youre creating the customer groups, make sure to add the `metadata` of the customer group a key `is_b2b` that acts as a flag to indicate that this is a B2B customer group. This is useful for later steps.
Alternatively, you may choose to create custom entities or a different mechanism that indicates a customer is a B2B customer. This is covered in the [Create Custom Entities section](#create-custom-entities).
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/customers/groups',
label: 'Option 1: Use Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the customer group using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/customers/admin/manage-customer-groups',
label: 'Option 2: Using the REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the customer group using the REST APIs.',
}
},
]} />
---
## Add B2B Customers
After adding your B2B customer group, you can add B2B customers and assign them to the B2B customer group. Alternatively, if you want to allow B2B customers to register themselves, you can implement that logic within your storefront.
You can create a customer either through the Medusa admin or Admin REST APIs.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/customers/manage',
label: 'Option 1: Use Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the customers using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/customers/admin/manage-customers',
label: 'Option 2: Using the REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the customers using the REST APIs.',
}
},
]} />
---
## Create B2B Price List
A price list allows you to set different prices on a set of products for different conditions. You can use this when building a B2B store to assign different prices for B2B customer groups.
You can use price lists to either set the same price for all B2B customers, or different prices for different B2B customers if youve created them in different customer groups.
You can create a price list either through the Medusa admin or Admin REST APIs. You can also import prices into your price list after creating it.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/user-guide/price-lists/manage',
label: 'Add Price List Using Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Create the price list using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/price-lists/admin/manage-price-lists',
label: 'Add Price List Using REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Create the price list using the REST APIs.',
}
},
{
type: 'link',
href: '/user-guide/price-lists/import',
label: 'Import Prices Using Medusa Admin',
customProps: {
icon: Icons['users-solid'],
description: 'Import the prices using the Medusa admin.',
}
},
{
type: 'link',
href: '/modules/price-lists/admin/import-prices',
label: 'Import Prices Using REST APIs',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Import the prices using the REST APIs.',
}
},
]} />
---
## Create Custom Entities
Your use case may be more elaborate than what is shown in this recipe. For example, you may need to have a Company entity that allows a business to register its employees as users with different privileges and configurations.
Medusa can be customized to add custom entities, endpoints, services, and more. If you require some additional entities or a different logic in how you choose to implement a B2B store, you can customize Medusa as you see fit.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/development/entities/create',
label: 'Create Entity',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create a custom entity.',
}
},
{
type: 'link',
href: '/development/overview',
label: 'Medusa Development',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Find all the development resources to customize Medusa.',
}
},
]} />
---
## Create an Endpoint to Check Customers
On the clients communicating with your store, such as the storefront, youll need to check if the currently logged-in customer is a normal customer or a B2B customer.
The endpoints logic is different based on how youve implemented the B2B logic. For example, if youve used the `is_b2b` flag in the customer group, your endpoint can check whether that flag is enabled for the logged-in customer.
Medusa allows you to create custom endpoints exposed as REST APIs.
<DocCard item={{
type: 'link',
href: '/development/endpoints/create',
label: 'Create an Endpoint',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create an endpoint in Medusa.',
}
}} />
<details>
<summary>
Example Implementation
</summary>
Heres an example of an endpoint that allows you to check the customers group and whether it has the `is_b2b` flag enabled:
```ts title=src/api/index.ts
import { CustomerService } from "@medusajs/medusa"
import { Router } from "express"
import { requireCustomerAuthentication } from "@medusajs/medusa"
import cors from "cors"
import { ConfigModule } from "@medusajs/medusa"
import { getConfigFile } from "medusa-core-utils"
export default (rootDirectory) => {
const router = Router()
const { configModule } = getConfigFile<ConfigModule>(
rootDirectory,
"medusa-config"
)
const corsOptions = {
origin: configModule.projectConfig.store_cors.split(","),
credentials: true,
}
router.options("/store/customers/is-b2b", cors(corsOptions))
router.get(
"/store/customers/is-b2b",
cors(corsOptions),
requireCustomerAuthentication(),
async (req, res) => {
const customerService: CustomerService = req.scope.resolve(
"customerService"
)
const customer = await customerService
.retrieve(req.user.customer_id, {
relations: ["groups"],
})
const is_b2b = customer.groups.some(
(group) => group.metadata.is_b2b === "true"
)
return res.json({
is_b2b,
})
})
return router
}
```
</details>
---
## Customize Admin
Based on your use case, you may need to customize the Medusa admin to add new widgets or pages.
The Medusa admin plugin can be extended to add widgets, UI routes, and setting pages.
<DocCardList colSize={4} items={[
{
type: 'link',
href: '/admin/widgets',
label: 'Create Admin Widget',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add widgets into existing admin pages.',
}
},
{
type: 'link',
href: '/admin/routes',
label: 'Create Admin UI Routes',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add new pages to your Medusa admin.',
}
},
{
type: 'link',
href: '/admin/setting-pages',
label: 'Create Admin Setting Page',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add new page to the Medusa admin settings.',
}
},
]} />
---
## Customize Storefront
On the storefront, you may want different login or registration interfaces for B2B and regular customers. You may also want to display products differently for each type of customer.
Medusa provides a Next.js starter that you can use and customize. You can also build your own storefront with any front-end technology. Medusa provides you with different client libraries and its REST APIs to use.
In the storefront, make sure to use the publishable API key you associated with your B2B sales channel to ensure only B2B products are retrieved.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/starters/nextjs-medusa-starter',
label: 'Option 1: Use Next.js Starter',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to install and use the Next.js starter template.',
}
},
{
type: 'link',
href: '/storefront/roadmap',
label: 'Options 2: Build Custom Storefront',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to build a custom storefront with your preferred technology.',
}
},
]} />
<DocCard item={{
type: 'link',
href: '/development/publishable-api-keys/storefront/use-in-requests',
label: 'Use Publishable API Keys',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to use the publishable API key in client requests.',
}
}} />
---
## Deploy B2B Store
Once you finish your development, you can deploy your B2B backend and storefront to your preferred hosting providers.
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/deployments/server',
label: 'Deploy Medusa Backend',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to deploy your Medusa backend to your preferred hosting provider.',
}
},
{
type: 'link',
href: '/deployments/storefront',
label: 'Deploy Storefront',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to deploy your storefront to your preferred hosting provider.',
}
},
]} />
---
## Additional Development
You can find other resources for your B2B development in the [Medusa Development section](../development/overview.mdx) of this documentation.

View File

@@ -63,17 +63,17 @@ Follow this guide if you want to implement subscription-based purhcases with Med
---
## Recipe: Role-Based Access Control
## Recipe: Build B2B Store
Follow this guide if you want to implement role-based access control (RBAC) in Medusa.
Follow this guide if you want to build a B2B/Wholesale store.
<DocCard item={{
type: 'link',
href: '/recipes/rbac',
label: 'Build RBAC',
href: '/recipes/b2b',
label: 'Build B2B Store',
customProps: {
icon: Icons['credit-card-solid'],
description: 'Learn how you can implement role-based access control in Medusa.'
description: 'Learn how you can build a B2B store in Medusa.'
}
}} />

View File

@@ -42,102 +42,102 @@ To associate these entities with the `Store` entity, you need to extend and cust
}} />
<details>
<summary>Example: Associate User with Store</summary>
<summary>Example: Associate User with Store</summary>
For example, to associate the `User` entity with the `Store` entity, create the file `src/models/user.ts` with the following content:
For example, to associate the `User` entity with the `Store` entity, create the file `src/models/user.ts` with the following content:
```ts title=src/models/user.ts
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
} from "typeorm"
import {
User as MedusaUser,
} from "@medusajs/medusa"
import { Store } from "./store"
@Entity()
export class User extends MedusaUser {
@Index("UserStoreId")
@Column({ nullable: true })
store_id?: string
@ManyToOne(() => Store, (store) => store.members)
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
store?: Store
}
```
```ts title=src/models/user.ts
import {
Column,
Entity,
Index,
JoinColumn,
ManyToOne,
} from "typeorm"
import {
User as MedusaUser,
} from "@medusajs/medusa"
import { Store } from "./store"
Then, you need to extend the `UserRepository` to point to your extended entity. To do that, create the file `src/repositories/user.ts` with the following content:
@Entity()
export class User extends MedusaUser {
@Index("UserStoreId")
@Column({ nullable: true })
store_id?: string
```ts title=src/repositories/user.ts
import { User } from "../models/user"
import {
dataSource,
} from "@medusajs/medusa/dist/loaders/database"
import {
UserRepository as MedusaUserRepository,
} from "@medusajs/medusa/dist/repositories/user"
@ManyToOne(() => Store, (store) => store.members)
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
store?: Store
}
```
export const UserRepository = dataSource
.getRepository(User)
.extend({
...Object.assign(
MedusaUserRepository,
{ target: User }
),
})
Then, you need to extend the `UserRepository` to point to your extended entity. To do that, create the file `src/repositories/user.ts` with the following content:
export default UserRepository
```
```ts title=src/repositories/user.ts
import { User } from "../models/user"
import {
dataSource,
} from "@medusajs/medusa/dist/loaders/database"
import {
UserRepository as MedusaUserRepository,
} from "@medusajs/medusa/dist/repositories/user"
Next, you need to create a migration that reflects the changes on the `User` entity in your database. To do that, run the following command to create a migration file:
export const UserRepository = dataSource
.getRepository(User)
.extend({
...Object.assign(
MedusaUserRepository,
{ target: User }
),
})
```bash
npx typeorm migration:create src/migrations/add-user-store-id
```
export default UserRepository
```
This creates a file in the `src/migrations` directory of the format `<TIMESTAMP>_add-user-store-id.ts`. Replace the `up` and `down` methods in that file with the methods here:
Next, you need to create a migration that reflects the changes on the `User` entity in your database. To do that, run the following command to create a migration file:
```ts title=src/migrations/<TIMESTAMP>_add-user-store-id.ts
// ...
```bash
npx typeorm migration:create src/migrations/add-user-store-id
```
export class AddUserStoreId1681287255173
implements MigrationInterface {
// ...
This creates a file in the `src/migrations` directory of the format `<TIMESTAMP>_add-user-store-id.ts`. Replace the `up` and `down` methods in that file with the methods here:
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ADD "store_id" character varying`
)
await queryRunner.query(
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
)
}
```ts title=src/migrations/<TIMESTAMP>_add-user-store-id.ts
// ...
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."UserStoreId"`
)
await queryRunner.query(
`ALTER TABLE "user" DROP COLUMN "store_id"`
)
}
export class AddUserStoreId1681287255173
implements MigrationInterface {
// ...
}
```
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "user" ADD "store_id" character varying`
)
await queryRunner.query(
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
)
}
Finally, to reflect these changes and start using them, `build` your changes and run migrations with the following commands:
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "public"."UserStoreId"`
)
await queryRunner.query(
`ALTER TABLE "user" DROP COLUMN "store_id"`
)
}
```bash npm2yarn
npm run build
npx medusa migrations run
```
}
```
You can extend other entities in a similar manner to associate them with a store.
Finally, to reflect these changes and start using them, `build` your changes and run migrations with the following commands:
```bash npm2yarn
npm run build
npx medusa migrations run
```
You can extend other entities in a similar manner to associate them with a store.
</details>
@@ -180,73 +180,73 @@ You can also extend services if you need to customize a functionality implemente
}} />
<details>
<summary>Example: Extend User Service</summary>
<summary>Example: Extend User Service</summary>
You can extend the user service to change how the `create` method is implemented.
You can extend the user service to change how the `create` method is implemented.
To extend the user service, create the file `src/services/user.ts` with the following content:
To extend the user service, create the file `src/services/user.ts` with the following content:
<!-- eslint-disable prefer-rest-params -->
```ts
import { Lifetime } from "awilix"
import {
UserService as MedusaUserService,
} from "@medusajs/medusa"
import { User } from "../models/user"
import {
CreateUserInput as MedusaCreateUserInput,
} from "@medusajs/medusa/dist/types/user"
import StoreRepository from "../repositories/store"
```ts
import { Lifetime } from "awilix"
import {
UserService as MedusaUserService,
} from "@medusajs/medusa"
import { User } from "../models/user"
import {
CreateUserInput as MedusaCreateUserInput,
} from "@medusajs/medusa/dist/types/user"
import StoreRepository from "../repositories/store"
type CreateUserInput = {
store_id?: string
} & MedusaCreateUserInput
type CreateUserInput = {
store_id?: string
} & MedusaCreateUserInput
class UserService extends MedusaUserService {
static LIFE_TIME = Lifetime.SCOPED
protected readonly loggedInUser_: User | null
protected readonly storeRepository_: typeof StoreRepository
constructor(container, options) {
super(...arguments)
this.storeRepository_ = container.storeRepository
class UserService extends MedusaUserService {
static LIFE_TIME = Lifetime.SCOPED
protected readonly loggedInUser_: User | null
protected readonly storeRepository_: typeof StoreRepository
try {
this.loggedInUser_ = container.loggedInUser
} catch (e) {
// avoid errors when backend first runs
}
}
constructor(container, options) {
super(...arguments)
this.storeRepository_ = container.storeRepository
async create(
user: CreateUserInput,
password: string
): Promise<User> {
if (!user.store_id) {
const storeRepo = this.manager_.withRepository(
this.storeRepository_
)
let newStore = storeRepo.create()
newStore = await storeRepo.save(newStore)
user.store_id = newStore.id
}
return await super.create(user, password)
try {
this.loggedInUser_ = container.loggedInUser
} catch (e) {
// avoid errors when backend first runs
}
}
export default UserService
```
async create(
user: CreateUserInput,
password: string
): Promise<User> {
if (!user.store_id) {
const storeRepo = this.manager_.withRepository(
this.storeRepository_
)
let newStore = storeRepo.create()
newStore = await storeRepo.save(newStore)
user.store_id = newStore.id
}
In the `create` method of this extended service, you create a new store if the user being created doesn't have a store associated with it.
return await super.create(user, password)
}
}
You can then test out your customization by running the `build` command and starting the backend:
export default UserService
```
```bash
npm run build
npx medusa develop
```
In the `create` method of this extended service, you create a new store if the user being created doesn't have a store associated with it.
You can then test out your customization by running the `build` command and starting the backend:
```bash
npm run build
npx medusa develop
```
</details>
---
@@ -268,32 +268,32 @@ To listen to events, you need to create Subscribers that subscribe a handler met
}} />
<details>
<summary>Example: Listen to Order Created Event</summary>
<summary>Example: Listen to Order Created Event</summary>
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
```ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
handleOrder = async (data) => {
// TODO perform functionality
}
```ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
export default OrderNotifierSubscriber
```
handleOrder = async (data) => {
// TODO perform functionality
}
}
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.
export default OrderNotifierSubscriber
```
You can then test out your subscriber by running the `build` command and starting the backend:
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.
```bash
npm run build
npx medusa develop
```
You can then test out your subscriber by running the `build` command and starting the backend:
```bash
npm run build
npx medusa develop
```
</details>
---
@@ -402,8 +402,8 @@ If you want to implement this functionality, you can follow the RBAC recipe.
<DocCard item={{
type: 'link',
href: '/recipes/rbac',
label: 'RBAC Recipe',
href: '/modules/users/backend/rbac',
label: 'Implement Role-Based Access Control',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to implement role-based access control (RBAC) in Medusa.',

View File

@@ -59,8 +59,8 @@ module.exports = {
},
{
type: "doc",
id: "recipes/rbac",
label: "RBAC",
id: "recipes/b2b",
label: "B2B / Wholesale",
},
],
},
@@ -1153,6 +1153,11 @@ module.exports = {
id: "modules/users/backend/send-invite",
label: "Backend: Send Invite",
},
{
type: "doc",
id: "modules/users/backend/rbac",
label: "Backend: Implement RBAC",
},
{
type: "doc",
id: "modules/users/admin/manage-profile",

View File

@@ -95,9 +95,24 @@ const paths: LearningPathType[] = [
</>
),
},
{
title: "Customize Admin",
path: "/admin/widgets",
descriptionJSX: (
<>
As you add marketplace features to your store, you&apos;ll most
likely need to customize the admin to provide an interface to manage
these features.
<br />
You can extend the admin plugin to add widgets,{" "}
<Link to="/admin/routes">UI routes</Link>, or{" "}
<Link to="/admin/setting-pages">setting pages</Link>.
</>
),
},
{
title: "Implement Role-Based Access Control",
path: "/recipes/rbac",
path: "/modules/users/backend/rbac",
description:
"In your marketplace, you may need to implement role-based access control (RBAC) within stores. This will restrict some users' permissions to specified functionalities or endpoints.",
},
@@ -169,6 +184,21 @@ const paths: LearningPathType[] = [
"Create a scheduled job that checks daily for subscriptions that needs renewal.",
path: "/development/scheduled-jobs/create",
},
{
title: "Customize Admin",
path: "/admin/widgets",
descriptionJSX: (
<>
As you add subscription features to your store, you may need to
customize the admin to provide an interface to manage these
features.
<br />
You can extend the admin plugin to add widgets,{" "}
<Link to="/admin/routes">UI routes</Link>, or{" "}
<Link to="/admin/setting-pages">setting pages</Link>.
</>
),
},
{
title: "Create a storefront",
path: "/starters/nextjs-medusa-starter",
@@ -199,6 +229,227 @@ const paths: LearningPathType[] = [
},
},
},
{
name: "b2b",
label: "Build a B2B store",
description:
"Utilize Medusa's features and customization capabilities to build a B2B store.",
steps: [
{
title: "Create a B2B Sales Channel",
path: "/user-guide/sales-channels/manage",
descriptionJSX: (
<>
You can create a B2B sales channel that will include only your
wholesale products.
<br />
You can either use the Medusa admin, or the{" "}
<Link to="/modules/sales-channels/admin/manage">
Admin REST APIs
</Link>
.
</>
),
},
{
title: "Create a Publishable API Key",
path: "/user-guide/settings/publishable-api-keys",
descriptionJSX: (
<>
Publishable API keys can be associated with one or more sales
channels. You can then use the publishable API key in your
storefront or client.
<br />
You can either use the Medusa admin, or the{" "}
<Link to="/development/publishable-api-keys/admin/manage-publishable-api-keys">
Admin REST APIs
</Link>
.
</>
),
},
{
title: "Add Wholesale Products",
path: "/user-guide/products/manage",
descriptionJSX: (
<>
You can add your wholesale products and make them only available in
the B2B sales channel.
<br />
You can use the Medusa admin to add the products. Other alternatives
are:
<ul>
<li>
<Link to="/modules/products/admin/manage-products">
Add Products Using REST APIs
</Link>
</li>
<li>
<Link to="/user-guide/products/import">
Import Products Using Medusa Admin
</Link>
</li>
<li>
<Link to="/modules/products/admin/import-products">
Import Products Using REST APIs
</Link>
</li>
</ul>
</>
),
},
{
title: "Create a B2B Customer Group",
path: "/user-guide/customers/groups",
descriptionJSX: (
<>
Customer groups can be used to apply different prices for different
subsets of customers, in this case B2B customers.
<br />
You can either use the Medusa admin, or the{" "}
<Link to="/modules/customers/admin/manage-customer-groups">
Admin REST APIs
</Link>
.
</>
),
},
{
title: "Add B2B Customers",
path: "/user-guide/customers/manage",
descriptionJSX: (
<>
You can now add B2B customers and assign them to the B2B customer
group. Alternatively, if you want to allow B2B customers to register
themselves, you can implement that logic within your storefront.
<br />
You can either use the Medusa admin, or the{" "}
<Link to="/modules/customers/admin/manage-customers">
Admin REST APIs
</Link>
.
</>
),
},
{
title: "Create B2B Price Lists",
path: "/user-guide/price-lists/manage",
descriptionJSX: (
<>
A price list allows you to set different prices on a set of products
for different conditions. You can use this when building a B2B store
to assign different prices for B2B customer groups.
<br />
You can use the Medusa admin to add the price list. Other
alternatives are:
<ul>
<li>
<Link to="/modules/price-lists/admin/manage-price-lists">
Add Price List Using REST APIs
</Link>
</li>
<li>
<Link to="/user-guide/price-lists/import">
Import Prices Using Medusa Admin
</Link>
</li>
<li>
<Link to="/modules/price-lists/admin/import-prices">
Import Prices Using REST APIs
</Link>
</li>
</ul>
</>
),
},
{
title: "Create Custom Entities",
path: "/development/entities/create",
descriptionJSX: (
<>
Your use case may be more elaborate than what is shown in this
recipe.
<br />
Medusa can be customized to add custom entities, endpoints,
services, and more.
<br />
You can find additional development resources in the{" "}
<Link to="/development/overview">Medusa development section</Link>.
</>
),
},
{
title: "Create an Endpoint to Check Customers",
path: "/development/entities/create",
descriptionJSX: (
<>
On the clients communicating with your store, such as the
storefront, youll need to check if the currently logged-in customer
is a normal customer or a B2B customer.
<br />
To do that, you need to create a custom endpoint that handles the
checking based on the custom logic you&apos;ve chosen to indicate a
customer is a B2B customer.
</>
),
},
{
title: "Customize Admin",
path: "/admin/widgets",
descriptionJSX: (
<>
As you add B2B features to your store, you may need to customize the
admin to provide an interface to manage these features.
<br />
You can extend the admin plugin to add widgets,{" "}
<Link to="/admin/routes">UI routes</Link>, or{" "}
<Link to="/admin/setting-pages">setting pages</Link>.
</>
),
},
{
title: "Customize Storefront",
path: "/starters/nextjs-medusa-starter",
descriptionJSX: (
<>
You may need to customize your storefront to add different
interfaces for B2B and regular customers, or show products
differently.
<br />
You can customize the Next.js storefront, or you can{" "}
<Link to="/storefront/roadmap">build a custom storefront</Link>.
<br />
In your storefront, make sure to{" "}
<Link to="/development/publishable-api-keys/storefront/use-in-requests">
use publishable API keys
</Link>{" "}
in your requests.
</>
),
},
{
title: "Deploy the B2B store",
path: "/deployments/server",
descriptionJSX: (
<>
Once you finish your development, you can deploy your B2B backend to
your preferred hosting provider. You can also{" "}
<Link to="/deployments/storefront">deploy your storefront</Link> to
your preferred hosting provider.
</>
),
},
],
finish: {
type: "rating",
step: {
title: "Congratulations on building a B2B store!",
description: "Please rate your experience using this recipe.",
eventName: "rating_path_b2b",
},
},
},
// TODO: Eventually remove these learning paths
{
name: "rbac",
label: "Role-based access control (RBAC)",
@@ -274,7 +525,6 @@ const paths: LearningPathType[] = [
},
},
},
// TODO: Eventually remove these learning paths
{
name: "entity-and-api",
label: "Create Entity and Expose it with Endpoints",

View File

@@ -472,6 +472,10 @@
{
"source": "/admin/development",
"destination": "/admin/widgets"
},
{
"source": "/recipes/rbac",
"destination": "/modules/users/backend/rbac"
}
]
}