Files
medusa-store/www/apps/resources/app/recipes/b2b/page.mdx
T
Shahed Nasser 6713d76db3 docs: prepare configuration (#7877)
* update configuration

* resolve todos + remove events guides

* disable v2 docs in v1 navbar

* remove v2 from v1 mobile sidebar

* resolve build errors

* fix build errors

* fix lint errors

* fix lint
2024-07-03 19:27:13 +03:00

813 lines
23 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AcademicCapSolid, UsersSolid } from "@medusajs/icons"
export const metadata = {
title: `B2B Recipe`,
}
# {metadata.title}
This recipe provides the general steps to implement a B2B store with Medusa.
<Note type="soon" title="In Development">
This recipe is a work in progress, as some features are not ready yet in Medusa V2.
</Note>
## Overview
In a B2B store, you provide different types of customers with relevant pricing, products, shopping experience, and more.
Medusas commerce modules, including Sales Channel, Customer, and Pricing modules facilitate implementing this setup. Medusas architecture and extendible nature allow you to customize your store based on your use case.
<Note title="Related use-case">
[Visionary: Frictionless B2B ecommerce with Medusa](https://medusajs.com/blog/visionary/)
</Note>
---
## Create B2B Sales Channel
Use sales channels to set product availability per channel. In this case, create a B2B sales channel that includes only B2B products.
You can create a sales channel through the Medusa Admin or Admin REST APIs.
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Option 1: Use Medusa Admin",
text: "Create the sales channel using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#sales-channels_postsaleschannels",
title: "Option 2: Using the REST APIs",
text: "Create the sales channel using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Create a Publishable API Key
Use publishable API keys to specify the context of client requests:
- You associate the publishable API key with one or more sales channels.
- In a client such as a storefront, you pass the publishable API key in the header of your requests.
Then, all products retrieved belong to the associated sales channel(s).
You can create a publishable API key through the Medusa Admin or the Admin REST APIs, then associate it with the B2B sales channel. Later, you'll use this key when developing your B2B storefront.
{/* TODO add links */}
### Create Key
<CardList items={[
{
href: "#",
title: "Option 1: Use Medusa Admin",
text: "Create the publishable API key using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#api-keys_postapikeys",
title: "Option 2: Using the REST APIs",
text: "Create the publishable API key using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
### Associate Key with Sales Channel
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Option 1: Use Medusa Admin",
text: "Associate the publishable API key with the B2B sales channel using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "#",
title: "Option 2: Using the REST APIs",
text: "Associate the publishable API key with the B2B sales channel using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Add Products to B2B Sales Channel
You can create new products or add existing ones to the B2B sales channel using the Medusa Admin or Admin REST APIs.
### Create Products
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Using Medusa Admin",
text: "Create products using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#products_postproducts",
title: "Using REST APIs",
text: "Create products using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
### Add Products to Sales Channel
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Using Medusa Admin",
text: "Add the products to the B2B sales channel using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#sales-channels_postsaleschannelsidproductsbatchadd",
title: "Using REST APIs",
text: "Add the products to the B2B sales channel using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Create B2B Module with Relationship to Customer Groups
Use customer groups to organize your customers into different groups. Then, you can apply different prices for each group.
This is useful in B2B sales, as you often negotiate special prices with each customer or company.
You can create a B2B module that adds necessary data models to represent a B2B company. Then, you link that company to a customer group. Any customer belonging to that group also belongs to the company, meaning they're a B2B customer.
<Note type="soon">
Module Relationships is coming soon.
</Note>
<CardList items={[
{
href: "!docs!/basics/modules-and-services",
title: "Create Module",
text: "Learn how to create a module in Medusa.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
{
href: "!docs!/basics/data-models",
title: "Create Data Models",
text: "Learn how to create data models.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
{/* <Card
href="!docs!/advanced-development/modules/module-relationships"
title="Create Module Relationships"
text="Learn how to create a relationship between modules."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
className="mt-1"
/> */}
{/* <Details summaryContent="Example">
In this section, you'll create a B2B module that has a `Company` data model. The `Company` data model has a relationship to the `CustomerGroup` data model of the Customer Module.
Start by creating the `src/modules/b2b` directory.
Then, create the file `src/modules/b2b/models/company.ts` with the following content:
```ts title="src/modules/b2b/models/company.ts" highlights={[["8", "", "The property will be used to create a relationship to customer groups."]]}
import { model } from "@medusajs/utils"
const Company = model.define("company", {
id: model.id().primaryKey(),
name: model.text(),
city: model.text(),
country_code: model.text(),
customer_group_id: model.text().nullable(),
})
export default Company
```
This creates a `Company` data model with some relevant properties. Most importantly, it has a `customer_group_id` property. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
Next, create the migration in the file `src/modules/b2b/migrations/Migration20240516081502.ts` with the following content:
```ts title="src/modules/b2b/migrations/Migration20240516081502.ts"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240516081502 extends Migration {
async up(): Promise<void> {
this.addSql("create table if not exists \"company\" (\"id\" text not null, \"name\" text not null, \"city\" text not null, \"country_code\" text not null, \"customer_group_id\" text not null, constraint \"company_pkey\" primary key (\"id\"));")
}
async down(): Promise<void> {
this.addSql("drop table if exists \"company\" cascade;")
}
}
```
You'll run the migration to reflect the data model in the database after finishing the module definition.
Then, create the module's main service at `src/modules/b2b/service.ts` with the following content:
```ts title="src/modules/b2b/service.ts"
import { MedusaService } from "@medusajs/utils"
import Company from "./models/company"
class B2bModuleService extends MedusaService({
Company,
}){
// TODO add custom methods
}
export default B2bModuleService
```
This creates a `B2bModuleService` that extends the service factory, which generates data-management functionalities for the `Company` data model.
Next, create the module definition at `src/modules/b2b/index.ts` with the following content:
```ts title="src/modules/b2b/index.ts"
import B2bModuleService from "./service"
import { Module } from "@medusajs/utils"
export default Module("b2b", {
service: B2bModuleService,
})
```
Finally, add the module to the `modules` object in `medusa-config.js`:
```js title="medusa-config.js"
module.exports = defineConfig({
// ...
modules: {
b2bModuleService: {
resolve: "./modules/b2b",
definition: {
isQueryable: true,
},
},
},
})
```
You can now run migrations with the following commands:
```bash npm2yarn
npx medusa migrations run
```
### Add Create Company API Route
To test out using the B2B Module, you'll add an API route to create a company.
Start by creating the file `src/types/b2b/index.ts` with some helper types:
```ts title="src/types/b2b/index.ts"
import { CustomerGroupDTO } from "@medusajs/types"
export type CompanyDTO = {
id: string
name: string
city: string
country_code: string
customer_group_id?: string
customer_group?: CustomerGroupDTO
}
export type CreateCompanyDTO = {
name: string
city: string
country_code: string
customer_group_id?: string
}
```
Then, create the file `src/workflows/create-company.ts` with the following content:
export const workflowHighlights = [
["23", "tryToCreateCustomerGroupStep", "This step creates the customer group if its data is passed in the `customer_group` property."],
["36", "createCustomerGroupsWorkflow", "Use the `createCustomerGroupsWorkflow` defined by Medusa to create the customer group."],
["44", "", "Set the ID of the new customer group in the `customer_group_id` property so that it's added to the created company."],
["54", "createCompanyStep", "This step creates the company."],
]
```ts title="src/workflows/create-company.ts" highlights={workflowHighlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
import {
StepResponse,
createStep,
createWorkflow,
} from "@medusajs/workflows-sdk"
import {
createCustomerGroupsWorkflow,
} from "@medusajs/core-flows"
import { CreateCustomerGroupDTO } from "@medusajs/types"
import { CompanyDTO, CreateCompanyDTO } from "../types/b2b"
import B2bModuleService from "../modules/b2b/service"
export type CreateCompanyWorkflowInput = CreateCompanyDTO & {
customer_group?: CreateCustomerGroupDTO
}
type CreateCompanyWorkflowOutput = {
company: CompanyDTO
}
type CreateCustomerGroupStepInput = CreateCompanyWorkflowInput
const tryToCreateCustomerGroupStep = createStep(
"try-to-create-customer-group-step",
async (
{
customer_group,
...company
}: CreateCustomerGroupStepInput,
{ container }) => {
if (!customer_group) {
return new StepResponse({ company })
}
// create customer group
const { result } = await createCustomerGroupsWorkflow(
container
).run({
input: {
customersData: [customer_group],
},
})
company.customer_group_id = result[0].id
return new StepResponse({ company })
}
)
export type CreateCompanyStep = {
companyData: CreateCompanyDTO
}
const createCompanyStep = createStep(
"create-company-step",
async (
{ companyData }: CreateCompanyStep,
{ container }) => {
const b2bModuleService: B2bModuleService = container
.resolve(
"b2bModuleService"
)
const company = await b2bModuleService.createCompany(
companyData
)
return new StepResponse({ company })
}
)
export const createCompanyWorkflow = createWorkflow<
CreateCompanyWorkflowInput,
CreateCompanyWorkflowOutput
>(
"create-company",
function (input) {
const {
company: companyData,
} = tryToCreateCustomerGroupStep(input)
const company = createCompanyStep({ companyData })
return company
}
)
```
You create a workflow with two steps:
1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` property.
2. The second one creates the company.
Finally, create the file `src/api/admin/b2b/company/route.ts` with the following content:
```ts title="src/api/admin/b2b/company/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import {
CreateCompanyWorkflowInput,
createCompanyWorkflow,
} from "../../../../workflows/create-company"
type CreateCompanyReq = CreateCompanyWorkflowInput
export async function POST(
req: MedusaRequest<CreateCompanyReq>,
res: MedusaResponse
) {
const { result } = await createCompanyWorkflow(req.scope)
.run({
input: req.body,
})
res.json({
company: result.company,
})
}
```
The API route uses the workflow to create the company. It passes the request body as the workflow's input.
### Test API Route
To test the API route, start the Medusa application:
```bash npm2yarn
npm run dev
```
Next, make sure you authenticate as an admin user as explained in [this Authentication guide](!api!/api/admin#authentication).
Then, send a `POST` request to the `/admin/b2b/company` API route:
```bash
curl -X POST 'localhost:9000/admin/b2b/company' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {jwt_token}' \
--data '{
"name": "Acme",
"city": "London",
"country_code": "gb",
"customer_group": {
"name": "B2B"
}
}'
```
This creates a company and its associated customer group.
<Note title="Tip">
You can alternatively pass a `customer_group_id` to use an existing customer group.
</Note>
</Details> */}
## Add B2B Customers
After adding your B2B customer group, add B2B customers and assign them to the B2B customer group.
You can do that through the Medusa Admin or Admin REST APIs.
### Create Customers
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Option 1: Use Medusa Admin",
text: "Create the customers using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#customers_postcustomers",
title: "Option 2: Using the REST APIs",
text: "Create the customers using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
### Assign Customers to Groups
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Option 1: Use Medusa Admin",
text: "Assign the customers to the B2B customer group using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#customer-groups_postcustomergroupsidcustomersbatch",
title: "Option 2: Using the REST APIs",
text: "Assign the customers to the B2B customer group using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Create B2B Price List
Use price lists to set different prices for each B2B customer group, among other conditions.
You can create a price list using the Medusa Admin or the Admin REST APIs. Make sure to set the B2B customer group as a condition.
{/* TODO add links */}
<CardList items={[
{
href: "#",
title: "Using Medusa Admin",
text: "Create the price list using the Medusa Admin.",
startIcon: <UsersSolid />,
showLinkIcon: false,
badge: {
variant: "blue",
children: "Guide Soon"
}
},
{
href: "!api!/api/admin#price-lists_postpricelists",
title: "Using REST APIs",
text: "Create the price list using the REST APIs.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Create Custom Data Model
To implement a more advanced B2B sales flow, add custom data models such as `Company`, `Employee`, `Admin`, and `Buyer` to your B2B module.
This provides more granular control of your B2B sales and allows you to build features like privileges, limits, and more.
<Card
href="!docs!/basics/data-models"
title="Create a Data Model"
text="Learn how to create a custom data model in a module."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
/>
---
## Create an API Route to Check Customers
On the frontend clients communicating with your store, such as the storefront, you need to check whether the currently logged-in customer is a B2B customer.
The API route can check if the customer has any group with an associated company.
<Card
href="!docs!/basics/api-routes"
title="Create an API Route"
text="Learn how to create an API Route in Medusa."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
/>
{/* <Details summaryContent="Example">
For example, create the API route `src/api/store/b2b/check-customer/route.ts` with the following content:
export const checkCustomerHighlights = [
["19", "retrieveCustomer", "Retrieve the customer along with its groups."],
["26", "listCompanies", "List the companies that have a customer group ID matching any of the customer's group IDs."],
["31", "", "Return whether there are any companies associated with the customer's groups."]
]
```ts title="src/api/store/b2b/check-customer/route.ts" highlights={checkCustomerHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
import type {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import { ModuleRegistrationName } from "@medusajs/utils"
import { ICustomerModuleService } from "@medusajs/types"
import B2bModuleService from "../../../../modules/b2b/service"
export async function GET(
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) {
const customerModuleService: ICustomerModuleService = req
.scope.resolve(ModuleRegistrationName.CUSTOMER)
const b2bModuleService: B2bModuleService = req.scope.resolve(
"b2bModuleService"
)
const customer = await customerModuleService.retrieveCustomer(
req.auth_context.actor_id,
{
relations: ["groups"],
}
)
const companies = await b2bModuleService.listCompanies({
customer_group_id: customer.groups.map((group) => group.id),
})
res.json({
is_b2b: companies.length > 0,
})
}
```
This creates a `GET` API Route at `/store/b2b/check-customer` that:
1. Retrieves the customer along with its groups using the Customer Module's main service.
2. Lists the companies that have a customer group ID matching any of the customer's group IDs.
3. Return an `is_b2b` field whose value is `true` if there are any companies associated with the customer's groups.
Before using the API route, create the file `src/api/middlewares.ts` with the following content:
```ts title="src/api/middlewares.ts"
import {
MiddlewaresConfig,
authenticate,
} from "@medusajs/medusa"
export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/store/b2b*",
middlewares: [
authenticate("store", ["bearer", "session"]),
],
},
],
}
```
This ensures that only logged-in customers can use the API route.
### Test API Route
To test out the API route:
1. Start the Medusa application.
2. Obtain an authentication JWT token for a new customer. Do that by sending a `POST` request to the `/auth/store/emailpass` API Route:
```bash
curl -X POST 'http://localhost:9000/auth/store/emailpass' \
-H 'Content-Type: application/json' \
--data-raw '{
"email": "test@medusajs.com",
"password": "supersecret"
}'
```
3. Send a `POST` request to the `/store/customers` API route that registers the customer. Make sure to pass the authentication JWT token from the previous token in the header:
```bash
curl -X POST 'http://localhost:9000/store/customers' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {jwt_token}' \
--data-raw '{
"email": "test@medusajs.com",
"password": "supersecret"
}'
```
4. Add the customer to the B2B group as explained in a [previous section](#add-b2b-customers).
5. Send a `GET` request to the `/store/b2b/check-customer` API route you created in this section:
```bash
curl 'http://localhost:9000/store/b2b/check-customer' \
--header 'Authorization: Bearer {jwt_token}'
```
You'll receive a JSON response as the following:
```json
{
"is_b2b": true
}
```
</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, new pages, and setting pages.
<CardList items={[
{
href: "!docs!/advanced-development/admin/widgets",
title: "Create Admin Widget",
text: "Learn how to add widgets into existing admin pages.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
{
href: "!docs!/advanced-development/admin/ui-routes",
title: "Create Admin UI Routes",
text: "Learn how to add new pages to your Medusa Admin.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
{
href: "!docs!/advanced-development/admin/setting-pages",
title: "Create Admin Setting Page",
text: "Learn how to add new page to the Medusa Admin settings.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
---
## Customize Storefront
Medusa provides a Next.js storefront to use with your application. You can either customize it or build your own to represent your B2B features.
Use the publishable API key you associated with your B2B sales channel in the storefront to ensure only B2B products are retrieved.
<CardList items={[
{
href: "/nextjs-starter",
title: "Next.js Storefront",
text: "Learn how to install and customize the Next.js storefront.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
{
href: "/storefront-development",
title: "Storefront Development",
text: "Find guides for your storefront development.",
startIcon: <AcademicCapSolid />,
showLinkIcon: false
},
]} />
<Card
href="!api!/api/store#publishable-api-key"
title="Use Publishable API Keys"
text="Learn how to use the publishable API key in client requests."
startIcon={<AcademicCapSolid />}
showLinkIcon={false}
className="mt-1"
/>