medusajs / medusa Goto Github PK
View Code? Open in Web Editor NEWBuilding blocks for digital commerce
Home Page: https://medusajs.com
License: MIT License
Building blocks for digital commerce
Home Page: https://medusajs.com
License: MIT License
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.
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.
Should we perhaps add a schema?
Move the test request from /helper
in medusa
to medusa-test-utils
, such that this can be used in all packages and plugins.
We need an order service that can create orders from a cart, handle communication with payment
and fulfillment providers and manage returns, changes etc.
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.
No errors.
Typeorm will throw an error due to conflicting product handles
The AuthService should depend on the UserModel through the UserService instead of directly injecting UserModel.
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
})
})
The discount service will help calculate discounts on carts and orders.
The service should be able to:
Fixed discounts may also work as gift cards - needs more research.
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,
...
},
]
}
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.
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.
There is no remove line item in the cart service.
Product Variant endpoints are missing.
Add authorizePayment
function to the stripe payment provider service. This should replace the client-side use of confirmCardPayment
.
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": "...",
...
},
{ ... },
.
.
.
]
}
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.
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
How can we allow blacklisted metadata?
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
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.
The Contentful plugin will send SKUs and other data to Contentful where operators can add additional data to the products.
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.
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:
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
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)
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
}
Product variant service manages the variants of the products.
The product variant service should be able to:
We need a fulfillment provider plugin - could be something like Webshipper or PrimeCargo.
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
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.
We need to create a plugin that enables Klarna payments
UserService line 100 should return
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
.
If posting a line item with a variant that the customer already has in her cart. The quantity should increase instead of a new item being created.
We need to offer authentication for customers. I.e. we need a /store/auth
endpoint where you can log in and out.
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.
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.
Product Service manages products and to a certain extent product variants through the product variant service.
The product must be able to:
We need to complete a plugin that enables payments via Stripe.
If you choose a non-existing customer email on draft-order creation, we should creation the customer
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.
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
.
We should support requesting a return for swapped items
When comparing ObjectIds we need to user the .equals()
method on the ObjectId class. This is required to match String representations of ObjectIds and ObjectId objects.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.