docs: documentation for v1.18 (#5652)

* docs: documentation for v.17.5

* fix links

* updated version number
This commit is contained in:
Shahed Nasser
2023-11-21 10:57:11 +02:00
committed by GitHub
parent adc60e519c
commit 9c7f95c3d5
36 changed files with 1499 additions and 1336 deletions

View File

@@ -135,9 +135,9 @@ export const GET = async (
You can learn more about API Route [here](../api-routes/overview.mdx).
### Services and Subscribers
### Services
As custom repositories are registered in the [dependency container](../fundamentals/dependency-injection.md#dependency-container-and-injection), they can be accessed through dependency injection in the constructor of a service or a subscriber.
As custom repositories are registered in the [dependency container](../fundamentals/dependency-injection.md#dependency-container-and-injection), they can be accessed through dependency injection in the constructor of a service.
For example:
@@ -167,6 +167,32 @@ class PostService extends TransactionBaseService {
You can learn more about services [here](../services/overview.mdx).
### Subscribers
A subscriber handler function can resolve a repository using the `container` property of its parameter. The `container` has a method `resolve` which accepts the registration name of the repository as a parameter.
For example:
```ts title=src/subscribers/post-handler.ts
import {
type SubscriberArgs,
} from "@medusajs/medusa"
import { PostRepository } from "../repositories/post"
export default async function postHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs) {
const postRepository: PostRepository = container.resolve(
"postRepository"
)
// ...
}
// ...
```
### Other Resources
Resources that have access to the dependency container can access repositories just like any other resources. You can learn more about the dependency container and dependency injection in [this documentation](../fundamentals/dependency-injection.md).

View File

@@ -62,9 +62,9 @@ In the class you must implement the `emit` method. You can optionally implement
### Note About the eventToSubscribersMap Property
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler methods are managed within a class property `eventToSubscribersMap`, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler methods.
The `AbstractEventBusModuleService` implements two methods for handling subscription: `subscribe` and `unsubscribe`. In these methods, the subscribed handler functions are managed within a class property `eventToSubscribersMap`, which is a JavaScript Map. The map's keys are the event names, whereas the value of each key is an array of subscribed handler functions.
In your custom implementation, you can use this property to manage the subscribed handler methods. For example, you can get the subscribers of a method using the `get` method of the map:
In your custom implementation, you can use this property to manage the subscribed handler functions. For example, you can get the subscribers of a method using the `get` method of the map:
```ts
const eventSubscribers =
@@ -144,13 +144,13 @@ class CustomEventBus extends AbstractEventBusModuleService {
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom subscribe logic if necessary.
The `subscribe` method attaches a handler method to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
The `subscribe` method attaches a handler function to the specified event, which is run when the event is triggered. It is typically used inside a subscriber class.
The `subscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is subscribing to.
1. The first parameter `eventName` is a required string. It indicates which event the handler function is subscribing to.
2. The second parameter `subscriber` is a required function that performs an action when the event is triggered.
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler methods when retrying a failed method. Its also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
3. The third parameter `context` is an optional object that has the property `subscriberId`. Subscriber IDs are useful to differentiate between handler functions when retrying a failed method. Its also useful for unsubscribing an event handler. Note that if you must implement the mechanism around assigning IDs to subscribers when you override the `subscribe` method.
The implementation of this method depends on the service youre using for the event bus:
@@ -170,11 +170,11 @@ class CustomEventBus extends AbstractEventBusModuleService {
As mentioned earlier, this method is already implemented in the `AbstractEventBusModuleService` class. This section explains how you can implement your custom unsubscribe logic if necessary.
The `unsubscribe` method is used to unsubscribe a handler method from an event.
The `unsubscribe` method is used to unsubscribe a handler function from an event.
The `unsubscribe` method accepts three parameters:
1. The first parameter `eventName` is a required string. It indicates which event the handler method is unsubscribing from.
1. The first parameter `eventName` is a required string. It indicates which event the handler function is unsubscribing from.
2. The second parameter `subscriber` is a required function that was initially subscribed to the event.
3. The third parameter `context` is an optional object that has the property `subscriberId`. It can be used to specify the ID of the subscriber to unsubscribe.

View File

@@ -0,0 +1,140 @@
---
description: 'Learn how to create a subscriber in Medusa. You can use subscribers to implement functionalities like sending an order confirmation email.'
addHowToData: true
---
# (Deprecated) How to Create a Subscriber
In this document, youll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
:::note
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create-subscriber.md) instead.
:::
## Implementation
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convention, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscribers constructor.
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/order-notifier.ts`:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
handleOrder = async (data) => {
console.log("New Order: " + data.id)
}
}
export default OrderNotifierSubscriber
```
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the orders details to perform any kind of task you need.
:::tip
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
:::
### Subscriber ID
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
An example of using the subscribe method with the third parameter:
```ts
eventBusService.subscribe("order.placed", this.handleOrder, {
subscriberId: "my-unique-subscriber",
})
```
---
## Retrieve Medusa Configurations
Within your subscriber, you may need to access the Medusa configuration exported from `medusa-config.js`. To do that, you can access `configModule` using dependency injection.
For example:
```ts
import { ConfigModule, EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
configModule: ConfigModule
}
class OrderNotifierSubscriber {
protected readonly configModule_: ConfigModule
constructor({
eventBusService,
configModule,
}: InjectedDependencies) {
this.configModule_ = configModule
eventBusService.subscribe("order.placed", this.handleOrder)
}
// ...
}
export default OrderNotifierSubscriber
```
---
## Using Services in Subscribers
You can access any service through the dependencies injected to your subscribers constructor.
For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ productService, eventBusService }) {
this.productService = productService
eventBusService.subscribe(
"order.placed",
this.handleOrder
)
}
// ...
}
```
You can then use `this.productService` anywhere in your subscribers methods. For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
// ...
handleOrder = async (data) => {
// ...
const product = this.productService.list()
}
}
```
:::note
When using attributes defined in the subscriber, such as the `productService` in the example above, you must use an arrow function to declare the method. Otherwise, the attribute will be undefined when used.
:::
---
## See Also
- [Example: send order confirmation email](../../modules/orders/backend/send-order-confirmation.md)
- [Example: send registration confirmation email](../../modules/customers/backend/send-confirmation.md)
- [Create a Plugin](../plugins/create.mdx)

View File

@@ -7,51 +7,98 @@ addHowToData: true
In this document, youll learn how to create a [Subscriber](./subscribers.mdx) in Medusa that listens to events to perform an action.
## Implementation
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. Its file name, by convention, should be the class name of the subscriber without the word `Subscriber`. For example, if the subscriber is `HelloSubscriber`, the file name should be `hello.ts`.
After creating the file under `src/subscribers`, in the constructor of your subscriber, listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscribers constructor.
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class created in `src/subscribers/order-notifier.ts`:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
handleOrder = async (data) => {
console.log("New Order: " + data.id)
}
}
export default OrderNotifierSubscriber
```
This subscriber registers the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed. It receives the order ID in the `data` parameter. You can then use the orders details to perform any kind of task you need.
:::tip
For the `order.placed` event, the `data` object won't contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
v1.18 of `@medusajs/medusa` introduced a new approach to create a subscriber. If you're looking for the old guide, you can find it [here](./create-subscriber-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
:::
### Subscriber ID
## Implementation
The `subscribe` method of the `eventBusService` accepts a third optional parameter which is a context object. This object has a property `subscriberId` with its value being a string. This ID is useful when there is more than one handler method attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler methods when retrying a failed one.
If a subscriber ID is not passed on subscription, all handler methods are run again. This can lead to data inconsistencies or general unwanted behavior in your system. On the other hand, if you want all handler methods to run again when one of them fails, you can omit passing a subscriber ID.
A subscriber is a TypeScript or JavaScript file that is created under `src/subscribers`. It can be created under subdirectories of `src/subscribers` as well. For example, you can place all subscribers to product events under the `src/subscribers/products` directory.
An example of using the subscribe method with the third parameter:
The subscriber file exports a default handler function, and the subscriber's configurations.
```ts
eventBusService.subscribe("order.placed", this.handleOrder, {
subscriberId: "my-unique-subscriber",
})
For example:
```ts title=src/subscribers/product-update-handler.ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, any>>) {
const productService: ProductService = container.resolve(
"productService"
)
const { id } = data
const product = await productService.retrieve(id)
// do something with the product...
}
export const config: SubscriberConfig = {
event: ProductService.Events.UPDATED,
context: {
subscriberId: "product-update-handler",
},
}
```
### Subscriber Configuration
The exported configuration object of type `SubscriberConfig` must include the following properties:
- `event`: A string or an array of strings, each being the name of the event that the subscriber handler function listens to.
- `context`: An object that defines the context of the subscriber. It can accept any properties along with the `subscriberId` property. Learn more about the subscriber ID and the context object in [this section](#context-with-subscriber-id).
### Subscriber Handler Function
The default-export of the subscriber file is a handler function that is executed when the events specified in the exported configuration is triggerd.
The function accepts a parameter of type `SubscriberArgs`, which has the following properties:
- `data`: The data payload of the emitted event. Its type is different for each event. So, make sure to check the [events reference](./events-list.md) for the expected payload of the events your subscriber listens to. You can then pass the expected payload type as a type parameter to `SubscriberArgs`, for example, `Record<string, string>`.
- `eventName`: A string indicating the name of the event. This is useful if your subscriber listens to more than one event and you want to differentiate between them.
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
- `pluginOptions`: When the subscriber is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
---
## Context with Subscriber ID
The `context` property of the subscriber configuration object is passed to the `eventBusService`. You can pass the `subscriberId` and any custom data in it.
:::note
The subscriber ID is useful when there is more than one handler function attached to a single event or if you have multiple Medusa backends running. This allows the events bus service to differentiate between handler functions when retrying a failed one, avoiding retrying all subscribers which can lead to data inconsistencies or general unwanted behavior in your system.
:::
### Inferred Subscriber ID
If you don't pass a subscriber ID to the subscriber configurations, the name of the subscriber function is used as the subscriber ID. If the subscriber function is an anonymous function, the name of the subscriber file is used instead.
---
## Caveats for Local Event Bus
If you use the `event-bus-local` as your event bus sevice, note the following:
- The `subscriberId` passed in the context is overwritten to a random ID when using `event-bus-local`. So, setting the subscriber ID in the context won't have any effect in this case.
- The `eventName` passed to the handler function will be `undefined` when using `event-bus-local` as it doesn't pass the event name properly.
:::note
While the local event bus is a good option for development, it's highly recommended to use the [Redis Event Module](./modules/redis.md) in production.
:::
---
## Retrieve Medusa Configurations
@@ -60,71 +107,32 @@ Within your subscriber, you may need to access the Medusa configuration exported
For example:
```ts
import { ConfigModule, EventBusService } from "@medusajs/medusa"
```ts title=src/subscribers/product-update-handler.ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
type ConfigModule,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
configModule: ConfigModule
}
class OrderNotifierSubscriber {
protected readonly configModule_: ConfigModule
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs) {
const configModule: ConfigModule = container.resolve(
"configModule"
)
constructor({
eventBusService,
configModule,
}: InjectedDependencies) {
this.configModule_ = configModule
eventBusService.subscribe("order.placed", this.handleOrder)
}
// ...
}
export default OrderNotifierSubscriber
```
---
## Using Services in Subscribers
You can access any service through the dependencies injected to your subscribers constructor.
For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
constructor({ productService, eventBusService }) {
this.productService = productService
eventBusService.subscribe(
"order.placed",
this.handleOrder
)
}
// ...
export const config: SubscriberConfig = {
event: ProductService.Events.UPDATED,
context: {
subscriberId: "product-update-handler",
},
}
```
You can then use `this.productService` anywhere in your subscribers methods. For example:
```ts title=src/subscribers/order-notifier.ts
class OrderNotifierSubscriber {
// ...
handleOrder = async (data) => {
// ...
const product = this.productService.list()
}
}
```
:::note
When using attributes defined in the subscriber, such as the `productService` in the example above, you must use an arrow function to declare the method. Otherwise, the attribute will be undefined when used.
:::
---
## See Also

View File

@@ -4,7 +4,7 @@ description: 'Learn about the available events and their data payloads in Medusa
# Events Reference
This document details all events in Medusa, when they are triggered, and what data your handler method will receive when the event is triggered.
This document details all events in Medusa, when they are triggered, and what data your handler function will receive when the event is triggered.
## Prerequisites

View File

@@ -15,9 +15,9 @@ Subscribers register handlers for an events and allows you to perform an action
Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.
Custom subscribers are TypeScript or JavaScript files in your project's `src/subscribers` directory. Files here should export classes, which will be treated as subscribers by Medusa. By convention, the class name should end with `Subscriber` and the file name should be the camel-case version of the class name without `Subscriber`. For example, the `WelcomeSubscriber` class is in the file `src/subscribers/welcome.ts`.
Custom subscribers are TypeScript or JavaScript files in your project's `src/subscribers` directory. Subscriber files must default export a handler function and export a configuration object.
Whenever an event is emitted, the subscribers registered handler method is executed. The handler method receives as a parameter an object that holds data related to the event. For example, if an order is placed the `order.placed` event will be emitted and all the handlers will receive the order id in the parameter object.
Whenever an event is emitted, the subscribers handler function is executed. The handler function receives as a parameter an object that includes the data payload, among other parameters. For example, if an order is placed, the `order.placed` event is emitted and all the handlers receive the order ID in the `data` object.
### Example Use Cases

View File

@@ -20,7 +20,7 @@ The backend doesn't have any tightly-coupled frontend. Instead, it exposes [API
Medusa also uses an [Events Architecture](../events/index.mdx) to trigger and handle events. Events are triggered when a specific action occurs, such as when an order is placed. To manage this events system, Medusa connects to a service that implements a pub/sub model, such as [Redis](https://redis.io/).
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript classes that add their methods as handlers for specific events. These handler methods are only executed when an event is triggered.
Events can be handled using [Subscribers](../events/subscribers.mdx). Subscribers are TypeScript or JavaScript files that export a handler function that's executed asynchronously when an event is triggered, and a configuration object defining what events the subscriber listens to.
You can create any of the resources in the backends architecture, such as entities, API Routes, services, and more, as part of your custom development without directly modifying the backend itself. The Medusa backend uses [loaders](../loaders/overview.mdx) to load the backends resources, as well as your custom resources and resources in [Plugins](../plugins/overview.mdx).

View File

@@ -704,14 +704,42 @@ To resolve resources, such as services, in API Routes, use the `MedusaRequest` o
For example:
```ts
const logger = req.scope.resolve("logger")
const productService: ProductService = req.scope.resolve(
"productService"
)
```
Please note that in API Routes some resources, such as repositories, aren't available. Refer to the [repositories](../entities/repositories.md#api-routes) documentation to learn how you can load them.
### In Subscribers
To resolve resources, such as services, in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of a resource as a parameter.
For example:
```ts
import {
ProductService,
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
export default async function productUpdateHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const productService: ProductService = container.resolve(
"productService"
)
// ...
}
// ...
```
### In Classes
In classes such as services, strategies, or subscribers, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
In classes such as services or strategies, you can load resources in the constructor function using dependency injection. The constructor receives an object of dependencies as a first parameter. Each dependency in the object should use the registration name of the resource that should be injected to the class.
For example:

View File

@@ -23,14 +23,14 @@ The loader file must export a function.
When the loader is defined in the Medusa backend or a plugin, the function receives the following parameters:
1. `container`: the first parameter, which is a `AwilixContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
2. `config`: the second parameter, which is an object that holds the loaders plugin options. If the loader is not created inside a plugin, the config object will be empty.
### Parameters of Loaders in Modules
When the loader is defined in a module, it receives the following parameters:
1. `container`: the first parameter, which is a `AwilixContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
1. `container`: the first parameter, which is a `MedusaContainer` object. You can use the container to register custom resources into the dependency container or resolve resources from the dependency container.
2. `logger`: the second parameter, which is a `Logger` object. The logger can be used to log messages in the console.
3. `config`: the third parameter, which is an object that holds the loaders module options.
@@ -39,11 +39,14 @@ When the loader is defined in a module, it receives the following parameters:
For example, this loader function resolves the `ProductService` and logs in the console the count of products in the Medusa backend:
```ts title=src/loaders/my-loader.ts
import { ProductService, ConfigModule } from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
import {
ProductService,
ConfigModule,
MedusaContainer,
} from "@medusajs/medusa"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
console.info("Starting loader...")

View File

@@ -21,11 +21,11 @@ import {
ProductService,
ConfigModule,
Logger,
MedusaContainer,
} from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
const logger = container.resolve<Logger>("logger")
@@ -90,11 +90,11 @@ import {
ProductService,
ConfigModule,
Logger,
MedusaContainer,
} from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
const logger = container.resolve<Logger>("logger")

View File

@@ -77,7 +77,7 @@ Notification Provider Services must have a static property `identifier`.
The `NotificationProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the Service class is used when the Notification Provider is created in the database.
The value of this property is also used later when you want to subscribe the Notification Provider to events in a Subscriber.
The value of this property is also used later when you want to subscribe the Notification Provider to events in a Loader.
For example, in the class you created in the previous code snippet you can add the following property:
@@ -257,39 +257,33 @@ The `to` and `data` properties are used in the `NotificationService` in Medusa
---
## Create a Subscriber
## Subscribe with Loaders
After creating your Notification Provider Service, you must create a Subscriber that registers this Service as a notification handler of events.
After creating your Notification Provider Service, you must create a [Loader](../loaders/overview.mdx) that registers this Service as a notification handler of events.
:::note
Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/loaders/notification.ts` with the following content:
This section will not cover the basics of Subscribers. You can read the [Subscribers](../events/create-subscriber.md) documentation to learn more about them and how to create them.
```ts title=src/loaders/notification.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
:::
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
Following the previous example, to make sure the `email-sender` Notification Provider handles the `order.placed` event, create the file `src/subscribers/notification.js` with the following content:
```ts title=src/subscribers/notification.js
class NotificationSubscriber {
constructor({ notificationService }) {
notificationService.subscribe(
"order.placed",
"email-sender"
)
}
// ...
notificationService.subscribe(
"order.placed",
"email-sender"
)
}
export default NotificationSubscriber
```
This subscriber accesses the `notificationService` using dependency injection. The `notificationService` contains a `subscribe` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that is subscribing to that event.
:::tip
Notice that the value of the `identifier` static property defined in the `EmailSenderService` is used to register the Notification Provider to handle the `order.placed` event.
:::
This loader accesses the `notificationService` through the [MedusaContainer](../fundamentals/dependency-injection.md). The `notificationService` has a `subscribe` method that accepts 2 parameters. The first one is the name of the event to subscribe to, and the second is the identifier of the Notification Provider that's subscribing to that event.
---

View File

@@ -0,0 +1,186 @@
---
description: 'Learn how to create a scheduled job in Medusa. The scheduled job in this example will simply change the status of draft products to published.'
addHowToData: true
---
# (Deprecated) How to Create a Scheduled Job
In this document, youll learn how to create a scheduled job in Medusa.
:::note
Following v1.18 of `@medusajs/medusa`, the approach in this guide is deprecated. It's recommended to follow [this guide](./create.md) instead.
:::
## Overview
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
This guide explains how to create a scheduled job on your Medusa backend. The scheduled job in this example will simply change the status of draft products to `published`.
---
## Prerequisites
### Medusa Components
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
---
## 1. Create a File
Each scheduled job should reside in a [loader](../loaders/overview.mdx), which is a TypeScript or JavaScript file located under the `src/loaders` directory.
Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that youll add the scheduled job in. You can use any name for the file.
For the example in this tutorial, you can create the file `src/loaders/publish.ts`.
---
## 2. Create Scheduled Job
To create a scheduled job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example:
```ts title=src/loaders/publish.ts
import { MedusaContainer } from "@medusajs/medusa"
const publishJob = async (
container: MedusaContainer,
options: Record<string, any>
) => {
const jobSchedulerService =
container.resolve("jobSchedulerService")
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// job to execute
const productService = container.resolve("productService")
const draftProducts = await productService.list({
status: "draft",
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: "published",
})
}
}
)
}
export default publishJob
```
:::info
The service taking care of background jobs was renamed in v1.7.1. If you are running a previous version, use `eventBusService` instead of `jobSchedulerService`.
:::
This file should export a function that accepts a `container` and `options` parameters. `container` is the dependency container that you can use to resolve services, such as the JobSchedulerService. `options` are the plugins options if this scheduled job is created in a plugin.
You then resolve the `JobSchedulerService` and use the `jobSchedulerService.create` method to create the scheduled job. This method accepts four parameters:
- The first parameter is a unique name to give to the scheduled job. In the example above, you use the name `publish-products`.
- The second parameter is an object which can be used to [pass data to the job](#pass-data-to-the-scheduled-job).
- The third parameter is the scheduled job expression pattern. In this example, it will execute the scheduled job once a day at 12 AM.
- The fourth parameter is the function to execute. This is where you add the code to execute once the scheduled job runs. In this example, you retrieve the draft products using the [ProductService](../../references/services/classes/ProductService.mdx) and update the status of each of these products to `published`.
:::tip
You can see examples of scheduled job expression patterns on [crontab guru](https://crontab.guru/examples.html).
:::
### Scheduled Job Name
As mentioned earlier, the first parameter of the `create` method is the name of the scheduled job. By default, if another scheduled job has the same name, your custom scheduled job will replace it.
If you want to ensure both scheduled jobs are registered and used, you can pass as a fifth parameter an options object with a `keepExisting` property set to `true`. For example:
```ts
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// ...
},
{
keepExisting: true,
}
)
```
### Pass Data to the Scheduled Job
To pass data to your scheduled job, add the data to the object passed as a second parameter under the `data` property. This is helpful if you use one function to handle multiple scheduled jobs.
For example:
```ts
jobSchedulerService.create("publish-products", {
data: {
productId,
},
}, "0 0 * * *", async (job) => {
console.log(job.data) // {productId: 'prod_124...'}
// ...
})
```
---
## 3. Run Medusa Backend
:::info
Scheduled Jobs only run while the Medusa backend is running.
:::
Before you run the Medusa backend, make sure to build your code that's under the `src` directory into the `dist` directory with the following command:
```bash npm2yarn
npm run build
```
Then, run the following command to start your Medusa backend:
```bash npm2yarn
npx medusa develop
```
If the scheduled job was registered successfully, you should see a message similar to this logged on your Medusa backend:
```bash
Registering publish-products
```
Where `publish-products` is the unique name you provided to the scheduled job.
Once it is time to run your scheduled job based on the scheduled job expression pattern, the scheduled job will run and you can see it logged on your Medusa backend.
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
```bash noReport
info: Processing scheduled job: publish-products
```
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, itll also be logged on your Medusa backend.
:::tip
To test the previous example out instantly, you can change the scheduled job expression pattern passed as the third parameter to `jobSchedulerService.create` to `* * * * *`. This will run the scheduled job every minute.
:::
---
## See Also
- [Create a Plugin](../plugins/create.mdx)

View File

@@ -7,6 +7,12 @@ addHowToData: true
In this document, youll learn how to create a scheduled job in Medusa.
:::tip
v1.18 of `@medusajs/medusa` introduced a new approach to create a scheduled job. If you're looking for the old guide, you can find it [here](./create-deprecated.md). However, it's highly recommended you follow this new approach, as the old one is deprecated.
:::
## Overview
Medusa allows you to create scheduled jobs that run at specific times during your backend's lifetime. For example, you can synchronize your inventory with an Enterprise Resource Planning (ERP) system once a day.
@@ -17,72 +23,59 @@ This guide explains how to create a scheduled job on your Medusa backend. The sc
## Prerequisites
### Medusa Components
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
To use scheduled jobs, you must configure Redis in your Medusa backend. Learn more in the [Configurations documentation](../backend/configurations.md#redis_url).
---
## 1. Create a File
## Create Scheduled Job
Each scheduled job should reside in a [loader](../loaders/overview.mdx), which is a TypeScript or JavaScript file located under the `src/loaders` directory.
Scheduled jobs are TypeScript or JavaScript guides placed under the `src/jobs` directory. It can be created under subdirectories of `src/job` as well.
Start by creating the `src/loaders` directory. Then, inside that directory, create the JavaScript or TypeScript file that youll add the scheduled job in. You can use any name for the file.
The scheduled job file exports a default handler function, and the scheduled job's configurations.
For the example in this tutorial, you can create the file `src/loaders/publish.ts`.
---
## 2. Create Scheduled Job
To create a scheduled job, add the following code in the file you created, which is `src/loaders/publish.ts` in this example:
For example:
```ts title=src/loaders/publish.ts
import { AwilixContainer } from "awilix"
import {
type ProductService,
type ScheduledJobConfig,
type ScheduledJobArgs,
ProductStatus,
} from "@medusajs/medusa"
const publishJob = async (
container: AwilixContainer,
options: Record<string, any>
) => {
const jobSchedulerService =
container.resolve("jobSchedulerService")
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// job to execute
const productService = container.resolve("productService")
const draftProducts = await productService.list({
status: "draft",
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: "published",
})
}
}
export default async function handler({
container,
data,
pluginOptions,
}: ScheduledJobArgs) {
const productService: ProductService = container.resolve(
"productService"
)
const draftProducts = await productService.list({
status: ProductStatus.DRAFT,
})
for (const product of draftProducts) {
await productService.update(product.id, {
status: ProductStatus.PUBLISHED,
})
}
}
export default publishJob
export const config: ScheduledJobConfig = {
name: "publish-once-a-day",
schedule: "0 0 * * *",
data: {},
}
```
:::info
### Scheduled Job Configuration
The service taking care of background jobs was renamed in v1.7.1. If you are running a previous version, use `eventBusService` instead of `jobSchedulerService`.
The exported configuration object of type `ScheduledJobConfig` must include the following properties:
:::
This file should export a function that accepts a `container` and `options` parameters. `container` is the dependency container that you can use to resolve services, such as the JobSchedulerService. `options` are the plugins options if this scheduled job is created in a plugin.
You then resolve the `JobSchedulerService` and use the `jobSchedulerService.create` method to create the scheduled job. This method accepts four parameters:
- The first parameter is a unique name to give to the scheduled job. In the example above, you use the name `publish-products`.
- The second parameter is an object which can be used to [pass data to the job](#pass-data-to-the-scheduled-job).
- The third parameter is the scheduled job expression pattern. In this example, it will execute the scheduled job once a day at 12 AM.
- The fourth parameter is the function to execute. This is where you add the code to execute once the scheduled job runs. In this example, you retrieve the draft products using the [ProductService](../../references/services/classes/ProductService.mdx) and update the status of each of these products to `published`.
- `name`: a string indicating a unique name to give to the scheduled job. If another scheduled job has the same name, your custom scheduled job will replace it.
- `schedule`: a string indicating a [cron schedule](https://crontab.guru/#0_0_*_*_*) that determines when the job should run. In this example, it will execute the scheduled job once a day at 12 AM.
- `data`: Optional data you want to pass to the job.
:::tip
@@ -90,46 +83,19 @@ You can see examples of scheduled job expression patterns on [crontab guru](http
:::
### Scheduled Job Name
### Scheduled Job Handler Function
As mentioned earlier, the first parameter of the `create` method is the name of the scheduled job. By default, if another scheduled job has the same name, your custom scheduled job will replace it.
The default-export of the scheduled job file is a handler function that is executed when the events specified in the exported configuration is triggerd.
If you want to ensure both scheduled jobs are registered and used, you can pass as a fifth parameter an options object with a `keepExisting` property set to `true`. For example:
The function accepts a parameter of type `ScheduledJobArgs`, which has the following properties:
```ts
jobSchedulerService.create(
"publish-products",
{},
"0 0 * * *",
async () => {
// ...
},
{
keepExisting: true,
}
)
```
### Pass Data to the Scheduled Job
To pass data to your scheduled job, add the data to the object passed as a second parameter under the `data` property. This is helpful if you use one function to handle multiple scheduled jobs.
For example:
```ts
jobSchedulerService.create("publish-products", {
data: {
productId,
},
}, "0 0 * * *", async (job) => {
console.log(job.data) // {productId: 'prod_124...'}
// ...
})
```
- `container`: The [dependency container](../fundamentals/dependency-injection.md) that allows you to resolve Medusa resources, such as services.
- `data`: The data passed in the [configuration object](#scheduled-job-configuration).
- `pluginOptions`: When the scheduled job is created within a plugin, this object holds the plugin's options defined in the [Medusa configurations](../backend/configurations.md).
---
## 3. Run Medusa Backend
## Test it Out
:::info
@@ -149,27 +115,27 @@ Then, run the following command to start your Medusa backend:
npx medusa develop
```
If the scheduled job was registered successfully, you should see a message similar to this logged on your Medusa backend:
If the scheduled job is registered successfully, you should see a message similar to this logged on your Medusa backend:
```bash
Registering publish-products
Registering publish-once-a-day
```
Where `publish-products` is the unique name you provided to the scheduled job.
Where `publish-once-a-day` is the unique name you provided to the scheduled job.
Once it is time to run your scheduled job based on the scheduled job expression pattern, the scheduled job will run and you can see it logged on your Medusa backend.
Once it's time to run your scheduled job based on the `schedule` configuration, the scheduled job will run and you can see it logged on your Medusa backend.
For example, the above scheduled job will run at 12 AM and, when it runs, you can see the following logged on your Medusa backend:
```bash noReport
info: Processing scheduled job: publish-products
info: Processing scheduled job: publish-once-a-day
```
If you log anything in the scheduled job, for example using `console.log`, or if any errors are thrown, itll also be logged on your Medusa backend.
:::tip
To test the previous example out instantly, you can change the scheduled job expression pattern passed as the third parameter to `jobSchedulerService.create` to `* * * * *`. This will run the scheduled job every minute.
To test the previous example out instantly, you can change the value of `schedule` in the exported configuration object to `* * * * *`. This will run the scheduled job every minute.
:::

View File

@@ -391,15 +391,29 @@ res.json({
### In a Subscriber
To use your custom service in a subscriber, you can have easy access to it in the subscribers dependencies injected to the constructor of your subscriber:
To resolve your custom service in subscriber handler functions, use the `container` property of the handler function's parameter. The `container` has a method `resolve` which accepts the registration name of the service as a parameter.
For example:
```ts
class MySubscriber {
constructor({ postService, eventBusService }) {
this.postService = postService
}
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
import { PostService } from "../services/post.ts"
export default async function postHandler({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const postService: PostService = container.resolve(
"postService"
)
// ...
}
// ...
```
---

View File

@@ -9,7 +9,9 @@ In this document, youll learn how to send a confirmation email to the custome
## Overview
When a customer registers, the event `customer.created` is triggered. You can then listen to this event in a subscriber to perform an action, such as send the customer a confirmation email.
When a customer registers, the event `customer.created` is triggered. You can then listen to this event in a subscriber to perform an action, such as send the customer a confirmation email.
Alternatively, you can use subscribe a Notification Provider to the event, if the Notification Provider Service implements the logic to handle the event.
This guide will explain how to create the subscriber and how to use SendGrid to send the confirmation email. SendGrid is only used to illustrate how the process works, but youre free to use any other notification service.
@@ -31,155 +33,73 @@ You can also find other available Notification provider plugins in the [Plugins
---
## Step 1: Create the Subscriber
## Method 1: Using a Subscriber
To subscribe to and handle an event, you must create a subscriber.
:::note
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
:::
To subscribe to an event, you must create a [subscriber](../../../development/events/subscribers.mdx).
Create the file `src/subscribers/customer-confirmation.ts` with the following content:
```ts title=src/subscribers/customer-confirmation.ts
type InjectedDependencies = {
// TODO add necessary dependencies
import {
type SubscriberConfig,
type SubscriberArgs,
CustomerService,
} from "@medusajs/medusa"
export default async function handleCustomerCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO: handle event
}
class CustomerConfirmationSubscriber {
constructor(container: InjectedDependencies) {
// TODO subscribe to event
}
export const config: SubscriberConfig = {
event: CustomerService.Events.CREATED,
context: {
subscriberId: "customer-created-handler",
},
}
export default CustomerConfirmationSubscriber
```
Youll be adding in the next step the necessary dependencies to the subscriber.
In this file, you export a configuration object indicating that the subscriber is listening to the `CustomerService.Events.CREATED` (or `customer.created`) event.
:::note
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
:::
---
## Step 2: Subscribe to the Event
In this step, youll subscribe to the `customer.created` event to send the customer a confirmation email.
There are two ways to do this:
### Method 1: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
```ts title=src/subscribers/customer-confirmation.ts
import { NotificationService } from "@medusajs/medusa"
type InjectedDependencies = {
notificationService: NotificationService
}
class CustomerConfirmationSubscriber {
constructor({ notificationService }: InjectedDependencies) {
notificationService.subscribe(
"customer.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
}
export default CustomerConfirmationSubscriber
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
### Method 2: Using the EventBusService
If the notification provider youre using isnt configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
```ts title=src/subscribers/customer-confirmation.ts
import { Customer, EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
}
class CustomerConfirmationSubscriber {
constructor({ eventBusService }: InjectedDependencies) {
eventBusService.subscribe(
"customer.created",
this.handleCustomerConfirmation
)
}
handleCustomerConfirmation = async (data: Customer) => {
// TODO: handle event
}
}
export default CustomerConfirmationSubscriber
```
When using this method, youll have to handle the logic of sending the confirmation email to the customer inside the handler function, which in this case is `handleCustomerConfirmation`.
## Step 3: Handle the Event
The `handleCustomerConfirmation` method receives a `data` object as a parameter which is a payload emitted when the event was triggered. This object is the entire customer object. So, you can find in it fields like `first_name`, `last_name`, `email`, and more.
You also export a handler function `handleCustomerConfirmation`. In the parameter it receives, the `data` object is the payload emitted when the event was triggered, which is the entire customer object. So, you can find in it fields like `first_name`, `last_name`, `email`, and more.
In this method, you should typically send an email to the customer. You can place any content in the email, such as welcoming them to your store or thanking them for registering.
### Example: Using SendGrid
For example, you can implement this subscriber to send emails using SendGrid:
For example, you can implement this subscriber to send emails using [SendGrid](../../../plugins/notifications/sendgrid.mdx):
```ts title=src/subscribers/customer-confirmation.ts
import { Customer, EventBusService } from "@medusajs/medusa"
import {
type SubscriberConfig,
type SubscriberArgs,
CustomerService,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService,
sendgridService: any
export default async function handleCustomerCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendGridService = container.resolve("sendgridService")
sendGridService.sendEmail({
templateId: "customer-confirmation",
from: "hello@medusajs.com",
to: data.email,
dynamic_template_data: {
// any data necessary for your template...
first_name: data.first_name,
last_name: data.last_name,
},
})
}
class CustomerConfirmationSubscriber {
protected sendGridService: any
constructor({
eventBusService,
sendgridService,
}: InjectedDependencies) {
this.sendGridService = sendgridService
eventBusService.subscribe(
"customer.created",
this.handleCustomerConfirmation
)
}
handleCustomerConfirmation = async (data: Customer) => {
this.sendGridService.sendEmail({
templateId: "customer-confirmation",
from: "hello@medusajs.com",
to: data.email,
dynamic_template_data: {
// any data necessary for your template...
first_name: data.first_name,
last_name: data.last_name,
},
})
}
export const config: SubscriberConfig = {
event: CustomerService.Events.CREATED,
context: {
subscriberId: "customer-created-handler",
},
}
export default CustomerConfirmationSubscriber
```
Notice that you should replace the values in the object passed to the `sendEmail` method:
@@ -188,3 +108,39 @@ Notice that you should replace the values in the object passed to the `sendEmail
- `from`: Should be the from email.
- `to`: Should be the customers email.
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.
---
## Method 2: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can create a [Loader](../../../development/loaders/overview.mdx) to subscribe the Notification provider to the `customer.created` event.
For example:
```ts title=src/loaders/customer-confirmation.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
notificationService.subscribe(
"customer.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, `sendgrid`.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::

View File

@@ -287,7 +287,7 @@ If the request has been processed successfully, it returns a `204` status code i
:::note
If the customer doesnt receive an email after this request, make sure that youve set up a Notification provider like [SendGrid](../../../plugins/notifications/sendgrid.mdx) successfully. You also need to add a subscriber that handles the [customer.password_reset](../../../development/events/events-list.md#customer-events) event and sends the email.
If the customer doesnt receive an email after this request, make sure that youve set up a Notification provider like [SendGrid](../../../plugins/notifications/sendgrid.mdx) successfully. You also need to add a [subscriber](../../../development/events/create-subscriber.md) that handles the [customer.password_reset](../../../development/events/events-list.md#customer-events) event and sends the email.
:::

View File

@@ -13,7 +13,7 @@ Once the customer purchases a gift card, they should receive the code of the gif
Typically, the code would be sent by email, however, youre free to choose how you deliver the gift card code to the customer.
This document shows you how to track when a gift card has been purchased so that you can send its code to the customer.
This document shows you how to track when a gift card is purchased so that you can send its code to the customer.
:::tip
@@ -31,121 +31,124 @@ It's assumed that you already have a Medusa backend installed and set up. If not
### Notification Provider
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an existing plugin or [create your own](../../../development/notification/create-notification-provider.md).
To send an email or another type of notification method, you must have a notification provider installed or configured. You can either install an [existing plugin](../../../plugins/notifications/index.mdx) or [create your own](../../../development/notification/create-notification-provider.md).
---
## Step 1: Create a Subscriber
## Methed 1: Using a Subscriber
To subscribe to and handle an event, you must create a subscriber.
:::info
You can learn more about subscribers in the [Subscribers](../../../development/events/subscribers.mdx) documentation.
:::
To subscribe to and handle an event, you must create a [subscriber](../../../development/events/subscribers.mdx).
Create the file `src/subscribers/gift-card.ts` with the following content:
```ts title=src/subscribers/gift-card.ts
type InjectedDependencies = {
// TODO add necessary dependencies
}
class GiftCardSubscriber {
constructor(container: InjectedDependencies) {
// TODO subscribe to event
}
}
export default GiftCardSubscriber
```
Youll be adding in the next step the necessary dependencies to the subscriber.
:::info
You can learn more about [dependency injection](../../../development/fundamentals/dependency-injection.md) in this documentation.
:::
---
## Step 2: Subscribe to the Event
In this step, youll subscribe to the event `gift_card.created` to send the customer a notification about their gift card.
There are two ways to do this:
### Method 1: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
```ts title=src/subscribers/gift-card.ts
import { NotificationService } from "@medusajs/medusa"
type InjectedDependencies = {
notificationService: NotificationService
}
class GiftCardSubscriber {
constructor({ notificationService }: InjectedDependencies) {
notificationService.subscribe(
"gift_card.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
}
export default GiftCardSubscriber
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, if youre using SendGrid, the identifier is `sendgrid`.
:::info
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
### Method 2: Using the EventBusService
If the notification provider youre using isnt configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
```ts title=src/subscribers/gift-card.ts
import {
EventBusService,
type SubscriberConfig,
type SubscriberArgs,
GiftCardService,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
giftCardService: GiftCardService
export default async function handleGiftCardCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO: handle event
}
class GiftCardSubscriber {
giftCardService: GiftCardService
constructor({
eventBusService,
giftCardService,
}: InjectedDependencies) {
this.giftCardService = giftCardService
eventBusService.subscribe(
"gift_card.created", this.handleGiftCard)
}
handleGiftCard = async (data) => {
const giftCard = await this.giftCardService.retrieve(
data.id
)
// TODO send customer the gift card code
}
export const config: SubscriberConfig = {
event: GiftCardService.Events.CREATED,
context: {
subscriberId: "gift-card-created-handler",
},
}
export default GiftCardSubscriber
```
When using this method, youll have to handle the logic of sending the code to the customer inside the handler function, which in this case is `handleGiftCard`.
In this file, you export a configuration object indicating that the subscriber is listening to the `GiftCardService.Events.CREATED` (or `gift_card.created`) event.
The `handleGiftCard` event receives a `data` object as a parameter. This object holds the `id` property which is the ID of the gift card. You can retrieve the full gift card object using the [GiftCardService](../../../references/services/classes/GiftCardService.mdx)
You also export a handler function `handleGiftCardCreated`. In the parameter it receives, the `data` object is the payload emitted when the event was triggered, which is an object containing the ID of the gift card in the `id` property.
In this method, you should typically send an email to the customer. You can place any content in the email, such as the code of the gift card.
### Example: Using SendGrid
For example, you can implement this subscriber to send emails using [SendGrid](../../../plugins/notifications/sendgrid.mdx):
```ts title=src/subscribers/gift-card.ts
import {
type SubscriberConfig,
type SubscriberArgs,
GiftCardService,
} from "@medusajs/medusa"
export default async function handleGiftCardCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendGridService = container.resolve("sendgridService")
const giftCardService: GiftCardService = container.resolve(
"giftCardService"
)
const giftCard = await giftCardService.retrieve(data.id, {
relations: ["order"],
})
sendGridService.sendEmail({
templateId: "gift-card-created",
from: "hello@medusajs.com",
to: giftCard.order.email,
dynamic_template_data: {
// any data necessary for your template...
code: giftCard.code,
},
})
}
export const config: SubscriberConfig = {
event: GiftCardService.Events.CREATED,
context: {
subscriberId: "gift-card-created-handler",
},
}
```
Notice that you should replace the values in the object passed to the `sendEmail` method:
- `templateId`: Should be the ID of your confirmation email template in SendGrid.
- `from`: Should be the from email.
- `to`: Should be the customers email.
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.
---
## Method 2: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can create a [Loader](../../../development/loaders/overview.mdx) to subscribe the Notification provider to the `gift_card.created` event.
For example:
```ts title=src/loaders/gift-card-event.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
notificationService.subscribe(
"gift_card.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, `sendgrid`.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::

View File

@@ -9,14 +9,12 @@ In this document, youll learn how to handle the order claim event and send a
## Overview
When a guest customer places an order, the order is not associated with a customer. It is associated with an email address.
When a guest customer places an order, the order isn't associated with a customer. It's associated with an email address.
After the customer registers, later on, they can claim that order by providing the orders ID.
When the customer requests to claim the order, the event `order-update-token.created` is triggered on the Medusa backend. This event should be used to send the customer a confirmation email.
### What Youll Learn
In this document, youll learn how to handle the `order-update-token.created` event on the backend to send the customer a confirmation email.
---
@@ -35,170 +33,132 @@ This document has an example using the [SendGrid](../../../plugins/notifications
---
## Step 1: Create a Subscriber
## Method 1: Using a Subscriber
To subscribe to and handle an event, you must create a subscriber.
To subscribe to an event, you must create a subscriber.
:::tip
:::note
You can learn more about subscribers in the [Subscribers](../../../development/events/subscribers.mdx) documentation.
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
:::
Create the file `src/subscribers/claim-order.ts` with the following content:
Create the file `src/subscribers/order-claim.ts` with the following content:
```ts title=src/subscribers/claim-order.ts
type InjectedDependencies = {
// TODO add necessary dependencies
```ts title=src/subscribers/order-claim.ts
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
export default async function handleOrderClaim({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO: handle event
}
class ClaimOrderSubscriber {
constructor(container: InjectedDependencies) {
// TODO subscribe to event
}
export const config: SubscriberConfig = {
event: "order-update-token.created",
context: {
subscriberId: "customer-created-handler",
},
}
export default ClaimOrderSubscriber
```
Youll be adding in the next step the necessary dependencies to the subscriber.
In this file, you export a configuration object indicating that the subscriber is listening to the `order-update-token.created` event.
:::info
You also export a handler function `handleOrderClaim`. In the parameter it receives, the `data` object is the payload emitted when the event was triggered, which is an object of the following format:
You can learn more about [dependency injection](../../../development/fundamentals/dependency-injection.md) in this documentation.
:::
---
## Step 2: Subscribe to the Event
In this step, youll subscribe to the `order-update-token.created` event to send the customer a notification about their order edit.
There are two ways to do this:
### Method 1: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
```ts title=src/subscribers/claim-order.ts
import { NotificationService } from "@medusajs/medusa"
type InjectedDependencies = {
notificationService: NotificationService
```ts
data = {
// string - email of order
old_email,
// string - ID of customer
new_customer_id,
// array of string - IDs of orders
orders,
// string - token used for verification
token,
}
class ClaimOrderSubscriber {
constructor({ notificationService }: InjectedDependencies) {
notificationService.subscribe(
"order-update-token.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
}
export default ClaimOrderSubscriber
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
In this method, you should typically send an email to the customer. You can place any content in the email, but should mainly include the link to confirm claiming the order.
:::info
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
### Method 2: Using the EventBusService
If the notification provider youre using isnt configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
```ts title=src/subscribers/claim-order.ts
import { EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
}
class ClaimOrderSubscriber {
constructor({ eventBusService }: InjectedDependencies) {
eventBusService.subscribe(
"order-update-token.created",
this.handleRequestClaimOrder
)
}
handleRequestClaimOrder = async (data) => {
// TODO: handle event
}
}
export default ClaimOrderSubscriber
```
When using this method, youll have to handle the logic of sending the confirmation email to the customer inside the handler function, which in this case is `handleRequestClaimOrder`.
The `handleRequestClaimOrder` event receives a `data` object as a parameter. This object holds the following properties:
1. `old_email`: The email associated with the orders.
2. `new_customer_id`: The ID of the customer claiming the orders.
3. `orders`: An array of the order IDs that the customer is requesting to claim.
4. `token`: A verification token. This token is used to later verify the claim request and associate the order with the customer.
In this method, you should typically send an email to the customers old email. In the email, you should link to a page in your storefront and pass the `token` as a parameter.
The page would then send a request to the backend to verify that the `token` is valid and associate the order with the customer. You can read more about how to implement this in your storefront in [this documentation](../storefront/implement-claim-order.mdx).
---
## Example: Using SendGrid
### Example: Using SendGrid
For example, you can implement this subscriber to send emails using SendGrid:
<!-- eslint-disable max-len -->
```ts title=src/subscribers/order-claim.ts
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
```ts title=src/subscribers/claim-order.ts
import { EventBusService } from "@medusajs/medusa"
export default async function handleOrderClaim({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendGridService = container.resolve("sendgridService")
type InjectedDependencies = {
eventBusService: EventBusService,
sendgridService: any
sendGridService.sendEmail({
templateId: "order-claim-confirmation",
from: "hello@medusajs.com",
to: data.old_email,
dynamic_template_data: {
link:
`http://example.com/confirm-order-claim/${data.token}`,
// other data...
},
})
}
class ClaimOrderSubscriber {
protected sendGridService: any
constructor({
eventBusService,
sendgridService,
}: InjectedDependencies) {
this.sendGridService = sendgridService
eventBusService.subscribe(
"order-update-token.created",
this.handleRequestClaimOrder
)
}
handleRequestClaimOrder = async (data) => {
this.sendGridService.sendEmail({
templateId: "order-claim-confirmation",
from: "hello@medusajs.com",
to: data.old_email,
dynamic_template_data: {
link: `http://example.com/confirm-order-claim/${data.token}`,
// other data...
},
})
}
export const config: SubscriberConfig = {
event: "order-update-token.created",
context: {
subscriberId: "customer-created-handler",
},
}
export default ClaimOrderSubscriber
```
Notice how the `token` is passed to the storefront link as a parameter.
---
## Method 2: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can create a [Loader](../../../development/loaders/overview.mdx) to subscribe the Notification provider to the `order-update-token.created` event.
For example:
```ts title=src/loaders/order-claim.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
notificationService.subscribe(
"order-update-token.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, `sendgrid`.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
---
## See Also
- [Implement claim-order flow in your storefront](../storefront/implement-claim-order.mdx)

View File

@@ -11,22 +11,12 @@ In this document, youll learn how to send an order confirmation email to the
When an order is placed, the `order.placed` event is triggered. You can listen to this event in a subscriber to perform an action, such as send the customer an order confirmation email.
This guide explains how to create the subscriber and how to use SendGrid to send the confirmation email. SendGrid is only used to illustrate how the process works, but youre free to use any other notification service.
:::note
SendGrid is already configured to send emails when an order has been placed. So, by installing and configuring the plugin, you don't need to actually handle sending the order confirmation email. It's used as an example here to illustrate the process only.
:::
This guide explains how you can listen to the `order.placed` event to send an email to the customer.
---
## Prerequisites
### Medusa Backend
Its assumed you already have the Medusa backend installed. If not, you can either use the [create-medusa-app command](../../../create-medusa-app.mdx) to install different Medusa tools, including the backend, or [install the backend only](../../../development/backend/install.mdx).
### Event Bus Module
The event bus module trigger the event to the listening subscribers. So, its required to have an event bus module installed and configured on your Medusa backend.
@@ -41,221 +31,128 @@ You can also find other available Notification provider plugins in the [Plugins
---
## Step 1: Create the Subscriber
## Method 1: Using a Subscriber
To subscribe to and handle an event, you must create a subscriber.
To subscribe to an event, you must create a [subscriber](../../../development/events/subscribers.mdx).
:::note
Create the file `src/subscribers/order-placed.ts` with the following content:
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
```ts title=src/subscribers/order-placed.ts
import {
type SubscriberConfig,
type SubscriberArgs,
OrderService,
} from "@medusajs/medusa"
:::
Create the file `src/subscribers/order-confirmation.ts` with the following content:
```ts title=src/subscribers/order-confirmation.ts
type InjectedDependencies = {
// TODO add necessary dependencies
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO: handle event
}
class OrderConfirmationSubscriber {
constructor(container: InjectedDependencies) {
// TODO subscribe to event
}
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
export default OrderConfirmationSubscriber
```
Youll be adding in the next step the necessary dependencies to the subscriber.
In this file, you export a configuration object indicating that the subscriber is listening to the `OrderService.Events.PLACED` (or `order.placed`) event.
You also export a handler function `handleCustomerConfirmation`. In the parameter it receives, the `data` object is the payload emitted when the event was triggered, which is an object that includes the ID of the order in the `id` property.
In this method, you should typically send an email to the customer. You can place any content in the email, such as the order's items and total.
### Example: Using SendGrid
:::note
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
This example is only used to illustrate how the functionality can be implemented. The SendGrid plugin automatically handles sending an email when an order is placed once you install and configure the plugin in your backend.
:::
For example, you can implement this subscriber to send emails using [SendGrid](../../../plugins/notifications/sendgrid.mdx):
```ts title=src/subscribers/order-placed.ts
import {
type SubscriberConfig,
type SubscriberArgs,
OrderService,
} from "@medusajs/medusa"
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendGridService = container.resolve("sendgridService")
const orderService: OrderService = container.resolve(
"orderService"
)
const order = await orderService.retrieve(data.id, {
// you can include other relations as well
relations: ["items"],
})
sendGridService.sendEmail({
templateId: "order-confirmation",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
items: order.items,
status: order.status,
},
})
}
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
Notice that you should replace the values in the object passed to the `sendEmail` method:
- `templateId`: Should be the ID of your confirmation email template in SendGrid.
- `from`: Should be the from email.
- `to`: Should be the customers email.
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.
---
## Step 2: Subscribe to the Event
## Method 2: Using the NotificationService
In this step, youll subscribe to the `order.placed` event to send the customer an order confirmation email.
If the notification provider youre using already implements the logic to handle this event, you can create a [Loader](../../../development/loaders/overview.mdx) to subscribe the Notification provider to the `order.placed` event.
There are two ways to do this:
For example:
### Method 1: Using the NotificationService
```ts title=src/loaders/customer-confirmation.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
If the notification provider youre using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
```ts title=src/subscribers/order-confirmation.ts
import { NotificationService } from "@medusajs/medusa"
type InjectedDependencies = {
notificationService: NotificationService
notificationService.subscribe(
"order.placed",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
class OrderConfirmationSubscriber {
constructor({ notificationService }: InjectedDependencies) {
notificationService.subscribe(
"order.placed",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
}
export default OrderConfirmationSubscriber
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, `sendgrid`.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
### Method 2: Using the EventBusService
If the notification provider youre using isnt configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
```ts title=src/subscribers/order-confirmation.ts
import { EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
}
class OrderConfirmationSubscriber {
constructor({ eventBusService }: InjectedDependencies) {
eventBusService.subscribe(
"order.placed",
this.handleOrderConfirmation
)
}
handleOrderConfirmation = async (
data: Record<string, any>
) => {
// TODO: handle event
}
}
export default OrderConfirmationSubscriber
```
When using this method, youll have to handle the logic of sending the order confirmation email to the customer inside the handler function, which in this case is `handleOrderConfirmation`.
## Step 3: Handle the Event
The `handleOrderConfirmation` event receives a `data` object as a parameter. This object holds two properties:
- `id`: the ID of the order that was placed.
- `no_notification`: a boolean value indicating whether the customer should receive notifications about the order or not.
In this method, you should typically send an email to the customer if `no_notification` is enabled.
To retrieve the order's details, you can add the `OrderService` into `InjectedDependencies` and use it within `handleOrderConfirmation`. For example:
```ts title=src/subscribers/order-confirmation.ts
import { EventBusService, OrderService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
orderService: OrderService
}
class OrderConfirmationSubscriber {
protected readonly orderService_: OrderService
constructor({
eventBusService,
orderService,
}: InjectedDependencies) {
this.orderService_ = orderService
eventBusService.subscribe(
"order.placed",
this.handleOrderConfirmation
)
}
handleOrderConfirmation = async (
data: Record<string, any>
) => {
const order = await this.orderService_.retrieve(data.id, {
// you can include other relations as well
relations: ["items"],
})
// TODO: handle event
}
}
export default OrderConfirmationSubscriber
```
After retrieving the order, you can add the logic necessary to send the email. In the email, you can include any content you want. For example, you can show the order's items or the order's status.
### Example: Using SendGrid
:::note
This example is only used to illustrate how the functionality can be implemented. As mentioned in the introduction, there's actually no need to implement this subscriber if you have the SendGrid plugin installed and configured, as it will automatically handle it.
:::
For example, you can implement this subscriber to send emails using SendGrid:
```ts title=src/subscribers/order-confirmation.ts
import { EventBusService, OrderService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
orderService: OrderService
sendgridService: any
}
class OrderConfirmationSubscriber {
protected readonly orderService_: OrderService
protected readonly sendgridService_: any
constructor({
eventBusService,
orderService,
sendgridService,
}: InjectedDependencies) {
this.orderService_ = orderService
this.sendgridService_ = sendgridService
eventBusService.subscribe(
"order.placed",
this.handleOrderConfirmation
)
}
handleOrderConfirmation = async (
data: Record<string, any>
) => {
const order = await this.orderService_.retrieve(data.id, {
// you can include other relations as well
relations: ["items"],
})
this.sendgridService_.sendEmail({
templateId: "order-confirmation",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
items: order.items,
status: order.status,
},
})
}
}
export default OrderConfirmationSubscriber
```
Notice that you should replace the values in the object passed to the `sendEmail` method:
- `templateId`: Should be the ID of your order confirmation email template in SendGrid.
- `from`: Should be the from email.
- `to`: Should be the email associated with the order.
- `data`: Should be an object holding any data that should be passed to your SendGrid email template.

View File

@@ -31,160 +31,81 @@ You can also find other available Notification provider plugins in the [Plugins
---
## Step 1: Create the Subscriber
## Method 1: Using a Subscriber
To subscribe to and handle an event, you must create a subscriber.
To subscribe to an event, you must create a [subscriber](../../../development/events/subscribers.mdx).
:::tip
Create the file `src/subscribers/invite-created.ts` with the following content:
You can learn more about subscribers in the [Subscribers documentation](../../../development/events/subscribers.mdx).
```ts title=src/subscribers/invite-created.ts
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
:::
Create the file `src/subscribers/invite.ts` with the following content:
```ts title=src/subscribers/invite.ts
type InjectedDependencies = {
// TODO add necessary dependencies
export default async function handleInviteCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO: handle event
}
class InviteSubscriber {
constructor(container: InjectedDependencies) {
// TODO subscribe to event
}
export const config: SubscriberConfig = {
event: "invite.created",
context: {
subscriberId: "invite-created-handler",
},
}
export default InviteSubscriber
```
Youll be adding in the next step the necessary dependencies to the subscriber.
In this file, you export a configuration object indicating that the subscriber is listening to the `invite.created` event.
:::tip
You also export a handler function `handleInviteCreated`. In the parameter it receives, the `data` object is the payload emitted when the event was triggered, which is an object of the following format:
You can learn more about dependency injection in [this documentation](../../../development/fundamentals/dependency-injection.md).
:::
---
## Step 2: Subscribe to the Event
In this step, youll subscribe to the `invite.created` event to send the user the invitation email.
There are two ways to do this:
### Method 1: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can subscribe to the event using the `NotificationService`:
```ts title=src/subscribers/invite.ts
import { NotificationService } from "@medusajs/medusa"
type InjectedDependencies = {
notificationService: NotificationService
```ts
{
// string - ID of invite
id
// string - token generated to validate the invited user
token,
// string - email of invited user
user_email
}
class InviteSubscriber {
constructor({ notificationService }: InjectedDependencies) {
notificationService.subscribe(
"invite.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
}
export default InviteSubscriber
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider.
:::tip
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::
### Method 2: Using the EventBusService
If the notification provider youre using isnt configured to handle this event, or you want to implement some other custom logic, you can subscribe to the event using the `EventBusService`:
```ts title=src/subscribers/invite.ts
import { EventBusService } from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
}
class InviteSubscriber {
constructor({ eventBusService }: InjectedDependencies) {
eventBusService.subscribe(
"invite.created",
this.handleInvite
)
}
handleInvite = async (data: Record<string, any>) => {
// TODO: handle event
}
}
export default InviteSubscriber
```
When using this method, youll have to handle the logic of sending the invitation email inside the handler function, which in this case is `handleInvite`.
---
## Step 3: Handle the Event
The `handleInvite` method receives a `data` object as a parameter which is a payload emitted when the event was triggered. This object has the following properties:
- `id`: a string indicating the ID of the invite.
- `token`: a string indicating the token of the invite. This token is useful to pass along to a frontend link that can be used to accept the invite.
- `user_email`: a string indicating the email of the invited user.
In this method, you should typically send an email to the invited user. You can place any content in the email, but typically you would include a link to your frontend that allows the invited user to enter their details and accept the invite.
In this method, you should typically send an email to the user. You can place any content in the email, but should mainly include the invite token.
### Example: Using SendGrid
For example, you can implement this subscriber to send emails using SendGrid:
```ts title=src/subscribers/invite.ts
import { EventBusService } from "@medusajs/medusa"
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
sendgridService: any
export default async function handleInviteCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendGridService = container.resolve("sendgridService")
sendGridService.sendEmail({
templateId: "send-invite",
from: "hello@medusajs.com",
to: data.user_email,
dynamic_template_data: {
// any data necessary for your template...
token: data.token,
},
})
}
class InviteSubscriber {
protected sendGridService: any
constructor({
eventBusService,
sendgridService,
}: InjectedDependencies) {
this.sendGridService = sendgridService
eventBusService.subscribe(
"invite.created",
this.handleInvite
)
}
handleInvite = async (data: Record<string, any>) => {
this.sendGridService.sendEmail({
templateId: "send-invite",
from: "hello@medusajs.com",
to: data.user_email,
dynamic_template_data: {
// any data necessary for your template...
token: data.token,
},
})
}
export const config: SubscriberConfig = {
event: "invite.created",
context: {
subscriberId: "invite-created-handler",
},
}
export default InviteSubscriber
```
Notice that you should replace the values in the object passed to the `sendEmail` method:
@@ -193,3 +114,39 @@ Notice that you should replace the values in the object passed to the `sendEmai
- `from`: Should be the from email.
- `to`: Should be the invited users email.
- `data`: Should be an object holding any data that should be passed to your SendGrid email template. In the example above, you pass the token, which you can use in the SendGrid template to format the frontend link (for example, `<FRONTEND_LINK>/invite?token={{token}}`, where `<FRONTEND_LINK>` is your frontends hostname.)
---
## Method 2: Using the NotificationService
If the notification provider youre using already implements the logic to handle this event, you can create a [Loader](../../../development/loaders/overview.mdx) to subscribe the Notification provider to the `invite.created` event.
For example:
```ts title=src/loaders/customer-confirmation.ts
import {
MedusaContainer,
NotificationService,
} from "@medusajs/medusa"
export default async (
container: MedusaContainer
): Promise<void> => {
const notificationService = container.resolve<
NotificationService
>("notificationService")
notificationService.subscribe(
"invite.created",
"<NOTIFICATION_PROVIDER_IDENTIFIER>"
)
}
```
Where `<NOTIFICATION_PROVIDER_IDENTIFIER>` is the identifier for your notification provider. For example, `sendgrid`.
:::note
You can learn more about handling events with the Notification Service using [this documentation](../../../development/notification/create-notification-provider.md).
:::

View File

@@ -153,43 +153,41 @@ If the data is not appearing on the destination, the issue is related to your co
In some cases, you might want to track more events or custom events. You can do that using the `SegmentService` provided by the Segment Plugin.
For example, you can add the following subscriber to listen to the `customer.created` event and add tracking for every customer created:
For example, you can add the following [subscriber](../../development/events/subscribers.mdx) to listen to the `customer.created` event and add tracking for every customer created:
```jsx title=src/subscribers/customer.ts
class CustomerSubscriber {
constructor({ segmentService, eventBusService }) {
this.segmentService = segmentService
```ts title=src/subscribers/customer.ts
import {
type SubscriberConfig,
type SubscriberArgs,
CustomerService,
} from "@medusajs/medusa"
eventBusService.subscribe(
"customer.created",
this.handleCustomer
)
}
export default async function handleCustomerCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const segmentService = container.resolve("segmentService")
handleCustomer = async (data) => {
const customerData = data
delete customerData["password_hash"]
const customerData = data
delete customerData["password_hash"]
this.segmentService.track({
event: "Customer Created",
userId: data.id,
properties: customerData,
})
}
this.segmentService.track({
event: "Customer Created",
userId: data.id,
properties: customerData,
})
}
export default CustomerSubscriber
export const config: SubscriberConfig = {
event: CustomerService.Events.CREATED,
context: {
subscriberId: "customer-created-handler",
},
}
```
You resolve the `SegmentService` using dependency injection. Then, when the `customer.created` event is triggered, you use the `track` method available in the `SegmentService` to send tracking data to Segment.
In the handler function, you resolve the `SegmentService` using the `container` of type [MedusaContainer](../../development/fundamentals/dependency-injection.md). Then, you use the `track` method of the `SegmentService` to send tracking data to Segment.
:::info
Services can be resolved and used in Subscribers, API Routes, and other Services. Learn [how to resolve services in the Services documentation](../../development/services/create-service.mdx#using-your-custom-service).
:::
`track` accepts an object of data, where the keys `event` and `userId` are required. Instead of `userId`, you can use `anonymousId` to pass an anonymous user ID.
The `track` method accepts an object of data, where the keys `event` and `userId` are required. Instead of `userId`, you can use `anonymousId` to pass an anonymous user ID.
If you want to pass additional data to Segment, pass them under the `properties` object key.
@@ -199,8 +197,6 @@ The `SegmentService` also provides the method `identify` to tie a user to their
After adding the above subscriber, run your backend again if it isnt running and create a customer using the REST APIs or one of the Medusa storefronts. If you check the Debugger in your Segment source, you should see a new event “Customer Created” tracked. If you click on it, youll see the data you passed to the `track` method.
![The customer created event is recorded on the Segment source](https://res.cloudinary.com/dza7lstvk/image/upload/v1668000759/Medusa%20Docs/Segment/4LD41xE_qungdw.png)
---
## See Also

View File

@@ -405,8 +405,11 @@ npm install --save-dev contentful-migration
Finally, create a [loader](../../development/loaders/overview.mdx) at `src/loaders/index.ts` with the following content:
```ts title=src/loaders/index.ts
import { ConfigModule, StoreService } from "@medusajs/medusa"
import { AwilixContainer } from "awilix"
import {
ConfigModule,
StoreService,
MedusaContainer,
} from "@medusajs/medusa"
import { runMigration } from "contentful-migration"
import {
productMigration,
@@ -434,7 +437,7 @@ type ContentfulPluginType = {
}
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
// ensure that migration only runs once

View File

@@ -18,7 +18,7 @@ The notification contains details about the order including:
- Order totals including Tax amount.
- Promotion details if there are any (this is optional and can be turned off).
The plugin registers a subscriber to the `order.placed` event. When an order is placed, the subscriber handler method uses the ID of the order to retrieve order details mentioned above.
The plugin registers a subscriber to the `order.placed` event. When an order is placed, the subscriber handler function uses the ID of the order to retrieve order details mentioned above.
Then, the order notification is sent to Slack using Webhooks. So, you'll need to create a Slack App, add it into your workspace, and activate Incoming Webhooks.

View File

@@ -73,63 +73,56 @@ const plugins = [
## Example Usage of the Plugin
This plugin adds the service `twilioSmsService` to your Medusa backend. To send SMS using it, all you have to do is resolve it in your file as explained in the [Services](../../development/services/create-service.mdx#using-your-custom-service) documentation.
This plugin adds the service `twilioSmsService` to your Medusa backend. To send SMS using it, all you have to do is resolve it in your file as explained in the [dependency injection](../../development/fundamentals/dependency-injection.md) documentation.
In this example, youll create a subscriber that listens to the `order.placed` event and sends an SMS to the customer to confirm their order.
In this example, youll create a [subscriber](../../development/events/subscribers.mdx) that listens to the `order.placed` event and sends an SMS to the customer to confirm their order.
:::tip
For this example to work, you'll need to have an event bus module installed and configured, which should be available by default.
For this example to work, you'll need to have an [event bus module](../../development/events/index.mdx) installed and configured, which should be available by default.
:::
Create the file `src/services/sms.js` in your Medusa backend with the following content:
Create the file `src/subscriber/sms.ts` in your Medusa backend with the following content:
```js title=src/services/sms.js
class SmsSubscriber {
constructor({
twilioSmsService,
orderService,
eventBusService,
}) {
this.twilioSmsService_ = twilioSmsService
this.orderService = orderService
```ts title=src/subscriber/sms.ts
import {
type SubscriberConfig,
type SubscriberArgs,
OrderService,
} from "@medusajs/medusa"
eventBusService.subscribe("order.placed", this.sendSMS)
}
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const twilioSmsService = container.resolve("twilioSmsService")
const orderService: OrderService = container.resolve(
"orderService"
)
sendSMS = async (data) => {
const order = await this.orderService.retrieve(data.id, {
relations: ["shipping_address"],
const order = await orderService.retrieve(data.id, {
relations: ["shipping_address"],
})
if (order.shipping_address.phone) {
twilioSmsService.sendSms({
to: order.shipping_address.phone,
body: "We have received your order #" + data.id,
})
if (order.shipping_address.phone) {
this.twilioSmsService_.sendSms({
to: order.shipping_address.phone,
body: "We have received your order #" + data.id,
})
}
}
}
export default SmsSubscriber
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
In the `constructor`, you resolve the `twilioSmsService` and `orderService` using dependency injection to use it later in the `sendSMS` method.
In the handler function, you resolve the `twilioSmsService` and `orderService` using `container` of type [MedusaContainer](../../development/fundamentals/dependency-injection.md). You then retrieve the order's details, and send an SMS to the customer based on the phone number in their shipping address.
You also subscribe to the event `order.placed` and sets the event handler to be `sendSMS`.
In `sendSMS`, you first retrieve the order with its relation to `shipping_address` which contains a `phone` field. If the phone is set, you send an SMS to the customer using the method `sendSms` in the `twilioSmsService`.
This method accepts an object of parameters. These parameters are based on Twilios SMS APIs. You can check their [API documentation](https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource) for more fields that you can add.
If you create an order now on your storefront, you should receive a message from Twilio on the phone number you entered in the shipping address.
:::tip
If you dont have a storefront set up yet, you can install the [Next.js Starter Template](../../starters/nextjs-medusa-starter.mdx).
:::
The `sendSms` method of the Twilio service accepts an object of parameters. These parameters are based on Twilios SMS APIs. You can check their [API documentation](https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource) for more fields that you can add.
:::caution
@@ -137,8 +130,6 @@ If youre on a Twilio trial make sure that the phone number you entered on che
:::
![Twilio Dashboard](https://res.cloudinary.com/dza7lstvk/image/upload/v1668001219/Medusa%20Docs/Stripe/MXtQMiL_kb7kxe.png)
---
## See Also

View File

@@ -28,7 +28,7 @@ Before you follow this guide, you must have a Medusa backend installed. If not,
### Event-Bus Module
To trigger events to the subscribed handler methods, you must have an event-bus module installed. For development purposes, you can use the [Local module](../../development/events/modules/local.md) which should be enabled by default in your Medusa backend.
To trigger events to the subscribed handler functions, you must have an event-bus module installed. For development purposes, you can use the [Local module](../../development/events/modules/local.md) which should be enabled by default in your Medusa backend.
For production, it's recommended to use the [Redis module](../../development/events/modules/redis.md).
@@ -114,64 +114,49 @@ The SendGrid plugin already listens to and handles the `restock-notification.res
:::
Here's an example of a subscriber that listens to the `restock-notification.restocked` event and uses the [SendGrid plugin](../notifications/sendgrid.mdx) to send the subscribed customers an email:
Here's an example of a [subscriber](../../development/events/subscribers.mdx) that listens to the `restock-notification.restocked` event and uses the [SendGrid plugin](../notifications/sendgrid.mdx) to send the subscribed customers an email:
```ts title=src/subscribers/restock-notification.ts
import {
EventBusService,
type SubscriberConfig,
type SubscriberArgs,
ProductVariantService,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService,
sendgridService: any
productVariantService: ProductVariantService
export default async function handleRestockNotification({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const sendgridService = container.resolve("sendgridService")
const productVariantService: ProductVariantService =
container.resolve("productVariantService")
// retrieve variant
const variant = await this.productVariantService_.retrieve(
data.variant_id
)
this.sendGridService_.sendEmail({
templateId: "restock-notification",
from: "hello@medusajs.com",
to: data.emails,
dynamic_template_data: {
// any data necessary for your template...
variant,
},
})
}
class RestockNotificationSubscriber {
protected sendGridService_: any
protected productVariantService_: ProductVariantService
constructor({
eventBusService,
sendgridService,
productVariantService,
}: InjectedDependencies) {
this.sendGridService_ = sendgridService
this.productVariantService_ = productVariantService
eventBusService.subscribe(
"restock-notification.restocked",
this.handleRestockNotification
)
}
handleRestockNotification = async ({
variant_id,
emails,
}) => {
// retrieve variant
const variant = await this.productVariantService_.retrieve(
variant_id
)
this.sendGridService_.sendEmail({
templateId: "restock-notification",
from: "hello@medusajs.com",
to: emails,
dynamic_template_data: {
// any data necessary for your template...
variant,
},
})
}
export const config: SubscriberConfig = {
event: "restock-notification.restocked",
context: {
subscriberId: "restock-handler",
},
}
export default RestockNotificationSubscriber
```
Handler methods subscribed to the `restock-notification.restocked` event, which in this case is the `handleRestockNotification` method, receive the following object data payload as a parameter:
The handler function receives in the `data` property of the first parameter the following properties:
- `variant_id`: The ID of the variant that has been restocked.
- `emails`: An array of strings indicating the email addresses subscribed to the restocked variant. Here, you pass it along to the SendGrid plugin directly to send the email to everyone subscribed. If necessary, you can also retrieve the customer of that email using the `CustomerService`'s [retrieveByEmail](../../references/services/classes/CustomerService.mdx#retrievebyemail) method.
In the method, you retrieve the variant by its ID using the `ProductVariantService`, then send the email using the SendGrid plugins' `SendGridService`.
In the handler function, you retrieve the variant by its ID using the `ProductVariantService`, then send the email using the SendGrid plugins' `SendGridService`.

View File

@@ -106,74 +106,63 @@ Medusa also provides official notification plugins that integrate with third-par
Heres an example of a subscriber that uses the SendGrid plugin to send an email to the customer when the order has a new note:
```ts title=src/subscribers/new-note.ts
import {
EventBusService,
import {
type SubscriberConfig,
type SubscriberArgs,
ProductVariantService,
NoteService,
OrderService,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
sendgridService: any
noteService: NoteService
orderService: OrderService
}
export default async function handleNoteCreated({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const noteService: NoteService = container.resolve(
"noteService"
)
const orderService: OrderService = container.resolve(
"orderService"
)
const sendgridService = container.resolve("sendgridService")
class NewNoteSubscriber {
protected sendGridService_: any
protected noteService_: NoteService
protected orderService_: OrderService
// retrieve note by id
const note = await noteService.retrieve(data.id, {
relations: ["author"],
})
constructor({
eventBusService,
sendgridService,
noteService,
orderService,
}: InjectedDependencies) {
this.noteService_ = noteService
this.orderService_ = orderService
this.sendGridService_ = sendgridService
eventBusService.subscribe(
"note.created",
this.handleNoteCreated
)
if (!note || note.resource_type !== "order") {
return
}
handleNoteCreated = async (data) => {
// retrieve note by id
const note = await this.noteService_.retrieve(data.id, {
relations: ["author"],
})
// retrieve note's order
const order = await orderService.retrieve(
note.resource_id
)
if (!note || note.resource_type !== "order") {
return
}
// retrieve note's order
const order = await this.orderService_.retrieve(
note.resource_id
)
if (!order) {
return
}
this.sendGridService_.sendEmail({
templateId: "order-update",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
note_text: note.value,
note_author: note.author.first_name,
note_date: note.created_at,
order_id: order.display_id,
},
})
if (!order) {
return
}
sendGridService.sendEmail({
templateId: "order-update",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
note_text: note.value,
note_author: note.author.first_name,
note_date: note.created_at,
order_id: order.display_id,
},
})
}
export default NewNoteSubscriber
export const config: SubscriberConfig = {
event: NoteService.Events.CREATED,
context: {
subscriberId: "note-created-handler",
},
}
```
</Details>
@@ -206,14 +195,14 @@ You can implement automatic synchronization in Medusa using scheduled jobs. A sc
Logger,
ProductService,
StoreService,
MedusaContainer,
} from "@medusajs/medusa"
import {
ProductSelector,
} from "@medusajs/medusa/dist/types/product"
import { AwilixContainer } from "awilix"
export default async (
container: AwilixContainer,
container: MedusaContainer,
config: Record<string, unknown>
): Promise<void> => {
const logger = container.resolve<Logger>("logger")
@@ -422,76 +411,65 @@ For example, if you're grouping customers with over twenty orders, you can use a
```ts title=src/subscribers/add-custom-to-vip.ts
import {
CustomerGroupService,
CustomerService,
EventBusService,
type SubscriberConfig,
type SubscriberArgs,
OrderService,
CustomerService,
CustomerGroupService,
} from "@medusajs/medusa"
type InjectedDependencies = {
orderService: OrderService
customerService: CustomerService
customerGroupService: CustomerGroupService
eventBusService: EventBusService
}
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const orderService: OrderService = container.resolve(
"orderService"
)
const customerService: CustomerService = container.resolve(
"customerService"
)
const customerGroupService: CustomerGroupService =
container.resolve("customerGroupService")
class AddCustomerToVipSubscriber {
protected orderService_: OrderService
protected customerService_: CustomerService
protected customerGroupService_: CustomerGroupService
// check if VIP group exists
const vipGroup = await customerGroupService.list({
name: "VIP",
}, {
relations: ["customers"],
})
if (!vipGroup.length) {
return
}
constructor({
orderService,
customerService,
customerGroupService,
eventBusService,
}: InjectedDependencies) {
this.orderService_ = orderService
this.customerService_ = customerService
this.customerGroupService_ = customerGroupService
eventBusService.subscribe(
"order.placed",
this.handleOrderPlaced
// retrieve order and its customer
const order = await orderService.retrieve(data.id)
if (!order || !order.customer_id ||
vipGroup[0].customers.find(
(customer) => customer.id === order.customer_id
) !== undefined) {
return
}
// retrieve orders of this customer
const [, count] = await orderService.listAndCount({
customer_id: order.customer_id,
})
if (count >= 20) {
// add customer to VIP group
customerGroupService.addCustomers(
vipGroup[0].id,
order.customer_id
)
}
handleOrderPlaced = async ({ id }) => {
// check if VIP group exists
const vipGroup = await this.customerGroupService_.list({
name: "VIP",
}, {
relations: ["customers"],
})
if (!vipGroup.length) {
return
}
// retrieve order and its customer
const order = await this.orderService_.retrieve(id)
if (!order || !order.customer_id ||
vipGroup[0].customers.find(
(customer) => customer.id === order.customer_id
) !== undefined) {
return
}
// retrieve orders of this customer
const [, count] = await this.orderService_.listAndCount({
customer_id: order.customer_id,
})
if (count >= 20) {
// add customer to VIP group
this.customerGroupService_.addCustomers(
vipGroup[0].id,
order.customer_id
)
}
}
}
export default AddCustomerToVipSubscriber
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
</Details>
@@ -544,93 +522,78 @@ You can alternatively have a Scheduled Job that checks if the number of new prod
```ts title=src/subscribers/send-products-newsletter.ts
import {
CustomerService,
EventBusService,
NoteService,
OrderService,
ProductService,
type SubscriberConfig,
type SubscriberArgs,
ProductService,
StoreService,
CustomerService,
} from "@medusajs/medusa"
import {
ProductSelector,
} from "@medusajs/medusa/dist/types/product"
type InjectedDependencies = {
eventBusService: EventBusService
sendgridService: any
productService: ProductService
storeService: StoreService
customerService: CustomerService
}
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const productService: ProductService = container.resolve(
"productService"
)
const storeService: StoreService = container.resolve(
"storeService"
)
const customerService: CustomerService = container.resolve(
"customerService"
)
const sendgridService = container.resolve("sendgridService")
class SendProductsNewsletterSubscriber {
protected sendGridService_: any
protected productService_: ProductService
protected storeService_: StoreService
protected customerService_: CustomerService
constructor({
eventBusService,
sendgridService,
productService,
storeService,
customerService,
}: InjectedDependencies) {
this.productService_ = productService
this.sendGridService_ = sendgridService
this.storeService_ = storeService
this.customerService_ = customerService
eventBusService.subscribe(
"product.created",
this.handleProductCreated
)
// retrieve store to have access to last send date
const store = await storeService.retrieve()
const productFilters: ProductSelector = {}
if (store.metadata.last_send_date) {
productFilters.created_at = {
gt: new Date(store.metadata.last_send_date as string),
}
}
handleProductCreated = async ({ id }) => {
// retrieve store to have access to last send date
const store = await this.storeService_.retrieve()
const productFilters: ProductSelector = {}
if (store.metadata.last_send_date) {
productFilters.created_at = {
gt: new Date(store.metadata.last_send_date as string),
}
}
const products = await productService.list(
productFilters
)
const products = await this.productService_.list(
productFilters
)
if (products.length > 10) {
// get subscribed customers
const customers = await customerService.list({
metadata: {
is_subscribed: true,
},
})
sendgridService.sendEmail({
templateId: "product-newsletter",
from: "hello@medusajs.com",
to: customers.map((customer) => ({
name: customer.first_name,
email: customer.email,
})),
dynamic_template_data: {
// any data necessary for your template...
products,
},
})
if (products.length > 10) {
// get subscribed customers
const customers = await this.customerService_.list({
metadata: {
is_subscribed: true,
},
})
this.sendGridService_.sendEmail({
templateId: "product-newsletter",
from: "hello@medusajs.com",
to: customers.map((customer) => ({
name: customer.first_name,
email: customer.email,
})),
dynamic_template_data: {
// any data necessary for your template...
products,
},
})
await this.storeService_.update({
metadata: {
last_send_date: new Date(),
},
})
}
await storeService.update({
metadata: {
last_send_date: new Date(),
},
})
}
}
export default SendProductsNewsletterSubscriber
export const config: SubscriberConfig = {
event: ProductService.Events.CREATED,
context: {
subscriberId: "product-create-handler",
},
}
```
</Details>

View File

@@ -921,7 +921,7 @@ To add an interface that allows the admin user to upload digital products, you c
When a customer purchases a digital product, they should receive a link to download it.
To implement that, you first need to listen to the `order.placed` event using a Subscriber. Then, in the handler method of the subscriber, you check for the digital products in the order and obtain the download URLs using the file service's `getPresignedDownloadUrl` method.
To implement that, you first need to listen to the `order.placed` event using a Subscriber. Then, in the subscriber handler function, you check for the digital products in the order and obtain the download URLs using the file service's `getPresignedDownloadUrl` method.
:::note
@@ -954,88 +954,74 @@ Finally, you can send a notification, such as an email, to the customer using th
```ts title=src/subscribers/handle-order.ts
import {
AbstractFileService,
EventBusService,
type SubscriberConfig,
type SubscriberArgs,
OrderService,
AbstractFileService,
} from "@medusajs/medusa"
type InjectedDependencies = {
eventBusService: EventBusService
orderService: OrderService
sendgridService: any
fileService: AbstractFileService
}
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
const orderService: OrderService = container.resolve(
"orderService"
)
const fileService: AbstractFileService = container.resolve(
"fileService"
)
const sendgridService = container.resolve("sendgridService")
class HandleOrderSubscribers {
protected readonly orderService_: OrderService
protected readonly sendgridService_: any
protected readonly fileService_: AbstractFileService
const order = await orderService.retrieve(data.id, {
relations: [
"items",
"items.variant",
"items.variant.product_medias",
],
})
constructor({
eventBusService,
orderService,
sendgridService,
fileService,
}: InjectedDependencies) {
this.orderService_ = orderService
this.sendgridService_ = sendgridService
this.fileService_ = fileService
eventBusService.subscribe(
"order.placed",
this.handleOrderPlaced
)
}
handleOrderPlaced = async (
data: Record<string, any>
) => {
const order = await this.orderService_.retrieve(data.id, {
relations: [
"items",
"items.variant",
"items.variant.product_medias",
],
})
// find product medias in the order
const urls = []
for (const item of order.items) {
if (!item.variant.product_medias.length) {
return
}
await Promise.all([
item.variant.product_medias.forEach(
async (productMedia) => {
// get the download URL from the file service
const downloadUrl = await
this.fileService_.getPresignedDownloadUrl({
fileKey: productMedia.file_key,
isPrivate: true,
})
urls.push(downloadUrl)
}),
])
}
if (!urls.length) {
// find product medias in the order
const urls = []
for (const item of order.items) {
if (!item.variant.product_medias.length) {
return
}
this.sendgridService_.sendEmail({
templateId: "digital-download",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
digital_download_urls: urls,
},
})
await Promise.all([
item.variant.product_medias.forEach(
async (productMedia) => {
// get the download URL from the file service
const downloadUrl = await
fileService.getPresignedDownloadUrl({
fileKey: productMedia.file_key,
isPrivate: true,
})
urls.push(downloadUrl)
}),
])
}
if (!urls.length) {
return
}
sendgridService.sendEmail({
templateId: "digital-download",
from: "hello@medusajs.com",
to: order.email,
dynamic_template_data: {
// any data necessary for your template...
digital_download_urls: urls,
},
})
}
export default HandleOrderSubscribers
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
Notice that regardless of what file service you have installed, you can access it using dependency injection under the name `fileService`.

View File

@@ -256,7 +256,7 @@ You can also extend services if you need to customize a functionality implemente
While implementing your marketplace, you'll typically need to listen to certain events then perform actions asynchronously. For example, you can listen to the `order.placed` event and, when triggered, create child orders of the order, separating ordered items by their associated store.
To listen to events, you need to create Subscribers that subscribe a handler method to an event. In that handler method, you can implement the desired functionality.
To listen to events, you need to create Subscribers that subscribe a handler function to an event. In that handler function, you can implement the desired functionality.
<DocCard item={{
type: 'link',
@@ -274,17 +274,24 @@ To listen to events, you need to create Subscribers that subscribe a handler met
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
```ts title=src/subscribers/orderNotifier.ts
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder)
}
import {
type SubscriberConfig,
type SubscriberArgs,
OrderService,
} from "@medusajs/medusa"
handleOrder = async (data) => {
// TODO perform functionality
}
export default async function handleOrderPlaced({
data, eventName, container, pluginOptions,
}: SubscriberArgs<Record<string, string>>) {
// TODO perform functionality
}
export default OrderNotifierSubscriber
export const config: SubscriberConfig = {
event: OrderService.Events.PLACED,
context: {
subscriberId: "order-placed-handler",
},
}
```
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.

View File

@@ -75,7 +75,7 @@ Medusa uses the fulfillment service whenever a fulfillment action is performed,
![Fulfilling orders with Medusa OMS](https://res.cloudinary.com/dza7lstvk/image/upload/v1695115456/Medusa%20Docs/Diagrams/oms-fulfillment_kfi1iz.jpg)
In addition, Medusa has an event bus that allows you to listen to events and perform a task asynchronously when that event is triggered. So, you can listen to fulfillment-related events, such as the `order.fulfillment_created` event, and handle that event in the subscribed handler method.
In addition, Medusa has an event bus that allows you to listen to events and perform a task asynchronously when that event is triggered. So, you can listen to fulfillment-related events, such as the `order.fulfillment_created` event, and handle that event in the subscribed handler function.
<DocCardList colSize={6} items={[
{

View File

@@ -93,7 +93,7 @@ By implementing the subscription logic within your backend, you can have full co
Implementing the logic depends on your use case, but you'll mainly need to do two things:
1. Perform an action when an order is placed, such as saving subscription details. This can be done using subscribers, which register handler methods to be triggered when an event is emitted. When an order is placed, the `order.placed` event is emitted.
1. Perform an action when an order is placed, such as saving subscription details. This can be done using subscribers, which register handler functions to be triggered when an event is emitted. When an order is placed, the `order.placed` event is emitted.
2. Check daily for subscriptions that need renewal. This can be done using a scheduled job, which is a cron job that can be executed on a defined interval. Within that job, you can define your renewal logic.
<DocCardList colSize={6} items={[

View File

@@ -0,0 +1,38 @@
---
description: "Actions Required for v.1.18"
sidebar_custom_props:
iconName: "server-stack-solid"
---
# v1.18
Version 1.18 of Medusa ships improved [Subscriber](../../development/events/create-subscriber.md) and [Scheduled Job](../../development/scheduled-jobs/create.md) APIs, renamed feature flags, and general big fixes.
## Breaking Changes
### Changed Feature Flags
This version replaced the module-specific feature flags used in the core `@medusajs/medusa` with a feature flag that encapsulates all the work around modularizing Medusa further.
The following feature flags no longer exist:
- `isolate_product_domain` or `MEDUSA_FF_ISOLATE_PRODUCT_DOMAIN`: Previously used as the feature flag for the Product Module in the core.
- `isolate_pricing_domain` or `MEDUSA_FF_ISOLATE_PRICING_DOMAIN`: Previously used as the feature flag for the Pricing Module in the core.
These feature flags are replaced by the `medusa_v2` feature flag name, or the `MEDUSA_FF_MEDUSA_V2` environment variable. All modularization work moving forward will be guarded by this feature flag.
### Subscribing Notification Providers
In previous versions, Notification Provider Services subscribed to events in a Subscriber. Following the improvement in the Subscriber API, you have to use a Loader to subscribe Notification Providers to events as explained [here](../../development/notification/create-notification-provider.md#subscribe-with-loaders).
---
## How to Update
Run the following command in your project:
```bash npm2yarn
npm install @medusajs/medusa@1.18
```
To avoid unexpected issues with dependencies, it's also recommended to update all other Medusa plugins or packages you have installed.

View File

@@ -12,6 +12,7 @@
* - To add a sidebar title, add in customProps sidebar_is_title: true
* - To add a group headline, add in customProps sidebar_is_group_headline: true
* - To add a coming soon link (with a badge), add in customProps sidebar_is_soon: true
* - To add a badge, add in customProps sidebar_badge with its value being the props to pass to the Badge component.
*/
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
@@ -1572,9 +1573,25 @@ module.exports = {
label: "Create an Event Module",
},
{
type: "doc",
id: "development/events/create-subscriber",
type: "category",
link: {
type: "doc",
id: "development/events/create-subscriber",
},
label: "Create a Subscriber",
items: [
{
type: "doc",
id: "development/events/create-subscriber-deprecated",
label: "Create a Subscriber",
customProps: {
sidebar_badge: {
variant: "orange",
children: "Deprecated",
},
},
},
],
},
],
},
@@ -1618,9 +1635,25 @@ module.exports = {
},
},
{
type: "doc",
id: "development/scheduled-jobs/create",
type: "category",
link: {
type: "doc",
id: "development/scheduled-jobs/create",
},
label: "Create a Scheduled Job",
items: [
{
type: "doc",
id: "development/scheduled-jobs/create-deprecated",
label: "Create a Scheduled Job",
customProps: {
sidebar_badge: {
variant: "orange",
children: "Deprecated",
},
},
},
],
},
],
},

View File

@@ -157,8 +157,9 @@ export default function DocSidebarItemCategory({
customProps?.sidebar_is_group_divider && "sidebar-group-divider",
customProps?.sidebar_is_divider_line && "sidebar-divider-line",
customProps?.sidebar_is_back_link && "sidebar-back-link",
customProps?.sidebar_is_soon &&
"sidebar-soon-link sidebar-badge-wrapper",
(customProps?.sidebar_is_soon || customProps?.sidebar_badge) &&
"sidebar-badge-wrapper",
customProps?.sidebar_is_soon && "sidebar-soon-link",
!customProps?.sidebar_is_title &&
"[&_.sidebar-item-icon]:w-[20px] [&_.sidebar-item-icon]:h-[20px]",
!customProps?.sidebar_is_title &&
@@ -221,6 +222,12 @@ export default function DocSidebarItemCategory({
Soon
</Badge>
)}
{customProps?.sidebar_badge && (
<Badge
{...customProps.sidebar_badge}
className={`sidebar-soon-badge`}
/>
)}
</div>
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>

View File

@@ -29,8 +29,9 @@ export default function DocSidebarItemHtml({
customProps?.sidebar_is_group_divider && "sidebar-group-divider",
customProps?.sidebar_is_divider_line && "sidebar-divider-line",
customProps?.sidebar_is_back_link && "sidebar-back-link",
customProps?.sidebar_is_soon &&
"sidebar-soon-link sidebar-badge-wrapper",
(customProps?.sidebar_is_soon || customProps?.sidebar_badge) &&
"sidebar-badge-wrapper",
customProps?.sidebar_is_soon && "sidebar-soon-link",
!customProps?.sidebar_is_title &&
"[&_.sidebar-item-icon]:w-[20px] [&_.sidebar-item-icon]:h-[20px]",
!customProps?.sidebar_is_title &&
@@ -55,6 +56,12 @@ export default function DocSidebarItemHtml({
Soon
</Badge>
)}
{customProps?.sidebar_badge && (
<Badge
{...customProps.sidebar_badge}
className={`sidebar-soon-badge`}
/>
)}
</li>
)
}

View File

@@ -67,6 +67,12 @@ export default function DocSidebarItemLink({
)}
{label}
{!isInternalLink && <IconExternalLink />}
{customProps?.sidebar_badge && (
<Badge
{...customProps.sidebar_badge}
className={`sidebar-soon-badge`}
/>
)}
</Link>
{customProps?.sidebar_is_soon && (
<Badge variant="purple" className={`sidebar-soon-badge`}>

View File

@@ -26,6 +26,8 @@ declare module "@medusajs/docs" {
PropSidebarItemCategory,
PropSidebarItemLink,
PropSidebarItemHtml,
SidebarCategoriesShorthand,
SidebarItemConfig,
} from "@docusaurus/plugin-content-docs"
import { BadgeProps, ButtonType, ButtonVariants } from "docs-ui"
import { IconProps } from "@medusajs/icons/dist/types"
@@ -55,6 +57,7 @@ declare module "@medusajs/docs" {
sidebar_is_group_divider?: boolean
sidebar_is_divider_line?: boolean
sidebar_is_back_link?: boolean
sidebar_badge?: BadgeProps
}
}