Git Product home page Git Product logo

medusa's Introduction

Medusa logo

Medusa

Building blocks for digital commerce

Medusa is released under the MIT license. PRs welcome!

Follow @medusajs Discord Chat

Getting Started

Visit the Quickstart Guide to set up a server.

Visit the Docs to learn more about our system requirements.

What is Medusa

Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.

Learn more about Medusa’s architecture and commerce modules in the Docs.

Roadmap, Upgrades & Plugins

You can view the planned, started and completed features in the Roadmap discussion.

Follow the Upgrade Guides to keep your Medusa project up-to-date.

Check out all available Medusa plugins.

Community & Contributions

The community and core team are available in GitHub Discussions, where you can ask for support, discuss roadmap, and share ideas.

Our Contribution Guide describes how to contribute to the codebase and Docs.

Join our Discord server to meet other community members.

Other channels

License

Licensed under the MIT License.

medusa's People

Contributors

adrien2p avatar carlos-r-l-rodrigues avatar chemicalkosek avatar dependabot[bot] avatar dwene avatar fpolic avatar github-actions[bot] avatar josetr avatar josipmatichr avatar kasperkristensen avatar medusanick avatar mkaychuks avatar olivermrbl avatar patrick-medusajs avatar pepijn-vanvlaanderen avatar pevey avatar pkorsholm avatar richardwardza avatar riqwan avatar saurabh042 avatar sebastiannicolajsen avatar shahednasser avatar shreedharhegde99 avatar sradevski avatar srindom avatar stephixone avatar timothy22000 avatar vilfredsikker avatar wangjue666 avatar zakariaelas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

medusa's Issues

Ensure line item bundles are fulfilled from the same provider

We need to add validation (maybe in Cart- or OrderService), that ensures the products in a line item bundle is fulfilled from the same provider. This means, thatlineItem.content[0]...lineItem.content[n-1] should all belong to the same shipping method / profile.

Our OrderService currently only support this scenario, hence the need for the validation.

Product Variant Service

Product variant service manages the variants of the products.

The product variant service should be able to:

  • Create a product variant
  • Update a product variant
  • Delete a product variant
  • Publish a product variant
  • Retrieve a product variant
  • Retrieve all product variants
  • (Decorate a product variant)

[ProductVariantService]: Deleting should remove from product

This has some commonality with #61 as doing this would normally require the product variant service to depend on the ProductService, which creates a circular dependency. We could consider making all product variant operations have to go through the ProductService to avoid this issue, or we can consider implement one of the proposed solutions in #61.

Build-openapi script fails to complete

Description

When running the build-openapi script it fails to generate as some path parameter is not appearing in the path.

Expected result

Running the script generates the expected docs

Actual result

the script throws an error

Skærmbillede 2021-06-09 kl  12 52 20

Discount Service

The discount service will help calculate discounts on carts and orders.

The service should be able to:

  • Create promo codes with percentage discounts and fixed discounts.
  • Create promo codes that give free shipping.

Fixed discounts may also work as gift cards - needs more research.

[PaymentProviderService]: Add updateSession and createSession

updateSession(paymentSession, cart)
createSession(providerId, cart)

The paymentSession argument in updateSession contains { provider_id, data } allowing the payment provider service to retrieve the payment provider via provider_id and give the method the data needed to update by passing data.

The createSession method will call the createSession method of the provider identified by providerId.

[service]: RegionService

The current region service file contains a few method signatures that should be included:

paymentProviders // retrieve the payment providers in the region
fulfillmentProviders // retrieve the fulfillmentProviders in the region
setCurrency // set the region's currency
setTaxRate // set the region's taxRate
putFulfillmentProvider // add a fulfillment provider to the region 
putPaymentProvider // add a payment provider to the region
removeFulfillmentProvider // remove a fulfillment provider to the region
removePaymentProvider // remove a payment provider to the region
listShippingMethods // this may not be needed - done via the shipping option service

[service] : ShippingProfileService

The shipping profile service will hold shipping options for each of the products in a store. A profile can have any number of products associated, e.g. you could have one profile that holds all made to measure "Suits" and another profile that holds "Shirts". Within the profiles the store operator can create different shipping options that each have a price along with a fulfillment provider. A product can only belong to one shipping profile - this means that if you want to restrict what regions a product can be shipped to you have to create a custom profile for that product and only define shipping rates for the regions that the product can be shipped to.

Adding products to profiles should happen in this service not in the ProductService. Therefore it is also this service that is responsible for ensuring that a product only belongs to one service.

Schema

name: name of the profile
products: array of product ids that belong to the profile
shipping_options: array of shipping option ids that belong to the profile

Medusa Middleware API

The Medusa Middleware API allows plugin and project developers to inject middleware into the API at various points.

For example, the medusa-plugin-permissions plugin provides middleware that checks if a user has permission to perform a certain operation. To do so the middleware needs to know who the authenticated user is. This information is not available until router.use(middlewares.authenticate) is called, but the plugin/project developer cannot control when that happens.

Medusa Middleware API will look for the file /api/middlewares.js in a project's plugins or root. If the file exists a number of exported functions can define when a plugin's middlewares are run. The initial methods are:

preAuth
postAuth

The return methods should be a middleware function.

The methods will be called on bootstrap and saved to the container:

const postAuthMiddleware = []
const pluginMiddleware = require("[plugindir]/api/middlewares.js")
if (pluginMiddleware.postAuth) {
  postAuthMiddleware.push(pluginMiddleware.postAuth())
}

container.register(postAuth)

In the core middleware we should have a file that then calls the different middlewares:

const preAuth = (router) => {
  const middlewares = req.scope.resolve("postAuth")
  middlewares.forEach(mw => {
    router.use(mw)
  })
}

(Note: not sure if this works)
Maybe use something like https://github.com/blakeembrey/compose-middleware

[ProductVariantService] : Improve reliability of option management

There are a few shortcomings in the product variant service's management of option values.

Firstly, right now it is possible to add an option value to a variant despite the fact that the product the variant belongs to doesn't have an option title for that value. I.e. a product can have an option with title "Size", but the ProductVariantService would allow us to add both a "Size" and a "Color" option.

Likewise, the ProductVariantService would allow us to delete the "Size" option, even though the product has the option defined.

We need to enforce that the product variants always have the same number of option values as the number of its top level product's options.

The reason why we have failed to do this initially is because injecting the ProductService as a dependency in the ProductVariantService would create a circular dependency. We therefore need to consider an alternative solution where the validation is done prior to making the call to addOptionValue, or we could inject the ProductModel as a dependency.

I feel that the latter is safest as that takes away the requirement from the interface layer to do validation, however, we haven't injected models not "belonging" to the service anywhere else, so we should maybe consider what implications this implementation could have.

[medusa]: Soft-delete conflicts with uniqueness

Description

When soft-deleting a product the handle will be kept as a unique identifier in the database. This means, that we can't create a new product with the same handle as the previously deleted product handle.

Steps to reproduce

  1. Delete a product
  2. Create a product with the same handle as deleted product

Expected result

No errors.

Actual result

Typeorm will throw an error due to conflicting product handles

image

[service] : Refactor 404 NOT_FOUND checks

Refactor all the 404 NOT_FOUND checks, we are currently doing after using someService.retrieve(id) in a function. This should be put into the retrieve function to avoid tons of code duplication.

Authentication for customers

We need to offer authentication for customers. I.e. we need a /store/auth endpoint where you can log in and out.

Tax Provider API

We need to allow store operators to define their own tax calculation services. You can either use the default tax calculator, which simply uses the region's tax rate and calculates the tax total using subtotal * tax rate. Or for more complex tax calculations you can use a plugin such as medusa-tax-taxjar.

Change REST responses to always return objects

If we call /products/:id we get a response of the sort:

{
  "_id": "...",
  ...
}

If we call /products we get:

[
  {
    "_id": "...",
    ...
  },
  { ... },
  .
  .
  .
]

It should be changed so that we get /products/:id:

{
  "product": { 
    "_id": "...",
    ...
  }
}

/products:

{
  "products": [
    {
      "_id": "...",
      ...
    },
    { ... },
    .
    .
    .
  ]
}

[medusa-fullfilment-manual]: Add return shipping option

We should add a return shipping option to the manual fulfillment provider.
Something along the lines of:

getFulfillmentOptions() {
    return [
      {
        id: "manual-fulfillment",
      },
      {
        id: "manual-return-fulfillment",
        is_return: true,
        ...
      },
    ]
  }

Development Docker-compose setup

Create a docker-compose setup including the mongo seeding data needed to develop.

Make sure to create new seed files when pushing to master. With this, our seeds will be up to date and potentially solve parallel development.

[service]: OrderService

We need an order service that can create orders from a cart, handle communication with payment
and fulfillment providers and manage returns, changes etc.

Transactional Emails - SendGrid

We need a plugin: medusa-plugin-sendgrid, which will hook into order.placed events and sends an order confirmation to the customer. Other emails may also be relevant.

[services]: Create `exclude`-method in services to omit properties

We currently only support decorating an object in all of our services. We should add a method blacklist or omit to omit properties from a given object, such that we don't have to decorate an object with 20 out of 21 props, because the single last property should be omitted.

Something along the lines of:

export const blacklist = (object, fields) => {
  const obj = { ...object }
  fields.forEach(f => delete obj[f])
  return obj
}

Customer Endpoints

We need endpoints to update customers - only first/last name, password and addresses can be updated. Creation is also allowed, but emails must be unique.

BaseService: Create decorator functionality

Plugins and backend-developers should be able to create custom decorator functions for services. E.g. a plugin transforms a cart to include some 3rd party data that it needs to fetch.

In BaseService

constructor() {
  this.decorators_ = []
} 

addDecorator(fn) {
  this.decorators_.push(fn)
}

runDecorators_(obj) {
  obj = await this.decorators_.reduce(async (acc, next) => {
    return acc.then(res => next(res))
  }, Promise.resolve(obj))
}

fn can return promises.

In services:

decorate(obj, options) {
...
obj = await this.runDecorators_(obj)
...
}

In a plugin's loader or the store's loader you will now be able to call:

const service = container.resolve("service")
service.addDecorator((obj) => {
  obj.customStuff = "yes"
  return fetchSomething().then(res => {
    obj.asyncStuff = res
    return obj
  })
})

[service] : ShippingOptionService

General description of shipping in Medusa

In Medusa a shipping option represents a way for the customer to receive their order. A traditional shipping option is through a carrier such as FedEx, GLS, DHL, etc. where a package is picked up at a warehouse by the carrier and then transported to the customer's address. Different shipping options may be available in a store; for example, a store may have both a standard shipping option and an express shipping option. The shipping options can have different prices, e.g. express shipping is usually more expensive than standard shipping. Furthermore, the carrier that a shipping option is processed through may differ, e.g. express shipping could be through GLS, while standard shipping is done through DHL. Finally, an order may be processed by different fulfillment providers, e.g. some products may come from one warehouse, and others from a different warehouse. Finally finally, some regions may fulfill orders in different ways than other regions, e.g. orders shipping to the US should be shipped from the Canadian warehouse while products shipping to Europe should be shipped from the Swedish warehouse.

To accommodate all the complexities of shipping in an easy manner Medusa's shipping workflow starts with the fulfillment providers. starts with Shipping Profiles. A Shipping Profile contains a list of products and a list of shipping options that can fulfill any of the products on the list. Each of the shipping options defined in the list will have a fulfillment provider, a region and a price. The fulfillment provider takes care of handling where the order is shipped from and can also provide additional functionality like calculate prices, etc.

A store can have any number of fulfillment providers usually integrated via plugins. For example, a store may have a ShipHero plugin to take care of orders that should be fulfilled with ShipHero. When the store operator creates a shipping option for a region they will have to provide the option with one of ShipHero's shipping options, say Express Shipping with DHL. This tells Medusa that orders that have this shipping option should be fulfilled by ShipHero and the integration will make sure to transfer the order to ShipHero and select Express Shipping with DHL.

All fulfillment provider services have a method called getShippingOptions, which returns an array of shipping options available via the fulfillment provider. The store operator can choose to create shipping options for all of the fulfillment provider's shipping options or only a couple of them.

When creating shipping options in Medusa the store operator gives the option a name and a price. Prices can be set in different ways, for example, if the fulfillment provider service can calculate rates for a shipping option the price can be variable depending on the cart's contents. Furthermore, a shipping option may only be available for some products or for orders greater than a certain amount.

ShippingOptionService

The shipping option service takes care of CRUD operations on the shipping options available in a store, and is responsible for communication between fulfillment provider services and the store operator.

Additionally, the shipping option service must be able to:

  • Determine if a cart qualifies for a given shipping option
  • Fetch the price of a shipping option depending on a cart's status (i.e. items, shipping destination, total). This may happen by asking the fulfillment provider for the shipping price, or by looking at the dimensions of the items in the cart.
  • Validate a shipping method: for example, when a customer picks a shipping option we want to validate that the method is available for the cart. Additionally, if the method takes some sort of input (e.g. a parcel shop id) the shipping option service has to ask the fulfillment provider if the input is valid.

Shipping Option Schema

name: the name of the shipping option (string)
provider_id: the id of the provider that fulfills this shipping method (string)
data: data that the provider needs to be able to fulfill the order (object)
price: {
  type: the type can be (flat_rate or calculated)
  amount: the price of the shipping option if the price type is flat_rate (float)
}
requirements: an array of requirements that must be satisfied for the shipping method to be available

RequirementsSchema (this is only an initial thought it will make sense to make this more advanced)

type: minimum_subtotal | maximum_subtotal | includes_variant_id | excludes_variant_id
value: a number or id

Signatures

retrieve(optionId)
create(shippingOption)
update(optionId, shippingOption)
delete(optionId)
checkAvailability(optionId, cart)
fetchCartOptions(cart) - returns all available shipping options for the cart
validate(optionId, data, cart)

Product Service

Product Service manages products and to a certain extent product variants through the product variant service.

The product must be able to:

  • Create a product
  • Update a product
  • Retrieve a product
  • Delete a product
  • Add a product variant
  • Change order of product variants
  • Remove a product variant
  • Add product images
  • Change order of product images
  • Remove Product images
  • List products depending on different queries
  • Decorate products
  • Add option to product
  • Delete option from product
  • Update option title and order

Common query patterns

We need to find a way to do common query tasks such as pagination, filtering, sorting, etc. in a reusable manner, that can be shared across services.

[UserService] : Password reset should be based on email not user_id

Instead of passing the user_id as a param to the reset password endpoint, pass the user's email through the payload. We can then do something like this:

// src/api/admin/users/reset-password.js
const user = await userService.retrieveByEmail(value.email)
const decodedToken = await jwt.verify(value.token, user.password_hash)
if (!decodedToken || decodedToken.user_id !== user._id) {
  // throw unauthenticated
}

We do this because it will be hard to ensure that the user_id is known at the time of password reset.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.