diff --git a/www/apps/docs/content/recipes/integrate-ecommerce-stack.mdx b/www/apps/docs/content/recipes/integrate-ecommerce-stack.mdx new file mode 100644 index 0000000000000..8e1d65f497103 --- /dev/null +++ b/www/apps/docs/content/recipes/integrate-ecommerce-stack.mdx @@ -0,0 +1,307 @@ +--- +addHowToData: true +--- + +import DocCard from '@theme/DocCard'; +import Icons from '@theme/Icon'; +import LearningPath from '@site/src/components/LearningPath'; + +# Integrate Ecommerce Stack Recipe + +This document guides you through integrating systems in your ecommerce stack with Medusa. + +## Overview + +Integrating third-party systems, such as ERP or a CMS, into your ecommerce stack can be challenging. It requires: + +- Establishing connections with the different systems based on each of their APIs. +- Building flows that span across multiple systems. +- Maintaining data consistency and syncing between your systems. + +Medusa’s architecture and functionalities allow you to integrate third-party systems and build flows around them. It also provides error-handling mechanisms and webhook capabilities that prevent data inconsistency between your systems. + + + +--- + +## Connect to External Systems with Services + +Medusa’s Services let you implement a client that connects and performs functionalities with your third-party system. You can then use the service to connect to your third-party system in other resources, such as a Workflow or an API Route. + +This increases the maintainability of your integration as it’s all implemented within the service. If the external system makes any changes to its APIs, you only need to make changes within the service. + + + +
+ Example: Create a service for an ERP system + + Here’s an example of a service created for an external ERP system: + + ```ts title="src/services/erp.ts" + import { ProductService, TransactionBaseService } from "@medusajs/medusa"; + import { MedusaError } from "@medusajs/utils" + import axios, { AxiosInstance } from "axios" + + type InjectedDependencies = { + productService: ProductService + } + + class ErpService extends TransactionBaseService { + private client_: AxiosInstance + private productService_: ProductService + + constructor(container: InjectedDependencies) { + super(container) + + this.productService_ = container.productService + this.client_ = axios.create({ + baseURL: `https://api.erp-example.com`, + headers: { + Authorization: `Bearer ${process.env.ERP_TOKEN}` + } + }) + } + + async getProductERPDetails (id: string) { + const product = await this.productService_.retrieve(id, { + select: ["external_id"] + }) + + if (!product) { + throw new MedusaError(MedusaError.Types.NOT_FOUND, `Product with id ${id} was not found.`) + } + + const erpProduct = await this.client_.get(`/product/${product.external_id}`) + + return erpProduct + } + + async updateProduct(product: any) { + // do stuff + } + } + + export default ErpService + ``` + + In this example, you create an `ErpService` that is used to interact with the third-party system. In the service, you create a client using [axios](https://axios-http.com/) to send requests to the ERP system. If the system you’re integrating provides a JavaScript SDK, you can use that instead. + + Then, in the service, you create methods for each ERP functionality. For example, the `getProductErpDetails` method retrieves the details of a product in the ERP system. + +:::tip + +Products have an `external_id` attribute that can be used to store the product's ID in an external system. + +::: + +
+ + +--- + +## Build Flows Across Systems + +With Medusa’s workflows, you can build flows with steps that may perform actions on different systems. For example, you can create a workflow that updates the product’s details in integrated systems like ERPs, WMSs, and CMSs. + +Workflows can be executed from anywhere. So, taking the workflow described in the above example, you can listen to the `product.updated` event using a [Subscriber](../development/events/subscribers.mdx) and execute the workflow whenever the event is triggered. + +![A flowchart of how the workflow is executed when the product.updated event is triggered](https://res.cloudinary.com/dza7lstvk/image/upload/v1701340569/Medusa%20Docs/Diagrams/workflow-recipe-example_wmwmo5.jpg) + +Workflows guarantee data consistency through their compensation feature. You can provide a compensation function to steps that roll back the actions of that step. Then, if an error occurs in any step, the actions of previous steps are rolled back using the compensation function. + + + +
+ Example: Update products across systems with workflows + + For example, create the following workflow in `src/workflows/update-product.ts`: + + ```ts title="src/workflows/update-product.ts" + import { Product, ProductService } from "@medusajs/medusa" + import { + createStep, + StepResponse, + createWorkflow + } from "@medusajs/workflows-sdk" + import ErpService from "../services/erp" + + type WorkflowInput = { + productId: string + } + + const updateInErpStep = createStep("update-in-erp", async ({ productId }: WorkflowInput, context) => { + const erpService: ErpService = context.container.resolve("erpService") + const productService: ProductService = context.container.resolve("productService") + + const updatedProductData = await productService.retrieve(productId) + + const oldProductData = await erpService.getProductERPDetails(productId) + + await erpService.updateProduct(updatedProductData) + + return new StepResponse({}, { + // provide the old product data as a parameter + // to the compensation function in case an error + // occurs. + oldProductData + }) + }, async ({ + oldProductData + }, context) => { + const erpService: ErpService = context.container.resolve("erpService") + + await erpService.updateProduct(oldProductData) + }) + + const updateInCmsStep = createStep("update-in-cms", async({ productId }: WorkflowInput, context) => { + // update product in CMS... + }) + + const updateInWmsStep = createStep("update-in-wms", async({ productId }: WorkflowInput, context) => { + // update product in WMS... + }) + + const updateProductWorkflow = createWorkflow< + WorkflowInput, void + >("update-product-in-systems", + function (input) { + updateInErpStep(input) + + updateInCmsStep(input) + + updateInWmsStep(input) + }) + + export default updateProductWorkflow + ``` + + In this workflow, you create three steps: one to update the product in an ERP, another in a CMS, and another in a WMS. + + In the `update-in-erp` step, you resolve the ERP’s service that you created and use it to update the product in the ERP. You also provide a compensation function, which is passed as a third parameter to the `createStep` function. The compensation function reverts the update in the ERP system in case an error occurs. + + You then create the workflow `update-product-in-systems` which uses the three steps in its constructor function. The workflow’s constructor function doesn’t run until it’s executed. + + Then, create the subscriber at `src/subscribers/update-product.ts`: + + ```ts title="src/subscribers/update-product.ts" + import { + type SubscriberConfig, + type SubscriberArgs, + ProductService, + } from "@medusajs/medusa" + import updateProductWorkflow from "../workflows/update-product" + + export default async function handleProductUpdate({ + data, eventName, container, pluginOptions + }: SubscriberArgs>) { + updateProductWorkflow(container) + .run({ + input: { + productId: data.id + } + }) + .then(() => { + console.log("Updated product across systems.") + }) + } + + export const config: SubscriberConfig = { + event: ProductService.Events.UPDATED, + context: { + subscriberId: "product-update" + } + } + ``` + + The subscriber executes the workflow whenever the `product.updated` event is triggered, passing it the ID of the updated product. + +
+ +--- + +## Create Webhook Listeners + +You can provide webhook listeners that your external systems call when their data is updated. This lets you synchronize data between your systems. Webhook listeners can be created in Medusa using API Routes. + +For example, suppose an administrator changes the product data in the ERP system. In that case, the system sends a request to the webhook you define in Medusa, which updates the product data in Medusa. + + + +
+ Example: Create a webhook listener for ERP changes + + For example, create the file `src/api/webhooks/erp/update/route.ts` with the following content: + + ```ts title="src/api/webhooks/erp/update/route.ts" + import { + MedusaRequest, + MedusaResponse, + ProductService + } from "@medusajs/medusa" + + export const POST = async (req: MedusaRequest, res: MedusaResponse) => { + const { id, updatedData} = req.body + + const productService: ProductService = req.scope.resolve( + "productService" + ) + + await productService.update(id, updatedData) + + res.status(200) + } + ``` + + This creates a webhook listener for an ERP system. It receives the ID of a product and its updated data, assuming that’s how your ERP system sends the data. + + Then, create the file `src/api/middlewares.ts` with the following content: + + ```ts title="src/api/middlewares.ts" + import { MiddlewaresConfig } from "@medusajs/medusa" + import { raw } from "body-parser" + + export const config: MiddlewaresConfig = { + routes: [ + { + method: ["POST", "PUT"], + matcher: "/webhooks/*", + bodyParser: false, + middlewares: [raw({ type: "application/json" })], + }, + ], + } + ``` + + This replaces the default JSON middleware with the raw middleware, which is useful for webhook routes. + +
+ +--- + +## Additional Development + +You can find other resources for your ecommerce development in the [Medusa Development section](../development/overview.mdx) of this documentation. \ No newline at end of file diff --git a/www/apps/docs/sidebars.js b/www/apps/docs/sidebars.js index d1be2e6bcbdb8..8dc2ee1ed1c33 100644 --- a/www/apps/docs/sidebars.js +++ b/www/apps/docs/sidebars.js @@ -73,6 +73,14 @@ module.exports = { iconName: "credit-card-solid", }, }, + { + type: "doc", + id: "recipes/integrate-ecommerce-stack", + label: "Integrate Ecommerce Stack", + customProps: { + iconName: "puzzle-solid", + }, + }, { type: "doc", id: "recipes/commerce-automation", diff --git a/www/apps/docs/src/components/LearningPath/Steps/index.tsx b/www/apps/docs/src/components/LearningPath/Steps/index.tsx index 60f45c1d3e9a6..deae2bf5e9aa7 100644 --- a/www/apps/docs/src/components/LearningPath/Steps/index.tsx +++ b/www/apps/docs/src/components/LearningPath/Steps/index.tsx @@ -78,7 +78,7 @@ const LearningPathSteps: React.FC = ({ ...rest }) => { )} key={index} > -
+
{index === currentStep && ( = ({ ...rest }) => { > {step.title} + { + e.preventDefault() + goToStep(index) + }} + />
{index === currentStep && (
@@ -113,14 +121,6 @@ const LearningPathSteps: React.FC = ({ ...rest }) => {
)} - { - e.preventDefault() - goToStep(index) - }} - />
))} diff --git a/www/apps/docs/src/utils/learning-paths.tsx b/www/apps/docs/src/utils/learning-paths.tsx index 975b32a6d398f..77ee8263d8f65 100644 --- a/www/apps/docs/src/utils/learning-paths.tsx +++ b/www/apps/docs/src/utils/learning-paths.tsx @@ -449,6 +449,69 @@ const paths: LearningPathType[] = [ }, }, }, + { + name: "integrate-ecommerce-stack", + label: "Integrate Ecommerce Stack", + description: + "Use Medusa’s architecture and functionalities to integrate third-party systems and build flows around them.", + steps: [ + { + title: "Connect to External Systems with Services", + path: "/development/services/create-service", + descriptionJSX: ( + <> + Medusa’s Services let you implement a client that connects and + performs functionalities with your third-party system. +
+
+ You can then use the service to connect to your third-party system + in other resources, such as a Workflow or an API Route. + + ), + }, + { + title: "Build Flows Across Systems", + path: "/development/workflows", + descriptionJSX: ( + <> + With Medusa’s workflows, you can build flows with steps that may + perform actions on different systems. Workflows can be executed from + anywhere. +
+
+ For example, you can create a workflow that updates the product’s + details in integrated systems like ERPs, WMSs, and CMSs. Then, you + can listen to the + product.updated event using a{" "} + Subscriber{" "} + and execute the workflow whenever the event is triggered. + + ), + }, + { + title: "Create Webhook Listeners", + path: "/development/api-routes/create", + descriptionJSX: ( + <> + You can provide webhook listeners that your external systems call + when their data is updated. This lets you synchronize data between + your systems. +
+
+ Webhook listeners can be created in Medusa using API Routes. + + ), + }, + ], + finish: { + type: "rating", + step: { + title: "Congratulations on integrating your ecommerce stack!", + description: "Please rate your experience using this recipe.", + eventName: "rating_path_integrate-ecommerce-stack", + }, + }, + }, // TODO: Eventually remove these learning paths { name: "rbac",