-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: added "Integrate Your Ecommerce Stack" recipe (#5774)
- Loading branch information
1 parent
4e9d954
commit c808317
Showing
4 changed files
with
387 additions
and
9 deletions.
There are no files selected for viewing
307 changes: 307 additions & 0 deletions
307
www/apps/docs/content/recipes/integrate-ecommerce-stack.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
|
||
<LearningPath pathName="integrate-ecommerce-stack" /> | ||
|
||
--- | ||
|
||
## 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. | ||
|
||
<DocCard item={{ | ||
type: 'link', | ||
href: '/development/services/create-service', | ||
label: 'Create a Service', | ||
customProps: { | ||
icon: Icons['academic-cap-solid'], | ||
description: 'Learn how to create a service in Medusa.', | ||
} | ||
}} /> | ||
|
||
<Details> | ||
<Summary>Example: Create a service for an ERP system</Summary> | ||
|
||
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. | ||
|
||
::: | ||
|
||
</Details> | ||
|
||
|
||
--- | ||
|
||
## 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. | ||
|
||
<DocCard item={{ | ||
type: 'link', | ||
href: '/development/workflows', | ||
label: 'Workflows', | ||
customProps: { | ||
icon: Icons['academic-cap-solid'], | ||
description: 'Learn more about Workflows and how to create them.', | ||
} | ||
}} /> | ||
|
||
<Details> | ||
<Summary>Example: Update products across systems with workflows</Summary> | ||
|
||
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<Record<string, any>>) { | ||
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. | ||
|
||
</Details> | ||
|
||
--- | ||
|
||
## 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. | ||
|
||
<DocCard item={{ | ||
type: 'link', | ||
href: '/development/api-routes/create', | ||
label: 'Create an API Route', | ||
customProps: { | ||
icon: Icons['academic-cap-solid'], | ||
description: 'Learn how to create an API Route in Medusa.', | ||
} | ||
}} /> | ||
|
||
<Details> | ||
<Summary>Example: Create a webhook listener for ERP changes</Summary> | ||
|
||
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. | ||
|
||
</Details> | ||
|
||
--- | ||
|
||
## Additional Development | ||
|
||
You can find other resources for your ecommerce development in the [Medusa Development section](../development/overview.mdx) of this documentation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
c808317
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
docs-ui – ./www/apps/ui
docs-ui-git-develop-medusajs.vercel.app
docs-ui-medusajs.vercel.app
docs-ui.vercel.app
c808317
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
api-reference – ./www/apps/api-reference
docs.medusajs.com
api-reference-git-develop-medusajs.vercel.app
api-reference-medusajs.vercel.app
api-reference-delta.vercel.app
c808317
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
medusa-docs – ./www/apps/docs
medusa-docs.vercel.app
medusa-docs-git-develop-medusajs.vercel.app
medusa-docs-medusajs.vercel.app