fix: merge conflicts with master
This commit is contained in:
@@ -36,7 +36,15 @@ This exports a function that returns an Express router. In that function, you ca
|
||||
|
||||
Now, if you run your server and send a request to `/admin/hello`, you will receive a JSON response message.
|
||||
|
||||
> Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while it’s running, and when you run `npm run build`.
|
||||
:::note
|
||||
|
||||
Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while it’s running, and when you run:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Accessing Endpoints from Admin
|
||||
|
||||
@@ -198,6 +206,18 @@ const userService = req.scope.resolve("userService")
|
||||
const user = await userService.retrieve(id)
|
||||
```
|
||||
|
||||
### Route Parameters
|
||||
|
||||
The routes you create receive 2 parameters. The first one is the absolute path to the root directory that your server is running from. The second one is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object.
|
||||
|
||||
```js
|
||||
export default (rootDirectory, pluginOptions) => {
|
||||
const router = Router()
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## What’s Next 🚀
|
||||
|
||||
- [Learn how to add an endpoint for the Storefront.](/advanced/backend/endpoints/add-storefront)
|
||||
|
||||
@@ -36,7 +36,15 @@ This exports a function that returns an Express router. In that function, you ca
|
||||
|
||||
Now, if you run your server and send a request to `/store/hello`, you will receive a JSON response message.
|
||||
|
||||
> Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while it’s running, and when you run `npm run build`.
|
||||
:::note
|
||||
|
||||
Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while it’s running, and when you run:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Multiple Endpoints
|
||||
|
||||
@@ -169,6 +177,18 @@ const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieve(id)
|
||||
```
|
||||
|
||||
### Route Parameters
|
||||
|
||||
The routes you create receive 2 parameters. The first one is the absolute path to the root directory that your server is running from. The second one is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object.
|
||||
|
||||
```js
|
||||
export default (rootDirectory, pluginOptions) => {
|
||||
const router = Router()
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## What’s Next :rocket:
|
||||
|
||||
- [Learn how to add an endpoint for the Admin.](/advanced/backend/endpoints/add-admin)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# Frontend Payment Flow in Checkout
|
||||
|
||||
## Introduction
|
||||
The purpose of this guide is to describe a checkout flow in Medusa. It is assumed that you've completed our [Quickstart](https://docs.medusajs.com/quickstart/quick-start) or [Tutorial](https://docs.medusajs.com/tutorial/set-up-your-development-environment) and are familiar with the technologies we use in our stack. Additionally, having an understanding of the [core API](https://docs.medusajs.com/api/store/auth) would serve as a great foundation for this walkthrough.
|
||||
> All code snippets in the following guide, use the JS SDK distributed through **npm**. To install it, run:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-js
|
||||
```
|
||||
|
||||
## Glossary
|
||||
- **Cart**: The Cart contains all the information needed for customers to complete an Order. In the Cart customers gather the items they wish to purchase, they add shipping and billing details and complete payment information.
|
||||
- **LineItem**: Line Items represent an expense added to a Cart. Typically this will be a Product Variant and a certain quantity of the same variant. Line Items hold descriptive fields that help communicate its contents and price.
|
||||
- **PaymentSession**: A Payment Session is a Medusa abstraction that unifies the APIs of multiple payment gateways. Payment Sessions are _initialized_ when the customer begins a checkout and are _authorized_ prior to an Order being placed. Payment Sessions are created based on the available Payment Providers configured in a Cart's region.
|
||||
- **ShippingOption**: A Shipping Option represents a way in which an Order can be fulfilled. Shipping Options have a price and are associated with a Fulfillment Provider that will handle the shipment later in the Order flow. Once a customer selects a Shipping Option it becomes a Shipping Method.
|
||||
- **ShippingMethod**: Shipping Methods are unique to each Cart and can thereby hold either overwrites for fields in a Shipping Option (e.g. price) or additional details (e.g. an id representing a parcel pickup location).
|
||||
|
||||
## Checkout flow
|
||||
To create an order from a cart, we go through the following flow.
|
||||
> At this point, it assumed that the customer has created a cart, added items and is now at the initial step of the checkout flow.
|
||||
|
||||
### Initializing the checkout
|
||||
The first step in the flow is to _initialize_ the configured Payment Sessions for the Cart. If you are using the `medusa-starter-default` starter, this call will result in the `cart.payment_sessions` array being filled with one Payment Session for the manual payment provider.
|
||||
|
||||
```javascript
|
||||
const { cart } = await medusa.carts.createPaymentSessions("cart_id")
|
||||
```
|
||||
|
||||
To give a more real life example, it is assumed that `medusa-payment-stripe` is installed in your project in which case the call will result in a [Stripe PaymentIntent](https://stripe.com/docs/api/payment_intents) being created. The unique provider data for each Payment Session can be found in the Payment Session's `data` field; this data can be used in front end implementations e.g. if using Stripe Elements the `client_secret` can be retrieved through `session.data.client_secret`.
|
||||
|
||||
### Adding customer information
|
||||
After initializing the checkout flow, you would typically have one big step or several smaller steps for gathering user information; email, address, country, and more. To store this data you may update the cart with each of field or all fields at the same time.
|
||||
|
||||
```javascript
|
||||
const { cart } = await medusa.carts.update("cart_id", {
|
||||
email: "lebron@james.com",
|
||||
shipping_address: {
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
...
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Selecting payment provider
|
||||
This step is only applicable if you have multiple Payment Sessions installed in your project. In cases where only one Payment Provider is configured the Payment Session will be preselected. In all other cases your implementations should call:
|
||||
|
||||
```javascript
|
||||
const { cart } = await medusa.carts.setPaymentSession("cart_id", {
|
||||
provider_id: "stripe"
|
||||
})
|
||||
```
|
||||
|
||||
### Choosing a shipping method
|
||||
Before reaching the payment step, you would typically require the customer to choose a Shipping Method from a number of options. In Medusa you can set rules that must be met for a Shipping Option to be available for a Cart. To get the available shipping options for a Cart you should call:
|
||||
```javascript
|
||||
const { shipping_options } = await medusa.carts.listCartOptions("cart_id")
|
||||
```
|
||||
|
||||
Choosing a Shipping Option, will create a Shipping Method and attach it to the Cart. The second argument to the function in the snippet below holds the id of the selected option.
|
||||
```javascript=
|
||||
const { cart } = await medusa.carts.addShippingMethod("cart_id", { option_id: "option_id"})
|
||||
```
|
||||
|
||||
### Collecting payment details
|
||||
The following snippet shows how we use Stripe to collect payment details from the customer. Note that we are using the `client_secret` from the Stripe PaymentIntent in `data` on the payment session as this is required by Stripe Elements.
|
||||
```jsx
|
||||
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
|
||||
|
||||
...
|
||||
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
|
||||
|
||||
const handleSubmit = () => {
|
||||
...
|
||||
const { paymentIntent, error } = await stripe.confirmCardPayment(
|
||||
cart.payment_session.data.client_secret,
|
||||
{
|
||||
payment_method: {
|
||||
card: elements.getElement(CardElement),
|
||||
},
|
||||
}
|
||||
);
|
||||
...
|
||||
}
|
||||
|
||||
return <CardElement id="card-element" />
|
||||
```
|
||||
After collecting the payment details, the customer can complete the checkout flow.
|
||||
|
||||
### Completing the cart
|
||||
When all relevant customer information has been captured, your implementation should proceed to the final step: completing the cart.
|
||||
```javascript
|
||||
const { order } = await medusa.carts.complete("cart_id")
|
||||
```
|
||||
If all information is collected correctly throughout the checkout flow, the call will place an Order based on the details gathered in the Cart.
|
||||
|
||||
## Summary
|
||||
You now have a solid foundation for creating your own checkout flows using Medusa. Throughout this guide, we've used Stripe as a Payment Provider. Stripe is one of the most popular providers and we have an official plugin that you can easily install in your project.
|
||||
|
||||
## What's next?
|
||||
See the checkout flow, explained in the previous sections, in one of our frontend starters:
|
||||
- [Nextjs Starter](https://github.com/medusajs/nextjs-starter-medusa)
|
||||
- [Gatsby Starter](https://github.com/medusajs/gatsby-starter-medusa)
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
# How to Create a Payment Provider
|
||||
|
||||
In this document, you’ll learn how to add a Payment Provider to your Medusa server. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](./overview.md) first.
|
||||
|
||||
## Overview
|
||||
|
||||
A Payment Provider is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Provider is Stripe.
|
||||
|
||||
By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-fulfillment-manual) that has minimal implementation. It can be synonymous with a Cash on Delivery payment method. It allows store operators to manage the payment themselves but still keep track of its different stages on Medusa.
|
||||
|
||||
Adding a Payment Provider is as simple as creating a [service](../services/create-service.md) file in `src/services`. A Payment Provider is essentially a service that extends `PaymentService` from `medusa-interfaces`.
|
||||
|
||||
Payment Provider Services must have a static property `identifier`. It is the name that will be used to install and refer to the Payment Provider in the Medusa server.
|
||||
|
||||
:::tip
|
||||
|
||||
Payment Providers are loaded and installed at the server startup.
|
||||
|
||||
:::
|
||||
|
||||
The Payment Provider service is also required to implement the following methods:
|
||||
|
||||
1. `createPayment`: Called when a Payment Session for the Payment Provider is to be created.
|
||||
2. `retrievePayment`: Used to retrieve payment data from the third-party provider, if there’s any.
|
||||
3. `getStatus`: Used to get the status of a Payment or Payment Session.
|
||||
4. `updatePayment`: Used to update the Payment Session whenever the cart and its related data are updated.
|
||||
5. `updatePaymentData`: Used to update the `data` field of Payment Sessions. Specifically called when a request is sent to the [Update Payment Session](https://docs.medusajs.com/api/store/cart/update-a-payment-session) endpoint.
|
||||
6. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted.
|
||||
7. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created.
|
||||
8. `getPaymentData`: Used to retrieve the data that should be stored in the `data` field of a new Payment instance after the payment amount has been authorized.
|
||||
9. `capturePayment`: Used to capture the payment amount of an order or swap.
|
||||
10. `refundPayment`: Used to refund a payment amount of an order or swap.
|
||||
11. `cancelPayment`: Used to perform any necessary action with the third-party payment provider when an order or swap is canceled.
|
||||
|
||||
:::note
|
||||
|
||||
All these methods must be declared async in the Payment Provider Service.
|
||||
|
||||
:::
|
||||
|
||||
These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed.
|
||||
|
||||

|
||||
|
||||
## Create a Fulfillment Provider
|
||||
|
||||
The first step to create a fulfillment provider is to create a file in `src/services` with the following content:
|
||||
|
||||
```jsx
|
||||
import { PaymentService } from "medusa-interfaces"
|
||||
|
||||
class MyPaymentService extends PaymentService {
|
||||
|
||||
}
|
||||
|
||||
export default MyPaymentService;
|
||||
```
|
||||
|
||||
Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`.
|
||||
|
||||
Payment Providers must extend `PaymentService` from `medusa-interfaces`.
|
||||
|
||||
:::tip
|
||||
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the Payment Provider, and the name of the class should be the camel case name of the Payment Provider suffixed with “Service”. In the example above, the name of the file should be `my-payment.js`. You can learn more in the [service documentation](../services/create-service.md).
|
||||
|
||||
:::
|
||||
|
||||
### Identifier
|
||||
|
||||
As mentioned in the overview, Payment Providers should have a static `identifier` property.
|
||||
|
||||
The `PaymentProvider` model has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Provider is created in the database.
|
||||
|
||||
The value of this property will also be used to reference the Payment Provider throughout the Medusa server. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](https://docs.medusajs.com/api/store/cart/select-a-payment-session).
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your Payment 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 provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re creating your Payment 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
|
||||
}
|
||||
```
|
||||
|
||||
### createPayment
|
||||
|
||||
This method is called during checkout when [Payment Sessions are initialized](https://docs.medusajs.com/api/store/cart/initialize-payment-sessions) to present payment options to the customer. It is used to allow you to make any necessary calls to the third-party provider to initialize the payment. For example, in Stripe this method is used to initialize a Payment Intent for the customer.
|
||||
|
||||
The method receives the cart as an object for its first parameter. It holds all the necessary information you need to know about the cart and the customer that owns this cart.
|
||||
|
||||
This method must return an object that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](./overview.md), the `data` field is useful to hold any data required by the third-party provider to process the payment or retrieve its details at a later point.
|
||||
|
||||
An example of a minimal implementation of `createPayment` that does not interact with any third-party providers:
|
||||
|
||||
```jsx
|
||||
async createPayment(cart) {
|
||||
return {
|
||||
id: 'test-payment',
|
||||
status: 'pending'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### retrievePayment
|
||||
|
||||
This method is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Provider Service this method is used to retrieve the payment intent details from Stripe.
|
||||
|
||||
This method accepts the `data` field of a Payment Session or a Payment. So, you should make sure to store in the `data` field any necessary data that would allow you to retrieve the payment data from the third-party provider.
|
||||
|
||||
This method must return an object containing the data from the third-party provider.
|
||||
|
||||
An example of a minimal implementation of `retrievePayment` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```jsx
|
||||
async retrievePayment(cart) {
|
||||
return {};
|
||||
}
|
||||
```
|
||||
|
||||
### getStatus
|
||||
|
||||
This method is used to get the status of a Payment or a Payment Session.
|
||||
|
||||
Its main usage is in the place order workflow. If the status returned is not `authorized`, then the payment is considered failed, an error will be thrown, and the order will not be placed.
|
||||
|
||||
This method accepts the `data` field of the Payment or Payment Session as a parameter. You can use this data to interact with the third-party provider to check the status of the payment if necessary.
|
||||
|
||||
This method returns a string that represents the status. The status must be one of the following values:
|
||||
|
||||
1. `authorized`: The payment was successfully authorized.
|
||||
2. `pending`: The payment is still pending. This is the default status of a Payment Session.
|
||||
3. `requires_more`: The payment requires more actions from the customer. For example, if the customer must complete a 3DS check before the payment is authorized.
|
||||
4. `error`: If an error occurred with the payment.
|
||||
5. `canceled`: If the payment was canceled.
|
||||
|
||||
An example of a minimal implementation of `getStatus` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```jsx
|
||||
async getStatus (data) {
|
||||
return data.status;
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This code block assumes the status is stored in the `data` field as demonstrated in the `createPayment` method.
|
||||
|
||||
:::
|
||||
|
||||
### updatePayment
|
||||
|
||||
This method is used to perform any necessary updates on the payment. This method is called whenever the cart or any of its related data is updated. For example, when a [line item is added to the cart](https://docs.medusajs.com/api/store/cart/add-a-line-item) or when a [shipping method is selected](https://docs.medusajs.com/api/store/cart/add-a-shipping-method).
|
||||
|
||||
:::tip
|
||||
|
||||
A line item refers to a product in the cart.
|
||||
|
||||
:::
|
||||
|
||||
It accepts the `data` field of the Payment Session as the first parameter and the cart as an object for the second parameter.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and update any details regarding the payment if necessary.
|
||||
|
||||
This method must return an object that will be stored in the `data` field of the Payment Session.
|
||||
|
||||
An example of a minimal implementation of `updatePayment` that does not need to make any updates on the third-party provider or the `data` field of the Payment Session:
|
||||
|
||||
```jsx
|
||||
async updatePayment(sessionData, cart) {
|
||||
return sessionData;
|
||||
}
|
||||
```
|
||||
|
||||
### updatePaymentData
|
||||
|
||||
This method is used to update the `data` field of a Payment Session. Particularly, it is called when a request is sent to the [Update Payment Session](https://docs.medusajs.com/api/store/cart/update-a-payment-session) endpoint. This endpoint receives a `data` object in the body of the request that should be used to update the existing `data` field of the Payment Session.
|
||||
|
||||
This method accepts the current `data` field of the Payment Session as the first parameter, and the new `data` field sent in the body request as the second parameter.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and make any necessary updates based on the `data` field passed in the body of the request.
|
||||
|
||||
This method must return an object that will be stored in the `data` field of the Payment Session.
|
||||
|
||||
An example of a minimal implementation of `updatePaymentData` that returns the `updatedData` passed in the body of the request as-is to update the `data` field of the Payment Session.
|
||||
|
||||
```jsx
|
||||
async updatePaymentData(sessionData, updatedData) {
|
||||
return updatedData;
|
||||
}
|
||||
```
|
||||
|
||||
### deletePayment
|
||||
|
||||
This method is used to perform any actions necessary before a Payment Session is deleted. The Payment Session is deleted in one of the following cases:
|
||||
|
||||
1. When a request is sent to [delete the Payment Session](https://docs.medusajs.com/api/store/cart/delete-a-payment-session).
|
||||
2. When the [Payment Session is refreshed](https://docs.medusajs.com/api/store/cart/refresh-a-payment-session). The Payment Session is deleted so that a newer one is initialized instead.
|
||||
3. When the Payment Provider is no longer available. This generally happens when the store operator removes it from the available Payment Provider in the admin.
|
||||
4. When the region of the store is changed based on the cart information and the Payment Provider is not available in the new region.
|
||||
|
||||
It accepts the Payment Session as an object for its first parameter.
|
||||
|
||||
You can use this method to interact with the third-party provider to delete data related to the Payment Session if necessary.
|
||||
|
||||
An example of a minimal implementation of `deletePayment` where no interaction with a third-party provider is required:
|
||||
|
||||
```jsx
|
||||
async deletePayment(paymentSession) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
### authorizePayment
|
||||
|
||||
This method is used to authorize payment using the Payment Session for an order. This is called when the [cart is completed](https://docs.medusajs.com/api/store/cart/complete-a-cart) and before the order is created.
|
||||
|
||||
This method is also used for authorizing payments of a swap of an order.
|
||||
|
||||
The payment authorization might require additional action from the customer before it is declared authorized. Once that additional action is performed, the `authorizePayment` method will be called again to validate that the payment is now fully authorized. So, you should make sure to implement it for this case as well, if necessary.
|
||||
|
||||
Once the payment is authorized successfully and the Payment Session status is set to `authorized`, the order can then be placed.
|
||||
|
||||
If the payment authorization fails, then an error will be thrown and the order will not be created.
|
||||
|
||||
:::note
|
||||
|
||||
The payment authorization status is determined using the `getStatus` method as mentioned earlier. If the status is `requires_more` then it means additional actions are required from the customer. If the workflow process reaches the “Start Create Order” step and the status is not `authorized`, then the payment is considered failed.
|
||||
|
||||
:::
|
||||
|
||||
This method accepts the Payment Session as an object for its first parameter, and a `context` object as a second parameter. The `context` object contains the following properties:
|
||||
|
||||
1. `ip`: The customer’s IP.
|
||||
2. `idempotency_key`: The [Idempotency Key](./overview.md#idempotency-key) that is associated with the current cart. It is useful when retrying payments, retrying checkout at a failed point, or for payments that require additional actions from the customer.
|
||||
|
||||
This method must return an object containing the property `status` which is a string that indicates the current status of the payment, and the property `data` which is an object containing any additional information required to perform additional payment processing such as capturing the payment. The values of both of these properties are stored in the Payment Session’s `status` and `data` fields respectively.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to authorize the payment.
|
||||
|
||||
An example of a minimal implementation of `authorizePayment` that doesn’t need to interact with any third-party provider:
|
||||
|
||||
```jsx
|
||||
async authorizePayment(paymentSession, context) {
|
||||
return {
|
||||
status: 'authorized',
|
||||
data: {
|
||||
id: 'test'
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### getPaymentData
|
||||
|
||||
After the payment is authorized using `authorizePayment`, a Payment instance will be created. The `data` field of the Payment instance will be set to the value returned from the `getPaymentData` method in the Payment Provider.
|
||||
|
||||
This method accepts the Payment Session as an object for its first parameter.
|
||||
|
||||
This method must return an object to be stored in the `data` field of the Payment instance. You can either use it as-is or make any changes to it if necessary.
|
||||
|
||||
An example of a minimal implementation of `getPaymentData`:
|
||||
|
||||
```jsx
|
||||
async getPaymentData(paymentSession) {
|
||||
return paymentSession.data;
|
||||
}
|
||||
```
|
||||
|
||||
### capturePayment
|
||||
|
||||
This method is used to capture the payment amount of an order. This is typically triggered manually by the store operator from the admin.
|
||||
|
||||
This method is also used for capturing payments of a swap of an order.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to capture the payment.
|
||||
|
||||
This method accepts the Payment as an object for its first parameter.
|
||||
|
||||
This method must return an object that will be stored in the `data` field of the Payment.
|
||||
|
||||
An example of a minimal implementation of `capturePayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```jsx
|
||||
async capturePayment(payment) {
|
||||
return {
|
||||
status: 'captured'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### refundPayment
|
||||
|
||||
This method is used to refund an order’s payment. This is typically triggered manually by the store operator from the admin. The refund amount might be the total order amount or part of it.
|
||||
|
||||
This method is also used for refunding payments of a swap of an order.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to refund the payment.
|
||||
|
||||
This method accepts the Payment as an object for its first parameter, and the amount to refund as a second parameter.
|
||||
|
||||
This method must return an object that is stored in the `data` field of the Payment.
|
||||
|
||||
An example of a minimal implementation of `refundPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```jsx
|
||||
async refundPayment(payment, amount) {
|
||||
return {
|
||||
id: 'test'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### cancelPayment
|
||||
|
||||
This method is used to cancel an order’s payment. This method is typically triggered by one of the following situations:
|
||||
|
||||
1. Before an order is placed and after the payment is authorized, an inventory check is done on products to ensure that products are still available for purchase. If the inventory check fails for any of the products, the payment is canceled.
|
||||
2. If the store operator cancels the order from the admin.
|
||||
|
||||
This method is also used for canceling payments of a swap of an order.
|
||||
|
||||
You can utilize this method to interact with the third-party provider and perform any actions necessary to cancel the payment.
|
||||
|
||||
This method accepts the Payment as an object for its first parameter.
|
||||
|
||||
This method must return an object that is stored in the `data` field of the Payment.
|
||||
|
||||
An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
```jsx
|
||||
async cancelPayment(payment) {
|
||||
return {
|
||||
id: 'test'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Optional Methods
|
||||
|
||||
### retrieveSavedMethods
|
||||
|
||||
This method can be added to your Payment Provider service if your third-party provider supports saving the customer’s payment methods. Please note that in Medusa there is no way to save payment methods.
|
||||
|
||||
This method is called when a request is sent to [Retrieve Saved Payment Methods](https://docs.medusajs.com/api/store/customer/retrieve-saved-payment-methods).
|
||||
|
||||
This method accepts the customer as an object for its first parameter.
|
||||
|
||||
This method returns an array of saved payment methods retrieved from the third-party provider. You have the freedom to shape the items in the array as you see fit since they will be returned as-is for the response to the request.
|
||||
|
||||
:::note
|
||||
|
||||
If you’re using Medusa’s [Next.js](../../../starters/nextjs-medusa-starter.md) or [Gatsby](../../../starters/gatsby-medusa-starter.md) storefront starters, note that the presentation of this method is not implemented. You’ll need to implement the UI and pages for this method based on your implementation and the provider you are using.
|
||||
|
||||
:::
|
||||
|
||||
An example of the implementation of `retrieveSavedMethods` taken from Stripe’s Payment Provider:
|
||||
|
||||
```jsx
|
||||
/**
|
||||
* Fetches a customers saved payment methods if registered in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @returns {Promise<Array<object>>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(customer) {
|
||||
if (customer.metadata && customer.metadata.stripe_id) {
|
||||
const methods = await this.stripe_.paymentMethods.list({
|
||||
customer: customer.metadata.stripe_id,
|
||||
type: "card",
|
||||
})
|
||||
|
||||
return methods.data
|
||||
}
|
||||
|
||||
return Promise.resolve([])
|
||||
}
|
||||
```
|
||||
|
||||
## What’s Next 🚀
|
||||
|
||||
- Check out the Payment Providers for [Stripe](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-stripe) and [PayPal](https://github.com/medusajs/medusa/tree/2e6622ec5d0ae19d1782e583e099000f0a93b051/packages/medusa-payment-paypal) for implementation examples.
|
||||
- Learn more about the [frontend checkout flow](./frontend-payment-flow-in-checkout.md).
|
||||
@@ -0,0 +1,130 @@
|
||||
# Architecture Overview
|
||||
|
||||
In this document, you’ll learn about the payment architecture in Medusa, specifically its 3 main components and the idempotency key.
|
||||
|
||||
## Introduction
|
||||
|
||||
The payment architecture refers to all operations in an ecommerce store related to processing a customer’s payment. It includes the checkout flow and order handling including refunds and swaps.
|
||||
|
||||
In Medusa, there are 3 main components in the payment architecture: Payment Provider, Payment Session, and Payment.
|
||||
|
||||
1. A **Payment Provider** is a service or method used to capture, authorize, and refund payments, among other functionalities.
|
||||
2. A **Payment Session** is a session associated with a cart and created during a customer’s checkout flow. It is controlled by the **Payment Provider** to authorize the payment and is used eventually to create a **Payment**.
|
||||
3. A **Payment** is associated with an order and it represents the amount authorized for the purchase. It is used later for further payment operations such as capturing or refunding payments.
|
||||
|
||||
An important part in the Payment architecture to understand is the **Idempotency Key**. It’s a unique value that’s generated for a cart and is used to retry payments during checkout if they fail.
|
||||
|
||||
## Payment Provider
|
||||
|
||||
### Overview
|
||||
|
||||
A Payment Provider in Medusa is a method to handle payments in selected regions. It is not associated with a cart, customer, or order in particular. It provides the necessary implementation to create Payment Sessions and Payments, as well as authorize and capture payments, among other functionalities.
|
||||
|
||||
Payment Providers can be integrated with third-party services that handle payment operations such as capturing a payment. An example of a Payment Provider is Stripe.
|
||||
|
||||
Payment Providers can also be related to a custom way of handling payment operations. An example of that is cash on delivery (COD) payment methods or Medusa’s [manual payment provider plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-manual) which provides a minimal implementation of a payment provider and allows store operators to manually handle order payments.
|
||||
|
||||
### How it is Created
|
||||
|
||||
A Payment Provider is essentially a Medusa [service](../services/create-service.md) with a unique identifier, and it extends the `PaymentService` provided by the `medusa-interfaces` package. It can be created as part of a [plugin](../../../guides/plugins.md), or it can be created just as a service file in your Medusa server.
|
||||
|
||||
As a developer, you will mainly work with the Payment Provider when integrating a payment method in Medusa.
|
||||
|
||||
When you run your Medusa server, the Payment Provider will be registered on your server if it hasn’t been already.
|
||||
|
||||
Once the Payment Provider is added to the server, the store operator will be able to choose on the [Medusa Admin](../../../admin/quickstart.md) the payment providers available in a region. These payment providers are shown to the customer at checkout to choose from and use.
|
||||
|
||||
:::caution
|
||||
|
||||
It’s important to choose a payment provider in the list of payment providers in a region, or else the payment provider cannot be used by customers on checkout.
|
||||
|
||||
:::
|
||||
|
||||
### Model Overview
|
||||
|
||||
The `PaymentProvider` model only has 2 attributes: `is_installed` to indicate if the payment provider is installed and its value is a boolean; and `id` which is the unique identifier that you define in the Payment Provider service.
|
||||
|
||||
## Payment Session
|
||||
|
||||
### Overview
|
||||
|
||||
Payment Sessions are linked to a customer’s cart. Each Payment Session is associated with a payment provider that is available in the customer cart’s region.
|
||||
|
||||
They hold the status of the payment flow throughout the checkout process which can be used to indicate different statuses such as an authorized payment or payment that requires more actions from the customer.
|
||||
|
||||
After the checkout process is completed and the Payment Session has been authorized successfully, a Payment instance will be created to be associated with the customer’s order and will be used for further actions related to that order.
|
||||
|
||||
### How it is Created
|
||||
|
||||
After the customer adds products to the cart, proceeds with the checkout flow, and reaches the payment method section, Payment Sessions are created for each Payment Provider available in that region.
|
||||
|
||||
During the creation of the Payment Session, the Payment Provider can interact with third-party services for any initialization necessary on their side. For example, when a Payment Session for Stripe is being created, a payment intent associated with the customer can be created with Stripe as well.
|
||||
|
||||
Payment Sessions can hold data that is necessary for the customer to complete their payment.
|
||||
|
||||
Among the Payment Sessions available only one will be selected based on the customer’s payment provider choice. For example, if the customer sees that they can pay with Stripe or PayPal and chooses Stripe, Stripe’s Payment Session will be the selected Payment Session of that cart.
|
||||
|
||||
### Model Overview
|
||||
|
||||
The `PaymentSession` model belongs to a `Cart`. This is the customer‘s cart that was used for checkout which lead to the creation of the Payment Session.
|
||||
|
||||
The `PaymentSession` also belongs to a `PaymentProvider`. This is the Payment Provider that was used to create the Payment Session and that controls it for further actions like authorizing the payment.
|
||||
|
||||
The `data` attribute is an object that holds any data required for the Payment Provider to perform payment operations like authorizing or capturing payment. For example, when a Stripe payment session is initialized, the `data` object will hold the payment intent among other data necessary to authorize the payment.
|
||||
|
||||
The `is_selected` attribute in the `PaymentSession` model is a boolean value that indicates whether this Payment Session was selected by the customer to pay for their purchase. Going back to the previous example of having Stripe and PayPal as the available Payment Providers, when the customer chooses Stripe, Stripe’s Payment Session will have `is_selected` set to true whereas PayPal’s Payment Session will have `is_selected` set to false.
|
||||
|
||||
The `status` attributes indicates the current status of the Payment Session. It can be one of the following values:
|
||||
|
||||
- `authorized`: The payment has been authorized which means the order can be placed successfully.
|
||||
- `pending`: The payment is still pending further actions. This is usually used when the payment session is initialized.
|
||||
- `requires_more`: The payment requires additional actions from the customer before the payment can be authorized successfully and the order can be placed. An example of this is payment methods that require 3-D Secure checks.
|
||||
- `error`: An error was encountered when an authorization was attempted. This status is usually used when an error has been encountered when authorizing the payment with a third-party payment provider.
|
||||
- `canceled`: The payment has been canceled.
|
||||
|
||||
These statuses are important in the checkout flow to determine the current step the customer is at and which action should come next. For example, if there is an attempt to place the order but the status of the Payment Session is not `authorized`, an error will be thrown.
|
||||
|
||||
## Payment
|
||||
|
||||
### Overview
|
||||
|
||||
A Payment is used to represent the amount authorized for a customer’s purchase. It is associated with the order placed by the customer and will be used after that for all operations related to the order’s payment such as capturing or refunding the payment.
|
||||
|
||||
Payments are generally created using data from the Payment Session and it holds any data that can be necessary to perform later payment operations.
|
||||
|
||||
### How it is Created
|
||||
|
||||
Once the customer completes their purchase and the payment has been authorized, a Payment instance will be created from the Payment Session. The Payment is associated first with the cart and then with the order once it’s created and placed.
|
||||
|
||||
When the store operator then chooses to capture the order from the Medusa Admin, the Payment is used by the Payment Provider to capture the payment. This is the same case for refunding the amount, canceling the order, or creating a swap.
|
||||
|
||||
### Model Overview
|
||||
|
||||
The `Payment` model belongs to the `Cart` that it was originally created from when the customer’s payment was authorized. It also belongs to an `Order` once it’s placed. Additionally, it belongs to a `PaymentProvider` which is the payment provider that the customer chose on checkout.
|
||||
|
||||
In case a `Swap` is created for an order, `Payment` will be associated with that swap to handle payment operations related to it.
|
||||
|
||||
Similar to `PaymentSession`, `Payment` has a `data` attribute which is an object that holds any data required to perform further actions with the payment such as capturing the payment.
|
||||
|
||||
`Payment` also holds attributes like `amount` which is the amount authorized for payment, and `amount_refunded` which is the amount refunded from the original amount if a refund has been initiated.
|
||||
|
||||
Additionally, `Payment` has the `captured_at` date-time attribute which is filled when the payment has been captured, and a `canceled_at` date-time attribute which is filled when the order has been canceled.
|
||||
|
||||
## Idempotency Key
|
||||
|
||||
An Idempotency Key is a unique key associated with a cart. It is generated at the last step of checkout before authorization of the payment is attempted.
|
||||
|
||||
That Idempotency Key is then set in the header under the `Idempotency-Key` response header field along with the header field `Access-Control-Expose-Headers` set to `Idempotency-Key`.
|
||||
|
||||
If an error occurs or the purchase is interrupted at any step, the client can retry the payment by adding the Idempotency Key of the cart as the `Idempotency-Key` header field in their subsequent requests.
|
||||
|
||||
The server wraps each essential part of the checkout completion in its own step and stores the current step of checkout with its associated Idempotency Key.
|
||||
|
||||
If then the request is interrupted for any reason or the payment fails, the client can retry completing the check out using the Idempotency Key, and the flow will continue from the last stored step.
|
||||
|
||||
This prevents any payment issues from occurring with the customers and allows for secure retries of failed payments or interrupted connections.
|
||||
|
||||
## What’s Next 🚀
|
||||
|
||||
- [Check out how the checkout flow is implemented on the frontend.](./frontend-payment-flow-in-checkout.md)
|
||||
- Check out payment plugins like [Stripe](../../../add-plugins/stripe.md), [Paypal](../../../add-plugins/paypal.md), and [Klarna](../../../add-plugins/klarna.md).
|
||||
@@ -0,0 +1,272 @@
|
||||
# How to Add a Fulfillment Provider
|
||||
|
||||
In this document, you’ll learn how to add a fulfillment provider to a Medusa server. If you’re unfamiliar with the Shipping architecture in Medusa, make sure to [check out the overview first](https://docs.medusajs.com/advanced/backend/shipping/overview/).
|
||||
|
||||
## 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 it’s 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 provider’s APIs, you can initialize it in the constructor and use it in other methods in the service.
|
||||
|
||||
Additionally, if you’re 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 you’re 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 customer’s 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 isn’t 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 customer’s 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({})
|
||||
}
|
||||
```
|
||||
|
||||
## What’s 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.
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
|
||||
title: Shipping Architecture Overview
|
||||
|
||||
---
|
||||
|
||||
# Architecture Overview
|
||||
|
||||
This document gives an overview of the shipping architecture and its 3 most important components.
|
||||
|
||||
## Introduction
|
||||
|
||||
In Medusa, the Shipping architecture relies on 3 components: **Shipping Profiles**, **Shipping Options**, and **Shipping Methods**.
|
||||
|
||||
The distinction between the 3 is important. It has been carefully planned and put together to support all the different ecommerce use cases and shipping providers that can be integrated.
|
||||
|
||||
It’s also constructed to support multiple regions, provide different shipment configurations and options for different product types, provide promotional shipments for your customers, and much more.
|
||||
|
||||
## Summary
|
||||
|
||||
- **Shipping Profiles:** created by the admin. They are used to group products that should be shipped in a different manner than the default. Shipping profiles can have multiple shipping options.
|
||||
- S**hipping Options:** created by the admin and belong to a shipping profile. They are specific to certain regions and can have cart conditions. They use an underlying fulfillment provider. Once a customer checks out, they can choose the shipping option that’s available and most relevant to them.
|
||||
- **Shipping Method:** created when the customer chooses a shipping option on checkout. The shipping method is basically a copy of the shipping option, but with values specific to the customer and the cart it’s associated with. When the order is placed, the shipping method will then be associated with the order and fulfilled based on the integration with the fulfillment provider.
|
||||
|
||||
:::note
|
||||
|
||||
Fulfillment providers are used to ship the products to your customers, whether physically or virtually. An example of a fulfillment provider would be FedEx.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## Shipping Profile
|
||||
|
||||
### Overview
|
||||
|
||||
Shipping profiles are the highest in the hierarchy in the shipping architecture.
|
||||
|
||||
Shipping profiles are created by the admin. The admin can specify the name of the shipping profile which will be a name that the customer can see.
|
||||
|
||||
A shipping profile is not associated with any fulfillment providers. It has multiple shipping options that can be associated with different providers.
|
||||
|
||||
### Purpose
|
||||
|
||||
Shipping profiles are used to group products that can be shipped in the same manner.
|
||||
|
||||
The default shipping profile is one that groups all of your store’s products. You also get a shipping profile that’s specific to gift cards. This is because, generally speaking, all products would be delivered similarly, whereas gift cards would be delivered in different behavior.
|
||||
|
||||
Although this might be the general case, there are still some use cases where you will have a set of products that should be shipped differently than others.
|
||||
|
||||
For example, shipping heavy items might be more expensive than others, which would enforce different price rates. In that case, you can create a new shipping profile that groups together heavy products. This would allow you to give these products more suitable price rates when creating their shipping options.
|
||||
|
||||
### Model Overview
|
||||
|
||||
The `ShippingProfile` model can have a set of `Product` instances. These would be the products the shipping profile is providing shipping options for.
|
||||
|
||||
The `ShippingProfile` has a `type` attribute that can be `default`, `gift_card`, or `custom`.
|
||||
|
||||
The `ShippingProfile` model also has an array of `ShippingOption` instances.
|
||||
|
||||
## Shipping Option
|
||||
|
||||
### Overview
|
||||
|
||||
After the admin adds a shipping profile, they can add shipping options that belong to that shipping profile from the admin dashboard.
|
||||
|
||||
Shipping options have a set of conditions like the region they’re available in or cart-specific conditions. For example, if your company operates in the United States as well as Germany, you might use a different shipping option for each of the two countries.
|
||||
|
||||
Among the configurations that the admin has to set when creating a shipping option is specifying the fulfillment provider it uses. This means that when you create a plugin for a fulfillment provider, that provider needs to be chosen as the fulfillment provider of a shipping option to be used in the store.
|
||||
|
||||
Shipping options are only shown to a customer during checkout if their cart satisfies the option’s conditions. Also, as they belong to a shipping profile, they’re only shown when products that belong to the same shipping profile are in the cart.
|
||||
|
||||
### Purpose
|
||||
|
||||
The first purpose that a shipping option has is showing the customer during checkout what shipping options are available for them.
|
||||
|
||||
Then, once the customer chooses a shipping option, that shipping option is used to create a shipping method with details specific to the customer and their cart. Then, the shipping method is associated with the cart, and the shipping option remains untouched.
|
||||
|
||||
Think of a shipping option as a template defined by the admin that indicates what data and values the shipping method should have when it’s chosen by the customer during checkout.
|
||||
|
||||
### Model Overview
|
||||
|
||||
The `ShippingOption` model belongs to the `ShippingProfile` model.
|
||||
|
||||
The `ShippingOption` model also belongs to a `FulfillmentProvider`. This can be either a custom third-party provider or one of Medusa’s default fulfillment providers.
|
||||
|
||||
It has the `price_type` attribute to indicate whether the shipping option’s rate is `calculated` by the provider or a fixed `flat_rate` price. It also has the `amount` attribute to set an amount for the shipping option if the `price_type` is `flat_rate`.
|
||||
|
||||
`ShippingOption` also belongs to a `Region`, which resembles one or more countries. This defines where the shipping option is available.
|
||||
|
||||
`ShippingOption` has a set of `ShippingOptionRequirement` instances. The `ShippingOptionRequirement` model allows defining cart rules which determine whether the shipping option will be available or not for a customer during checkout. For example, you can set a minimum subtotal amount for a shipping option to be available for a customer’s cart.
|
||||
|
||||
The `is_return` attribute is used to indicate whether the shipping option is used for shipping orders or returning orders. Shipping options can only be used for one or the other.
|
||||
|
||||
The `data` attribute is used to specify any data necessary for fulfilling the shipment based on the underlying fulfillment provider. When you integrate a fulfillment provider, you can check in that provider’s documentation for any data necessary when creating a new shipment.
|
||||
|
||||
The `data` attribute does not have any specific format. It’s up to you to choose whatever data is included here.
|
||||
|
||||
## Shipping Method
|
||||
|
||||
### Overview
|
||||
|
||||
Unlike the previous two components, a shipping method is not created by the admin. It’s created when a `POST` request is sent to `/store/carts/:id/shipping-methods` after the customer chooses a shipping option.
|
||||
|
||||
The shipping method will be created based on the chosen shipping option and it’ll be associated with the customer’s cart. Then, when the order is placed, the shipping method is associated with the order.
|
||||
|
||||
A shipping method can be fulfilled automatically or manually through the admin dashboard. This is based on the fulfillment provider associated with the shipping option the shipping method is based on.
|
||||
|
||||
### Purpose
|
||||
|
||||
It’s important to understand the distinction between shipping methods and shipping options. Shipping options are templates created by the admin to indicate what shipping options should be shown to a customer. This provides customization capabilities in a store, as an admin is free to specify configurations for that option such as what fulfillment provider it uses or what are its rates.
|
||||
|
||||
When handling the order and fulfilling it, you, as a developer, will be mostly interacting with the shipping method.
|
||||
|
||||
This separation allows for developers to implement the custom integration with third-party fulfillment providers as necessary while also ensuring that the admin has full control of their store.
|
||||
|
||||
## Model Overview
|
||||
|
||||
A lot of the shipping method’s attributes are similar to the shipping option’s attribute.
|
||||
|
||||
The `ShippingMethod` model belongs to a `ShippingOption`.
|
||||
|
||||
Similar to the `data` attribute explained for the `ShippingOption` model, a `ShippingMethod` has a similar `data` attribute that includes all the data to be sent to the fulfillment provider when fulfilling the order.
|
||||
|
||||
The `ShippingMethod` belongs to a `Cart`. This is the cart the customer is checking out with.
|
||||
|
||||
The `ShippingMethod` also belongs to the `Order` model. This association is accomplished when the order is placed.
|
||||
|
||||
The `ShippingMethod` instance holds a `price` attribute, which will either be the flat rate price or the calculated price.
|
||||
|
||||
## What’s 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).
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
|
||||
title: Create a Subscriber
|
||||
|
||||
---
|
||||
|
||||
# Create a Subscriber
|
||||
|
||||
In this document, you’ll learn how you create a subscriber in your Medusa server that listens to events to perform an action.
|
||||
|
||||
## Overview
|
||||
|
||||
In Medusa, there are events that are emitted when a certain action occurs. For example, if a customer places an order, the `order.placed` event is emitted with the order data.
|
||||
|
||||
The purpose of these events is to allow other parts of the platform, or third-party integrations, to listen to those events and perform a certain action. That is done by creating subscribers.
|
||||
|
||||
Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the `order.placed` event and send the email when the event is emitted.
|
||||
|
||||
Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.
|
||||
|
||||
Custom subscribers reside in your project's `src/subscribers` directory. Files here should export classes, which will be treated as subscribers by Medusa. By convention, the class name should end with `Subscriber` and the file name should be the camel-case version of the class name without `Subscriber`. For example, the `WelcomeSubscriber` class is in the file `src/subscribers/welcome.js`.
|
||||
|
||||
Whenever an event is emitted, the subscriber’s registered handler method is executed. The handler method receives as a parameter an object that holds data related to the event. For example, if an order is placed the `order.placed` event will be emitted and all the handlers will receive the order id in the parameter object.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis and you will therefore need to make sure that [Redis](https://redis.io) is installed and configured for your Medusa project.
|
||||
|
||||
Then, you need to set your Redis URL in your Medusa server. By default, the Redis URL is `redis://localhost:6379`. If you use a different one, set the following environment variable in `.env`:
|
||||
|
||||
```bash
|
||||
REDIS_URL=<YOUR_REDIS_URL>
|
||||
```
|
||||
|
||||
After that, in `medusa-config.js`, you’ll need to comment out the following line:
|
||||
|
||||
```jsx
|
||||
module.exports = {
|
||||
projectConfig: {
|
||||
redis_url: REDIS_URL, //this line is commented out
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After that, you are able to listen to events on your server.
|
||||
|
||||
## Implementation
|
||||
|
||||
After creating the file under `src/subscribers`, in the constructor of your subscriber, you should listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscriber’s constructor.
|
||||
|
||||
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
|
||||
|
||||
For example, here is the `OrderNotifierSubscriber` class which is created in `src/subscribers/orderNotifier.js`:
|
||||
|
||||
```jsx
|
||||
class OrderNotifierSubscriber {
|
||||
constructor({ eventBusService }) {
|
||||
eventBusService.subscribe("order.placed", this.handleOrder);
|
||||
}
|
||||
|
||||
handleOrder = async (data) => {
|
||||
console.log("New Order: " + data.id)
|
||||
};
|
||||
}
|
||||
|
||||
export default OrderNotifierSubscriber;
|
||||
```
|
||||
|
||||
This subscriber will register the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed, and it will receive the order ID in the `data` parameter. You can then use the order’s details to perform any kind of task you need.
|
||||
|
||||
> The `data` object will not contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
|
||||
|
||||
## Using Services in Subscribers
|
||||
|
||||
You can access any service through the dependencies injected to your subscriber’s constructor.
|
||||
|
||||
For example:
|
||||
|
||||
```jsx
|
||||
constructor({ productService, eventBusService }) {
|
||||
this.productService = productService;
|
||||
|
||||
eventBusService.subscribe("order.placed", this.handleOrder);
|
||||
}
|
||||
```
|
||||
|
||||
You can then use `this.productService` anywhere in your subscriber’s methods.
|
||||
|
||||
## What’s Next 🚀
|
||||
|
||||
- [Learn how to create a service.](/advanced/backend/services/create-service)
|
||||
Reference in New Issue
Block a user