docs: add documentation for v1.8 (#3669)
This commit is contained in:
1 parent
0cca13779d
commit
c6bfad14d8
123 files changed
+7610
-2697
No files matched your search
@@ -1,158 +1,167 @@
|
||||
---
|
||||
description: 'Learn how to create a payment provider in the Medusa backend. This guide explains the different methods available in a fulfillment provider.'
|
||||
description: 'Learn how to create a payment processor in the Medusa. This guide explains the different methods available in a payment processor.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Create a Payment Provider
|
||||
# How to Create a Payment Processor
|
||||
|
||||
In this document, you’ll learn how to add a Payment Provider to your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.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/master/packages/medusa-payment-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](../../../development/services/create-service.md) file in `src/services`. A Payment Provider is essentially a service that extends `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`.
|
||||
|
||||
Payment Provider Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Provider in the Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Payment Providers are loaded and installed at the backend 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](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) 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.
|
||||
In this document, you’ll learn how to create a Payment Processor in your Medusa backend. If you’re unfamiliar with the Payment architecture in Medusa, make sure to check out the [overview](../payment.md) first.
|
||||
|
||||
:::note
|
||||
|
||||
All these methods must be declared async in the Payment Provider Service.
|
||||
Before v1.8 of Medusa, this guide explained how to create a payment provider. Payment Providers are now considered legacy and are deprecated. Moving forward, it's recommended to create a Payment Processor that implements the Payment Processor API.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
A Payment Processor is the payment method used to authorize, capture, and refund payment, among other actions. An example of a Payment Processor is Stripe.
|
||||
|
||||
By default, Medusa has a [manual payment provider](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-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 Processor is as simple as creating a [service](../../../development/services/create-service.md) file in `src/services`. A Payment Processor is essentially a service that extends `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`.
|
||||
|
||||
Payment Processor Services must have a static property `identifier`. It's the name that will be used to install and refer to the Payment Processor in the Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
Payment Processors are loaded and installed at the server startup. If not already saved, they're saved in the database and are represented by the `PaymentProvider` entity.
|
||||
|
||||
:::
|
||||
|
||||
The Payment Processor is also required to implement the following methods:
|
||||
|
||||
1. `initiatePayment`: Called when a Payment Session for the Payment Provider is to be created.
|
||||
2. `retrievePayment`: Used to retrieve payment session data, which can be retrieved from a third-party provider.
|
||||
3. `getPaymentStatus`: 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. `deletePayment`: Used to perform any action necessary before a Payment Session is deleted. For example, you can cancel the payment with the third-party provider.
|
||||
6. `authorizePayment`: Used to authorize the payment amount of the cart before the order or swap is created.
|
||||
7. `capturePayment`: Used to capture the payment amount of an order or swap.
|
||||
8. `refundPayment`: Used to refund a payment amount of an order or swap.
|
||||
9. `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 Processor.
|
||||
|
||||
:::
|
||||
|
||||
These methods are used at different points in the Checkout flow as well as when processing the order after it’s placed.
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
## Create a Payment Provider
|
||||
## Create a Payment Processor
|
||||
|
||||
The first step to create a payment provider is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment provider.
|
||||
The first step to create a payment processor is to create a JavaScript or TypeScript file in `src/services`. The file's name should be the name of the payment processor, and it should be in snake case.
|
||||
|
||||
For example, create the file `src/services/my-payment.ts` with the following content:
|
||||
For example, create the file `src/services/my-payment-processor.ts` with the following content:
|
||||
|
||||
```ts title=src/services/my-payment.ts
|
||||
```ts title=src/services/my-payment-processor.ts
|
||||
import {
|
||||
AbstractPaymentService, PaymentContext, Data,
|
||||
Payment, PaymentSession, PaymentSessionStatus,
|
||||
PaymentSessionData, Cart, PaymentData,
|
||||
PaymentSessionResponse } from "@medusajs/medusa"
|
||||
AbstractPaymentProcessor,
|
||||
PaymentProcessorContext,
|
||||
PaymentProcessorError,
|
||||
PaymentProcessorSessionResponse,
|
||||
PaymentSessionStatus,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import { EntityManager } from "typeorm"
|
||||
class MyPaymentProcessor extends AbstractPaymentProcessor {
|
||||
static identifier = "my-payment"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
protected manager_: EntityManager
|
||||
protected transactionManager_: EntityManager | undefined
|
||||
|
||||
async getPaymentData(
|
||||
paymentSession: PaymentSession
|
||||
): Promise<Data> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async updatePaymentData(
|
||||
paymentSessionData: PaymentSessionData,
|
||||
data: Data
|
||||
): Promise<PaymentSessionData> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async createPayment(
|
||||
context: Cart & PaymentContext
|
||||
): Promise<PaymentSessionResponse> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async retrievePayment(paymentData: Data): Promise<Data> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async updatePayment(
|
||||
paymentSessionData: PaymentSessionData,
|
||||
cart: Cart
|
||||
): Promise<PaymentSessionData> {
|
||||
async capturePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async authorizePayment(
|
||||
paymentSession: PaymentSession,
|
||||
context: Data
|
||||
): Promise<{
|
||||
data: PaymentSessionData;
|
||||
status: PaymentSessionStatus
|
||||
}> {
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async capturePayment(payment: Payment): Promise<PaymentData> {
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async refundPayment(
|
||||
payment: Payment,
|
||||
refundAmount: number
|
||||
): Promise<PaymentData> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async cancelPayment(payment: Payment): Promise<PaymentData> {
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async deletePayment(
|
||||
paymentSession: PaymentSession
|
||||
): Promise<void> {
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async getStatus(data: Data): Promise<PaymentSessionStatus> {
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async refundPayment(
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
async updatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
void |
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse
|
||||
> {
|
||||
throw new Error("Method not implemented.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MyPaymentService
|
||||
export default MyPaymentProcessor
|
||||
```
|
||||
|
||||
Where `MyPaymentService` is the name of your Payment Provider service. For example, Stripe’s Payment Provider Service is called `StripeProviderService`.
|
||||
Where `MyPaymentProcessor` is the name of your Payment Processor service.
|
||||
|
||||
Payment Providers must extend `AbstractPaymentService` from the core Medusa package `@medusajs/medusa`.
|
||||
Payment Processors must extend `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`.
|
||||
|
||||
:::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](../../../development/services/create-service.md).
|
||||
Following the naming convention of Services, the name of the file should be the slug name of the Payment Processor, and the name of the class should be the camel case name of the Payment Processors suffixed with “Service”. In the example above, the name of the file should be `my-payment.ts`. You can learn more in the [service documentation](../../../development/services/create-service.md).
|
||||
|
||||
:::
|
||||
|
||||
### Identifier
|
||||
### identifier
|
||||
|
||||
As mentioned in the overview, Payment Providers should have a static `identifier` property.
|
||||
As mentioned in the overview, Payment Processors should have a static `identifier` property.
|
||||
|
||||
The `PaymentProvider` entity 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 `PaymentProvider` entity has 2 properties: `identifier` and `is_installed`. The value of the `identifier` property in the class will be used when the Payment Processor is created in the database.
|
||||
|
||||
The value of this property will also be used to reference the Payment Provider throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession).
|
||||
The value of this property will also be used to reference the Payment Processor throughout the Medusa backend. For example, the identifier is used when a [Payment Session in a cart is selected on checkout](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession).
|
||||
|
||||
The identifier can be retrieved using the `getIdentifier` method, which is defined in `AbstractPaymentProcessor`.
|
||||
|
||||
### constructor
|
||||
|
||||
You can use the `constructor` of your Payment Provider to have access to different services in Medusa through dependency injection.
|
||||
You can use the `constructor` of your Payment Processor to have access to different services in Medusa through [dependency injection](../../../development/fundamentals/dependency-injection.md).
|
||||
|
||||
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 backend 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:
|
||||
Additionally, if you’re creating your Payment Processor as an external plugin to be installed on any Medusa backend 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:
|
||||
|
||||
```ts
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
@@ -165,42 +174,86 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
}
|
||||
```
|
||||
|
||||
### createPayment
|
||||
### PaymentProcessorError
|
||||
|
||||
This method is called during checkout when [Payment Sessions are initialized](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) 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 a context object as a first parameter. This object is of type `PaymentContext` and has the following properties:
|
||||
Before diving into the methods you'll need to implement, you'll notice that part of the expected return signature of these method includes `PaymentProcessorError`. This is an interface of the following definition:
|
||||
|
||||
```ts
|
||||
type PaymentContext = {
|
||||
cart: {
|
||||
context: Record<string, unknown>
|
||||
id: string
|
||||
email: string
|
||||
shipping_address: Address | null
|
||||
shipping_methods: ShippingMethod[]
|
||||
}
|
||||
currency_code: string
|
||||
amount: number
|
||||
resource_id?: string
|
||||
customer?: Customer
|
||||
interface PaymentProcessorError {
|
||||
error: string
|
||||
code?: string
|
||||
detail?: any
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
While implementing the following methods, if you need to inform the Medusa core that an error occurred at a certain stage, return an object having the attributes defined in the `PaymentProcessorError` interface.
|
||||
|
||||
Before v1.7.2, the first parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`.
|
||||
|
||||
:::
|
||||
|
||||
This method must return an object of type `PaymentSessionResponse`. It should have the following properties:
|
||||
For example, the Stripe payment processor has the following method to create the error object, which is used within other methods:
|
||||
|
||||
```ts
|
||||
type PaymentSessionResponse = {
|
||||
update_requests: {
|
||||
customer_metadata: Record<string, unknown>
|
||||
abstract class StripeBase extends AbstractPaymentProcessor {
|
||||
// ...
|
||||
protected buildError(
|
||||
message: string,
|
||||
e: Stripe.StripeRawError | PaymentProcessorError | Error
|
||||
): PaymentProcessorError {
|
||||
return {
|
||||
error: message,
|
||||
code: "code" in e ? e.code : "",
|
||||
detail: isPaymentProcessorError(e)
|
||||
? `${e.error}${EOL}${e.detail ?? ""}`
|
||||
: "detail" in e
|
||||
? e.detail
|
||||
: e.message ?? "",
|
||||
}
|
||||
}
|
||||
|
||||
// used in other methods
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse["session_data"]
|
||||
> {
|
||||
try {
|
||||
// ...
|
||||
} catch (e) {
|
||||
return this.buildError(
|
||||
"An error occurred in retrievePayment",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### initiatePayment
|
||||
|
||||
This method is called either if a region has only one payment provider enabled or when [a Payment Session is selected](/api/store#tag/Cart/operation/PostCartsCartPaymentSession), which occurs when the customer selects their preferred payment method during checkout. 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 create a Payment Intent for the customer.
|
||||
|
||||
The method receives a context object as a first parameter. This object is of type `PaymentProcessorContext` and has the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorContext = {
|
||||
billing_address?: Address | null
|
||||
email: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
resource_id: string
|
||||
customer?: Customer
|
||||
context: Record<string, unknown>
|
||||
paymentSessionData: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
This method must return an object of type `PaymentProcessorSessionResponse`. It should have the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentProcessorSessionResponse = {
|
||||
update_requests?: {
|
||||
customer_metadata?: Record<string, unknown>
|
||||
}
|
||||
session_data: Record<string, unknown>
|
||||
}
|
||||
@@ -209,21 +262,23 @@ type PaymentSessionResponse = {
|
||||
Where:
|
||||
|
||||
- `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.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.
|
||||
- `update_requests` is an object that can be used to pass data from the payment provider plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
|
||||
- `update_requests` is an object that can be used to pass data from the Payment Processor plugin to the core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
|
||||
|
||||
An example of a minimal implementation of `createPayment`:
|
||||
An example of a minimal implementation of `initiatePayment`:
|
||||
|
||||
```ts
|
||||
import {
|
||||
PaymentContext,
|
||||
import {
|
||||
PaymentContext,
|
||||
PaymentSessionResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async createPayment(
|
||||
context: Cart & PaymentContext
|
||||
): Promise<PaymentSessionResponse> {
|
||||
async initiatePayment(
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
PaymentProcessorError | PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
@@ -235,9 +290,9 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
|
||||
### 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 is used to provide a uniform way of retrieving the payment information from the third-party provider. For example, in Stripe’s Payment Processor 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 accepts the `data` field of a Payment Session. 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.
|
||||
|
||||
@@ -249,21 +304,23 @@ import { Data } from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async retrievePayment(paymentData: Data): Promise<Data> {
|
||||
async retrievePayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### getStatus
|
||||
### getPaymentStatus
|
||||
|
||||
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.
|
||||
Its main usage is in the place order and create swap workflows. If the status returned is not `authorized`, then the payment is considered failed and an error will be thrown, stopping the task from completion.
|
||||
|
||||
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 accepts the `data` field of a Payment 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:
|
||||
This method returns a string that represents the status. This string can be from the enum `PaymentSessionStatus` which can be imported from `@medusajs/medusa`. 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.
|
||||
@@ -271,7 +328,7 @@ This method returns a string that represents the status. The status must be one
|
||||
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:
|
||||
An example of a minimal implementation of `getPaymentStatus` where you don’t need to interact with the third-party provider:
|
||||
|
||||
```ts
|
||||
import { Data, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
@@ -279,18 +336,14 @@ import { Data, PaymentSessionStatus } from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async getStatus(data: Data): Promise<PaymentSessionStatus> {
|
||||
async getPaymentStatus(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<PaymentSessionStatus> {
|
||||
return PaymentSessionStatus.AUTHORIZED
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::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](/api/store/#tag/Cart/operation/PostCartsCartLineItems) or when a [shipping method is selected](/api/store/#tag/Cart/operation/PostCartsCartShippingMethod).
|
||||
@@ -301,47 +354,35 @@ A line item refers to a product in the cart.
|
||||
|
||||
:::
|
||||
|
||||
It accepts the `data` field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type `PaymentContext` and has the following properties:
|
||||
It accepts the `data` field of the Payment Session as the first parameter and a context object as a second parameter. This object is of type `PaymentProcessorContext` and has the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentContext = {
|
||||
cart: {
|
||||
context: Record<string, unknown>
|
||||
id: string
|
||||
email: string
|
||||
shipping_address: Address | null
|
||||
shipping_methods: ShippingMethod[]
|
||||
}
|
||||
type PaymentProcessorContext = {
|
||||
billing_address?: Address | null
|
||||
email: string
|
||||
currency_code: string
|
||||
amount: number
|
||||
resource_id?: string
|
||||
resource_id: string
|
||||
customer?: Customer
|
||||
context: Record<string, unknown>
|
||||
paymentSessionData: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
Before v1.7.2, the second parameter was of type `Cart`. This method remains backwards compatible, but will be changed in the future. So, it's recommended to change the type of the first parameter to `PaymentContext`.
|
||||
|
||||
:::
|
||||
|
||||
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 of type `PaymentSessionResponse`. It should have the following properties:
|
||||
|
||||
```ts
|
||||
type PaymentSessionResponse = {
|
||||
update_requests: {
|
||||
customer_metadata: Record<string, unknown>
|
||||
type PaymentProcessorSessionResponse = {
|
||||
update_requests?: {
|
||||
customer_metadata?: Record<string, unknown>
|
||||
}
|
||||
session_data: Record<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `session_data` is the data that is going to be stored in the `data` field of the Payment Session to be created. As mentioned in the [Architecture Overview](../payment.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.
|
||||
- `update_requests` is an object that can be used to request from the Medusa core to update internal resources. Currently, it only has one attribute `customer_metadata` which allows updating the `metadata` field of the customer.
|
||||
These are the same fields explained in the [initiatePayment](#initiatepayment) section.
|
||||
|
||||
An example of a minimal implementation of `updatePayment`:
|
||||
|
||||
@@ -357,9 +398,12 @@ import {
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async updatePayment(
|
||||
paymentSessionData: PaymentSessionData,
|
||||
cart: Cart
|
||||
): Promise<PaymentSessionData> {
|
||||
context: PaymentProcessorContext
|
||||
): Promise<
|
||||
void |
|
||||
PaymentProcessorError |
|
||||
PaymentProcessorSessionResponse
|
||||
> {
|
||||
// prepare data
|
||||
return {
|
||||
session_data,
|
||||
@@ -369,43 +413,16 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
}
|
||||
```
|
||||
|
||||
### 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](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) 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.
|
||||
|
||||
```ts
|
||||
import { Data, PaymentSessionData } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async updatePaymentData(
|
||||
paymentSessionData: PaymentSessionData,
|
||||
data: Data
|
||||
): Promise<PaymentSessionData> {
|
||||
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](/api/store/#tag/Cart/operation/DeleteCartsCartPaymentSessionsSession).
|
||||
2. When the [Payment Session is refreshed](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionsSession). 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.
|
||||
3. When the Payment Processor is no longer available. This generally happens when the store operator removes it from the available Payment Processor in the admin.
|
||||
4. When the region of the store is changed based on the cart information and the Payment Processor is not available in the new region.
|
||||
|
||||
It accepts the Payment Session as an object for its first parameter.
|
||||
It accepts the `data` field of the payment session 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.
|
||||
|
||||
@@ -418,18 +435,18 @@ import { PaymentSession } from "@medusajs/medusa"
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async deletePayment(
|
||||
paymentSession: PaymentSession
|
||||
): Promise<void> {
|
||||
return
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return paymentSessionData
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### authorizePayment
|
||||
|
||||
This method is used to authorize payment using the Payment Session for an order. This is called when the [cart is completed](/api/store/#tag/Cart/operation/PostCartsCartComplete) and before the order is created.
|
||||
This method is used to authorize payment using the Payment Session of an order. This is called when the [cart is completed](/api/store/#tag/Cart/operation/PostCartsCartComplete) and before the order is created.
|
||||
|
||||
This method is also used for authorizing payments of a swap of an order.
|
||||
This method is also used for authorizing payments of a swap of an order and when authorizing sessions in a payment collection.
|
||||
|
||||
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.
|
||||
|
||||
@@ -439,16 +456,20 @@ If the payment authorization fails, then an error will be thrown and the order w
|
||||
|
||||
:::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.
|
||||
The payment authorization status is determined using the `getPaymentStatus` 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:
|
||||
This method accepts the `data` field of a payment session for its first parameter, and a `context` object as a second parameter. The `context` object may contain the following properties:
|
||||
|
||||
1. `ip`: The customer’s IP.
|
||||
2. `idempotency_key`: The [Idempotency Key](../payment.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.
|
||||
3. `cart_id`: The ID of a cart. This is only during operations like placing an order or creating a swap.
|
||||
|
||||
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.
|
||||
This method must return an object containing the following properties:
|
||||
|
||||
- `status` which is a string that indicates the current status of the payment.
|
||||
- `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.
|
||||
|
||||
@@ -466,12 +487,15 @@ import {
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async authorizePayment(
|
||||
paymentSession: PaymentSession,
|
||||
context: Data
|
||||
): Promise<{
|
||||
data: PaymentSessionData;
|
||||
status: PaymentSessionStatus
|
||||
}> {
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
context: Record<string, unknown>
|
||||
): Promise<
|
||||
PaymentProcessorError |
|
||||
{
|
||||
status: PaymentSessionStatus;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
> {
|
||||
return {
|
||||
status: PaymentSessionStatus.AUTHORIZED,
|
||||
data: {
|
||||
@@ -482,39 +506,15 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
}
|
||||
```
|
||||
|
||||
### 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`:
|
||||
|
||||
```ts
|
||||
import { Data, PaymentSession } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async getPaymentData(
|
||||
paymentSession: PaymentSession
|
||||
): Promise<Data> {
|
||||
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.
|
||||
This method is also used for capturing payments of a swap of an order, or when the [Capture Payment](/api/admin#tag/Payment/operation/PostPaymentsPaymentCapture) endpoint is called.
|
||||
|
||||
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 accepts the `data` field of the Payment for its first parameter.
|
||||
|
||||
This method must return an object that will be stored in the `data` field of the Payment.
|
||||
|
||||
@@ -538,11 +538,11 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
|
||||
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.
|
||||
This method is also used for refunding payments of a swap or a claim of an order, or when the [Refund Payment](/api/admin#tag/Payment/operation/PostPaymentsPaymentRefunds) endpoint is called.
|
||||
|
||||
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 accepts the `data` field of a Payment 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.
|
||||
|
||||
@@ -555,9 +555,9 @@ import { Data, Payment } from "@medusajs/medusa"
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async refundPayment(
|
||||
payment: Payment,
|
||||
paymentSessionData: Record<string, unknown>,
|
||||
refundAmount: number
|
||||
): Promise<Data> {
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
@@ -571,14 +571,11 @@ This method is used to cancel an order’s payment. This method is typically tri
|
||||
|
||||
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.
|
||||
3. When the payment of an order's swap is canceled.
|
||||
|
||||
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.
|
||||
This method accepts the `data` field of the Payment for its first parameter.
|
||||
|
||||
An example of a minimal implementation of `cancelPayment` that doesn’t need to interact with a third-party provider:
|
||||
|
||||
@@ -588,7 +585,9 @@ import { Data, Payment } from "@medusajs/medusa"
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
async cancelPayment(payment: Payment): Promise<Data> {
|
||||
async cancelPayment(
|
||||
paymentSessionData: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | PaymentProcessorError> {
|
||||
return {
|
||||
id: "test",
|
||||
}
|
||||
@@ -598,57 +597,7 @@ class MyPaymentService extends AbstractPaymentService {
|
||||
|
||||
---
|
||||
|
||||
## 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](/api/store/#tag/Customer/operation/GetCustomersCustomerPaymentMethods).
|
||||
|
||||
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.mdx) storefront starter, 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:
|
||||
|
||||
```ts
|
||||
import { Customer, Data } from "@medusajs/medusa"
|
||||
// ...
|
||||
|
||||
class MyPaymentService extends AbstractPaymentService {
|
||||
// ...
|
||||
/**
|
||||
* Fetches a customers saved payment methods
|
||||
* if they're saved in Stripe.
|
||||
* @param {object} customer - customer to fetch saved cards for
|
||||
* @return {Promise<Array<object>>} saved payments methods
|
||||
*/
|
||||
async retrieveSavedMethods(
|
||||
customer: Customer
|
||||
): Promise<Data[]> {
|
||||
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([])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- Implementation Examples: [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) payment providers.
|
||||
- Implementation Examples: [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) Payment Processors.
|
||||
- [Implement checkout flow on the storefront](../storefront/implement-checkout-flow.mdx).
|
||||
@@ -40,7 +40,7 @@ Customers can manage their cart including adding, updating, and removing items f
|
||||
|
||||
Developers can integrate any third-party provider or custom logic to offer shipping and payment options for customers during checkout. They can integrate them using existing plugins or by creating their own.
|
||||
|
||||
Admins can specify available shipping and payment providers during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx).
|
||||
Admins can specify available shipping and payment processors during checkout for customers based on their [Region](../regions-and-currencies/overview.mdx).
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
@@ -55,10 +55,10 @@ Admins can specify available shipping and payment providers during checkout for
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Backend: Create Payment Provider',
|
||||
label: 'Backend: Create Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create a fulfillment provider in the backend.'
|
||||
description: 'Learn how to create a payment processor in the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,51 +8,63 @@ In this document, you’ll learn about the payment architecture in Medusa, speci
|
||||
|
||||
## 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.
|
||||
The payment architecture refers to all operations in a commerce application 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.
|
||||
In Medusa, there are 3 main components in the payment architecture: Payment Processor, 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**.
|
||||
:::note
|
||||
|
||||
Payment Processors were previously named Payment Provider. With the roll-out of the Payment Processor API following v1.8 of the core Medusa package, Payment Provider is considered a legacy now. However, there are certain entities and services that still use the name Payment Provider as they still don't follow this change.
|
||||
|
||||
:::
|
||||
|
||||
1. A **Payment Processor** 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 Processor** 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
|
||||
## Payment Processor
|
||||
|
||||
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.
|
||||
A Payment Processor 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 Processors can be integrated with third-party services that handle payment operations such as capturing a payment. An example of a Payment Processor 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.
|
||||
Payment Processors 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 Payment Provider is Created
|
||||
:::tip
|
||||
|
||||
A Payment Provider is essentially a Medusa [service](../../development/services/create-service.md) with a unique identifier, and it extends the ``AbstractPaymentService` from the core Medusa package `@medusajs/medusa`. It can be created as part of a [plugin](../../development/plugins/overview.mdx), or it can be created just as a service file in your Medusa backend.
|
||||
The manual payment plugin is still considered a payment provider since it does not follow the Payment Processor API introduced in v1.8 of the core Medusa package.
|
||||
|
||||
As a developer, you will mainly work with the Payment Provider when integrating a payment method in Medusa.
|
||||
:::
|
||||
|
||||
When you run your Medusa backend, the Payment Provider will be registered on your backend if it hasn’t been already.
|
||||
### How Payment Processor is Created
|
||||
|
||||
Once the Payment Provider is added to the backend, the store operator will be able to choose on the [Medusa Admin](../../admin/quickstart.mdx) the payment providers available in a region. These payment providers are shown to the customer at checkout to choose from and use.
|
||||
A Payment Processor is essentially a Medusa [service](../../development/services/create-service.md) with a unique identifier, and it extends the `AbstractPaymentProcessor` from the core Medusa package `@medusajs/medusa`. You can create it as part of a [plugin](../../development/plugins/overview.mdx), or just as a service file in your Medusa backend.
|
||||
|
||||
As a developer, you will mainly work with the Payment Processor when integrating a payment method in Medusa.
|
||||
|
||||
When you run your Medusa backend, the Payment Processor will be registered on your backend if it hasn’t been already.
|
||||
|
||||
Once the Payment Processor is added to the backend, the store operator will be able to choose using the [admin dashboard](../../admin/quickstart.mdx) the payment processors available in a region. You can alternatively do that using the [admin APIs](/api/admin). These payment processors are shown to the customer at checkout as payment methods 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.
|
||||
It’s important to enable a payment processor in a region, or else the payment processor cannot be used by customers on checkout.
|
||||
|
||||
:::
|
||||
|
||||
### PaymentProvider Entity Overview
|
||||
|
||||
The [`PaymentProvider`](../../references/entities/classes/PaymentProvider.md) entity 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.
|
||||
The [`PaymentProvider`](../../references/entities/classes/PaymentProvider.md) entity only has 2 attributes: `is_installed` which is a boolean value indicating whether the Payment Processor is installed; and `id` which is the unique identifier that you define in the Payment Processor service.
|
||||
|
||||
---
|
||||
|
||||
## Payment Session
|
||||
|
||||
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.
|
||||
Payment Sessions are linked to a customer’s cart. Each Payment Session is associated with a payment processor 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.
|
||||
|
||||
@@ -60,30 +72,30 @@ After the checkout process is completed and the Payment Session has been authori
|
||||
|
||||
### How Payment Session 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.
|
||||
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 Processor 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.
|
||||
During the creation of the Payment Session, the Payment Processor 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 is 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.
|
||||
Among the Payment Sessions available only one will be selected based on the customer’s payment processor of 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.
|
||||
|
||||
### PaymentSession Entity Overview
|
||||
|
||||
The [`PaymentSession`](../../references/entities/classes/PaymentSession.md) entity 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 `PaymentSession` instance also belongs to a `PaymentProvider` instance. This is the Payment Processor 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 `data` attribute is an object that holds any data required for the Payment Processor 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` entity 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 `is_selected` attribute in the `PaymentSession` entity 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 Processors, 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.
|
||||
- `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 processor.
|
||||
- `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.
|
||||
@@ -100,11 +112,11 @@ Payments are generally created using data from the Payment Session and it holds
|
||||
|
||||
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.
|
||||
When the store operator then chooses to capture the order from the Medusa Admin, the Payment is used by the Payment Processor to capture the payment. This is the same case for refunding the amount, canceling the order, or creating a swap.
|
||||
|
||||
### Payment Entity Overview
|
||||
|
||||
The [`Payment`](../../references/entities/classes/Payment.md) entity 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.
|
||||
The [`Payment`](../../references/entities/classes/Payment.md) entity 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 processor 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.
|
||||
|
||||
@@ -135,4 +147,4 @@ This prevents any payment issues from occurring with the customers and allows fo
|
||||
## See Also
|
||||
|
||||
- [Available Payment Plugins](../../plugins/payment/index.mdx)
|
||||
- [Create a Payment Provider](./backend/add-payment-provider.md)
|
||||
- [Create a Payment Processor](./backend/add-payment-provider.md)
|
||||
@@ -8,13 +8,13 @@ import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Implement Checkout Flow
|
||||
|
||||
This document will guide you through the steps needed to implement the checkout flow in a Medusa storefront, including steps related to adding a custom payment provider.
|
||||
This document will guide you through the steps needed to implement the checkout flow in a Medusa storefront, including steps related to adding a custom payment processor.
|
||||
|
||||
## Overview
|
||||
|
||||
A checkout flow is composed of the necessary steps to allow a customer to perform a successful checkout. It’s generally made up of two primary steps: the shipping and payment steps.
|
||||
|
||||
This document will take you through the general process of a checkout flow. You should follow along with this document if you’re creating a custom storefront, if you’re adding a custom payment provider, or if you’re just interested in learning more about how checkout works in Medusa.
|
||||
This document will take you through the general process of a checkout flow. You should follow along with this document if you’re creating a custom storefront, if you’re adding a custom payment processor, or if you’re just interested in learning more about how checkout works in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
@@ -88,7 +88,7 @@ medusa.carts.update(cartId, {
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const Cart = () => {
|
||||
@@ -252,7 +252,7 @@ medusa.carts.addShippingMethod(cartId, {
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
import { useAddShippingMethodToCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
@@ -303,11 +303,11 @@ It returns the updated cart, with the created shipping method available in the a
|
||||
|
||||
## Payment Step
|
||||
|
||||
In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment providers is done differently for each provider, so this section will just show the general steps you should follow when implementing this step.
|
||||
In this step, the customer generally chooses a payment method to complete their purchase. The implementation of payment processors is done differently for each processor, so this section will just show the general steps you should follow when implementing this step.
|
||||
|
||||
### Initialize Payment Sessions
|
||||
### Display Payment Methods
|
||||
|
||||
When the page opens and before the payment providers are displayed to the customer to choose from, you must initialize the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with.
|
||||
When the page opens and before the payment providers are displayed to the customer to choose from, you must create the [payment sessions](../payment.md#payment-session) for the current cart. Each payment provider will have a payment session associated with it. These payment sessions will be used later when the customer chooses the payment provider they want to complete their purchase with.
|
||||
|
||||
To initialize the payment sessions, send a `POST` request to the [Initialize Payment Sessions](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessions) API endpoint:
|
||||
|
||||
@@ -339,7 +339,7 @@ const PaymentProviders = () => {
|
||||
return (
|
||||
<div>
|
||||
{!cart?.payment_sessions.length && (
|
||||
<span>No payment providers</span>
|
||||
<span>No payment processors</span>
|
||||
)}
|
||||
<ul>
|
||||
{cart?.payment_sessions.map(
|
||||
@@ -378,7 +378,7 @@ This endpoint accepts the ID of the cart as a path parameter. It returns the upd
|
||||
|
||||
### Select Payment Session
|
||||
|
||||
When the customer chooses the payment provider they want to complete purchase with, you should select the payment session associated with that payment provider. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint:
|
||||
When the customer chooses the payment processor they want to complete purchase with, you should select the payment session associated with that payment processor. To do that, send a `POST` request to the [Select a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSession) API endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
@@ -396,7 +396,7 @@ medusa.carts.setPaymentSession(cartId, {
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
import { useCart } from "medusa-react"
|
||||
|
||||
const PaymentProviders = () => {
|
||||
@@ -439,19 +439,19 @@ fetch(`<BACKEND_URL>/store/carts/${cartId}/payment-session`, {
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
The request accepts the ID of the cart as a path parameter, and the ID of the payment provider in the request's body.
|
||||
The request accepts the ID of the cart as a path parameter, and the ID of the payment processor in the request's body.
|
||||
|
||||
It returns the updated cart, with the selected payment session available under `cart.payment_session`.
|
||||
|
||||
:::tip
|
||||
|
||||
If you have one payment provider or if only one payment provider is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not.
|
||||
If you have one payment processor or if only one payment processor is available for the current cart, its payment session will be automatically selected in the “[Initialize Payment Session](#initialize-payment-sessions)” step and this step becomes unnecessary. You can check whether there is a payment session selected or not by checking whether `cart.payment_session` is `null` or not.
|
||||
|
||||
:::
|
||||
|
||||
### Update Payment Session
|
||||
|
||||
This step is optional and is only necessary for some payment providers. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Provider to perform payment operations such as capturing payment.
|
||||
This step is optional and is only necessary for some payment processors. As mentioned in the [Payment Architecture](../payment.md#overview) documentation, the `PaymentSession` model has a `data` attribute that holds any data required for the Payment Processor to perform payment operations such as capturing payment.
|
||||
|
||||
If you need to update that data at any point before the purchase is made, send a request to [Update a Payment Session](/api/store/#tag/Cart/operation/PostCartsCartPaymentSessionUpdate) API endpoint:
|
||||
|
||||
@@ -474,7 +474,7 @@ medusa.carts.updatePaymentSession(cartId, paymentProviderId, {
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
import { useUpdatePaymentSession, useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
@@ -531,13 +531,13 @@ fetch(
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request accepts the ID of the cart and the ID of the payment session's payment provider as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment provider.
|
||||
This request accepts the ID of the cart and the ID of the payment session's payment processor as path parameters. In the request's body, it accepts a `data` object where you can pass any data relevant for the payment processor.
|
||||
|
||||
It returns the updated cart. You can access the payment session's data on `cart.payment_session.data`.
|
||||
|
||||
### Complete Cart
|
||||
|
||||
The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment provider first to make sure the authorization is successful when you send the request to complete the cart.
|
||||
The last step is to place the order by completing the cart. When you complete the cart, your Medusa backend will try to authorize the payment first, then place the order if the authorization is successful. So, you should perform any necessary action with your payment processor first to make sure the authorization is successful when you send the request to complete the cart.
|
||||
|
||||
To complete a cart, send a `POST` request to the [Complete a Cart](/api/store/#tag/Cart/operation/PostCartsCartComplete) API endpoint:
|
||||
|
||||
@@ -554,7 +554,7 @@ medusa.carts.complete(cartId)
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
```ts
|
||||
import { useCart } from "medusa-react"
|
||||
// ...
|
||||
|
||||
|
||||
@@ -195,9 +195,9 @@ export default CreateCustomer
|
||||
fetch(`<BACKEND_URL>/admin/customers`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
|
||||
@@ -27,11 +27,7 @@ You can alternatively use the [SendGrid](../../../plugins/notifications/sendgrid
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
### Redis
|
||||
|
||||
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### Notification Provider
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
description: "In this document, you’ll learn how to install multi-warehouse related modules using NPM in the Medusa backend."
|
||||
---
|
||||
|
||||
# Install Multi-Warehouse Modules
|
||||
|
||||
In this document, you’ll learn how to install multi-warehouse related modules using NPM in the Medusa backend.
|
||||
|
||||
:::tip
|
||||
|
||||
You can also install these modules in any NPM project.
|
||||
|
||||
:::
|
||||
|
||||
## Inventory Module
|
||||
|
||||
### Step 1: Install Inventory Module
|
||||
|
||||
To install the Inventory Module, run the following command in the root directory of the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/inventory
|
||||
```
|
||||
|
||||
### Step 2: Add Inventory Module to Configurations
|
||||
|
||||
In `medusa-config.js`, add the inventory module to the exported object under the `modules` property:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
inventoryService: {
|
||||
resolve: "@medusajs/inventory",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Run Migrations of Inventory Module
|
||||
|
||||
Run the following command to reflect schema changes into your database:
|
||||
|
||||
```bash
|
||||
medusa migrations run
|
||||
```
|
||||
|
||||
You can now start the Medusa backend and use the inventory module in your commerce application.
|
||||
|
||||
---
|
||||
|
||||
## Stock Location Module
|
||||
|
||||
### Step 1: Install Stock Location Module
|
||||
|
||||
To install the Stock Location Module, run the following command in the root directory of the Medusa backend:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/stock-location
|
||||
```
|
||||
|
||||
### Step 2: Add Stock Location Module to Configurations
|
||||
|
||||
In `medusa-config.js`, add the stock location module to the exported object under the `modules` property:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
// ...
|
||||
modules: {
|
||||
// ...
|
||||
stockLocationService: {
|
||||
resolve: "@medusajs/stock-location",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Run Migrations of Stock Location Module
|
||||
|
||||
Run the following command to reflect schema changes into your database:
|
||||
|
||||
```bash
|
||||
medusa migrations run
|
||||
```
|
||||
|
||||
You can now start the Medusa backend and use the stock location module in your commerce application.
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
description: "In this document, you’ll learn about the inventory module, how it works, and its relation to other processes in your commerce application."
|
||||
---
|
||||
|
||||
# Inventory Module
|
||||
|
||||
In this document, you’ll learn about the inventory module and how it works.
|
||||
|
||||
## Overview
|
||||
|
||||
The inventory module includes all functionalities related to product inventory. It implements inventory management for a product, confirming whether a product is available across inventory levels, and updating the inventory availability of a product variant at different points in the order lifecycle.
|
||||
|
||||
Medusa's Inventory module is a standalone module that can be used in any commerce application, not just in a Medusa backend. This document gives a general overview of how the inventory module is designed, then explains how the Medusa core orchestrates relations and processes around this module when it's used with the Medusa backend.
|
||||
|
||||
---
|
||||
|
||||
## Entities Overview
|
||||
|
||||

|
||||
|
||||
### InventoryItem
|
||||
|
||||
The `InventoryItem` entity represents a stock-kept item. It holds inventory details, such as `origin_country` or `hs_code`. It's not associated with any entity that represents a stock-kept item, enabling you to choose how you integrate it with your existing commerce application.
|
||||
|
||||
The `InventoryItem` entity doesn’t hold data regarding where the item is stored or its available quantity. This data is stored in the `InventoryLevel` entity.
|
||||
|
||||
### InventoryLevel
|
||||
|
||||
The `InventoryLevel` entity represents the quantity of an inventory item in a stock location. This ensures that an inventory item can be located in multiple stock locations.
|
||||
|
||||
Among the `InventoryLevel` entity’s attributes, the following provide insights into the available quantity within that entity:
|
||||
|
||||
- `stocked_quantity`: a number indicating the available stock of that item in the associated location.
|
||||
- `reserved_quantity`: a number indicating the quantity that should be reserved from the available `stocked_quantity`. It can be used to indicate a quantity that is still not removed from stock, but should be considered as unavailable which confirming that an item is in stock.
|
||||
- `incoming_quantity`: a number indicating an incoming stock quantity of the item into the associated location. This attribute doesn't play into the `stocked_quantity` or when confirming whether a stock-kept item is in stock.
|
||||
|
||||
The `InventoryLevel` entity is associated with a location through the `location_id` attribute. The entity representing a location isn't implemented with this module, allowing for greater flexibility in how you choose to implement a location.
|
||||
|
||||
### ReservationItem
|
||||
|
||||
The `ReservationItem` entity represents a quantity of an inventory item that is reserved when an order is placed but not fulfilled yet. This indicates that the stock-kept item still hasn't been moved from stock, but should be considered as unavailable.
|
||||
|
||||
The `ReservationItem` entity has the following notable attributes, among others:
|
||||
|
||||
- `line_item_id`: The ID of the line item in the order that this reservation item refers to.
|
||||
- `inventory_item_id`: The ID of the inventory item this reservation item refers to.
|
||||
- `location_id`: The ID of the location that this item is reserved from.
|
||||
- `quantity`: A number indicating the quantity to be reserved.
|
||||
|
||||
---
|
||||
|
||||
## How the Module Integrates into Medusa
|
||||
|
||||
This section explains how the Medusa backend uses the inventory module along with its entities and other modules, and in its processes.
|
||||
|
||||
### Entities Relation Overview
|
||||
|
||||
The core Medusa package contains an entity `ProductVariantInventoryItem` that is used to establish a relation between a product variant and an inventory item. This enables you to use inventory management features on the product variant level, while maintaining the modularity that allows you to use Medusa's inventory module or implement your custom inventory module.
|
||||
|
||||
When you use Medusa's Inventory Module, the Medusa backend uses the `ProductVariantInventoryItem` entity as a bridge between the `InventoryItem` entity and the `ProductVariant` entity.
|
||||
|
||||

|
||||
|
||||
The Medusa backend also orchestrates between the installed inventory and stock location modules. The association between an Inventory Level and a location is handled by passing the ID of a location from the stock location module to the inventory module when an Inventory Level is being created. When using Medusa's [Stock Location module](./stock-location-module.md), the entity representing the location is `StockLocation`.
|
||||
|
||||

|
||||
|
||||
Similarly, the Medusa backend associates the `ReservationItem` entity with a line item and a location by passing the IDs of each to the inventory module when a reservation item is created.
|
||||
|
||||
### Product Variant Creation Process
|
||||
|
||||
In the Medusa backend, when a product variant that has an enabled `manage_inventory` attribute is created, the backend uses the inventory module to automatically create an inventory item along with the product variant. When the inventory item is created, the Medusa backend attaches it to the product variant using the `ProductVariantInventoryItem` entity as explained earlier.
|
||||
|
||||
The Medusa backend uses the inventory module to create Inventory Levels when the admin sets the available quantity of a product variant in a stock location.
|
||||
|
||||
### Cart and Checkout
|
||||
|
||||
During the cart and checkout workflow, for example when a product variant is added to the cart or during cart validation, the Medusa backend uses the inventory module to confirm that items in the cart have sufficient stock to be purchased in the desired quantity. If a product variant doesn't have an inventory item, which is the case when the `manage_inventory` attribute of the variant is disabled, the variant is assumed to be available in stock.
|
||||
|
||||
As an inventory item can exist in multiple locations, the inventory module checks across those locations. The Medusa backend retrieves the locations based on the sales channel of the cart, as each location is associated with a sales channel, and passes them along to the inventory module to perform the checking.
|
||||
|
||||
:::tip
|
||||
|
||||
You can learn more about the relation between Stock Locations and Sales Channels in the [Stock Location documentation](./stock-location-module.md).
|
||||
|
||||
:::
|
||||
|
||||
Then, the inventory module confirms that the product variant has sufficient quantity across these locations by summing all the `stocked_quantity` of the inventory levels associated with these locations. When retrieving the `stocked_quantity` of each of the inventory levels, the `reserved_quantity` is subtracted from it to ensure accurate availability.
|
||||
|
||||
### Order Placement
|
||||
|
||||
When an order is placed, the Medusa backend uses the inventory module to reserve the ordered quantity of line items that are associated with product variants having an enabled `manage_inventory` attribute. The reserved quantity is indicated by creating a reservation item for each line item, associating it with its inventory item and a stock location.
|
||||
|
||||
The Medusa backend chooses the stock location randomly from the available stock locations associated with the order’s sales channel. The admin can later change which stock location the item will be fulfilled from.
|
||||
|
||||
### Order Fulfillment
|
||||
|
||||
When an item in the order is fulfilled, and the item is associated with a product variant that has an enabled `manage_inventory`, the Medusa backend uses the inventory module to subtract the inventory level's `reserved_quantity` from the `stocked_quantity`. The inventory module also resets the `reserved_quantity` to `0`.
|
||||
|
||||
### Order Return
|
||||
|
||||
When an item in the order is returned, and the item is associated with a product variant that has an enabled `manage_inventory`, the Medusa backend uses the inventory module to increment the inventory level's `stocked_quantity` with the returned amount.
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
description: "Multi-warehouse allows merchants to store a product in multiple locations with accurate and consistent inventory data within the commerce application."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import DocCard from '@theme/DocCard';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Multi-Warehouse
|
||||
|
||||
Multi-warehouse allows merchants to store a product in multiple locations with accurate and consistent inventory data within the commerce application.
|
||||
|
||||
Multi-warehouse in Medusa is composed of two modules: an inventory module - which is the NPM package `@medusajs/inventory` - and a stock location module - which is the NPM package `@medusajs/stock-location`.
|
||||
|
||||
<DocCard item={{
|
||||
type: 'link',
|
||||
href: '/modules/multiwarehouse/install-modules',
|
||||
label: 'Install Modules',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to install the modules related to multi-warehouse',
|
||||
}
|
||||
}} />
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Multiple Stock Locations
|
||||
|
||||
Admins can manage the stock locations, which are the places they store their products. Stock locations are associated with different sales channels.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/multiwarehouse/locations',
|
||||
label: 'User Guide: Manage Stock Locations',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage stock locations in Medusa admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
label: 'Admin: Manage Stock Locations',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage stock locations using admin APIs.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Inventory Management Across Locations
|
||||
|
||||
Admins can manage the inventory of product variants across the different stock locations.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/multiwarehouse/inventory',
|
||||
label: 'User Guide: Manage Inventory',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage inventory using the Medusa admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
label: 'Admin: Manage Inventory',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage inventory using the admin APIs.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
### Manage Item Allocations in Orders
|
||||
|
||||
Admins can manage item allocations to choose which stock location to fulfill items from or return items to. Item quantities are reserved in a stock location until the item is fulfilled to ensure data consistency.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/orders/manage#manage-item-allocation',
|
||||
label: 'User Guide: Manage Allocations in Orders',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage allocations of items in an order using the Medusa admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
label: 'Admin: Manage Allocations in Orders',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage allocations of items in an order using the admin APIs.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Understanding the Architecture
|
||||
|
||||
The commerce modules automatically detect whether a product variant is in stock, decrement the variant’s stock on fulfillment, and increment the variant’s stock on returns.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/multiwarehouse/inventory-module',
|
||||
label: 'Architecture: Inventory Module',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Inventory Module and how it works.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/multiwarehouse/stock-location-module',
|
||||
label: 'Architecture: Stock Location Module',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Stock Location Module and how it works.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Related Modules
|
||||
|
||||
Discover Multi-warehouse’s relation to other modules in Medusa.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/overview',
|
||||
label: 'Products',
|
||||
customProps: {
|
||||
icon: Icons['tag-solid'],
|
||||
description: 'Manage the inventory of product variants across locations.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/sales-channels/overview',
|
||||
label: 'Sales Channels',
|
||||
customProps: {
|
||||
icon: Icons['channels-solid'],
|
||||
description: 'Stock locations are associated with sales channels.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/orders/overview',
|
||||
label: 'Orders',
|
||||
customProps: {
|
||||
icon: Icons['check-circle-solid'],
|
||||
description: 'Change available stock based on returns and fulfillment.',
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
description: "In this document, you’ll learn about the inventory module, how it works, and its relation to other processes in your commerce application."
|
||||
---
|
||||
|
||||
# Stock Level Module
|
||||
|
||||
In this document, you’ll learn about the Stock Level module and how it works.
|
||||
|
||||
## Overview
|
||||
|
||||
A stock location indicates a physical address that stock-kept items can be stored in. The stock location module handles functionalities related to managing stock locations and their addresses.
|
||||
|
||||
Medusa's Stock Location module is a standalone module that can be used in any commerce application, not just in a Medusa backend. This document gives a general overview of how the stock location module is designed, and highlights how the Medusa core orchestrates relations around this module when it's used with the Medusa backend.
|
||||
|
||||
---
|
||||
|
||||
## StockLocation Entity
|
||||
|
||||
The `StockLocation` entity represents a stock location. It has minimal attributes including a `name` attribute. It’s associated with the `StockLocationAddress` entity.
|
||||
|
||||
---
|
||||
|
||||
## StockLocationAddress Entity
|
||||
|
||||
The `StockLocationAddress` is an entitiy that contains address-related fields, such as `city` or `country_code`.
|
||||
|
||||
The `StockLocationAddress` entity belongs to the `StockLocation` entity. It is used to store the address details of a stock location.
|
||||
|
||||
---
|
||||
|
||||
## How the Module Integrates into Medusa
|
||||
|
||||
This section explains how the Medusa backend uses the stock location module along with its entities and other modules.
|
||||
|
||||
### Entities Relation Overview
|
||||
|
||||
The following entities in the Medusa backend each have an attribute that is used to associate them with a stock location:
|
||||
|
||||
- `Fulfillment`: The `location_id` attribute within the `Fulfillment` entity is used to indicate from which stock location an order item is fulfilled.
|
||||
- `Return`: The `location_id` attribute within the `Return` entity is used to indicate to which stock location an order item is returned.
|
||||
- `Store`: The `default_location_id` attribute within the `Store` entity is used to indicate the default stock location to use in the ecommerce store.
|
||||
- `SalesChannelLocation`: This entity is used to attach a stock location to a `SalesChannel`. The relation between these two entities is explained further in the [Relation to Sales Channel section](#relation-to-saleschannel).
|
||||
|
||||
When the Medusa's Stock Location module is used with the Medusa backend, the ID that is associated with the attributes mentioned above is from the `StockLocation` module.
|
||||
|
||||
The Medusa backend also orchestrates between different modules. The [Inventory Module](./inventory-module.md)'s entities contain the following attributes to handle associations between them and a stock location:
|
||||
|
||||
- `InventoryLevel`: This entity is used to indicate the stocked quantity of an inventory item in a stock location. As explained in the [Inventory Module documentation](./inventory-module.md#inventorylevel), the `InventoryLevel` entity has an attribute `location_id`.
|
||||
- `ReservationItem`: This entity is used to indicate the reserved quantity of an inventory item in a stock location. As explained in the [Inventory Module documentation](./inventory-module.md#reservationitem), the `ReservationItem` entity has an attribute `location_id`.
|
||||
|
||||
When both modules are used within the Medusa backend, the Medusa backend bridges between these modules by passing the ID of a `StockLocation` from the stock location module to the inventory module, and the inventory module uses the ID in its entities.
|
||||
|
||||
### Relation to SalesChannel
|
||||
|
||||
A stock location can be associated with more than one sales channel. For example, a physical store and an online store can use the same stock location.
|
||||
|
||||
As the `StockLocation` and `SalesChannel` entities are available in separate modules, the Medusa backend handles attaching the stock location with the sales channel within the `SalesChannelLocation` entity.
|
||||
|
||||
This relation is used across the Medusa backend and within checkout and order workflows:
|
||||
|
||||
- When checking the availability of an inventory item during checkout, the Medusa backend retrieves the location IDs that are associated with the cart’s sales channel using the `SalesChannelLocation`, then passes it along to the inventory module to perform the quantity check.
|
||||
- When an order is placed, the Medusa backend retrieves the location IDs that are associated with the cart’s sales channel using the `SalesChannelLocation` entity, and passes it to the inventory module that reserves the ordered quantity of the inventory item from that location. The admin can later change the stock location if necessary.
|
||||
- When an item in an order is fulfilled, the admin chooses a stock location to fulfill the item from. Similarly, when an item in an order is returned, the admin can choose a stock location to return the item to. The Medusa backend then passes the ID of the location from the stock module to the inventory module to perform inventory management functionalities.
|
||||
@@ -25,11 +25,7 @@ In this document, you’ll learn how to handle the `order-update-token.created`
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
### Redis
|
||||
|
||||
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### Notification Provider
|
||||
|
||||
|
||||
@@ -191,14 +191,14 @@ If `difference_due` is less than 0, then the amount will be refunded to the cust
|
||||
|
||||
:::note
|
||||
|
||||
💡 This section explains how to authorize the payment using one payment provider and payment session. However, payment collections allow customers to pay in installments or with more than one provider. You can learn more about how to do that using the [batch endpoints of the Payment APIs](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsBatchAuthorize)
|
||||
💡 This section explains how to authorize the payment using one payment processor and payment session. However, payment collections allow customers to pay in installments or with more than one provider. You can learn more about how to do that using the [batch endpoints of the Payment APIs](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsBatchAuthorize)
|
||||
|
||||
:::
|
||||
|
||||
If `difference_due` is greater than 0, then additional payment from the customer is required. In this case, you must implement these steps to allow the customer to authorize the payment:
|
||||
|
||||
1. Show the customer the available payment providers. These can be retrieved from the details of [the region of the order](/api/store/#tag/Region/operation/GetRegionsRegion).
|
||||
2. When the customer selects the payment provider, initialize the payment session of that provider in the payment collection. You can do that by sending a request to the [Manage Payment Sessions](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessions) endpoint, passing it the payment collection’s ID as a path parameter, and the payment provider’s ID as a request body parameter:
|
||||
1. Show the customer the available payment processors. These can be retrieved from the details of [the region of the order](/api/store/#tag/Region/operation/GetRegionsRegion).
|
||||
2. When the customer selects the payment processor, initialize the payment session of that provider in the payment collection. You can do that by sending a request to the [Manage Payment Sessions](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessions) endpoint, passing it the payment collection’s ID as a path parameter, and the payment processor's ID as a request body parameter:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
@@ -267,7 +267,7 @@ fetch(
|
||||
</Tabs>
|
||||
|
||||
1. Show the customer the payment details form based on the payment session’s provider. For example, if the provider ID of a payment session is `stripe`, you must show Stripe’s card component to enter the customer’s card details.
|
||||
2. Authorize the payment using the payment provider. The [Authorize Payment Session](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsSessionAuthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters:
|
||||
2. Authorize the payment using the payment processor. The [Authorize Payment Session](/api/store/#tag/Payment/operation/PostPaymentCollectionsSessionsSessionAuthorize) endpoint accepts the payment collection’s ID and the ID of the payment session as path parameters:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
@@ -39,6 +39,21 @@ Medusa provides the necessary features to build a customizable shopping experien
|
||||
- Create draft orders without direct involvement from the customer.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
<LargeCard
|
||||
Icon={Icons['building-solid']}
|
||||
title='Inventory (Multi-Warehouse)'
|
||||
action={{
|
||||
label: 'Learn more',
|
||||
href: '/modules/multiwarehouse/overview'
|
||||
}}
|
||||
>
|
||||
|
||||
- Create and manage multiple stock locations that represent where you physically store your products.
|
||||
- Manage the product variants' inventory across stock locations, and associate those locations with sales channels.
|
||||
- Associate order fulfillemnts and returns with a stock location. The management of inventory item quantity is handled automatically.
|
||||
|
||||
|
||||
</LargeCard>
|
||||
<LargeCard
|
||||
Icon={Icons['shopping-cart-solid']}
|
||||
@@ -49,7 +64,7 @@ Medusa provides the necessary features to build a customizable shopping experien
|
||||
}}
|
||||
>
|
||||
|
||||
- Accept payments with payment providers including Stripe and PayPal.
|
||||
- Accept payments with payment processors including Stripe and PayPal.
|
||||
- Calculate taxes of a cart through custom logic or third-party tax providers.
|
||||
- Allow customers to apply discount codes and gift cards during checkout.
|
||||
- Fully customize the frontend experience of the checkout process.
|
||||
@@ -144,7 +159,7 @@ Medusa's multi-region setup and sales channels allow businesses to sell internat
|
||||
>
|
||||
|
||||
- Create an unlimited number of regions, each associated with a currency and at least one country.
|
||||
- Manage each region’s settings including payment providers, shipping options, and more.
|
||||
- Manage each region’s settings including payment processors, shipping options, and more.
|
||||
- Set prices for products and shipping options specific to a currency or a region.
|
||||
|
||||
|
||||
@@ -211,10 +226,10 @@ If you have any questions about Medusa, its features, and development with it, f
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/carts-and-checkout/backend/add-payment-provider',
|
||||
label: 'Create a Payment Provider',
|
||||
label: 'Create a Payment Processor',
|
||||
customProps: {
|
||||
icon: Icons['credit-card-solid'],
|
||||
description: 'Create a payment provider.',
|
||||
description: 'Learn how to create a payment processor.',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -244,4 +259,4 @@ If you have any questions about Medusa, its features, and development with it, f
|
||||
description: 'Manage sales channels.',
|
||||
}
|
||||
}
|
||||
]} />
|
||||
]} />
|
||||
@@ -26,11 +26,7 @@ Importing prices into a price list removes all existing prices in the price list
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
### Redis
|
||||
|
||||
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow our [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### File Service Plugin
|
||||
|
||||
|
||||
@@ -20,11 +20,7 @@ Using Medusa’s [Batch Job Admin APIs](/api/admin/#tag/Batch-Job), you can impo
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
### Redis
|
||||
|
||||
Redis is required for batch jobs to work. Make sure you [install Redis](../../../development/backend/prepare-environment.mdx#redis) and [configure it with the Medusa backend](../../../development/backend/configurations.md#redis).
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started. The Medusa backend must also have an event bus module installed, which is available when using the default Medusa backend starter.
|
||||
|
||||
### File Service Plugin
|
||||
|
||||
|
||||
@@ -0,0 +1,678 @@
|
||||
---
|
||||
description: 'Learn how to how to manage product categories using the admin REST APIs. Learn how to create, update, and delete categories, and how to manage products in a category.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Manage Product Categories
|
||||
|
||||
In this document, you’ll learn how to manage product categories using the admin REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the product category admin REST APIs, you can manage the categories in your commerce application.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following admin functionalities:
|
||||
|
||||
- Manage categories including list, create, edit, and delete categories.
|
||||
- Manage products in a category, including adding or removing products from a category.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client](../../../js-client/overview.md) installed and have [created an instance of the client](../../../js-client/overview.md#configuration).
|
||||
|
||||
### Medusa React
|
||||
|
||||
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||
|
||||
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
|
||||
|
||||
### Authenticated Admin User
|
||||
|
||||
You must be an authenticated admin user before following along with the steps in the tutorial.
|
||||
|
||||
You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).
|
||||
|
||||
---
|
||||
|
||||
## List Categories
|
||||
|
||||
You can retrieve available categories by sending a request to the [List Categories](/api/admin#tag/Product-Category/operation/GetProductCategories) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.list()
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
console.log(product_categories.length)
|
||||
// display categories
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminProductCategories } from "medusa-react"
|
||||
import { ProductCategory } from "@medusajs/medusa"
|
||||
|
||||
function Categories() {
|
||||
const {
|
||||
product_categories,
|
||||
isLoading } = useAdminProductCategories()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product_categories && !product_categories.length && (
|
||||
<span>No Product</span>
|
||||
)}
|
||||
{product_categories && product_categories.length > 0 && (
|
||||
<ul>
|
||||
{product_categories.map(
|
||||
(category: ProductCategory) => (
|
||||
<li key={category.id}>{category.name}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Categories
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_categories, limit, offset, count }) => {
|
||||
console.log(product_categories.length)
|
||||
// display product categories
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/product-categories' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request returns an array of product categories, as well as [pagination fields](/api/admin#section/Pagination).
|
||||
|
||||
You can also pass filters and other selection query parameters to the request. Check out the [API reference](/api/admin#tag/Product-Category/operation/GetProductCategories) for more details on available query parameters.
|
||||
|
||||
---
|
||||
|
||||
## Create a Category
|
||||
|
||||
You can create a category by sending a request to the [Create a Category](/api/admin#tag/Product-Category/operation/PostProductCategories) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.create({
|
||||
name: "Skinny Jeans",
|
||||
})
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminCreateProductCategory } from "medusa-react"
|
||||
|
||||
const CreateCategory = () => {
|
||||
const createCategory = useAdminCreateProductCategory()
|
||||
// ...
|
||||
|
||||
const handleCreate = () => {
|
||||
createCategory.mutate({
|
||||
name: "Skinny Jeans",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default CreateCategory
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Skinny Jeans",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/product-categories' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Skinny Jeans",
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires one body parameter `name`, which is the name of the category. The request also accepts the following optional body parameters:
|
||||
|
||||
- `handle`: a string that is typically used as a URL path or slug. If you don’t provide a `handle`, it will be the kebab case format of the name.
|
||||
- `is_internal`: a boolean that indicates whether the category is visible to customers. By default, it’s `false`.
|
||||
- `is_active`: a boolean that indicates whether the category is active. By default, it’s `true`.
|
||||
- `parent_category_id`: An ID of another category that this new category should be a child of.
|
||||
|
||||
The request returns the newly created product category.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Category
|
||||
|
||||
You can retrieve a product category by sending a request to the [Get a Product Category](/api/admin#tag/Product-Category/operation/GetProductCategoriesCategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.retrieve(productCategoryId)
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminProductCategory } from "medusa-react"
|
||||
|
||||
const Category = () => {
|
||||
const {
|
||||
product_category,
|
||||
isLoading,
|
||||
} = useAdminProductCategory(productCategoryId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product_category && <span>{product_category.name}</span>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Category
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the category to be passed as a path parameter.
|
||||
|
||||
It returns the full object of the product category.
|
||||
|
||||
---
|
||||
|
||||
## Edit a Category
|
||||
|
||||
You can edit a product category by sending a request to the [Update a Product Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.update(productCategoryId, {
|
||||
name: "Skinny Jeans",
|
||||
})
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminUpdateProductCategory } from "medusa-react"
|
||||
|
||||
const UpdateCategory = () => {
|
||||
const updateCategory = useAdminUpdateProductCategory(
|
||||
productCategoryId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleUpdate = () => {
|
||||
updateCategory.mutate({
|
||||
name: "Skinny Jeans",
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateCategory
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Skinny Jeans",
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"name": "Skinny Jeans",
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the category to be passed as a path parameter.
|
||||
|
||||
In its body, you can pass any of the category’s fields that you want to update. In the code snippet above, you update the name of the category.
|
||||
|
||||
You can check the list of accepted fields in the [API reference](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory).
|
||||
|
||||
The request returns the full object of the updated product category.
|
||||
|
||||
---
|
||||
|
||||
## Manage Products in a Category
|
||||
|
||||
:::note
|
||||
|
||||
You can manage the categories of each product individually using the [product APIs](/api/admin#tag/Product). This section explores the other approach of managing a product’s category using the product category APIs.
|
||||
|
||||
:::
|
||||
|
||||
### Add Products to a Category
|
||||
|
||||
You can add more than one product to a category using the [Add Products to a Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategoryProductsBatch) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.addProducts(productCategoryId, {
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
})
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminAddProductsToCategory } from "medusa-react"
|
||||
|
||||
const UpdateProductsInCategory = () => {
|
||||
const addProductsToCategory = useAdminAddProductsToCategory(
|
||||
productCategoryId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleAdd = () => {
|
||||
addProductsToCategory.mutate({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default UpdateProductsInCategory
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}/products/batch`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/admin/product-categories/<CAT_ID>/products/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"product_ids": [
|
||||
{
|
||||
"id": "productId1"
|
||||
},
|
||||
{
|
||||
"id": "productId2"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the category to be passed as a path parameter.
|
||||
|
||||
In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to add. So, each product you want to add you pass it to the array as an object having the property `id`.
|
||||
|
||||
The request returns the full object of the product category updated with the new products.
|
||||
|
||||
### Remove Products from a Category
|
||||
|
||||
You can remove products from a category by sending a request to the [Delete Products](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategoryProductsBatch) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.removeProducts(
|
||||
productCategoryId,
|
||||
{
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import {
|
||||
useAdminDeleteProductsFromCategory,
|
||||
} from "medusa-react"
|
||||
|
||||
const DeleteProductsFromCategory = () => {
|
||||
const deleteProductsFromCategory =
|
||||
useAdminDeleteProductsFromCategory(productCategoryId)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteProductsFromCategory.mutate({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default DeleteProductsFromCategory
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}/products/batch`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
product_ids: [
|
||||
{
|
||||
id: productId1,
|
||||
},
|
||||
{
|
||||
id: productId2,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product_category }) => {
|
||||
console.log(product_category.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/product-categories/<CAT_ID>/products/batch' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>' \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"product_ids": [
|
||||
{
|
||||
"id": "productId1"
|
||||
},
|
||||
{
|
||||
"id": "productId2"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the category to be passed as a path parameter.
|
||||
|
||||
In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to remove. So, each product you want to remove you pass it to the array as an object having the property `id`.
|
||||
|
||||
The request returns the full object of the product category updated with the its products.
|
||||
|
||||
---
|
||||
|
||||
## Delete a Category
|
||||
|
||||
You can delete a product category by sending a request to the [Delete a Product Category](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategory) endpoint:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.admin.productCategories.delete(productCategoryId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useAdminDeleteProductCategory } from "medusa-react"
|
||||
|
||||
const Category = () => {
|
||||
const deleteCategory = useAdminDeleteProductCategory(
|
||||
productCategoryId
|
||||
)
|
||||
// ...
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteCategory.mutate()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
export default Category
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
|
||||
credentials: "include",
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X DELETE '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
|
||||
-H 'Authorization: Bearer <API_TOKEN>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This request requires the ID of the category to be passed as a path parameter.
|
||||
|
||||
The request returns the following fields:
|
||||
|
||||
- `id`: The ID of the deleted category.
|
||||
- `object`: The type of object that was deleted. In this case, the value will be `product-category`.
|
||||
- `deleted`: A boolean value indicating whether the category was deleted.
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
description: 'Learn what product categories are and how they work in a Medusa backend. Product categories can be used to organize products using nested collections.'
|
||||
---
|
||||
|
||||
# Product Categories
|
||||
|
||||
In this document, you’ll learn about Product Categories and how they can be used in Medusa.
|
||||
|
||||
:::note
|
||||
|
||||
Product Category feature is currently in beta mode and guarded by a feature flag. To use Product Categories either:
|
||||
|
||||
1. Enable the `MEDUSA_FF_PRODUCT_CATEGORIES` environment variable;
|
||||
2. Or enable the `product_categories` key in the Medusa backend's settings.
|
||||
|
||||
You can learn more about enabling it in the [feature flags](../../development/feature-flags/toggle.md) documentation.
|
||||
|
||||
:::
|
||||
|
||||
## Introduction
|
||||
|
||||
Product Categories allow you to categorize your products. You can manage categories in your store. You can also nest categories in one another, creating a parent-child hierarchy, and there is no limit to how far you can nest the categories.
|
||||
|
||||
In addition, you can give a category a ranking. As a product can have multiple categories, rankings can be useful in different cases. For example, when you’re unsure what main category to show in the product’s page in the frontend.
|
||||
|
||||
### Example Use Cases
|
||||
|
||||
Here are some examples of the usage of Product Categories:
|
||||
|
||||
- Organizing your large catalog store in a way that showcases to the customer what products you provide.
|
||||
- Create a multi-brand store, where each category acts as a brand.
|
||||
|
||||
---
|
||||
|
||||
## Product Category vs Product Collection
|
||||
|
||||
Before v1.8 of Medusa, Product Collection was used as the main way of organizing products. It was used similarly to how you would use a Product Category. However, a Product Collection is not only different in what it actually represents, it also doesn’t provide the same functionalities that a Product Category does.
|
||||
|
||||
A Product Collection is used to group together a list of products, generally for a marketing purpose. For example, you can create a summer collection when summer comes around with a variety of products in them including shorts, t-shirts, and more.
|
||||
|
||||
On the other hand, a Product Category is used to provide easy and clear navigation for customers to help them find what they need. For example, you can have the following nested product categories:
|
||||
|
||||
- Women
|
||||
- Tops
|
||||
- Jackets
|
||||
- Sweatshirt
|
||||
- T-shirt
|
||||
- etc…
|
||||
- Bottoms
|
||||
- Jeans
|
||||
- Shorts
|
||||
- etc…
|
||||
|
||||
This allows customers to directly choose the category that the product they’re looking for belongs to, and gives them an idea of what products your store offers. If you were to use Product Collections here, as they don’t support nesting, all of these categories would have to be separate collections that are not tied together.
|
||||
|
||||
---
|
||||
|
||||
## ProductCategory Entity Overview
|
||||
|
||||
A product category is stored in the database as a `ProductCategory` entity. Some of its important attributes are:
|
||||
|
||||
- `id`: The ID of the product category.
|
||||
- `name`: The name of the product category.
|
||||
- `handle`: A string indicating a slug path of the category. It’s useful when creating a page on your storefront for the category, as the `handle` can be used as the path in the URL.
|
||||
- `is_active`: A boolean value indicating the status of the Product Category.
|
||||
- `is_internal`: A boolean value indicating the visibility of the Product Category.
|
||||
- `mpath`: A string value that keeps the IDs of all the category’s ancestors, delimited by a dot (`.`). This attribute is generated by Typeorm. For example, `pcat_id1.pcat_id2.pcat_id3`.
|
||||
|
||||
---
|
||||
|
||||
## Relations to other Entities
|
||||
|
||||
### Products
|
||||
|
||||
Products can be available in more than one category. You can then filter products by a single or list of product categories.
|
||||
|
||||
The relation is implemented in the Product entity. You can access the product categories a product is available in by expanding the `categories` relation and using `product.categories`
|
||||
|
||||
### ProductCategory
|
||||
|
||||
The parent-child hierarchy is represented in the `ProductCategory` entity through the following relations:
|
||||
|
||||
- You can access the parent of a category by expanding the `parent_category` relation and accessing `category.parent_category`. You can also access the ID of the parent category by accessing `category.parent_category_id`. If a category doesn’t have a parent, the relation and the ID will be `null`.
|
||||
- You can access the children of a category by expanding the `category_children` relation and accessing `category.category_children`. If a category doesn’t have children, the relation will be an empty array. By default, when expanding this relation in Product Category endpoints, only the immediate child categories are returned. If you want to get the entire heirarchy of child categories, you must pass the `include_descendants_tree` flag to the endpoint setting its value to `true`.
|
||||
|
||||
Aside from these relations, the `mpath` attribute, which is a [Materialized Path](https://typeorm.io/tree-entities#materialized-path-aka-path-enumeration) that is automatically generated by Typeorm, can be used to query deep trees faster.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to manage product categories using the admin APIs](./admin/manage-categories.mdx)
|
||||
@@ -29,7 +29,6 @@ Admins can manage unlimited amount of products and their variants. They can mana
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage products using Admin APIs.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55,37 +54,37 @@ Admins can manage unlimited amount of products and their variants. They can mana
|
||||
|
||||
### Product Organization
|
||||
|
||||
Admins can organize products into collections, tags, types, and more.
|
||||
Admins can organize products into categories, collections, tags, types, and more.
|
||||
|
||||
Customers can use this organization to filter products while browsing them.
|
||||
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
label: 'Admin: Manage Collections',
|
||||
href: '/modules/products/admin/manage-categories',
|
||||
label: 'Admin: Manage Categories',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to manage collections using Admin APIs.',
|
||||
description: 'Learn how to manage categories using Admin APIs.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/user-guide/products/collections',
|
||||
label: 'User Guide: Manage Collections',
|
||||
href: '/user-guide/products/categories',
|
||||
label: 'User Guide: Manage Categories',
|
||||
customProps: {
|
||||
icon: Icons['users-solid'],
|
||||
description: 'Learn how to manage collections in Medusa Admin.'
|
||||
description: 'Learn how to manage categories in the Medusa Admin.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
label: 'Storefront: Show Collections',
|
||||
label: 'Storefront: Use Categories',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
description: 'Learn how to show collections in a storefront.',
|
||||
description: 'Learn how to use categories in a storefront.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
@@ -131,7 +130,7 @@ Admins can bulk import and export products using CSV files. This facilitates mov
|
||||
|
||||
Learn how product-related entities are build, their relation to other modules, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
<DocCardList colSize={4} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
@@ -142,6 +141,15 @@ Learn how product-related entities are build, their relation to other modules, a
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/categories',
|
||||
label: 'Architecture: Category',
|
||||
customProps: {
|
||||
icon: Icons['circle-stack-solid'],
|
||||
description: 'Learn about the Product Category Architecture.',
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
|
||||
@@ -199,6 +199,9 @@ export default CreateRegion
|
||||
fetch(`<BACKEND_URL>/admin/regions`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "Europe",
|
||||
currency_code: "eur",
|
||||
@@ -251,7 +254,7 @@ This request requires the following body parameters:
|
||||
- `name`: The name of the region.
|
||||
- `currency_code`: The 3 character ISO currency code.
|
||||
- `tax_rate`: The tax rate in the Region.
|
||||
- `payment_providers`: An array of payment provider IDs. The array must contain at least one item.
|
||||
- `payment_providers`: An array of payment processor IDs. The array must contain at least one item.
|
||||
- `fulfillment_providers`: An array of fulfillment provider IDs. The array must contain at least one item.
|
||||
- `countries`: An array of the 2 character ISO code of the countries included in the region.
|
||||
|
||||
@@ -314,6 +317,9 @@ export default UpdateRegion
|
||||
fetch(`<BACKEND_URL>/admin/regions/${regionId}`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
countries: [
|
||||
"DK",
|
||||
@@ -416,6 +422,9 @@ export default Region
|
||||
fetch(`<BACKEND_URL>/admin/shipping-options`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: "PostFake",
|
||||
region_id: regionId,
|
||||
|
||||
@@ -23,7 +23,7 @@ In Medusa, you can create an unlimited number of regions. A region can be associ
|
||||
|
||||
For example, you can create a region to represent the European region, and associate European countries with that region. You can also create a region that represents each of these countries on their own.
|
||||
|
||||
Each region can have its own settings such as the currency, payment providers, tax providers, and more.
|
||||
Each region can have its own settings such as the currency, payment processors, tax providers, and more.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
@@ -78,7 +78,7 @@ You have complete control over the format and value of the price of a product pe
|
||||
|
||||
Your customers can choose the region that matches their location. This would automatically show the correct product prices to the customer with the currency of that region.
|
||||
|
||||
During checkout, customers will only see the available shipping options and payment providers in their region. Taxes are also calculated based on that region. This ensures a seamless checkout experience tailored for every region.
|
||||
During checkout, customers will only see the available shipping options and payment processors in their region. Taxes are also calculated based on that region. This ensures a seamless checkout experience tailored for every region.
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ In this document, you’ll learn about the Region entity and its architecture.
|
||||
|
||||
Regions represent at least one country on your Medusa backend. They're used to define different business logic and configurations for a set of countries.
|
||||
|
||||
For example, you can enable one payment provider for region A, and a different payment provider for region B. Customers can then use the payment provider enabled in their region.
|
||||
For example, you can enable one payment processor for region A, and a different payment processor for region B. Customers can then use the payment processor enabled in their region.
|
||||
|
||||
This applies to other relations and entities in your store such as different currencies, fulfillment providers, and tax providers.
|
||||
|
||||
@@ -23,7 +23,7 @@ The following configurations can be set for each region:
|
||||
1. The currency used.
|
||||
2. The tax provider and rates.
|
||||
3. The countries that belong to that region. A country can only belong to one region.
|
||||
4. The enabled payment providers.
|
||||
4. The enabled payment processors.
|
||||
5. The enabled fulfillment providers.
|
||||
6. The shipping and return shipping options.
|
||||
|
||||
@@ -33,9 +33,9 @@ The following configurations can be set for each region:
|
||||
|
||||
Your store needs at least one region so that your customers can place orders.
|
||||
|
||||
If you serve customers in different countries that use the same configurations, such as the same currency and payment providers, then you can include more countries in the same region.
|
||||
If you serve customers in different countries that use the same configurations, such as the same currency and payment processors, then you can include more countries in the same region.
|
||||
|
||||
If you serve customers in different countries that have at least one different configuration, such as different payment providers, you need to create a new region for those countries.
|
||||
If you serve customers in different countries that have at least one different configuration, such as different payment processors, you need to create a new region for those countries.
|
||||
|
||||
There is no limit on how many regions you can create, and regions can share similar configurations.
|
||||
|
||||
@@ -84,7 +84,7 @@ The relation is available on a region by expanding the `fulfillment_providers` r
|
||||
|
||||
### PaymentProvider
|
||||
|
||||
A region must have at least one payment provider. A payment provider can be used in more than one region.
|
||||
A region must have at least one payment processor. A payment processor can be used in more than one region.
|
||||
|
||||
The relation is available on a region by expanding the `payment_providers` relation and accessing `region.payment_providers`.
|
||||
|
||||
|
||||
Reference in new issue
Block a user