docs: integrate Vale for documentation linting (#2242)
* added value rules * resolved errors raised by vale * added github action * fixes to github action * added details in contribution guidelines * added rule for numbers * limited checks to errors
This commit is contained in:
@@ -1,174 +0,0 @@
|
||||
---
|
||||
title: Create a file plugin
|
||||
---
|
||||
|
||||
# File Plugin
|
||||
|
||||
This guide will give an introduction about the File Service and steps required to create a custom file uploader plugin for Medusa. It build on article about [creating custom plugins](../advanced/backend/payment/overview.md).
|
||||
|
||||
As an example, we will create a File plugin that uploads the product images to Cloudinary.
|
||||
|
||||
## The File API
|
||||
|
||||
The file api is used to upload and manage product images used in Medusa.
|
||||
|
||||
## Implementing the File Service
|
||||
|
||||
The File Service is an built in interface in Medusa Core. It has two methods which must be overidden by our custom implementation of the service: `upload` and `delete`.
|
||||
|
||||
As the names suggest, upload allows us to specify the logic for uploading the file/image to our service while delete is used for deleting the image.
|
||||
|
||||
Both of the methods must return an promise.
|
||||
The upload method should return the url of uploaded image.
|
||||
In case of any error uploading, both methods should reject the promise and return an appropriate error message.
|
||||
|
||||
Here is the implementation of the base file service that we will extend.
|
||||
```javascript
|
||||
/**
|
||||
* Interface for file connectors
|
||||
* @interface
|
||||
*/
|
||||
class BaseFileService extends BaseService {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
upload() {
|
||||
throw Error("upload must be overridden by the child class")
|
||||
}
|
||||
|
||||
delete() {
|
||||
throw Error("delete must be overridden by the child class")
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseFileService
|
||||
```
|
||||
|
||||
See the example below for detailed usage.
|
||||
|
||||
## Example
|
||||
|
||||
We will create an plugin for uploading product images to [Cloudinary](https://cloudinary.com/)
|
||||
|
||||
The first step is to create an account on Cloudinary and copy the credentials like given below.
|
||||
|
||||
```
|
||||
cloud_name: "xx"
|
||||
api_key: "xx"
|
||||
api_secret: "xx"
|
||||
|
||||
```
|
||||
|
||||
The first step in creating a plugin is to initialize the Node.js project:
|
||||
|
||||
```bash npm2yarn
|
||||
npm init
|
||||
```
|
||||
|
||||
This command will ask you to fill out your project's metadata, which will eventually be used when publishing the package to NPM. After this command completes, you are ready to start implementing the functionality.
|
||||
|
||||
Next up, we need to install cloudinary's Node.js SDK.
|
||||
|
||||
```bash npm2yarn
|
||||
npm install cloudinary
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
To quickly get started with the implementation, we advise you to copy `/services/`, `/api/`, `/subscribers/` and the config files from the tutorial and add them in `/src`. As a result, you should have the following folder structure:
|
||||
|
||||
```js
|
||||
.
|
||||
├── src
|
||||
│ ├── api
|
||||
│ └── services
|
||||
│ └── subscribers
|
||||
├── .babelrc
|
||||
├── .gitignore
|
||||
├── medusa-config.js
|
||||
├── README.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
We will create a new file `cloudinary.js` in the `services` directory.
|
||||
|
||||
```javascript
|
||||
import fs from "fs";
|
||||
|
||||
import { v2 as cloudinary } from "cloudinary";
|
||||
import { FileService } from "medusa-interfaces";
|
||||
|
||||
class CloudinaryService extends FileService {
|
||||
constructor({}, options) {
|
||||
super();
|
||||
|
||||
// Initialize the Cloudinary sdk
|
||||
cloudinary.config({
|
||||
cloud_name: options.cloud_name,
|
||||
api_key: options.api_key,
|
||||
api_secret: options.api_secret,
|
||||
secure: options.secure,
|
||||
});
|
||||
}
|
||||
|
||||
// File upload
|
||||
upload(file) {
|
||||
console.log("Starting image upload");
|
||||
return new Promise((resolve, reject) => {
|
||||
// upload_stream allows to upload a file stream
|
||||
var upload_stream = cloudinary.uploader.upload_stream(
|
||||
{},
|
||||
function (err, image) {
|
||||
if (err) {
|
||||
// Reject and return an error if the file upload failed
|
||||
console.error(err);
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
// Return the url of image uploaded
|
||||
resolve({ url: image.url });
|
||||
}
|
||||
);
|
||||
// Create a file stream from path and forward it to our uploader
|
||||
fs.createReadStream(file.path).pipe(upload_stream);
|
||||
});
|
||||
}
|
||||
|
||||
delete(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Pass the name of file to be deleted
|
||||
cloudinary.uploader.destroy(file, function (result) {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CloudinaryService;
|
||||
```
|
||||
|
||||
### Publishing
|
||||
|
||||
In order for your plugin to become a part of the Medusa plugin ecosystem, you need to publish it to NPM. Make sure that you've included the `package.json` file. NPM uses the details of this file to configure the publishing. Please include `medusa` and `medusa-plugin` and possibly more in the `keywords` field of the `package.json`.
|
||||
|
||||
```bash
|
||||
{
|
||||
"name": "medusa-file-cloudinary",
|
||||
...
|
||||
"keywords": [
|
||||
"medusa",
|
||||
"medusa-file",
|
||||
"medusa-plugin"
|
||||
],
|
||||
"description": "Cloudinary File Plugin for Medusa Commerce",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, you should add a README for the plugin, such that the community understands the purpose of the plugin and how to install it.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
As a result of following this guide, you should now be able to both implement a custom file plugin for your Medusa project.
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
title: Fulfillment API
|
||||
---
|
||||
|
||||
## **Introduction**
|
||||
|
||||
This guide will give an overview of Medusa's Fulfillment API and how it can be implemented to work with different fulfillment providers. Before digging deeper into the API it should be clarified what is meant by a fulfillment provider; in Medusa a fulfillment provider is typically a 3rd party service that can handle order data for the purpose of shipping the items in the order to a customer. Examples of fulfillment providers are: a carrier like UPS, a logistics platform like ShipBob or a 3PL solution.
|
||||
|
||||
Implementations of the Fulfillment API can be distributed as npm packages for easy installation through the plugin system, there are already a couple of examples of fulfillment plugins in the Medusa monorepo, you can identify these by looking for `medusa-fulfillment-*`. For further details on building and publishing plugins [please check this guide](../advanced/backend/payment/overview.md).
|
||||
|
||||
## Fulfillment Service Interface
|
||||
|
||||
The fulfillment service interface can be found in the [`medusa-interfaces` package](https://github.com/medusajs/medusa/blob/master/packages/medusa-interfaces/src/fulfillment-service.js) where you can see the full list of methods that can be implemented. The methods in the interface allow you to implement plugins that can calculate shipping rates, create fulfillments in other systems, create return labels, retrieve customs documents and more.
|
||||
|
||||
In this guide we will focus on the methods involved in a typical fulfillment flow i.e. creating a shipping option, adding a shipping method to a cart, creating a fulfillment of items in a cart and finally marking the fulfillment as shipped. The methods involved in this flow are:
|
||||
|
||||
```jsx
|
||||
getFulfillmentOptions()
|
||||
validateOption(optionData)
|
||||
validateFulfillmentData(fulfillmentData, cart)
|
||||
createFulfillment(fulfillmentData, fulfillmentItems, order, fulfillment)
|
||||
```
|
||||
|
||||
The figure below shows at what points in the flow the Fulfillment API is called.
|
||||
|
||||

|
||||
|
||||
- **Get fulfillment options for Region**
|
||||
|
||||
In Medusa Shipping Options are defined and configured for each region - this allows store operators to granularly control how orders can be fulfilled in different countries. When creating a shipping option on a Region the Fulfillment API calls `getFulfillmentOptions` which will return the ways in which a fulfillment provider can process an order. Let's imagine that you have built a UPS plugin that can be used to fulfill orders with UPS; in this case `getFulfillmentOptions` might respond with UPS Express Shipping and UPS Access Point.
|
||||
|
||||
- **Create Shipping Option**
|
||||
|
||||
When creating the Shipping Option in Medusa, a store operator selects which underlying fulfillment option will be used when customers create Orders with the Shipping Option. To build from the UPS example a store operator may select the UPS Access Point option from the list of fulfillment options, she will then add an appropriate name for the Shipping Option, set the Shipping Option's price and if needed adjust the requirements that must be met for the Shipping Option to be active (e.g. minimum cart subtotal). Before the Shipping Option is created, `validateOption` will be called to ensure that the selected fulfillment option is in fact valid and correctly formatted. `validateOption` should respond with the validated fulfillment option; this also provides a point at which you may add additional details to the fulfillment data before the Shipping Option is created.
|
||||
|
||||
- **Get Shipping Options for Cart**
|
||||
|
||||
Moving to the customer perspective it is assumed that a cart has been created, items have been added to the cart and the customer is now looking towards selecting the Shipping Option they wish to have their Order fulfilled with. The first step from the storefront perspective is to retrieve the list of Shipping Options that are available for the Cart; these will be filtered based on the Region the Cart belongs to and the items that are in the cart. There are no calls to the Fulfillment API associated with the retrieval.
|
||||
|
||||
- **Add Shipping Method to Cart**
|
||||
|
||||
At this point the customer has selected a Shipping Option and may have configured additional details about the fulfillment. For example, building on the UPS example, the customer may have selected a Shipping Option that uses the UPS Access Point fulfillment option; in this case the customer sends an `access_point_id` in the `data` object of the `POST /store/carts/:id/shipping-methods` payload. At this point the Shipping Option becomes a Shipping Method, the distinction between the two is that a Shipping Option is a template for how a cart may be shipped whereas a Shipping Method is the instantiated way that the cart will be shipped (which may include specific details like the `access_point_id`). In order to ensure that the details that the customer selects are valid the Fulfillment API's `validateFulfillmentData` is called, this is where a fulfillment implementation may check that the `access_point_id` is in fact a valid value, etc.
|
||||
|
||||
- **Create Fulfillment**
|
||||
|
||||
It is now assumed that the customer has completed their purchase with a given Shipping Method and the order has been confirmed. At this point we are ready to create the fulfillment in the 3rd party system. When a store operator (or an automation) attempts to fulfill an order the `createFulfillment` method will be called in the Fulfillment API, in our UPS example this method should then book a shipment via UPS's API.
|
||||
Note: in Medusa you can make partial fulfillments of an order it is therefore important that the id sent to the 3rd party is unique by fulfillment and not only order.
|
||||
|
||||
- **Mark as shipped**
|
||||
|
||||
The final step of the fulfillment process is to mark the fulfillment as shipped. It is also at this stage that you would record a tracking number that can be shared with the customer. This step typically happens asynchronously through a webhook.
|
||||
|
||||
## Other useful methods
|
||||
|
||||
The flow above describes the Fulfillment API in high level terms; check out the `[medusa-fulfillment-webshipper](https://github.com/medusajs/medusa/blob/master/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js)` plugin for a full implementation of the Fulfillment API. In the implementation you will find examples of all the method described above.
|
||||
|
||||
### `createReturn`
|
||||
|
||||
You will find that the Webshipper plugin has an implementation for `createReturn` which allows fulfillment providers to implement a way for generating return labels. When implemented this can be used to allow customers to create self managed returns or to integrate automatic return label generation in the admin dashboard.
|
||||
|
||||
### `canCalculate(fulfillmentOption)`
|
||||
|
||||
If implemented this method can be used to dynamically calculate prices based on a cart's contents or details. The method returns a boolean value indicating if a given fulfillment option can have a dynamically calculated price or not.
|
||||
|
||||
### `calculatePrice(fulfillmentOption, fulfillmentData, cart)`
|
||||
|
||||
If the shipping option is configured to dynamically calculate the price of the this method will be called when Shipping Options are fetched for the Cart and when Shipping Methods are created on a Cart.
|
||||
|
||||
## What's next?
|
||||
|
||||
Now that you have an overview of the Fulfillment API you can start developing your own fulfillment plugin. For a guide on how to create plugins [check this guide](../advanced/backend/payment/overview.md). If you have questions or issues please feel free to [join our Discord server](https://discord.gg/medusajs) for direct access to the engineering team.
|
||||
Reference in New Issue
Block a user