Create an Open Source Ecommerce Marketplace Part 2: Vender-Specific Orders

Within the first part of this collection, you discovered about Medusa Extender and tips on how to use it to create a market ecommerce platform. The primary half demonstrated tips on how to hyperlink shops to customers so that every new person has their very own retailer, then tips on how to hyperlink merchandise to a retailer in order that the person can see and handle solely their merchandise.

On this half, you’ll learn to hyperlink orders to their respective shops. This can cowl use circumstances the place a buyer purchases merchandise from a number of shops, and tips on how to handle the general standing of that order.

Yow will discover the complete code for this tutorial in this GitHub repository.

You may alternatively use the Medusa Market plugin as indicated within the README of the GitHub repository. If you happen to’re already utilizing it be certain to replace to the newest model:

npm set up medusa-marketplace@newest
Enter fullscreen mode

Exit fullscreen mode


It’s assumed that you simply’ve adopted together with the primary a part of the collection earlier than persevering with this half. If you happen to haven’t, please begin from there.

If you happen to don’t have the Medusa Admin put in, it’s endorsed that you simply set up it so that you could simply view merchandise and orders, amongst different functionalities.

Alternatively, you should use Medusa’s Admin APIs to entry the info in your server. Nonetheless, the remainder of the tutorial will principally showcase options by way of the Medusa Admin.

So as to add merchandise with pictures you additionally want a file service plugin like MinIO put in in your Medusa server. You too can verify the documentation for extra choices for a file service.

This tutorial moreover makes use of the Next.js starter storefront to showcase inserting orders. That is additionally non-obligatory and you’re free to make use of different storefronts or Medusa’s Storefront APIs as a substitute.

Lastly, this half makes use of model 1.6.5 of the Medusa Extender which launched new options and a greater developer expertise.

You probably have an outdated model of the Medusa Extender put in, replace the extender in your Medusa server:

npm set up medusa-extender@1.6.5
Enter fullscreen mode

Exit fullscreen mode

Change the content material of tsconfig.json to the next:

    "compilerOptions": {
        "module": "CommonJS",
        "declaration": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "moduleResolution": "node",
        "goal": "es2017",
        "sourceMap": true,
        "skipLibCheck": true,
        "allowJs": true,
        "outDir": "dist",
        "rootDir": "src",
        "esModuleInterop": true
    "embrace": [
    "exclude": [
Enter fullscreen mode

Exit fullscreen mode

And alter scripts in bundle.json to the next:

"scripts": {
    "seed": "medusa seed -f ./information/seed.json",
    "construct": "rm -rf dist && ./node_modules/.bin/tsc -p tsconfig.json",
    "begin": "npm run construct && NODE_ENV=growth node ./dist/major.js",
    "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
    "begin:watch": "nodemon --watch './src/**/*.ts' --exec 'ts-node' ./src/major.ts",
    "begin:prod": "npm run construct && NODE_ENV=manufacturing node dist/major"
Enter fullscreen mode

Exit fullscreen mode

What You Will Be Constructing

As talked about within the introduction, this a part of the collection will information you thru linking orders to shops. To do this, you’ll override the Order mannequin so as to add the relation between it and the Retailer mannequin.

In a market, prospects ought to have the ability to buy merchandise from a number of distributors on the similar time. So, you’ll additionally add a subscriber that, when an order is positioned, will create “baby” orders. Youngster orders will likely be linked to a retailer, will solely have merchandise from the unique order that belongs to that retailer, and will likely be linked to the guardian order.

For that purpose, you’ll additionally add a parent-child relation between Order fashions. This relation will moreover allow you to handle the guardian order’s standing based mostly on the statuses of kid orders.

Furthermore, you’ll add a filter that ensures when a person retrieves the checklist of orders of their retailer, solely orders that belong to their retailer are retrieved. This may also permit a brilliant admin who doesn’t belong to any retailer to trace the guardian orders.

Add Relations to the Order Mannequin

Step one is including the relation between the Order and Retailer mannequin, and between Order fashions. To do this, you have to override the Order mannequin.

Create a brand new listing src/modules/order which is able to maintain all order-related courses that you simply create all through this tutorial.

Then, create the file src/modules/order/order.entity.ts with the next content material:

import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from "typeorm";

import { Entity as MedusaEntity } from "medusa-extender";
import { Order as MedusaOrder } from "@medusajs/medusa";
import { Retailer } from "../retailer/entities/retailer.entity";

@MedusaEntity({override: MedusaOrder})
export class Order extends MedusaOrder {
    @Column({ nullable: true })
    store_id: string;

    @Column({ nullable: false })
    order_parent_id: string;

    @ManyToOne(() => Retailer, (retailer) => retailer.orders)
    @JoinColumn({ title: 'store_id' })
    retailer: Retailer;

    @ManyToOne(() => Order, (order) => order.youngsters)
    @JoinColumn({ title: 'order_parent_id' })
    guardian: Order;

    @OneToMany(() => Order, (order) =>
    @JoinColumn({ title: 'id', referencedColumnName: 'order_parent_id' })
    youngsters: Order[];
Enter fullscreen mode

Exit fullscreen mode

You add the mannequin Order which overrides and extends Medusa’s Order mannequin. On this mannequin, you add 2 new columns: store_id and order_parent_id. The store_id column will likely be used for the many-to-one relation between the Order mannequin and Retailer mannequin, which you display by way of the retailer property.

The order_parent_id column will likely be used for the many-to-one and one-to-many relation between Order fashions. This results in guardian and youngsters properties ensuing from these relations.

Subsequent, in src/modules/retailer/entities/retailer.entity.ts add a brand new import for the Order mannequin initially of the file:

import { Order } from '../../order/order.entity';
Enter fullscreen mode

Exit fullscreen mode

And contained in the Retailer class add the relation to the Order mannequin:

@OneToMany(() => Order, (order) => order.retailer)
@JoinColumn({ title: 'id', referencedColumnName: 'store_id' })
orders: Order[];
Enter fullscreen mode

Exit fullscreen mode

Add a New Migration

To mirror the brand new columns within the database, you have to create a migration file within the order module.

As migration recordsdata have the format <timestamp>-order.migration.ts, a migration file is exclusive to you so you have to create it your self.

Fortunately, the brand new replace of Medusa Extender added plenty of useful CLI instructions to make redundant duties simpler for you. You may generate the migration file utilizing the next command:

./node_modules/.bin/medex g -mi order
Enter fullscreen mode

Exit fullscreen mode

This can create the file src/modules/order/<timestamp>-order.migration.ts for you. Open that file and substitute the up and down strategies with the next implementation:

public async up(queryRunner: QueryRunner): Promise<void> {
    const question = `
        ALTER TABLE public."order" ADD COLUMN IF NOT EXISTS "store_id" textual content; 
        ALTER TABLE public."order" ADD COLUMN IF NOT EXISTS "order_parent_id" textual content;
        ALTER TABLE public."order" ADD CONSTRAINT "FK_8a96dde86e3cad9d2fcc6cb171f87" FOREIGN KEY ("order_parent_id") REFERENCES "order"("id") ON DELETE CASCADE ON UPDATE CASCADE;
    await queryRunner.question(question);

public async down(queryRunner: QueryRunner): Promise<void> {
    const question = `
        ALTER TABLE public."order" DROP COLUMN "store_id";
        ALTER TABLE public."order" DROP COLUMN "order_parent_id";
        ALTER TABLE public."order" DROP FOREIGN KEY "FK_8a96dde86e3cad9d2fcc6cb171f87cb2"; 
    await queryRunner.question(question);
Enter fullscreen mode

Exit fullscreen mode

The up technique provides the columns store_id and order_parent_id to the order desk with a international key, and the down technique removes these columns and international key from the order desk.

Run Migrations

A part of the Medusa Extender new CLI instructions is the [migrate command]( which seems to be contained in the src and dist directories for each recordsdata ending with .migration.js and JavaScript recordsdata inside a migrations sub-directory of the two directories.

You may check with the Medusa Marketplace plugin to study how one can run migrations from it.

Then, if the migrations inside these recordsdata haven’t been run earlier than it runs or present them based mostly on the choice you cross to the command.

Because the migration file you’ve created is a TypeScript file, you have to transpile it to JavaScript first earlier than migrating the modifications. So, run the next command:

npm run construct
Enter fullscreen mode

Exit fullscreen mode

This can transpile all TypeScript recordsdata contained in the src listing into JavaScript recordsdata contained in the dist listing.

Lastly, run the migration with the next command:

./node_modules/.bin/medex migrate --run
Enter fullscreen mode

Exit fullscreen mode

If you happen to get an error about duplicate migrations due to migrations from the earlier a part of this collection, go forward and take away the outdated ones from the dist listing and check out working the command once more.

If you happen to verify your database as soon as the migration is run efficiently, you may see that the two new columns have been added to the order desk.

Override OrderRepository

Because you’ve overridden the Order mannequin, it is best to override OrderRepository to make it possible for when an order is retrieved, the overridden mannequin is used.

Create the file src/modules/order/order.repository.ts with the next content material:

import { Repository as MedusaRepository, Utils } from "medusa-extender";

import { EntityRepository } from "typeorm";
import { OrderRepository as MedusaOrderRepository } from "@medusajs/medusa/dist/repositories/order";
import { Order } from "./order.entity";

@MedusaRepository({override: MedusaOrderRepository})
export class OrderRepository extends Utils.repositoryMixin<Order, MedusaOrderRepository>(MedusaOrderRepository) {}
Enter fullscreen mode

Exit fullscreen mode

Record Orders By Retailer

On this part, you’ll retrieve orders based mostly on the shop of the at the moment logged-in person.

Modify LoggedInUserMiddleware

Within the earlier half, you created a middleware LoggedInUserMiddleware which checks if a person is logged in and registers them within the scope. This lets you entry the logged-in person from providers and subscribers, and this was used to retrieve merchandise based mostly on the logged-in person’s retailer.

Nonetheless, the earlier implementation impacts each storefront and admin routes in Medusa. This will trigger inconsistencies for purchasers accessing the storefront.

To make sure that the logged-in person is simply added to the scope for admin routes, change the code in src/modules/person/middlewares/loggedInUser.middleware.ts to the next content material:

import { MedusaAuthenticatedRequest, MedusaMiddleware, Middleware } from 'medusa-extender';
import { NextFunction, Response } from 'specific';

import UserService from '../providers/person.service';

@Middleware({ requireAuth: true, routes: [{ method: "all", path: '*' }] })
export class LoggedInUserMiddleware implements MedusaMiddleware {
    public async eat(req: MedusaAuthenticatedRequest, res: Response, subsequent: NextFunction): Promise<void> {
        let loggedInUser = null;
        if (req.person && req.person.userId && **/^/admin/.take a look at(req.originalUrl)**) {
            const userService = req.scope.resolve('userService') as UserService;
            loggedInUser = await userService.retrieve(req.person.userId, {
                choose: ['id', 'store_id'],
            loggedInUser: {
                resolve: () => loggedInUser,
Enter fullscreen mode

Exit fullscreen mode

The brand new change provides a brand new situation to verify if the present route begins with /admin. If it does and if the person is logged in, the logged-in person is added to the scope. In any other case, the worth of loggedInUser within the scope will likely be null.

Though you may specify the trail of the middleware to be /admin/* to register this middleware for admin routes solely, this strategy is critical as a result of if the loggedInUser just isn’t registered within the scope an error will likely be thrown in any service or subscriber that makes use of it.

Override the OrderService

The Medusa server makes use of the strategy [buildQuery_]( in OrderService to construct the question essential to retrieve the orders from the database. You’ll be overriding the OrderService, and significantly the buildQuery_ technique so as to add a selector situation for the store_id if there’s a at the moment logged-in person that has a retailer.

Create the file src/modules/order.service.ts with the next content material:

import { EntityManager } from 'typeorm';
import { OrderService as MedusaOrderService } from "@medusajs/medusa/dist/providers";
import { OrderRepository } from './order.repository';
import { Service } from 'medusa-extender';
import { Consumer } from "../person/entities/person.entity";

sort InjectedDependencies = {
    supervisor: EntityManager;
    orderRepository: typeof OrderRepository;
    customerService: any;
    paymentProviderService: any;
    shippingOptionService: any;
    shippingProfileService: any;
    discountService: any;
    fulfillmentProviderService: any;
    fulfillmentService: any;
    lineItemService: any;
    totalsService: any;
    regionService: any;
    cartService: any;
    addressRepository: any;
    giftCardService: any;
    draftOrderService: any;
    inventoryService: any;
    eventBusService: any;
    loggedInUser: Consumer;
    orderService: OrderService;

@Service({ scope: 'SCOPED', override: MedusaOrderService })
export class OrderService extends MedusaOrderService {
    non-public readonly supervisor: EntityManager;
    non-public readonly container: InjectedDependencies;

    constructor(container: InjectedDependencies) {

        this.supervisor = container.supervisor;
        this.container = container;

    buildQuery_(selector: object, config: {relations: string[], choose: string[]}): object {
        if (this.container.loggedInUser && this.container.loggedInUser.store_id) {
            selector['store_id'] = this.container.loggedInUser.store_id;


        config.relations = config.relations ?? []

        config.relations.push("youngsters", "guardian", "retailer")

        return tremendous.buildQuery_(selector, config);
Enter fullscreen mode

Exit fullscreen mode

Inside buildQuery_ you first verify if there’s a logged-in person and if that person has a retailer. If true, you add to the selector parameter (which is used to filter out information from the database) a brand new property store_id and set its worth to the shop ID of the logged-in person.

You additionally add to the chosen fields store_id, and also you add the youngsters, guardian, and retailer relations to be retrieved together with the order.

Create the Order Module

The very last thing earlier than you may take a look at out the modifications you’ve simply made is you have to create an order module that imports these new courses you created into Medusa.

Create the file src/modules/order/order.module.ts with the next content material:

import { Module } from 'medusa-extender';
import { Order } from './order.entity';
import { OrderMigration1652101349791 } from './1652101349791-order.migration';
import { OrderRepository } from './order.repository';
import { OrderService } from './order.service';
import { OrderSubscriber } from './order.subscriber';

    imports: [Order, OrderRepository, OrderService, OrderMigration1652101349791]
export class OrderModule {}
Enter fullscreen mode

Exit fullscreen mode

Please discover that you have to change the import and sophistication title of the migration class based mostly in your migration’s title.

Then, import this new module initially of the file src/major.ts:

import { OrderModule } from './modules/order/order.module';
Enter fullscreen mode

Exit fullscreen mode

And contained in the array handed to the load technique cross the OrderModule:

await new Medusa(__dirname + '/../', expressInstance).load([
Enter fullscreen mode

Exit fullscreen mode

Check it Out

To check it out, begin the server with the next command:

npm begin
Enter fullscreen mode

Exit fullscreen mode

This can begin the server on port 9000

Then, begin your Medusa admin and log in with the person you created within the first a part of the collection. It’s best to see on the orders web page that there are not any orders for this person.

If you happen to’re utilizing Medusa’s APIs you may view the orders by sending a GET request to localhost:9000/admin/orders.

Deal with Order Place Occasion

On this part, you’ll add a subscriber to deal with the order.positioned occasion that’s triggered every time a brand new order is positioned by a buyer. As talked about earlier within the tutorial, you’ll use this handler to create baby orders for every retailer that the client bought merchandise from of their order.

Create a brand new file src/modules/order/order.subscriber.ts with the next content material:

import { EventBusService, OrderService } from "@medusajs/medusa/dist/providers";
import { LineItem, OrderStatus } from '@medusajs/medusa';

import { EntityManager } from "typeorm";
import { LineItemRepository } from '@medusajs/medusa/dist/repositories/line-item';
import { Order } from './order.entity';
import { OrderRepository } from "./order.repository";
import { PaymentRepository } from "@medusajs/medusa/dist/repositories/fee";
import { Product } from "../product/entities/product.entity";
import { ProductService } from './../product/providers/product.service';
import { ShippingMethodRepository } from "@medusajs/medusa/dist/repositories/shipping-method";
import { Subscriber } from 'medusa-extender';

sort InjectedDependencies = { 
  eventBusService: EventBusService;
  orderService: OrderService;
  orderRepository: typeof OrderRepository;
  productService: ProductService;
  supervisor: EntityManager;
  lineItemRepository: typeof LineItemRepository;
  shippingMethodRepository: typeof ShippingMethodRepository;
  paymentRepository: typeof PaymentRepository;

export class OrderSubscriber {
    non-public readonly supervisor: EntityManager;
    non-public readonly eventBusService: EventBusService;
    non-public readonly orderService: OrderService;
    non-public readonly orderRepository: typeof OrderRepository;
    non-public readonly productService: ProductService;
    non-public readonly lineItemRepository: typeof LineItemRepository;
    non-public readonly shippingMethodRepository: typeof ShippingMethodRepository;

    constructor({ eventBusService, orderService, orderRepository, productService, supervisor, lineItemRepository, shippingMethodRepository, paymentRepository}: InjectedDependencies) {
        this.eventBusService = eventBusService;
        this.orderService = orderService;
        this.orderRepository = orderRepository;
        this.productService = productService;
        this.supervisor = supervisor;
        this.lineItemRepository = lineItemRepository;
        this.shippingMethodRepository = shippingMethodRepository;

    non-public async handleOrderPlaced({ id }: {id: string}): Promise<void> {
        //create baby orders
        //retrieve order
        const order: Order = await this.orderService.retrieve(id, {
          relations: ['items', 'items.variant', 'cart', 'shipping_methods', 'payments']
        //group gadgets by retailer id
        const groupedItems = {};

        for (const merchandise of order.gadgets) {
          const product: Product = await this.productService.retrieve(merchandise.variant.product_id, { choose: ['store_id']});
          const store_id = product.store_id;
          if (!store_id) {
          if (!groupedItems.hasOwnProperty(store_id)) {
            groupedItems[store_id] = [];


        const orderRepo = this.supervisor.getCustomRepository(this.orderRepository);
        const lineItemRepo = this.supervisor.getCustomRepository(this.lineItemRepository);
        const shippingMethodRepo = this.supervisor.getCustomRepository(this.shippingMethodRepository);

        for (const store_id in groupedItems) {
          //create order
          const childOrder = orderRepo.create({
            order_parent_id: id,
            store_id: store_id,
            cart_id: null,
            cart: null,
            id: null,
            shipping_methods: []
          }) as Order;
          const orderResult = await;

          //create transport strategies
          for (const shippingMethod of order.shipping_methods) {
            const newShippingMethod = shippingMethodRepo.create({
              id: null,
              cart_id: null,
              cart: null,


          //create line gadgets
          const gadgets: LineItem[] = groupedItems[store_id];
          for (const merchandise of gadgets) {
            const newItem = lineItemRepo.create({
              id: null,
              cart_id: null
Enter fullscreen mode

Exit fullscreen mode

Right here’s a abstract of this code:

  • Within the constructor, you register the strategy handleOrderPlaced as a handler for the occasion order.positioned.
  • Inside handleOrderPlaced you first retrieve the order utilizing the ID handed to the strategy with the required relations for the creation of kid orders.
  • You then loop over the gadgets bought within the order and group then inside the article groupedItems with the important thing being the distinctive retailer IDs and the worth being an array of things.
  • You then loop over the keys in groupedItems and create a childOrder for every retailer. The kid orders have the identical information because the guardian order however they’ve parent_id set to the ID of the guardian order and store_id set to the ID of the shop it’s related to.
  • For every baby order, you have to create shippingMethods which might be equivalent to the transport strategies of the guardian order however related to the kid order.
  • For every baby order, you have to add the gadgets that have been within the order for that particular retailer, as every vendor ought to solely see the gadgets ordered from their retailer.

Ensure you have Redis put in and configured with Medusa for this subscriber to work.

Check it Out

To check it out, first, restart your Medusa server, then run the storefront that you simply’re utilizing to your retailer and add one of many merchandise you created for a vendor to the cart then place an order.

Order Summary

If you happen to then open the admin panel once more and verify orders, it is best to see a brand new order on the orders web page of the seller. If you happen to open it you’ll see particulars associated to the order.

Order Details on Admin

Attempt creating extra customers and including merchandise for various customers and shops. You’ll see that every person will see the order with gadgets solely associated to their retailer.

Deal with Order Standing Modified Occasions

To make sure that the standing of the guardian order modifications as needed with the change of standing of the kid orders, it’s essential to hearken to the occasions triggered every time an order’s standing modifications.

Within the constructor of the OrderSubscriber class in src/modules/order/order.subscriber.ts add the next code:

//add handler for various standing modifications
Enter fullscreen mode

Exit fullscreen mode

This provides the identical technique checkStatus because the order handler of the occasions Canceled, Up to date, and Accomplished of an order.

Subsequent, add inside the category the next strategies:

public async checkStatus({ id }: {id: string}): Promise<void> {
    //retrieve order
    const order: Order = await this.orderService.retrieve(id);

    if (order.order_parent_id) {
      //retrieve guardian
      const orderRepo = this.supervisor.getCustomRepository(this.orderRepository);
      const parentOrder = await this.orderService.retrieve(order.order_parent_id, {
          relations: ['children']

      const newStatus = this.getStatusFromChildren(parentOrder);
      if (newStatus !== parentOrder.standing) {
          change (newStatus) {
            case OrderStatus.CANCELED:
            case OrderStatus.ARCHIVED:
            case OrderStatus.COMPLETED:
              parentOrder.standing = newStatus;
              parentOrder.fulfillment_status = newStatus;
              parentOrder.payment_status = newStatus;

public getStatusFromChildren (order: Order): string {
    if (!order.youngsters) {
        return order.standing;

    //gather all statuses
    let statuses = => baby.standing);

    //take away duplicate statuses
    statuses = [ Set(statuses)];

    if (statuses.size === 1) {
        return statuses[0];

    //take away archived and canceled orders
    statuses = statuses.filter((standing) => standing !== OrderStatus.CANCELED && standing !== OrderStatus.ARCHIVED);

    if (!statuses.size) {
        //all baby orders are archived or canceled
        return OrderStatus.CANCELED;

    if (statuses.size === 1) {
        return statuses[0];

    //verify if any order requires motion
    const hasRequiresAction = statuses.some((standing) => standing === OrderStatus.REQUIRES_ACTION);
    if (hasRequiresAction) {
        return OrderStatus.REQUIRES_ACTION;

    //since a couple of standing is left and we filtered out canceled, archived,
    //and requires motion statuses, solely pending and full left. So, return pending
    return OrderStatus.PENDING;
Enter fullscreen mode

Exit fullscreen mode

Right here’s a abstract of the code snippet:

  1. In checkStatus you first retrieve the order’s information utilizing its ID.
  2. You verify if the order has a guardian order. That is to keep away from dealing with occasions triggered for the guardian order as it’s not needed.
  3. You then retrieve the guardian order with its relation to its youngsters orders.
  4. You make use of one other technique getStatusFromChildren to infer the standing of the guardian order from the youngsters:
    1. You first retrieve all statuses from the kid orders then take away any duplicates.
    2. If the results of eradicating the duplicates results in just one standing, then it implies that all orders have the identical standing and the guardian can have that very same standing as properly.
    3. In any other case, if there’s a couple of standing, you take away the archived and canceled orders.
    4. If this results in no statuses, which means that all youngsters are both canceled or archived and the guardian ought to have the identical standing. The code snippet defaults to the “canceled” standing right here however you may change that.
    5. In any other case, if there’s solely standing left after eradicating canceled and archived orders you come that standing.
    6. In any other case, if there’s a couple of standing left, you verify if a type of statuses is requires_action and return that because the standing.
    7. If there’s no requires_action standing you may infer there are solely pending and full orders left. Because it’s logical to contemplate that if at the very least one order is pending then you may take into account the guardian order pending, you default to that standing.
  5. After retrieving the deduced standing of the guardian order, if that standing is totally different than the present standing of the guardian order, you replace its standing. Relying on the brand new standing, you both use present strategies within the OrderService to replace the standing, or manually set the standing within the order.

Check it Out

Restart your Medusa server. Then, open the Medusa admin to the order you simply created earlier. Attempt canceling the order by clicking on the highest 3 dots then clicking “Cancel Order”.

Cancel Order

After canceling the order, sign off and log in with a brilliant admin person. By default, the tremendous admin person is the person created whenever you seed the database initially of your Medusa server arrange. This person has the e-mail “” and password “supersecret”.

If you happen to open the guardian order you’ll see that it’s now canceled as properly.

Canceled Parent Order


By following these 2 components, it is best to now have shops for every person with merchandise and orders linked to them.

Within the subsequent a part of the collection, you’ll study tips on how to add a couple of person to a retailer, tips on how to add tremendous admins, and tips on how to customise different settings.

Ought to you’ve any points or questions associated to Medusa, then be at liberty to succeed in out to the Medusa crew by way of Discord.

Add a Comment

Your email address will not be published. Required fields are marked *