diff --git a/www/apps/resources/app/recipes/b2b/page.mdx b/www/apps/resources/app/recipes/b2b/page.mdx
index 66ae087b56..9726234407 100644
--- a/www/apps/resources/app/recipes/b2b/page.mdx
+++ b/www/apps/resources/app/recipes/b2b/page.mdx
@@ -1,14 +1,19 @@
import { AcademicCapSolid, UsersSolid } from "@medusajs/icons"
-import { BetaBadge } from "docs-ui"
export const metadata = {
title: `B2B Recipe`,
}
-# {metadata.title}
+# {metadata.title}
This recipe provides the general steps to implement a B2B store with Medusa.
+
+
+Medusa has a ready-to-use B2B starter that you install and use in [this GitHub repository](https://github.com/medusajs/b2b-starter-medusa).
+
+
+
## Overview
In a B2B store, you provide different types of customers with relevant pricing, products, shopping experience, and more.
@@ -29,26 +34,12 @@ Use sales channels to set product availability per channel. In this case, create
You can create a sales channel through the Medusa Admin or Admin REST APIs.
-{/* TODO add links */}
-
-
+
---
@@ -63,51 +54,23 @@ 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
-
+
### Associate Key with Sales Channel
-{/* TODO add links */}
-
-
+
---
@@ -117,49 +80,21 @@ You can create new products or add existing ones to the B2B sales channel using
### Create Products
-{/* TODO add links */}
-
-
+
### Add Products to Sales Channel
-{/* TODO add links */}
-
-
+
---
@@ -169,7 +104,7 @@ Use customer groups to organize your customers into different groups. Then, you
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.
+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, which is defined in the Customer Module. Any customer belonging to that group also belongs to the company, meaning they're a B2B customer.
-{/* */}
-
-{/*
- 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/framework/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 {
- 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 {
- 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/framework/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/framework/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 db:migrate
- ```
-
- ### 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/framework/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/framework/workflows-sdk"
- import {
- createCustomerGroupsWorkflow,
- } from "@medusajs/medusa/core-flows"
- import { CreateCustomerGroupDTO } from "@medusajs/framework/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,
- 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!/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.
-
-
-
- You can alternatively pass a `customer_group_id` to use an existing customer group.
-
-
-
- */}
+/>
## Add B2B Customers
@@ -491,49 +137,21 @@ You can do that through the Medusa Admin or Admin REST APIs.
### Create Customers
-{/* TODO add links */}
-
-
+
### Assign Customers to Groups
-{/* TODO add links */}
-
-
+
---
@@ -543,26 +161,12 @@ Use price lists to set different prices for each B2B customer group, among other
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 */}
-
-
+
---
@@ -594,126 +198,6 @@ The API route can check if the customer has any group with an associated company
icon={AcademicCapSolid}
/>
-{/*
-
- 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 { Modules } from "@medusajs/framework/utils"
- import { ICustomerModuleService } from "@medusajs/framework/types"
- import B2bModuleService from "../../../../modules/b2b/service"
-
- export async function GET(
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
- ) {
- const customerModuleService: ICustomerModuleService = req
- .scope.resolve(Modules.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
- }
- ```
-
- */}
-
---
## Customize Admin
@@ -726,23 +210,25 @@ The Medusa Admin plugin can be extended to add widgets, new pages, and setting p
{
href: "!docs!/learn/fundamentals/admin/widgets",
title: "Create Admin Widget",
- text: "Learn how to add widgets into existing admin pages.",
+ text: "Add widgets into existing admin pages.",
icon: AcademicCapSolid,
},
{
href: "!docs!/learn/fundamentals/admin/ui-routes",
title: "Create Admin UI Routes",
- text: "Learn how to add new pages to your Medusa Admin.",
- icon: AcademicCapSolid,
- },
- {
- href: "!docs!/learn/fundamentals/admin/ui-routes#create-settings-page",
- title: "Create Admin Setting Page",
- text: "Learn how to add new page to the Medusa Admin settings.",
+ text: "Add new pages to your Medusa Admin.",
icon: AcademicCapSolid,
},
]} />
+
+
---
## Customize Storefront
diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs
index 2f0b70aa76..f475cebc92 100644
--- a/www/apps/resources/generated/edit-dates.mjs
+++ b/www/apps/resources/generated/edit-dates.mjs
@@ -110,7 +110,7 @@ export const generatedEditDates = {
"app/medusa-container-resources/page.mdx": "2025-01-06T11:19:35.623Z",
"app/medusa-workflows-reference/page.mdx": "2025-01-20T08:21:29.962Z",
"app/nextjs-starter/page.mdx": "2025-01-06T12:19:31.143Z",
- "app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z",
+ "app/recipes/b2b/page.mdx": "2025-01-29T11:35:23.247Z",
"app/recipes/commerce-automation/page.mdx": "2024-10-16T08:52:01.585Z",
"app/recipes/digital-products/examples/standard/page.mdx": "2025-01-13T11:31:35.362Z",
"app/recipes/digital-products/page.mdx": "2025-01-06T11:19:35.623Z",