docs: How to add a Fulfillment Provider (#1300)

* added docs

* added link to the how-to in the overview
This commit is contained in:
Shahed Nasser
2022-04-15 11:59:13 +03:00
committed by GitHub
parent 1ebeb13441
commit d0b70ca9fd
3 changed files with 278 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
# How to Add a Fulfillment Provider
In this document, youll learn how to add a fulfillment provider to a Medusa server. If youre unfamiliar with the Shipping architecture in Medusa, make sure to check out the overview first.
## Overview
A fulfillment provider is the shipping provider used to fulfill orders and deliver them to customers. An example of a fulfillment provider is FedEx.
By default, a Medusa Server has a `manual` fulfillment provider which has minimal implementation. It allows you to accept orders and fulfill them manually. However, you can integrate any fulfillment provider into Medusa, and your fulfillment provider can interact with third-party shipping providers.
Adding a fulfillment provider is as simple as creating one [service](../services/create-service.md) file in `src/services`. A fulfillment provider is essentially a service that extends the `FulfillmentService`. It requires implementing 4 methods:
1. `getFulfillmentOptions`: used to retrieve available fulfillment options provided by this fulfillment provider.
2. `validateOption`: used to validate the shipping option when its being created by the admin.
3. `validateFulfillmentData`: used to validate the shipping method when the customer chooses a shipping option on checkout.
4. `createFulfillment`: used to perform any additional actions when fulfillment is being created for an order.
Also, the fulfillment provider class should have a static property `identifier`. It is the name that will be used to install and refer to the fulfillment provider throughout Medusa.
Fulfillment providers are loaded and installed on the server startup.
## Create a Fulfillment Provider
The first step is to create the file that will hold the fulfillment provider class in `src/services`:
```jsx
import { FulfillmentService } from "medusa-interfaces"
class MyFulfillmentService extends FulfillmentService {
}
export default MyFulfillmentService;
```
Fulfillment provider services should extend `FulfillmentService` imported from `medusa-interfaces`.
:::note
Following the naming convention of Services, the name of the file should be the slug name of the fulfillment provider, and the name of the class should be the camel case name of the fulfillment provider suffixed with “Service”. You can learn more in the [service documentation](../services/create-service.md).
:::
### Identifier
As mentioned in the overview, fulfillment providers should have a static `identifier` property.
The `FulfillmentProvider` model has 2 properties: `identifier` and `is_installed`. The `identifier` property in the class will be used when the fulfillment provider is created in the database.
The value of this property will also be used to reference the fulfillment provider throughout Medusa. For example, it is used to [add a fulfillment provider](https://docs.medusajs.com/api/admin/region/add-fulfillment-provider) to a region.
```jsx
import { FulfillmentService } from "medusa-interfaces"
class MyFulfillmentService extends FulfillmentService {
static identifier = 'my-fulfillment';
}
export default MyFulfillmentService;
```
## constructor
You can use the `constructor` of your fulfillment provider to have access to different services in Medusa through dependency injection.
You can also use the constructor to initialize your integration with the third-party provider. For example, if you use a client to connect to the third-party providers APIs, you can initialize it in the constructor and use it in other methods in the service.
Additionally, if youre creating your fulfillment provider as an external plugin to be installed on any Medusa server and you want to access the options added for the plugin, you can access it in the constructor. The options are passed as a second parameter:
```jsx
constructor({}, options) {
//you can access options here
}
```
### getFulfillmentOptions
When the admin is creating shipping options available for customers during checkout, they choose one of the fulfillment options provided by underlying fulfillment providers.
For example, if youre integrating UPS as a fulfillment provider, you might support 2 fulfillment options: UPS Express Shipping and UPS Access Point.
These fulfillment options are defined in the `getFulfillmentOptions` method. This method should return an array of options.
For example:
```jsx
async getFulfillmentOptions () {
return [
{
id: 'my-fulfillment'
},
{
id: 'my-fulfillment-dynamic'
}
];
}
```
When the admin chooses one of those fulfillment options, the data of the chosen fulfillment option is stored in the `data` property of the shipping option created. This property is used to add any additional data you need to fulfill the order with the third-party provider.
For that reason, the fulfillment option does not have any required structure and can be of any format that works for your integration.
### validateOption
Once the admin creates the shipping option, the data will be validated first using this method in the underlying fulfillment provider of that shipping option. This method is called when a `POST` request is sent to `[/admin/shipping-options](https://docs.medusajs.com/api/admin/shipping-option/create-shipping-option)`.
This method accepts the `data` object that is sent in the body of the request. You can use this data to validate the shipping option before it is saved.
This method returns a boolean. If the result is false, an error is thrown and the shipping option will not be saved.
For example, you can use this method to ensure that the `id` in the `data` object is correct:
```jsx
async validateOption (data) {
return data.id == 'my-fulfillment';
}
```
If your fulfillment provider does not need to run any validation, you can simply return `true`.
### validateFulfillmentOption
When the customer chooses a shipping option on checkout, the shipping option and its data are validated before the shipping method is created.
`validateFulfillmentOption` is called when a `POST` request is sent to `[/carts/:id/shipping-methods](https://docs.medusajs.com/api/store/cart/add-a-shipping-method)`.
This method accepts 3 parameters:
1. The shipping option data.
2. The `data` object passed in the body of the request.
3. The customers cart data.
You can use these parameters to validate the chosen shipping option. For example, you can check if the `data` object includes all data needed to fulfill the shipment later on.
If any of the data is invalid, you can throw an error. This error will stop Medusa from creating a shipping method and the error message will be returned as a result to the endpoint.
If everything is valid, this method must return a value that will be stored in the `data` property of the shipping method to be created. So, make sure the value you return contains everything you need to fulfill the shipment later on.
For example:
```jsx
async validateFulfillmentData(optionData, data, cart) {
if (data.id !== "my-fulfillment") {
throw new Error("invalid data");
}
return {
...data
}
}
```
### createFulfillment
After an order is placed, it can be fulfilled either manually by the admin or using automation.
This method gives you access to the fulfillment being created as well as other details in case you need to perform any additional actions with the third-party provider.
This method accepts 4 parameters:
1. The data of the shipping method associated with the order.
2. An array of items in the order to be fulfilled. The admin can choose all or some of the items to fulfill.
3. The data of the order
4. The data of the fulfillment being created.
You can use the `data` property in the shipping method (first parameter) to access the data specific to the shipping option. This is based on your implementation of previous methods.
Here is a basic implementation of `createFulfillment` for a fulfillment provider that does not interact with any third-party provider to create the fulfillment:
```jsx
createFulfillment(
methodData,
fulfillmentItems,
fromOrder,
fulfillment
) {
// No data is being sent anywhere
return Promise.resolve({})
}
```
:::note
This method is also used to create claims and swaps. The fulfillment object has the fields `claim_id`, `swap_id`, and `order_id`. You can check which isnt null to determine what type of fulfillment is being created.
:::
### Useful Methods
The above-detailed methods are the required methods for every fulfillment provider. However, there are additional methods that you can use in your fulfillment provider to customize it further or add additional features.
#### canCalculate
This method validates whether a shipping option is calculated dynamically or flat rate. It is called if the `price_type` of the shipping option being created is set to `calculated`.
If this method returns `true`, that means that the price should be calculated dynamically. The `amount` property of the shipping option will then be set to `null`. The amount will be created later when the shipping method is created on checkout using the `calculatePrice` method (explained next).
If the method returns `false`, an error is thrown as it means the selected shipping option can only be chosen if the price type is set to `flat_rate`.
This method receives as a parameter the `data` object sent with the request that [creates the shipping option.](https://docs.medusajs.com/api/admin/shipping-option/create-shipping-option) You can use this data to determine whether the shipping option should be calculated or not. This is useful if the fulfillment provider you are integrating has both flat rate and dynamically priced fulfillment options.
For example:
```jsx
canCalculate(data) {
return data.id === 'my-fulfillment-dynamic';
}
```
#### calculatePrice
This method is called on checkout when the shipping method is being created if the `price_type` of the selected shipping option is `calculated`.
This method receives 3 parameters:
1. The `data` parameter of the selected shipping option.
2. The `data` parameter sent with [the request](https://docs.medusajs.com/api/store/cart/add-a-shipping-method).
3. The customers cart data.
If your fulfillment provider does not provide any dynamically calculated rates you can keep the function empty:
```jsx
calculatePrice() {
}
```
Otherwise, you can use it to calculate the price with a custom logic. For example:
```jsx
calculatePrice (optionData, data, cart) {
return cart.items.length * 1000;
}
```
#### createReturn
Fulfillment providers can also be used to return products. A shipping option can be used for returns if the `is_return` property is `true` or if an admin creates a Return Shipping Option from the settings.
This method is called when the admin [creates a return request](https://docs.medusajs.com/api/admin/order/request-a-return) for an order or when the customer [creates a return of their order](https://docs.medusajs.com/api/store/return/create-return).
It gives you access to the return being created in case you need to perform any additional actions with the third-party provider.
It receives the return created as a parameter. The value it returns is set to the `shipping_data` of the return instance.
This is the basic implementation of the method for a fulfillment provider that does not contact with a third-party provider to fulfill the return:
```jsx
createReturn(returnOrder) {
return Promise.resolve({})
}
```
#### cancelFulfillment
This method is called when a fulfillment is cancelled by the admin. This fulfillment can be for an order, a claim, or a swap.
It gives you access to the fulfillment being canceled in case you need to perform any additional actions with your third-party provider.
This method receives the fulfillment being cancelled as a parameter.
This is the basic implementation of the method for a fulfillment provider that does not interact with a third-party provider to cancel the fulfillment:
```jsx
cancelFulfillment(fulfillment) {
return Promise.resolve({})
}
```
## Whats Next 🚀
- Check out the [Webshipper plugin](https://github.com/medusajs/medusa/tree/cab5821f55cfa448c575a20250c918b7fc6835c9/packages/medusa-fulfillment-webshipper) for an example of a fulfillment provider that interacts with a third-party providers.
- Check out the [manual fulfillment plugin](https://github.com/medusajs/medusa/tree/cab5821f55cfa448c575a20250c918b7fc6835c9/packages/medusa-payment-manual) for a basic implementation of a fulfillment provider.

View File

@@ -130,4 +130,5 @@ The `ShippingMethod` instance holds a `price` attribute, which will either b
## Whats Next :rocket:
- [Learn how to Create a Fulfillment Provider.](./add-fulfillment-provider.md)
- Check out [available shipping plugins](https://github.com/medusajs/medusa/tree/master/packages).

View File

@@ -165,6 +165,11 @@ module.exports = {
id: "advanced/backend/shipping/overview",
label: "Architecture Overview"
},
{
type: "doc",
id: "advanced/backend/shipping/add-fulfillment-provider",
label: "Add Fulfillment Provider"
}
]
},
{